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*

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.")

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

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

Make it executable with:

chmod +x run_everything.sh

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()

You’ll need LuaSocket and dkjson:

luarocks install luasocket
luarocks install dkjson
luarocks install copas

Run the server:

lua server.lua

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

Install the UI plugin for web-based setup:

sudo npm install -g homebridge-config-ui-x

Then install the required accessory plugin:

sudo npm install -g homebridge-http-temperature-humidity

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"
    }
  ]
}

Start Homebridge with:

homebridge -I

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.

Share this post on :

Building an Android Application to Connect BleuIO via Serial Port Using Angular & Capacitor

In this tutorial, we will walk through the process of building an Android application that connects to the BleuIO USB dongle via a serial port using Capacitor 6, Angular, and TypeScript. This application allows users to send and receive AT commands to configure and interact with BLE (Bluetooth Low Energy) devices directly from an Android device.

BleuIO is a powerful BLE USB dongle that enables communication with Bluetooth devices, making it an excellent tool for developing, debugging, and testing BLE applications. With this project, we can use an Android device to send AT commands to BleuIO over a serial connection, making it possible to configure and manage BLE operations without requiring a PC. This project works both on BleuIO and BleuIO Pro

Why Use Capacitor, Angular, and TypeScript?

This project is built using Capacitor 6, which provides seamless access to native device functionality, including USB serial communication. Angular is chosen for its structured approach, and TypeScript ensures type safety and better maintainability.

Capacitor plugins allow us to communicate with hardware like USB devices, and in this project, we use the @adeunis/capacitor-serial plugin to establish the serial communication with the BleuIO dongle.

Project Features

  • Establish a serial connection with BleuIO on an Android device.
  • Send AT commands to BleuIO and read responses.
  • Scan for nearby BLE devices directly from the Android app.
  • Built with Angular + TypeScript for scalability and maintainability.
  • Uses Capacitor 6 for direct hardware interaction.

Prerequisites

Before starting, ensure you have the following installed:

  • Node.js 18+
  • Angular CLI (npm install -g @angular/cli)
  • Capacitor CLI (npm install -g @capacitor/cli)
  • Java 17 (required for Capacitor 6)
  • Android Studio
  • Android device with OTG support
  • BleuIO USB Dongle
  • OTG cable (to connect BleuIO to your Android device)

Cloning the Repository & Setting Up the Project

1. Clone the Repository

First, clone the GitHub repository and navigate to the project folder:

git clone https://github.com/smart-sensor-devices-ab/bleuio-serial-android-angular-typescript.git
cd bleuio-serial-android-angular-typescript

2. Install Dependencies

Run the following command to install all required dependencies:

npm install

3. Build the Angular Application

Next, build the Angular app for production:

ng build --configuration production

4. Sync with Capacitor for Android

After building the Angular app, we need to sync it with Capacitor so it can run on an Android device:

npx cap sync android

5. Open the Project in Android Studio

To launch the project in Android Studio, run:

npx cap open android

6. Run the Application on an Android Device

  1. Connect your Android device via USB.
  2. Enable USB debugging in Developer Options.
  3. Click Run in Android Studio to install and launch the app.

Using the Application

1. Connect to BleuIO

  • Press the Connect to BleuIO button.
  • The app will request USB permissions for BleuIO.
  • Once granted, a serial connection is established.

2. Send AT Command

  • Press the Send ATI Command button.
  • This command retrieves device information from BleuIO.
  • The response will be displayed on the screen.

3. Scan for BLE Devices

  • Press the Scan BLE Devices button.
  • This will send the AT+CENTRAL command, followed by AT+GAPSCAN=3.
  • The list of nearby BLE devices will be displayed.

Understanding the Code

1. Serial Service (Handling Communication with BleuIO)

The SerialService handles serial communication with BleuIO. It requests USB permissions, opens the connection, sends AT commands, and processes responses.

Key Methods

  • connectToSerial(): Requests OTG permission, establishes a serial connection, and listens for incoming data.
  • sendATCommand(): Sends AT commands and processes the response.
  • scanBLEDevices(): Sends the BLE scanning commands and displays detected devices.

2. App Component (User Interface Logic)

The AppComponent connects the UI with the SerialService to handle user interactions.

Key Features

  • Calls connectToSerial() when the user clicks Connect to BleuIO.
  • Calls sendATCommand() when the user clicks Send ATI Command.
  • Calls scanBLEDevices() when the user clicks Scan BLE Devices.

3. UI (HTML & Styling)

The application provides three buttons:

<button (click)="connect()">Connect to BleuIO</button>
<button (click)="sendATCommand()">Send ATI Command</button>
<button (click)="scanBLEDevices()">Scan BLE Devices</button>

A response area is updated dynamically:

<pre>{{ responseText }}</pre>

Troubleshooting & Common Issues

1. USB Permission Denied

  • Ensure that you grant permissions when prompted.
  • If the issue persists, check OTG support on your Android device.

2. App Not Detecting BleuIO

  • Verify that the OTG cable is properly connected.
  • Ensure that the BleuIO dongle is recognized by your Android device.

3. npx cap sync android Fails

  • Make sure that Android Studio and Java 17 are installed.
  • Run npm install again to ensure all dependencies are installed.

Output

Source code

Source code is available on this GitHub Repository.

This project demonstrates how to connect and communicate with BleuIO using Capacitor 6, Angular, and TypeScript on an Android device. By leveraging the @adeunis/capacitor-serial plugin, we can send AT commands, retrieve device information, and even scan for BLE devices directly from an Android app.

With this foundation, developers can expand the application by adding custom AT commands, integrating data logging, or even building more advanced BLE applications. The full source code is available on GitHub for further customization and enhancements.

Share this post on :

How to Use BleuIO as a USB Serial Device on Android with Capacitor

This project demonstrates how to use the BleuIO USB dongle as a serial port on an Android device via OTG (On-The-Go) USB. With Capacitor 6 and the @adeunis/capacitor-serial plugin, we establish a serial connection, send AT commands, and read responses in real time. This project works with both BleuIO and BleuIO Pro.

This tutorial provides a step-by-step guide to setting up serial communication with BleuIO on Android. The project connects to BleuIO via a serial port, sends basic ATI commands, and displays the received response on the screen. This example serves as a starting point for creating, testing, and debugging Bluetooth Low Energy (BLE) applications. It can be expanded to support additional commands and functionalities based on specific needs. The full source code is available for customization and further development.

Use Cases

The BleuIO USB dongle can be used in various applications that require serial communication on Android. One of the most common applications is Bluetooth Low Energy (BLE) development, where developers need to configure and test BLE modules by sending and receiving AT commands. This project allows for mobile debugging and real-time configuration of BLE devices without needing a PC.

Additionally, this setup is valuable for IoT and embedded systems, where devices communicate over a serial connection. With an Android phone acting as a serial terminal, engineers and developers can test, monitor, and debug hardware components in the field without requiring a laptop. Another important use case is USB-to-serial debugging, where embedded system engineers need to send commands and receive logs directly from an Android device using OTG.

For those working with sensor modules, microcontrollers, or custom embedded systems, this project simplifies the process of sending commands and reading responses directly from a mobile device. It also serves as an excellent starting point for developing Android applications that require serial communication through Capacitor.

Why Do We Need This?

