DEV Community

Cover image for LUA parser for Apple Homekit using BleuIO
Bleuio tech
Bleuio tech

Posted on

LUA parser for Apple Homekit using BleuIO

LUA parser for Apple Homekit using BleuIO

This project demonstrates how to turn a BLE advertisement-based air quality sensor—in this case, the HibouAir—into a fully integrated Apple Home accessory. By combining the flexibility of the BleuIO USB BLE dongle and the simplicity of Lua scripting, we’ve created a bridge that reads raw BLE advertisement packets, decodes the environmental data, and makes it available to the Apple Home app through Homebridge.

Unlike most HomeKit integrations that rely on cloud APIs or native HomeKit devices, this setup is completely offline and works on any platform. The use of BleuIO allows BLE communication to be handled reliably across macOS, Linux, or Windows—something most native BLE libraries struggle to achieve consistently across platforms. Lua adds a lightweight, embeddable scripting engine perfect for decoding raw advertisement data in real time. Together, they make a fast, minimalist, and cross-platform solution for BLE-to-HomeKit integration.

What This Project Does

At a high level, the system continuously scans BLE advertisements from the HibouAir sensor using BleuIO. The data is decoded using Lua, which extracts temperature and humidity values. These values are then served via a local HTTP server, and finally read by Homebridge using the homebridge-http-temperature-humidity plugin. This enables you to view real-time air quality data directly in the Apple Home app on your iPhone, iPad, or Mac.

Project Components and Tools Used

Step-by-Step Setup

1. Connect and Configure BleuIO

Start by plugging in your BleuIO USB dongle. On macOS or Linux, find its serial port using:

ls /dev/tty.usb*
Enter fullscreen mode Exit fullscreen mode

Once you’ve identified the port (e.g., /dev/tty.usbmodemXXXXXX), update the Python script accordingly.

2. Create bleuio_scan.py

This script will initiate a BLE scan using BleuIO’s AT command interface and capture the raw advertisement data from the HibouAir sensor. It filters for packets containing a specific manufacturer ID and writes the first match to a file.

import serial
import time
import re

# Update this with your actual port
port = "/dev/tty.usbmodem4048FDE52DAF1"

ser = serial.Serial(port, 9600, timeout=1)
ser.write(b"AT+FINDSCANDATA=5B07050=2\r\n")
time.sleep(4)

data = ser.read_all().decode()
ser.close()

print("RAW OUTPUT:\n", data)

# Extract first adv line
matches = re.findall(r"Device Data \[ADV\]: ([0-9A-F]+)", data)
if matches:
    with open("adv_data.txt", "w") as f:
        f.write(matches[0])
    print("✅ Wrote ADV data:", matches[0])
else:
    print("❌ No HibouAir data found.")
Enter fullscreen mode Exit fullscreen mode

3. Parse the BLE Data Using Lua

Lua is ideal for embedded processing due to its speed and small footprint. We use it here to decode the hex-formatted BLE advertisement string into readable sensor values.

Create a file named parse_ble_adv.lua with the following:

local http = require("socket.http")
local ltn12 = require("ltn12")
local json = require("dkjson")

-- Reverse byte order (e.g., "1234" -> "3412")
local function reverse_bytes(hexstr)
    local bytes = {}
    for i = 1, #hexstr, 2 do
        table.insert(bytes, 1, hexstr:sub(i, i+1))
    end
    return table.concat(bytes)
end

-- Parse HibouAir BLE advertisement data
local function parse_adv_data(adv)
    local pos = string.find(adv, "5B070504")
    if not pos then return nil end
    pos = pos - 1 -- Lua is 1-indexed

    local function read_val(start, len, divide_by, signed)
        local hex = reverse_bytes(adv:sub(start, start+len-1))
        local val = tonumber(hex, 16)
        if signed and val > 0x7FFF then
            val = val - 0x10000
        end
        return divide_by and val / divide_by or val
    end

    return {
        temp = read_val(pos+23, 4, 10, true),
        hum = read_val(pos+27, 4, 10),
        pressure = read_val(pos+19, 4, 10), 
        voc = read_val(pos+31, 4),
        pm1 = read_val(pos+35, 4, 10),
        pm25 = read_val(pos+39, 4, 10),
        pm10 = read_val(pos+43, 4, 10),
        co2 = tonumber(adv:sub(pos+47, pos+50), 16),
        vocType = tonumber(adv:sub(pos+51, pos+52), 16),
        ts = os.date("%Y-%m-%d %H:%M:%S")
    }
end

-- Example BLE advertisement data (replace with real data from BleuIO scan)
-- local adv_data = "0201061BFF5B070504220069130010273A0160017E0000000000000001B703"
local file = io.open("adv_data.txt", "r")
local adv_data = file:read("*a")
file:close()