Android devices do not natively support USB-to-serial communication. Unlike computers, which come with built-in serial drivers and terminal software, Android does not provide a direct way to communicate with serial devices over USB. This makes it difficult for developers, engineers, and embedded system designers to interact with BLE modules, sensors, and microcontrollers.

By using Capacitor 6 with the @adeunis/capacitor-serial plugin, we can bridge this gap and allow Android devices to function as serial terminals. This is especially useful when working with devices like BleuIO, where real-time communication is essential for configuring, testing, and debugging Bluetooth applications.

This project removes the need for external adapters or complex Android development, leveraging Capacitor’s web-based approach. It offers a simple and scalable way to integrate serial communication into mobile applications without requiring in-depth knowledge of Android’s USB APIs or native development tools.

Requirements

To use this project, you need a few essential components to establish serial communication between an Android device and BleuIO.

  • BleuIO or BleuIO Pro – The USB dongle used for Bluetooth Low Energy (BLE) communication. This project is designed to interact with BleuIO over a USB serial connection.
  • Android Device – A smartphone or tablet that supports USB OTG (On-The-Go), allowing it to function as a USB host and communicate with external devices.
  • OTG Cable or Adapter – Required to connect BleuIO to the Android device. Since most smartphones have USB-C or Micro-USB ports, an OTG adapter is needed to interface with the USB-A connector of BleuIO.

Installation & Setup

Install Node.js & Capacitor 6

BleuIO communication requires the @adeunis/capacitor-serial plugin, which is only compatible with Capacitor 6.

👉 First, install Node.js (if not installed):
Download Node.js

Create a Capacitor 6 Project

mkdir bleuio-serial
cd bleuio-serial
npm init -y
npm install @capacitor/core@6 @capacitor/cli@6

Install Android Platform

npm install @capacitor/android@6
npx cap add android

Install Serial Communication Plugin

npm install @adeunis/capacitor-serial

Ensure JDK 17 is Installed

Capacitor 6 requires JDK 17. Install it via:

sudo apt install openjdk-17-jdk  # Linux
brew install openjdk@17 # macOS

Verify installation:

java -version

It should output something like:

openjdk version "17.0.x"

OTG Permissions on Android

Why Do We Need OTG Permissions?

  • Android devices do not natively support serial USB communication.
  • OTG permissions allow USB host mode, so Android can communicate with external serial devices.

How to Enable OTG Permissions?

Modify AndroidManifest.xml:

<uses-feature android:name="android.hardware.usb.host"/>
<uses-permission android:name="android.permission.USB_PERMISSION"/>

Then, create device_filter.xml inside android/app/src/main/res/xml/:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="0x2DCF" product-id="0x6002" />
</resources>

These values match the BleuIO Vendor ID & Product ID, allowing the system to recognize it.

Project Code Explanation

index.html

This file provides buttons to connect to BleuIO and send AT commands.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BleuIO Serial Communication</title>
</head>
<body>
<h2>BleuIO Serial Communication</h2>

<button onclick="connectToSerial()">Connect to BleuIO</button>
<button onclick="sendATCommand()">Send ATI Command</button>

<p><strong>Response:</strong></p>
<pre id="response">Waiting for response...</pre>

<script src="index.js" defer></script>
</body>
</html>

index.js

This file:

  • Requests USB permission
  • Opens a serial connection
  • Sends and reads AT commands
  • Handles continuous data streaming from BleuIO
const Serial = window.Capacitor.Plugins.Serial;

// Vendor ID & Product ID for BleuIO
const VENDOR_ID = 0x2DCF;
const PRODUCT_ID = 0x6002;

let fullResponse = ""; // Stores incoming data

async function connectToSerial() {
console.log("Requesting permission for BleuIO...");

try {
const permissionResponse = await Serial.requestSerialPermissions({
vendorId: VENDOR_ID,
productId: PRODUCT_ID,
driver: "CDC_ACM_SERIAL_DRIVER"
});

if (!permissionResponse.granted) {
console.error("Permission denied!");
document.getElementById("response").textContent = "Permission denied!";
return;
}

console.log("Opening serial connection...");
await Serial.openConnection({
baudRate: 115200,
dataBits: 8,
stopBits: 1,
parity: 0,
dtr: true,
rts: true
});

console.log("Serial connection opened!");
document.getElementById("response").textContent = "Connected to BleuIO!";

fullResponse = ""; // Clear previous response

// Register read callback to receive data continuously
await Serial.registerReadCallback((message, error) => {
if (message && message.data) {
console.log("Received Data:", message.data);
fullResponse += message.data + "\n";
document.getElementById("response").textContent = fullResponse;
} else if (error) {
console.error("Read error:", error);
}
});

} catch (error) {
console.error("Connection error:", error);
document.getElementById("response").textContent = "Connection error.";
}
}

async function sendATCommand() {
console.log("Sending ATI command...");

try {
fullResponse = ""; // Clear response buffer
await Serial.write({ data: "ATI\r\n" });

console.log("ATI command sent!");
console.log("Waiting for response...");

} catch (error) {
console.error("Command error:", error);
}
}

// Expose functions globally
window.connectToSerial = connectToSerial;
window.sendATCommand = sendATCommand;

Running the App

Sync and Build

npx cap sync android
npx cap run android

Open the App

  1. Connect BleuIO to Android via OTG.
  2. Click “Connect to BleuIO” → A permission prompt should appear.
  3. Click “Send ATI Command” → Response should appear.

Final Results

After running, you should see output like:

Smart Sensor Devices AB
DA14695
BleuIO Pro
Firmware Version: 1.0.3b8

Dual role

Not Connected

Not Advertising

Output

Source code

https://github.com/smart-sensor-devices-ab/bleuio-serial-android

This project demonstrates how to use the BleuIO USB dongle as a serial communication device on an Android phone. By leveraging Capacitor 6 and the @adeunis/capacitor-serial plugin, we successfully established a serial connection, sent AT commands, and received responses via USB OTG.

This project is also available in typescript. Here is the source code with typescript

https://github.com/smart-sensor-devices-ab/bleuio-serial-android-typescript

With the growing demand for mobile-first development and hardware interaction via smartphones, this project provides a solid foundation for further expanding serial communication capabilities on Android.



Share this post on :

Monitoring Air Quality with BleuIO and Renesas RRH62000 on EK-RA4M2

In this tutorial, we demonstrate how to use the Renesas EK-RA4M2 MCU, the Renesas RRH62000 All-in-One Air Quality Sensor Module, and the BleuIO Bluetooth Low Energy (BLE) USB Dongle to collect and transmit air quality data wirelessly. The RRH62000 sensor module measures key environmental parameters such as:

  • eCO₂ (Equivalent Carbon Dioxide)
  • Humidity (%)
  • Temperature (°C)
  • Particulate Matter (PM1, PM2.5, PM10) (µm/cm³)
  • Total Volatile Organic Compounds (TVOC) (mg/m³)
  • Indoor Air Quality Index (IAQ)

The EK-RA4M2 MCU reads data from the sensor via I²C, processes the information, and transmits it over BLE using the BleuIO USB dongle. The advertised data can be monitored using BLE scanning applications, and the sensor values can also be displayed in RTTViewer on a connected PC.

Requirements

Before getting started, ensure you have the following hardware and software:

Hardware

Software

You can download the complete example project here:
➡️ GitHub Repository

Hardware Setup

Connecting EK-RA4M2 and BleuIO Dongle

  1. Connect EK-RA4M2 to your PC using a micro-USB cable via the J10 (Debug1) port.
  2. Plug the BleuIO dongle into a USB OTG cable and connect it to J11 (USB Full Speed) on the EK-RA4M2 board.
  3. Set jumpers:
    • Place J12 on pins 1-2.
    • Remove J15 completely.

Reference Diagram:

Connecting RRH62000 Air Quality Sensor

Connect the RRH62000 sensor module to EK-RA4M2 as follows:

  • Power: Connect 5V and GND from RRH62000 to 5V and GND on EK-RA4M2.
  • I²C Communication:
    • SCL (Clock) → SCL on EK-RA4M2
    • SDA (Data) → SDA on EK-RA4M2
    • GND → GND on EK-RA4M2

Reference Diagrams:


Importing the Project into e² Studio

  1. Open e² Studio IDE and choose a workspace. Click Launch.
  2. Download or clone the project from GitHub and place the “bleuio_ra4m2_rrh62000_example” folder inside your workspace.
  3. Go to File → Import and select Existing Projects into Workspace under the General tab.
  4. Click Browse… and locate the project folder.
  5. Select the project and click Finish to import it.

Importing Example Project:

Building and Running the Example

  1. Build the project by clicking the build icon.
  2. Set up debugging:
    • Click the down arrow next to the Debug icon and select Debug Configurations…
    • Under Renesas GDB Hardware Debugging, choose bleuio_ra4m2_sensor_rrh62000_example Debug_Flat and click Debug.


  3. Run the program:
    • Open RTTViewer and connect using the following settings:
      • Connection to J-Link: USB
      • Target Device: R7FA4M2AD
      • Interface & Speed: SWD, 4000 kHz
      • RTT Control Block Address: 0x200009dc
  4. In e² Studio, click Resume twice to start execution.
  5. The program starts running:
    • All LEDs turn on for 1 second, then only the red LED remains on.
    • The red LED turns off when BleuIO is initialized.
    • The green LED turns on when advertising starts.
  6. Sensor data is displayed in RTTViewer.

Scanning and Decoding BLE Advertising Data

Scan the Dongle using nRF Connect

Use a BLE scanning app like nRF Connect to view the advertised data:

Decoding the Advertising Message

Example raw BLE advertisement:

02010619FF3600016491803010300030105060306080192

All air quality sensor values except eCO2 is split into two bytes. The first byte is the whole number and the second byte is the decimal. For example
1649 is the temperature value. The whole number is 16 and the decimal is 49. Converting it from hex gives
us: 23.73 °C

The eCO2 value is 2 bytes, big endian.

Breaking it down:

DataDescriptionDecoded Value
020106Advertising flag (connectable)
19Message size
FFManufacturer Specific Data
3600Renesas Manufacturer ID (Little Endian)
Air Quality Advertised Data
1649Temperature (°C)23.73°C
1803Humidity (%RH)24.3% RH
0103IAQ Index1.3
0003TVOC (mg/m³)0.3 mg/m³
0105PM1 (µm/cm³)1.5
0603PM2.5 (µm/cm³)6.3
0608PM10 (µm/cm³)6.8
0192eCO₂ (ppm)402 ppm

This project successfully demonstrates how to use the BleuIO Bluetooth dongle, EK-RA4M2 MCU, and Renesas RRH62000 sensor to wirelessly monitor air quality. The BLE advertisements can be scanned and decoded to extract real-time air quality data.

For the full source code and updates, visit:
➡️ GitHub Repository

Share this post on :

Chat with HibouAir using BleuIO: Smart Air Quality Analysis with Google Technologies 

Indoor air quality is crucial for maintaining a healthy living and working environment. HibouAir is a powerful air quality monitoring device that provides real-time data on CO2 levels, temperature, humidity, and air pressure.  

This project demonstrates how BleuIO enables communication with HibouAir, allowing real-time environmental data to be retrieved, while Google’s Gemma model processes and analyzes the data to provide meaningful, easy-to-understand responses through a chat interface.

Users can interact with HibouAir’s smart assistant and ask questions such as:

  • “What is my room temperature?”
  • “What is the humidity level?”
  • “How is the air quality in my room?”

The system retrieves real-time sensor data from HibouAir and provides contextual recommendations based on environmental conditions.

By leveraging Google’s lightweight Gemma model, this project ensures efficient and intelligent analysis of air quality data, making it accessible for various applications, from smart homes to research.

Features of This Project

  • Live CO2, Temperature, Humidity, and Pressure Monitoring
  • Google-Powered Analysis for Meaningful Insights
  • Conversational Chat Interface (“Chat with HibouAir”)
  • Completely Local – No Internet Required
  • Lightweight & Efficient Processing with Gemma

Step-by-Step Guide: How It Works

Install Required Software

We need:

  • BleuIO for Bluetooth communication.
  • Ollama to run Google’s Gemma model locally.
  • HibouAir for air quality monitoring
  • Gemma for efficient data analysis and meaningful responses.

Install Python Dependencies

pip install flask bleuio

Install Ollama (For Local Processing)
For Mac/Linux:

curl -fsSL https://ollama.com/install.sh | sh

For Windows, download it from Ollama’s official site.

Install Google’s Gemma Model

ollama pull gemma

Why Gemma? What Are the Alternatives?

For this project, we chose Gemma, a lightweight, open-source model developed by Google, because it aligns with Google’s ecosystem and provides efficient, real-time insights for environmental data.

Why Use Google’s Gemma?

  • Optimized for Efficiency – Runs well on low-power machines without requiring cloud resources.
  • Google-Backed & Open Source – Developed by Google DeepMind, ensuring high-quality performance with full transparency.
  • No API Costs & Fully Local – No need for an internet connection or paid APIs, making it a cost-effective solution.
  • Designed for Meaningful Responses – Processes real-time air quality data and provides insightful, structured feedback.

Other Model Alternatives

  • Phi-2 – Even lighter but lacks detailed contextual understanding.
  • Llama3 – More powerful but requires more computational resources.
  • Mistral – Previously used, efficient, but not part of the Google ecosystem.

Connecting HibouAir via Bluetooth (BleuIO)

HibouAir continuously broadcasts CO2, temperature, humidity, and pressure via Bluetooth. We use BleuIO to scan and retrieve these values in real-time.

Setting Up the Chat Interface

Users can type questions like:

  • “What is the temperature?”
  • “What is my CO2 level?”
  • “How is the air quality?”

The system fetches real-time sensor data from HibouAir and provides Google-powered analysis and recommendations.

app.py (Backend)

  • This script:
  • Scans for HibouAir data
  • Extracts CO2, temperature, humidity, and pressure
  • Uses Google’s Gemma model for intelligent responses
  • Serves the chat interface via Flask