local data = parse_adv_data(adv_data)
-- Write latest data to file
print("DEBUG: Writing this to latest_data.json")
print("Temperature:", data.temp)
print("Humidity:", data.hum)

local file = io.open("latest_data.json", "w")
file:write(json.encode({
    temperature = data.temp,
    humidity = data.hum,
}))

file:close()



if not data then
    print("Failed to parse advertisement data")
    return
end

print("Parsed BLE Data:")
for k, v in pairs(data) do
    print(k, v)
end
Enter fullscreen mode Exit fullscreen mode

4. Automate Scanning and Parsing

To keep your data fresh, use a simple shell script to run both the scanner and parser every 10 seconds.

Create run_everything.sh:

#!/bin/bash
while true; do
  echo "Scanning and updating Homebridge..."
  python3 bleuio_scan.py && lua parse_ble_adv.lua
  sleep 10
done
Enter fullscreen mode Exit fullscreen mode

Make it executable with:

chmod +x run_everything.sh
Enter fullscreen mode Exit fullscreen mode

Run it in the background or with tmux to keep it alive.

5. Create the HTTP Server in Lua

This server reads from the JSON file and exposes it via an HTTP endpoint (/temp) that Homebridge can read.

local copas = require("copas")
local socket = require("socket")
local json = require("dkjson")

-- Start TCP server on port 8081
local server = socket.bind("*", 8081)
copas.addserver(server, function(c)
    c = copas.wrap(c)
    local request = c:receive("*l")

    if request and request:match("GET /temp") then
        -- Read latest temp/hum from file
        local file = io.open("latest_data.json", "r")
        local temp_hum = { temperature = 0, humidity = 0 }

        if file then
            local contents = file:read("*a")
            file:close()
            local decoded, _, err = json.decode(contents)
            if decoded then
                temp_hum = decoded
            end
        end

        local body = json.encode(temp_hum)

        local response = {
            "HTTP/1.1 200 OK",
            "Content-Type: application/json",
            "Content-Length: " .. #body,
            "",
            body
        }
        c:send(table.concat(response, "\r\n"))
    else
        c:send("HTTP/1.1 404 Not Found\r\n\r\n")
    end
end)

print("Lua HTTP server running on http://localhost:8081")
copas.loop()
Enter fullscreen mode Exit fullscreen mode

You’ll need LuaSocket and dkjson:

luarocks install luasocketluarocks install dkjsonluarocks install copas
Enter fullscreen mode Exit fullscreen mode

Run the server:

lua server.lua
Enter fullscreen mode Exit fullscreen mode

It will listen on http://localhost:8081/temp and serve the current temperature and humidity.

Setting Up Homebridge and Home App Integration

Install Homebridge globally:

sudo npm install -g homebridge
Enter fullscreen mode Exit fullscreen mode

Install the UI plugin for web-based setup:

sudo npm install -g homebridge-config-ui-x
Enter fullscreen mode Exit fullscreen mode

Then install the required accessory plugin:

sudo npm install -g homebridge-http-temperature-humidity
Enter fullscreen mode Exit fullscreen mode

Create or update your Homebridge config.json (usually located in ~/.homebridge/):

{
  "bridge": {
    "name": "Homebridge",
    "username": "0E:4E:20:2F:2E:BC",
    "port": 51826,
    "pin": "031-45-154"
  },
  "description": "Homebridge setup",
  "accessories": [
    {
      "accessory": "HttpTemphum",
      "name": "HibouAir Sensor",
      "url": "http://localhost:8081/temp",
      "http_method": "GET",
      "sendimmediately": "",
      "timeout": 3000
    }
  ],
  "platforms": [
    {
      "platform": "config",
      "name": "Config"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Start Homebridge with:

homebridge -I
Enter fullscreen mode Exit fullscreen mode

Use the Homebridge UI (usually on http://localhost:8081) to add the bridge to your Apple Home app by scanning the QR code. Once added, you’ll see temperature and humidity from your HibouAir sensor as real HomeKit accessories.

Output

This project showcases how BleuIO and Lua can be used to create a fast, simple, and platform-independent BLE integration with Apple Home. Unlike heavyweight setups or cloud-connected devices, this approach is minimal, local-first, and highly customizable. With BleuIO’s cross-platform compatibility and Lua’s tiny footprint, you can integrate BLE advertisement data into almost any platform—from macOS to Raspberry Pi to embedded Linux. This is just the beginning. You can extend this setup to support more metrics like VOC, CO₂, pressure, light.

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

DevCycle image

Ship Faster, Stay Flexible.

DevCycle is the first feature flag platform with OpenFeature built-in to every open source SDK, designed to help developers ship faster while avoiding vendor-lock in.

Start shipping