def chat():
    """Handles user input, fetches air quality data if needed, and returns response."""
    user_input = request.json.get("message", "").lower()

    with Manager() as manager:
        air_data = manager.dict({"co2": 0, "pressure": 0, "temperature": 0, "humidity": 0})
        process = Process(target=scan_for_air_quality_process, args=(air_data,))
        process.start()
        process.join()

        # Check for specific sensor queries
        if "temperature" in user_input:
            if air_data["temperature"] > 0:
                response = f"The current temperature in your room is {air_data['temperature']}°C."
            else:
                response = "⚠️ Unable to retrieve temperature data. Ensure HibouAir is in range."
            return jsonify({"response": response})

        elif "humidity" in user_input:
            if air_data["humidity"] > 0:
                response = f"The current humidity level in your room is {air_data['humidity']}%."
            else:
                response = "⚠️ Unable to retrieve humidity data. Ensure HibouAir is in range."
            return jsonify({"response": response})

        elif "pressure" in user_input:
            if air_data["pressure"] > 0:
                response = f"The current air pressure in your room is {air_data['pressure']} hPa."
            else:
                response = "⚠️ Unable to retrieve air pressure data. Ensure HibouAir is in range."
            return jsonify({"response": response})
        elif "co2" in user_input:
            if air_data["co2"] > 0:
                response = f"The current CO2 in your room is {air_data['co2']} ppm."
            else:
                response = "⚠️ Unable to retrieve co2 data. Ensure HibouAir is in range."
            return jsonify({"response": response})

        elif "air quality" in user_input :
            if air_data["co2"] > 0:
                prompt = (
                    f"The current air quality readings are:\n"
                    f"- CO2 Level: {air_data['co2']} ppm\n"
                    f"- Temperature: {air_data['temperature']}°C\n"
                    f"- Humidity: {air_data['humidity']}%\n"
                    f"- Pressure: {air_data['pressure']} hPa\n"
                    f"First give all the data. This is my room data. Give me short analysis on this data. and give me short suggestions "
                )
            else:
                return jsonify({"response": "⚠️ Unable to retrieve air quality data. Ensure HibouAir is in range and try again."})
        else:
            # Normal response for non-air quality queries
            prompt = user_input

    ai_response = subprocess.run(
        ["ollama", "run", "gemma", prompt],
        capture_output=True,
        text=True
    ).stdout.strip()

    return jsonify({"response": ai_response})

Get full source code from Github

index.html (Frontend – Chat Interface)

<div class="card">
        <div class="card-header">Chat with HibouAir</div>
        <div class="card-body">
          <div
            id="chatbox"
            class="border rounded p-3"
            style="height: 400px; overflow-y: auto; background: #f8f9fa"
          >
            <div class="alert alert-secondary">
              <b>HibouAir:</b> Ask me about air quality!
            </div>
          </div>
        </div>
      </div>

Expected Responses

You: “What is my CO2 level?”
HibouAir: “The current CO2 level is 850 ppm.”

You: “What is the air quality in my room?”
HibouAir:

CO2 Level: 850 ppm  
Temperature: 24°C  
Humidity: 55%  
Pressure: 1010 hPa  
Based on these readings, the air quality is good.

Using the Chat Interface

The web interface allows users to ask about specific values like temperature, humidity, pressure, CO2, or overall air quality.

Output

Get the Source Code

This project is open-source! You can access the full code and modify it for your own needs.

👉 [GitHub Repository]

This project showcases how HibouAir and BleuIO can be integrated to provide real-time air quality analysis in a way that is easy to understand. Instead of requiring users to interpret raw sensor data, the chat interface translates complex air quality values into clear, meaningful insights. Powered by Google’s Gemma model, it delivers simple and actionable responses—helping users understand their indoor air quality without needing to be experts.  

Share this post on :

Using BleuIO with Angular & TypeScript via Web Serial API

In modern web development, Bluetooth Low Energy (BLE) integration is becoming increasingly important for IoT applications. With the Web Serial API, we can now directly communicate with hardware devices like BleuIO using just a browser, eliminating the need for native drivers or middleware.

In this tutorial, we will demonstrate how to use Angular 19 and TypeScript to communicate with a BleuIO USB Dongle via Web Serial API. We will build a simple web application that connects to BleuIO, sends AT commands, and displays responses in real time.

This approach is beneficial because:

  • No additional backend services are required—everything runs directly in the browser.
  • Simplicity—Web Serial API allows communication with serial devices in JavaScript/TypeScript without native code.
  • Cross-platform compatibility—Works on any OS with Google Chrome or Microsoft Edge.

Why Use Angular & TypeScript?

Angular is a powerful framework for building scalable, maintainable, and modern web applications. TypeScript, which is the foundation of Angular, brings type safety and better tooling, making development smoother and reducing runtime errors.

By using Angular 19, we ensure that our application is built on the latest and most optimized framework version, taking advantage of standalone components and improved performance features.

Setting Up the Angular Project

To get started, ensure you have Node.js and Angular CLI installed.

Create an Angular Project

Run the following command to generate a new Angular 19 project:

new angular-bleuio --strict
cd angular-bleuio

Choose No for routing and CSS as the styling option.

add Bootstrap to styles.css:

@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css');

This ensures our application has a clean, professional UI.

Building the BleuIO Web Serial Connection

We will now implement the core functionality:

  1. Connect to BleuIO via Web Serial API.
  2. Send AT commands and receive responses.
  3. Display formatted responses using delimiters.

Creating the Angular Component

In src/app/app.component.ts, replace the contents with:

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true, //
  imports: [FormsModule],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  port: any | undefined;
  writer: WritableStreamDefaultWriter | undefined;
  reader: ReadableStreamDefaultReader | undefined;
  command: string = '';
  response: string = 'Not connected.';

  async connectToBleuIO() {
    try {
      if (!('serial' in navigator)) {
        this.response = 'Web Serial API not supported in this browser.';
        return;
      }

      this.port = await (navigator as any).serial.requestPort();
      await this.port.open({ baudRate: 115200 });

      this.response = 'Connected to BleuIO.';
      this.writer = this.port.writable?.getWriter();
      this.reader = this.port.readable?.getReader();

      this.readData();
    } catch (error) {
      console.error('Connection failed:', error);
      this.response = 'Failed to connect.';
    }
  }

  async sendCommand() {
    if (!this.port || !this.writer || !this.command) {
      this.response = 'Not connected or empty command.';
      return;
    }

    try {
      // Reset the response before sending a new command
      this.response = 'Waiting for response...';

      // Clear any old responses before sending new data
      if (this.reader) {
        await this.reader.cancel();
        this.reader.releaseLock();
        this.reader = this.port.readable?.getReader(); // Reinitialize reader
      }

      // Send the AT command
      const encoder = new TextEncoder();
      await this.writer.write(encoder.encode(this.command + '\r\n'));

      this.response = 'Command sent: ' + this.command;
      this.readData(); // Start reading the new response
    } catch (error) {
      console.error('Send error:', error);
      this.response = 'Error sending command.';
    }
  }

  async readData() {
    if (!this.reader) return;

    const decoder = new TextDecoder();
    let fullResponse = '';

    try {
      while (true) {
        const { value, done } = await this.reader.read();
        if (done) break;

        let textChunk = decoder.decode(value);
        fullResponse += textChunk; // Append new data to the response

        this.response = fullResponse
          .replace(/(\w)\.(\s[A-Z])/g, '$1.\n$2') // Add newline after full stops (but not inside numbers)
          .replace(
            /(BleuIO|Firmware Version|Peripheral role|Central role|Not Connected|Not Advertising)/g,
            '\n$1'
          ); // New line before keywords
      }
    } catch (error) {
      console.error('Read error:', error);
    }
  }
}

Creating the UI

Modify src/app/app.component.html:

<div class="container text-center mt-5">
  <h1 class="mb-4 text-primary">
    Angular & TypeScript: Connect to BleuIO via Serial port
  </h1>

  <!-- Connect Button -->
  <button class="btn btn-primary mb-3" (click)="connectToBleuIO()">
    Connect to BleuIO
  </button>

  <div class="input-group mb-3 w-50 mx-auto">
    <input
      type="text"
      [(ngModel)]="command"
      class="form-control"
      placeholder="Enter AT Command"
    />
    <button class="btn btn-success" (click)="sendCommand()">
      Send Command
    </button>
  </div>

  <h3 class="mt-4">Response:</h3>

  <div
    class="border p-3 w-75 mx-auto bg-light rounded shadow-sm"
    style="white-space: pre-line"
  >
    {{ response }}
  </div>
</div>

Update src/main.ts

Replace its contents with:

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { importProvidersFrom } from '@angular/core';
import { FormsModule } from '@angular/forms';

bootstrapApplication(AppComponent, {
  providers: [importProvidersFrom(FormsModule)],
}).catch((err) => console.error(err));

Running the Project

Start the Angular app:

ng serve

Open Google Chrome and go to http://localhost:4200/.
Test by clicking “Connect to BleuIO”, entering an AT command, and clicking “Send Command”.

Complete Source Code

Find the full source code on GitHub:
🔗 GitHub Repository

Output

Use Cases: Why This is Helpful

  • This tutorial is beneficial for developers who:
  • Want a simple, browser-based way to communicate with BleuIO.
  • Need a cross-platform solution that works on Windows, macOS, and Linux.
  • Are building IoT applications that require BLE communication.
  • Want to use Angular & TypeScript for a scalable frontend solution.

This tutorial demonstrates how to use Angular 19, TypeScript, and the Web Serial API to communicate with BleuIO. The project is lightweight, scalable, and works entirely in the browser—perfect for IoT applications!

Share this post on :

Smart CO2-Based Fan Control Using BleuIO and Renesas RA4M2

This project showcases how to integrate the Renesas EK-RA4M2 microcontroller with a BleuIO BLE USB dongle to create a smart air ventilation system. By using HibouAir‘s CO2 parameter, an air quality monitoring device, the system continuously monitors indoor air quality and automatically controls a fan based on CO2 levels.

The BleuIO dongle scans for HibouAir’s BLE advertising data to retrieve real-time CO2 readings. When the CO2 concentration exceeds 600 ppm, the system activates the fan to improve air circulation. Once the CO2 level drops below 550 ppm, the fan is turned off to conserve energy.

This implementation demonstrates a practical IoT-based air quality control solution, making indoor environments healthier and more efficient.

The EK-RA4M2 board prints the CO2 values, as they change, on the RTTViewer.

Requirements

Setup

  • Connect a Micro USB device cable (type-A male to micro-B male) between J10 (Debug1) and a Computer USB port.
  • Plug in a BleuIO Dongle in the USB OTG Cable (type-A female to micro-B male) and connect it to J11 (USB Full Speed).
  • Make sure Jumper J12 is placed on pins 1-2
  • Remove Jumper J15 pins
  • Connect the fan power adapter to 3V3 and GND on the developer kit like this:

  • The fan power adapter will also need to be connected to GPIO pin 505 on the developer kit to turn it on and off:

Importing project

  • Open e² studio IDE
  • Choose a workspace and click ‘Launch’
  • Download or clone the example project. Place the folder ‘bleuio_ra4m2_fan_example’ in workspace.
  • Choose Import Project
  • Select ‘Existing Projects into Workspace’ under the ‘General’ tab:
  • Click the ‘Browse…’ button and open the folder where the ‘bleuio_ra4m2_fan_example’ project folder is located:
  • Finally select the project and click ‘Finish’. You have now imported the the project!

Running the example

  • Go to file ‘usb_hcdc_app.c’ under ‘src/’ and edit line 41 to the board ID of the HibouAir Sensor:
  • #define BOARD_ID_TO_SCAN “2202B3”
  • The board ID is printed on the back of the HibouAir sensor:
  • You can also threshold values to change when the fan should start and stop.

    The defines can be found on row 45 and 47 in ‘usb_hcdc_app.c’ under ‘src/’:
  • /* CO2 threshold value 1. If at this value or above, the fan will start. */
    #define CO2_FAN_ROOF 600
    /* CO2 threshold value 2. If at this value or below, the fan will stop. */
    #define CO2_FAN_FLOOR 550

Build the project by clicking the building icon:

  • Use Debug to download and run the project. The first time you need to configure the debug settings. Click down arrow to the right of the Debug icon and select ‘Debug Configurations…’


Under ‘Renesas GDB Hardware Debugging’ select ‘bleuio_ra4m2_fan_example.elf’ and click ‘Debug’

  • The debug is now configured and the ‘Debug’ icon can be used next time to run the project.
  • Open RTTViewer. Connect and use these settings:

    Connection to J-Link: USB

    Specify Target Device: R7FA4M2AD

    Target Interface & Speed: SWD 4000kHz

    RTT Control Block: Address 0x2000095c


On the debugger screen in e² studio click the ‘Resume’ icon twice to run the project.

  • The application is now running. When starting up you should notice all LEDs lighting up for one second then only the red LED will be on. It will turn off as soon as the BleuIO is configured.
  • You should now see the output on the RTTViewer. 


If CO2 value is 600ppm or above, the fan will turn on.

If CO2 value is 550ppm or below, the fan will turn off.


The LEDs will light up like the previous CO2 Monitor Example:

When the CO2 level is less than 600 ppm only the blue LED will be turned on.


If the CO2 level is over 600 ppm but below 1000 ppm then the green LED will be on.


If the CO2 level is above 1000 ppm then the red LED will be on.

Share this post on :

Building a Secure Proximity-Based Login System with Bluetooth Low Energy (BLE)

Bluetooth Low Energy (BLE) provides a powerful mechanism to enhance security through proximity-based authentication. This tutorial showcases how to create a secure login system using Node.js, Express, and BLE technology. The project demonstrates how BLE scanning can verify the presence of an authorized BLE device (such as Close Beacon) in range before granting access. This offers an extra layer of protection for your applications.

Why This Project?

With the increasing need for secure authentication mechanisms, BLE (Bluetooth Low Energy) offers a lightweight, reliable, and energy-efficient solution. This project demonstrates how you can utilize a BleuIO USB dongle to securely check the proximity of a specific Close Beacon’s mac address before granting access to sensitive resources.

Key Features:

  • Node.js Integration: Simplifies server-side logic using JavaScript.
  • BLE Device Scanning: Checks for the presence of an authorized device based on its MAC address.
  • Flexible Hardware Support: While this project uses Close Beacon, any BLE device can be used for authentication.
  • Enhanced Security: Adds an additional layer of security to the login process.
  • Practical Example: Serves as a foundation for more advanced BLE-based applications.

Practical Use Cases

  • Extra Security Layer: Combine BLE proximity detection with traditional authentication methods for added security.
  • Access Control Systems: Enable access to sensitive areas only when an authorized device is nearby.
  • BLE-Powered IoT Applications: Use BleuIO for real-time device monitoring and communication.

Project Overview

This project uses:

  • Node.js for server-side scripting.
  • Express for handling routes and server logic.
  • BleuIO USB Dongle for BLE scanning.
  • A BLE Device (e.g., Close Beacon): Ensure the device is powered on and within range.
  • Bootstrap for a simple and clean UI.

When the user clicks the “Login Securely” button, the application:

  • Connects to the BleuIO dongle via a serial port (tested on macOS; Windows code is commented in the source).
  • Puts the dongle into central mode with the AT+CENTRAL command.
  • Sets an RSSI filter (AT+FRSSI=-56) to filter out weak signals.
  • Initiates a BLE scan for nearby devices (AT+GAPSCAN=3).
  • Checks if a target device with a specific MAC address is present in the scan results.
  • Logs the user into the dashboard if the device is nearby; otherwise, it denies access.

Setting Up the Project

Prerequisites

  1. BleuIO Dongle: Ensure the BleuIO dongle is plugged into your computer.
  2. Node.js: Install Node.js from nodejs.org.
  3. Dependencies: Install express and serialport using npm:
    npm install express serialport

How It Works

Step 1: Frontend (HTML and JavaScript)

The index page displays a button, “Login to dashboard securely.” When clicked, it sends a request to the server to scan for BLE devices.

index.html:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
      crossorigin="anonymous"
    />

    <title>Login Securely</title>
  </head>
  <body>
    <div class="container text-center mt-5">
      <br />
      <div class="row">
        <div class="col-md-6 offset-md-3">
          <p class="lead">
            This login checks if an authorized BLE device is nearby. The
            <strong>BleuIO</strong>
            dongle scans for the authorized device's MAC address. If it is
            nearby, the system will log you into the dashboard.
          </p>
        </div>
      </div>

      <br />
      <button class="btn btn-success btn-lg" id="scanBtn">
        Login to dashboard securely
      </button>
      <div id="listScan"></div>
    </div>

    <script>
      document.getElementById('scanBtn').addEventListener('click', () => {
        fetch('/scanbledevice')
          .then((response) => response.json())
          .then((data) => {
            // Log the response for debugging
            console.log(data.message);

            // Redirect to /dashboard if the device is nearby
            if (data.success) {
              window.location.href = '/dashboard';
            } else {
              alert(data.message); // Show a message if the device is not nearby
            }
          })
          .catch((err) => {
            console.error('Error:', err);
            alert('An error occurred while scanning for devices.');
          });
      });
    </script>
  </body>
</html>

dashboard.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
      crossorigin="anonymous"
    />
    <title>Dashboard</title>
  </head>
  <body>
    <div class="container">
      <h3 class="mt-5">Welcome to Smart Dashboard</h3>
      <br /><br />
      <a href="/" class="btn btn-danger btn-lg" id="logoutBtn">Logout</a>
    </div>
    <script>
      document
        .getElementById('logoutBtn')
        .addEventListener('click', (event) => {
          event.preventDefault(); // Prevent default navigation
          fetch('/disconnect')
            .then((response) => response.json())
            .then((data) => {
              if (data.success) {
                console.log(data.message);
                window.location.href = '/'; // Redirect to the index page
              } else {
                alert('Failed to disconnect: ' + data.message);
              }
            })
            .catch((err) => {
              console.error('Error during disconnect:', err);
              alert('An error occurred during logout.');
            });
        });
    </script>
  </body>
</html>

Step 2: Backend Logic

The server handles requests to scan for BLE devices, manage the serial port connection, and redirect users to the dashboard.

server.js:

// server.js
const express = require('express');
const { scanBLE, disconnectBLE } = require('./script');

const app = express();
const port = 3000;

// Serve the HTML file
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});
app.get('/dashboard', (req, res) => {
  res.sendFile(__dirname + '/dashboard.html');
});
app.get('/disconnect', (req, res) => {
  disconnectBLE()
    .then((message) => {
      res.json({ success: true, message });
    })
    .catch((err) => {
      console.error('Error during disconnect:', err);
      res
        .status(500)
        .json({ success: false, message: 'Failed to disconnect serial port' });
    });
});

// Endpoint to fetch the list of serial ports
app.get('/scanbledevice', (req, res) => {
  scanBLE()
    .then((bleDevice) => {
      const targetMacAddress = '[1]D1:79:29:DB:CB:CC';

      // Check if the target device is nearby
      const isNearby = bleDevice.some((line) =>
        line.includes(targetMacAddress)
      );

      // Respond with a message
      if (isNearby) {
        res.json({
          success: true,
          message: `Device ${targetMacAddress} is nearby.`,
        });
      } else {
        res.json({
          success: false,
          message: `Login Failed ! Device ${targetMacAddress} is not nearby.`,
        });
      }
    })
    .catch((err) => {
      console.error('Error during BLE scan:', err);
      res
        .status(500)
        .json({ success: false, message: 'Error fetching BLE devices.' });
    });
});

// Start the server
app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

Step 3: BLE Scanning Logic

The script connects to the BleuIO dongle, configures it, and scans for nearby devices.

script.js:

// script.js
const { SerialPort } = require('serialport');
var firstPort = '';
var readDataArray = [];
var openPort;
// Function to fetch the ble device list
function scanBLE() {
  return new Promise((resolve, reject) => {
    SerialPort.list()
      .then((ports) => {
        //filter ports to get BleuIO path
        // windows
        /* result = ports.filter(
            (portVal) =>
              portVal.pnpId && portVal.pnpId.includes("VID_2DCF&PID_6002")
          ); */
        //Mac
        result = ports.filter(
          (portVal) =>
            portVal.manufacturer &&
            portVal.manufacturer.includes('Smart Sensor Devices')
        );
        // get the first port path of the BleuIO connected to computer
        firstPort = result[0] && result[0].path;
        openPort = new SerialPort({ path: firstPort, baudRate: 115200 });
        // function to read serial port data
        const readData = (dm) => {
          return new Promise((resolve, reject) => {
            openPort.on('readable', () => {
              let data = openPort.read();
              let enc = new TextDecoder();
              let arr = new Uint8Array(data);
              let rawString = enc.decode(arr);

              // Split response into lines and trim extra spaces or empty lines
              let lines = rawString
                .split(/[\r\n]+/)
                .filter((line) => line.trim() !== '');
              readDataArray.push(...lines);

              // Log each line for better readability
              lines.forEach((line) => console.log(line));

              if (rawString.includes(dm)) {
                return resolve(readDataArray);
              } else {
                return resolve(readDataArray);
              }
            });
          });
        };

        // put the dongle to central role
        openPort.write('AT+CENTRAL\r', (err) => {
          if (err) {
            return reject(
              new Error('Error setting dongle to central role ' + err.message)
            );
          } else {
            // Set the RSSI filter
            openPort.write('AT+FRSSI=-56\r', (err) => {
              if (err) {
                return reject(
                  new Error('Error setting RSSI filter ' + err.message)
                );
              } else {
                // Scan for BLE devices for three seconds
                openPort.write('AT+GAPSCAN=3\r', (err) => {
                  if (err) {
                    return reject(
                      new Error('Error initiating BLE scan ' + err.message)
                    );
                  } else {
                    setTimeout(() => {
                      resolve(readData('SCAN COMPLETE'));
                    }, 3500);
                  }
                });
              }
            });
          }
        });
      })
      .catch((err) => {
        console.error('Error listing serial ports:', err);
        reject(err);
      });
  });
}
function disconnectBLE() {
  return new Promise((resolve, reject) => {
    if (openPort && openPort.isOpen) {
      openPort.close((err) => {
        if (err) {
          console.error('Error closing serial port:', err);
          return reject(err);
        }
        console.log('Serial port disconnected.');
        resolve('Disconnected');
      });
    } else {
      console.log('No serial port to disconnect.');
      resolve('No port to disconnect');
    }
  });
}

module.exports = { scanBLE, disconnectBLE };

Full source code is available at github. Source code

Output

This project is an example of how to integrate BleuIO with Node.js to build a BLE-powered secure login system. The source code is available and can be adapted for your use case. Start experimenting with BleuIO today and unlock the potential of BLE in your applications!

Share this post on :

Real-Time CO₂ Monitoring App with Go and BleuIO

Awareness of Air quality monitoring importance for health and productivity has been increasing lately, especially in indoor environments like offices and homes. In this tutorial, we’ll demonstrate how to create a real-time CO₂ monitoring application using Go, a modern programming language with a vibrant community, alongside the BleuIO BLE USB dongle and HibouAir, a BLE-enabled air quality sensor.

This project showcases how to use Go’s simplicity and performance to build an efficient application that scans for CO₂ data, decodes it, and provides real-time notifications on macOS when the CO₂ level exceeds a critical threshold. By using BleuIO’s integrated AT commands, you can focus on your application logic without worrying about complex embedded BLE programming.

Project Overview

The goal of this project is to:

  1. Use BleuIO to scan for BLE advertisements from HibouAir, which broadcasts real-time CO₂ levels.
  2. Decode the advertised data to extract CO₂ concentration.
  3. Send a real-time macOS notification when CO₂ levels exceed a specified threshold (1000 ppm in this example).

Notifications are implemented using the macOS osascript utility, ensuring you are immediately alerted about high CO₂ levels on your laptop screen.

Why This Project Is Useful

When you’re focused on work, you might not notice subtle changes in your environment. This application ensures you’re notified directly on your laptop screen when CO₂ levels become unsafe. This is especially helpful for:

  • Office Workers: Monitor meeting rooms or shared spaces where ventilation may be insufficient.
  • Remote Workers: Ensure a healthy workspace at home without distractions.
  • Educational Settings: Keep classrooms or labs safe for students and staff.

Technical Details

Tools and Devices

  • Programming Language: Go – Chosen for its simplicity, performance, and active community.
  • BLE USB Dongle: BleuIO – Simplifies BLE communication with built-in AT commands.
  • CO₂ Monitoring Device: HibouAir – Provides real-time air quality metrics over BLE.

How It Works

  1. Initialize the Dongle:
    • Set the BleuIO dongle to the central role to enable scanning for BLE devices.
  2. Scan for Advertised Data:
    • Use the AT+FINDSCANDATA command to scan for HibouAir’s advertisements containing air quality data.
  3. Decode CO₂ Information:
    • Extract and convert the relevant part of the advertisement to get the CO₂ level in ppm.
  4. Send Notifications:
    • Use Go’s exec.Command to invoke macOS osascript and display a desktop notification if the CO₂ level exceeds the threshold.

Implementation

Here is the source code for the project:

package main

import (
"bufio"
"fmt"
"log"
"os/exec"
"strconv"
"strings"
"time"

"go.bug.st/serial"
)

func main() {
// Open the serial port
mode := &serial.Mode{
BaudRate: 9600,
}
port, err := serial.Open("/dev/cu.usbmodem4048FDE52CF21", mode)
if err != nil {
log.Fatalf("Failed to open port: %v", err)
}
defer port.Close()

// Initial setup: Set the dongle to central mode
err = setupDongle(port)
if err != nil {
log.Fatalf("Failed to set up dongle: %v", err)
}

// Repeatedly scan for advertised data and process it
for {
err := scanAndProcessData(port)
if err != nil {
log.Printf("Error during scan and process: %v", err)
}
time.Sleep(10 * time.Second) // Wait before the next scan (interval)
}
}

// setupDongle sets the dongle to central mode
func setupDongle(port serial.Port) error {
_, err := port.Write([]byte("AT+CENTRAL\r"))
if err != nil {
return fmt.Errorf("failed to write AT+CENTRAL: %w", err)
}
time.Sleep(1 * time.Second) // Ensure the command is processed

buf := make([]byte, 100)
_, err = port.Read(buf)
if err != nil {
return fmt.Errorf("failed to read response from AT+CENTRAL: %w", err)
}

fmt.Println("Dongle set to central mode.")
return nil
}

// scanAndProcessData scans for advertised data and processes it
func scanAndProcessData(port serial.Port) error {
_, err := port.Write([]byte("AT+FINDSCANDATA=220069=2\r"))
if err != nil {
return fmt.Errorf("failed to write AT+FINDSCANDATA: %w", err)
}

time.Sleep(3 * time.Second) // Wait for scan to complete

buf := make([]byte, 1000)
n, err := port.Read(buf)
if err != nil {
return fmt.Errorf("failed to read scan response: %w", err)
}

response := string(buf[:n])

// Extract the first advertised data
firstAdvertisedData := extractFirstAdvertisedData(response)
if firstAdvertisedData == "" {
fmt.Println("No advertised data found.")
return nil
}

// Extract the specific part (6th from last to 3rd from last) and convert to decimal
if len(firstAdvertisedData) >= 6 {
extractedHex := firstAdvertisedData[len(firstAdvertisedData)-6 : len(firstAdvertisedData)-2]

decimalValue, err := strconv.ParseInt(extractedHex, 16, 64)
if err != nil {
return fmt.Errorf("failed to convert hex to decimal: %w", err)
}
fmt.Printf("CO₂ Value: %d ppm\n", decimalValue)

// Send notification if CO₂ value exceeds 1000
if decimalValue > 1000 {
sendNotification("CO₂ Alert", fmt.Sprintf("High CO₂ level detected: %d ppm", decimalValue))
}
} else {
fmt.Println("Advertised data is too short to extract the desired part.")
}
return nil
}

// extractFirstAdvertisedData extracts the first advertised data from the response
func extractFirstAdvertisedData(response string) string {
scanner := bufio.NewScanner(strings.NewReader(response))
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "Device Data [ADV]:") {
parts := strings.Split(line, ": ")
if len(parts) > 1 {
return parts[1]
}
}
}
if err := scanner.Err(); err != nil {
log.Printf("Error scanning response: %v", err)
}
return ""
}

// sendNotification sends a macOS notification with the specified title and message
func sendNotification(title, message string) {
script := `display notification "` + message + `" with title "` + title + `"`
cmd := exec.Command("osascript", "-e", script)
err := cmd.Run()
if err != nil {
log.Printf("Error sending notification: %v", err)
}
}

Source code

Source code is available on https://github.com/smart-sensor-devices-ab/monitor-realtime-co2-go

Output

This project demonstrates how to build a real-time CO₂ monitoring application using Go, BleuIO, and HibouAir. By using Go’s capabilities and BleuIO’s ease of use, you can focus on the logic of your application and quickly adapt the solution to your specific needs.

Share this post on :

Building a BLE Real-Time macOS Menu Bar App Using BleuIO

In this tutorial, we will guide you through creating a BLE real-time macOS menu bar application using the BleuIO USB BLE dongle. BleuIO is an incredibly versatile tool that simplifies the development of BLE (Bluetooth Low Energy) applications, making it ideal for developers looking to build innovative projects with ease.

macOS menu bar applications offer a seamless way to monitor and interact with data in real time without requiring a dedicated application window. By leveraging the power of the BleuIO dongle, we can create a menu bar app that provides live updates on environmental metrics like temperature, humidity, and CO2 levels. This project demonstrates how BleuIO can be integrated into real-time applications, showcasing its potential for BLE-based projects.

Why is This Project Useful?

  • Real-Time Updates: The app fetches BLE data at regular intervals and updates the macOS menu bar dynamically.
  • Ease of Access: The macOS menu bar provides a non-intrusive interface, allowing users to access live data at a glance.
  • Extensibility: This tutorial serves as a starting point for developers to explore more advanced BLE applications with BleuIO.

Requirements

To complete this project, you will need:

  1. BleuIO USB BLE Dongle: A powerful and easy-to-use BLE dongle for developing BLE applications.
  2. HibouAir – Air Quality Monitor: A BLE-enabled air quality monitor that broadcasts real-time environmental data such as temperature,pressure,voc,light, humidity, and CO2 levels.
  3. macOS System: A macOS device with Python 3 installed.
  4. Python Libraries:
    • rumps: For creating macOS menu bar applications.
    • bleuio: For communicating with the BleuIO dongle.

How Real-Time Updates Are Handled

The app connects to the BleuIO dongle and scans for BLE advertisements air quality data from HibouAir. Using a timer, the app periodically initiates a scan every 2 minutes. The decoded data is then displayed directly in the macOS menu bar, providing real-time updates without user intervention.

Step-by-Step Guide

Step 1: Set Up the Environment

  1. Ensure you have a macOS system with Python 3 installed.
  2. Install the necessary dependencies using pip:
    pip install rumps bleuio
  3. Plug in your BleuIO USB dongle.

Step 2: Project Overview

Our goal is to:

  • Connect to the BleuIO dongle.
  • Put the dongle in Central Mode to scan for BLE advertisements.
  • Scan for real time air quality data from HibouAir
  • Decode the advertisement data to extract temperature, humidity, pressure, and CO2 levels.
  • Update the macOS menu bar with the decoded data in real time.

Step 3: Writing the Code

Below is the Python script for the macOS menu bar app. This code handles the dongle initialization, data scanning, decoding, and menu updates.

import rumps
import time
import json
from datetime import datetime
from bleuio_lib.bleuio_funcs import BleuIO
boardID="220069"

# Function to decode advertisement data
def adv_data_decode(adv):
    try:
        pos = adv.find("5B0705")
        if pos == -1:
            raise ValueError("Invalid advertisement data: '5B0705' not found.")

        dt = datetime.now()
        current_ts = dt.strftime("%Y/%m/%d %H:%M:%S")

        # Temperature decoding
        temp_hex = int(adv[pos + 22:pos + 26][::-1], 16)  # Reversed bytes
        if temp_hex > 1000:
            temp_hex = (temp_hex - (65535 + 1)) / 10
        else:
            temp_hex = temp_hex / 10

        # Pressure decoding (convert from little-endian)
        pressure_bytes = bytes.fromhex(adv[pos + 18:pos + 22])
        pressure = int.from_bytes(pressure_bytes, byteorder='little') / 10

        # Humidity decoding (convert from little-endian)
        humidity_bytes = bytes.fromhex(adv[pos + 26:pos + 30])
        humidity = int.from_bytes(humidity_bytes, byteorder='little') / 10

        return {
            "boardID": adv[pos + 8:pos + 14],
            "pressure": pressure,
            "temp": temp_hex,
            "hum": humidity,
            "co2": int(adv[pos + 46:pos + 50], 16),
            "ts": current_ts,
        }
    except Exception as e:
        print(f"Error decoding advertisement data: {e}")
        return {}


# Callback function for scan results
def my_scan_callback(scan_input):
    try:
        scan_result = json.loads(scan_input[0])
        data = scan_result.get("data", "")

        decoded_data = adv_data_decode(data)

        # Update menu with decoded data
        app.update_menu(decoded_data)

    except Exception as e:
        print(f"Error parsing scan result: {e}")

# Callback function for events
def my_evt_callback(evt_input):
    cbTime = datetime.now()
    currentTime = cbTime.strftime("%H:%M:%S")
    print(f"\n\n[{currentTime}] Event: {evt_input}")

class AirQualityApp(rumps.App):
    def __init__(self):
        super(AirQualityApp, self).__init__("CO2 : 550")
        self.my_dongle = None  # Placeholder for the dongle object
        self.co2_item = rumps.MenuItem(title="CO2 : 550ppm", callback=lambda _: None)
        self.temp_item = rumps.MenuItem(title="Temperature: 25°C", callback=lambda _: None)
        self.hum_item = rumps.MenuItem(title="Humidity: 65 %rh", callback=lambda _: None)
        self.press_item = rumps.MenuItem(title="Pressure: 1000 mbar", callback=lambda _: None)

        self.menu = [
            self.co2_item,
            self.temp_item,
            self.hum_item,
            self.press_item,
            None  # Separator
        ]

        # Establish connection and start scanning on startup
        self.connect_dongle()
        self.start_periodic_scan()

    def connect_dongle(self):
        try:
            self.my_dongle = BleuIO()  # Initialize the dongle
            self.my_dongle.register_evt_cb(my_evt_callback)  # Register event callback
            self.my_dongle.register_scan_cb(my_scan_callback)  # Register scan callback
            print("Dongle connected successfully.")

            # Set the dongle to central mode
            response = self.my_dongle.at_central()
            print("Dongle is now in central mode.")

        except Exception as e:
            print(f"Error connecting to dongle: {e}")

    def scan(self, _=None):  # Added `_` to accept the timer argument
        try:
            # Start scanning for specific data
            response = self.my_dongle.at_findscandata(boardID, 3)
            print(f"Scan initiated. Response: {response.Rsp}")
        except Exception as e:
            print(f"Error during scan: {e}")

    def start_periodic_scan(self):
        try:
            rumps.timer(120)(self.scan)  # Run the scan method every 30 seconds
        except Exception as e:
            print(f"Error setting up periodic scan: {e}")

    def update_menu(self, decoded_data):
        try:
            self.title = f"CO2 : {decoded_data.get('co2', 'N/A')}ppm"  # Update app title
            self.co2_item.title = f"CO2 : {decoded_data.get('co2', 'N/A')}ppm"
            self.temp_item.title = f"Temperature: {decoded_data.get('temp', 'N/A')}°C"
            self.hum_item.title = f"Humidity: {decoded_data.get('hum', 'N/A')} %rh"
            self.press_item.title = f"Pressure: {decoded_data.get('pressure', 'N/A')} mbar"
        except Exception as e:
            print(f"Error updating menu: {e}")

if __name__ == "__main__":
    app = AirQualityApp()
    app.run()

Note : Make sure to change the BoardID to your HibouAir CO2 device on line 6

Step 4: Run the App

  1. Save the script as bleuio.py.
  2. Run the script using:
    python bleuio.py
  3. The app will appear in the macOS menu bar with latest CO2 value. Click the icon to view the live BLE data updates.

Output

Extending the Project

This project is a foundation for exploring the capabilities of BleuIO. You can extend it to:

  • Monitor additional BLE devices.
  • Implement alert notifications for specific data thresholds.
  • Log the data to a file or send it to a cloud service for further analysis.

This tutorial demonstrates how to create a real-time macOS menu bar application using the BleuIO dongle. By following this guide, you’ll not only learn how to handle BLE data but also understand how to integrate it into user-friendly macOS applications. BleuIO opens up endless possibilities for BLE-based projects, and we’re excited to see what you create next!

Share this post on :