Streaming BLE Sensor Data into Microsoft Power BI using BleuIO

In this project, we demonstrate how to stream Bluetooth Low Energy (BLE) sensor data directly into Microsoft Power BI using the BleuIO USB dongle. By combining a HibouAir environmental sensor with BleuIO and a simple Python script, we can capture live readings of CO2, temperature, and humidity and display them in real time on a Power BI dashboard.

The goal of this project is to make BLE data visualization simple and accessible. Instead of dealing with complex server setups or containers, BleuIO provides an easy way to turn raw BLE advertising packets into meaningful insights that anyone can understand at a glance.

Why Power BI?

Microsoft Power BI is a business analytics platform designed to turn raw data into interactive dashboards and reports. One of its most powerful features is the ability to handle real-time streaming datasets, allowing live updates from sensors or IoT devices.

For IoT developers and organizations, this is a game-changer. Imagine watching air quality readings from your office appear in real time, or combining BLE sensor data with other business metrics to get a fuller picture of your environment. By using BleuIO as a BLE-to-cloud bridge, developers can integrate IoT data into Power BI dashboards quickly, without heavy infrastructure.

Requirements

To follow this tutorial, you will need:

  • A BleuIO USB dongle.
  • A HibouAir air quality monitor (for CO2, temperature, and humidity).
  • A free or paid Microsoft Power BI account.
  • The Python libraries pyserial and requests, which can be installed with: pip install pyserial requests

Setup: Power BI Streaming Dataset

Before writing any code, we need to set up a streaming dataset in Power BI.

  1. Log in to Power BI Service and go to My workspace.
  2. Select New → Streaming dataset → API.
  3. Define the fields you’ll collect from the sensor:
    • CO2 (Number)
    • temperature (Number)
    • humidity (Number)
    • timestamp (DateTime or Text)
  4. Toggle Historic data analysis ON if you want Power BI to store rows for reporting.
  5. Save the dataset and copy the Push URL that Power BI generates. This will look something like: https://api.powerbi.com/beta/.../datasets/{id}/rows?key=...

This Push URL is what the Python script will use to send live sensor data to your dashboard.

The Script

We wrote a Python script that takes care of the entire process. Once it starts, the script connects to the BleuIO dongle through the serial port and switches it into central mode (this is done only the first time it runs). From then on, it performs a BLE scan every 10 seconds, specifically looking for HibouAir sensor advertising data. When the sensor is found, the script decodes the broadcast payload into CO2, temperature, and humidity values. These values are then packaged into the required JSON format and pushed directly to Power BI, where they appear instantly on your dashboard.

Before running the script, make sure to update two important details:

  • Dongle port location: On macOS it will look like /dev/cu.usbmodemXXXX, while on Windows it will appear as COMX.
  • Power BI Push URL: Use the one you generated earlier during the dataset setup.

We’ve published the full script on GitHub here:
GitHub Link for Script

To run it:

python script.py

Setup Dashboard

With the script running and sending live data, the next step is to build your Power BI dashboard.

  1. Go to My workspace in Power BI and click New → Dashboard.
  2. Give the dashboard a descriptive name, for example HibouAir Live Data.
  3. Select + Add a tile → Custom streaming data, then choose the dataset you created earlier.
  4. Pick a visualization type that suits your needs:
    • A Card to display the current CO₂ value.
    • A Gauge to track temperature within a target range.
    • A Line chart to watch humidity changes over time.
  5. Map the fields (CO2, temperature, humidity, timestamp) to each visual and pin them to your dashboard.

Within seconds of running the script, you’ll see live sensor readings begin to appear in your Power BI dashboard — updating automatically with every scan.

Output

Here’s what the final result looks like when the dashboard starts receiving data from the HibouAir sensor.

Use Cases

This project shows just one way to use BLE and Power BI together, but the possibilities are broad. For example, you could build air quality monitoring dashboards in offices, schools, or factories to help maintain healthier environments. In agriculture, farmers could create smart dashboards that combine soil and environmental sensors to optimize crop conditions. The same method can be applied to cold chain logistics, where monitoring temperature and humidity is essential for transporting food or medicine. Fitness and health enthusiasts could stream real-time data from BLE wearables into personal dashboards, making progress more visible and motivating. And for developers, Power BI is an excellent tool for rapid IoT prototyping, offering instant visualization of new sensor data without building a complex backend system.

With BleuIO and Microsoft Power BI, it’s easy to transform BLE sensor broadcasts into live dashboards. This integration makes it possible to visualize environmental data in real time, share insights instantly, and build prototypes faster than ever before. Whether you’re a developer, researcher, or business professional, combining BLE sensors with Power BI opens the door to smarter, data-driven decisions.

Share this post on :

Ambient-Adaptive CO2 Bar with BleuIO & HibouAir

This tutorial walks you through a tiny, privacy-first web app that reads only the advertised CO2 level from a nearby HibouAir sensor using a BleuIO USB BLE dongle. There’s no pairing, cloud, or backend—just your browser, the dongle, and a page that decodes a numeric CO2 value broadcast in BLE advertisements and renders it as a color bar (default window 400–2000 ppm) with a simple “High CO2” warning when your threshold is crossed.

This project is a follow-up to the Ambient-Adaptive Noise Bar with BleuIO & HibouAir. We reused the same structure and UI, but swap the decoder to read CO2 instead of noise.

What you’ll build

A single HTML file that talks to BleuIO over the Web Serial API. The page puts BleuIO in the central role, periodically runs a targeted scan for your HibouAir Board ID, and parses the Manufacturer Specific Data (MSD) bytes in each advertisement to extract CO2 (ppm). The value drives a horizontal gradient bar; cross the threshold and a warning banner appears. Everything runs locally in the browser.

Why a CO2-only, browser-based monitor?

CO2 is a practical proxy for ventilation. Elevated levels are associated with stale air, drowsiness, and reduced productivity. Many spaces—meeting rooms, classrooms, offices, homes—benefit from quick visual feedback so people know when to air out the room. Reading only a single, device-computed number from BLE advertisements keeps the design simple, fast, and privacy-preserving.

Hardware & software

How it works (at a glance)

BLE devices periodically broadcast short advertisement packets with real-time CO2 values. We can read them without pairing.

This page filters to a specific Board ID, captures the advertisement line, extracts the longest hex payload, and then decodes CO2 from a fixed position inside the MSD. The result is mapped to a 0–100% fill of the bar (for a display window of 400–2000 ppm), and we show a banner when CO2 ≥ threshold (default 1000 ppm).

Below is the exact function used in this project:

function decodeCo2FromAdv(hex) {
  // sanitize → bytes
  hex = (hex || '').replace(/[^0-9A-F]/gi, '');
  if (hex.length % 2) hex = hex.slice(0, -1);
  const b = new Uint8Array(hex.length / 2);
  for (let i = 0; i < b.length; i++) b[i] = parseInt(hex.substr(i*2,2), 16);

  // locate MSD anchor and read CO2 at fixed offset (big-endian)
  for (let i = 0; i <= b.length - 5; i++) {
    if (b[i] === 0x5B && b[i+1] === 0x07 && b[i+2] === 0x05) {
      const idx = i + 23;                // CO2 MSB position in this layout
      if (idx + 1 < b.length) {
        return (b[idx] << 8) | b[idx+1]; // ppm
      }
    }
  }
  return null;
}

The BLE flow

When you click Connect, the page opens a serial session to BleuIO and sends:

  • AT+CENTRAL once, to enter scanning mode
  • AT+FINDSCANDATA=<BOARD_ID>=3 every cycle to run a 3-second targeted scan
  • The reader consumes lines until BleuIO prints SCAN COMPLETE, then waits and repeats

Each time an advertisement arrives, the page extracts the hex payload, decodes CO2, updates the bar, and toggles the High CO2 banner if the threshold is exceeded.

Output

You’ll see a horizontal color bar labeled with the current CO2 ppm. The bar fills from left to right as values rise within the 400–2000 ppm window. A bold High CO2 banner appears when the reading crosses your threshold (default 1000 ppm), serving as a polite nudge to improve ventilation.

Use cases

This simple CO2 bar works well anywhere people gather and air can get stale. In meeting rooms and classrooms it provides a live cue to crack a window or switch on ventilation as occupancy rises. In open offices it nudges teams toward timely air exchanges, helping reduce stuffiness and afternoon dips in alertness. At home it’s a lightweight way to keep bedrooms and living spaces fresh during gatherings or winter months with closed windows. Shared studios and makerspaces also benefit from quick, ambient feedback without the overhead of dashboards or wall displays.

Because the app reads only a single numeric value that HibouAir already broadcasts, it avoids handling personal data and is easy to deploy in privacy-sensitive environments.

Accuracy & practical notes

This is a lightweight indicator, not a calibration tool. CO2 readings in advertisements update periodically and represent the sensor’s current value. Placement matters: keep your HibouAir within a reasonable range of BleuIO to reduce missed packets. If your environment regularly exceeds the default window, you can adjust the display range and threshold in the code.

Extend the project

You can grow this prototype in several practical directions. Start by logging readings to CSV or IndexedDB for simple trend analysis over days or weeks. If you have multiple sensors, add a multi-device view that scans several Board IDs and presents compact tiles in one page. For automation, trigger a webhook or send a serial command to control a fan or relay whenever CO2 exceeds your threshold. You can also pair it with the earlier Noise Bar and show Noise + CO2 side-by-side for a fuller picture of comfort and productivity.

Share this post on :

Build a BLE Web App in Flutter Using BleuIO

This guide shows how to create a web-only Flutter application that talks to a BleuIO USB BLE dongle directly from the browser. The app is a minimal console: click Connect, send AT commands like AT+CENTRAL or AT+GAPSCAN=3, and watch responses appear in real time. Treat it as a starting point for richer BLE web apps—dashboards, scanners, or device tools—without native installs.

Requirements

What this post is about

The focus is Flutter for the web. Instead of a mobile build, you’ll run Flutter in the browser and use the Web Serial API to communicate with BleuIO. The included script acts as an example of how to open the serial port, send AT commands, and stream lines back into a Flutter UI. You can use the same pattern to build any BLE-adjacent web app: scan for devices, filter output, parse manufacturer data, or add visualizations—completely in Flutter Web.

How it works

Flutter Web can call browser APIs through dart:html and dart:js_util. The app asks Chrome/Edge to show the serial-port picker, opens the selected BleuIO port at 115200, and writes commands terminated by \r\n. A small pre-newline and micro delays are used so commands don’t concatenate (avoiding errors like AT+CENTRALA). A read loop collects bytes, splits them into lines, and renders them into a console-style panel. Everything runs locally in the browser; nothing is recorded or sent to a server.

Guide

Create a web-only Flutter app

From Terminal, create a project that targets only the web so you don’t see iOS/Android code-sign prompts:

flutter create --platforms=web bleuio-flutter
cd bleuio-flutter

You’ll run exclusively against Chrome (or Edge). No mobile setup is needed.

Select Chrome as the build target

Use Chrome so the Web Serial API is available:

flutter run -d chrome

If you use an IDE, choose Chrome (web) in the device selector. Safari is not supported for Web Serial.

Add the example script

Open lib/main.dart and replace its contents with the example from the repository. That file defines a tiny Web-Serial service (connect, writeLine, continuous read loop) and a simple UI with a status, quick command buttons, a custom command input, and a full-width terminal output.

Try live commands

Click Connect BleuIO and choose your dongle. Send ATI to verify, then AT+CENTRAL to enter central role, and AT+GAPSCAN=3 to perform a three-second scan. The responses stream into the on-page console immediately. Because it’s just AT over serial, you can experiment with any command that BleuIO supports.

Understanding the example

The script is intentionally small so you can lift it into other projects. The service wraps Web Serial and exposes a line stream you can subscribe to from widgets. The UI is a single page that prints lines to a terminal-style view and keeps the scroll pinned to the bottom.

Extending this into a BLE web app

Once you’re comfortable with the console, you can add features that turn it into a BLE tool. Start by parsing common outputs such as GAP scan lines into structured objects with fields like address, RSSI, and name. Add filters and search to highlight target devices.

Use cases

This web-only approach is ideal for demos, workshops, and quick bring-up labs where you want a zero-install experience. It’s handy for field diagnostics when you need to peek at advertisements, confirm firmware state, or prove connectivity from any machine with Chrome. It also serves as a foundation for privacy-respecting dashboards that only read broadcast data and never require native packaging.

Source code & demo

Repository:
https://github.com/smart-sensor-devices-ab/bleuio-flutter

Live demo (open in Chrome/Edge, connect your dongle, and try AT commands):
https://smart-sensor-devices-ab.github.io/bleuio-flutter/

Output, hosted and real time

The live page streams output as soon as the device is connected. You can keep the tab open as a lightweight serial console while developing other features.

Flutter Web is a great fit for BLE-adjacent tooling when you want the reach of a URL and the feel of a native UI. Start with the example console today, then grow it into the BLE web application your project needs.

Share this post on :

Using BleuIO with Waveshare RP2350A USB Mini Based On Raspberry Pi RP2350A 

In this tutorial, we demonstrate how to connect the BleuIO Bluetooth Low Energy dongle to the Waveshare RP2350A USB Mini development board to create a dual-mode USB serial bridge. This example highlights the process of communicating with BleuIO using AT commands via USB host mode. It serves as a great starting point for anyone looking to build their own BLE application using this compact and powerful board.

About the Waveshare RP2350A USB Mini

The Waveshare RP2350A USB Mini is a development board built around the Raspberry Pi RP2350 microcontroller. It features a dual-core architecture, combining ARM Cortex-M33 and RISC-V Hazard3 cores, running at up to 150 MHz. The chip includes 520KB of SRAM and 2MB of onboard flash, making it suitable for advanced embedded applications.

What sets this board apart is its USB-A connector with support for USB host mode, allowing it to communicate directly with USB peripherals like the BleuIO dongle. This makes it an ideal host controller for Bluetooth Low Energy (BLE) applications.

Project Overview

This project demonstrates a dual-mode USB serial bridge implemented on the RP2350A board. The bridge allows the board to simultaneously function as:

  • A USB host using PIO-USB on GPIO 12/13, connected to a device like BleuIO.
  • A USB device, appearing as a virtual COM port when connected to a PC.

Data is transparently forwarded between the host and device interfaces, allowing you to communicate with BleuIO from a terminal on your PC.

This example project is useful for anyone looking to build a standalone USB host application using BleuIO. You can use the source code as a base and expand it into a more complex BLE project by sending and receiving AT commands directly from the RP2350A board.

Use Case

Imagine a scenario where you want to build a small, embedded BLE sensor gateway. Using the RP2350A as a USB host and BleuIO as the BLE interface, you can develop a powerful BLE solution without needing a full-sized computer. This approach is ideal for prototyping custom BLE applications, sensor data acquisition, or even building a mini BLE scanner.

Hardware Requirements

Software Requirements

Host Mode (PIO-USB on GPIO 12/13)

In host mode, the RP2350 board uses PIO-USB to emulate USB host functionality on GPIO pins 12 (D+) and 13 (D-). You can connect any CDC-compatible device, such as a USB-to-serial adapter or the BleuIO dongle, to these pins.

Once connected, the application automatically detects and configures the device. All incoming and outgoing serial data is forwarded and can be monitored through the debug UART interface.

Device Mode (Native USB Port)

When the RP2350A’s native USB port is connected to your PC, it appears as a virtual serial port. This allows you to communicate with the BLE dongle (connected via host mode) using a terminal application like PuTTY or Tera Term.

This enables full-duplex communication, letting you send AT commands to BleuIO and receive responses directly from your PC.

Debug Output (UART on GP0/GP1)

To observe internal debug messages, connect a UART adapter to GPIO 0 (TX) and GPIO 1 (RX) with a baud rate of 115200. This output includes information such as device enumeration, data flow, and potential errors—essential for troubleshooting and development.

Building the Project

To build the firmware, you can either use the provided build script or follow a manual setup.

You can find the complete source code for this project on GitHub:

GitHub Repository: bleuio-rp2350

Method 1: Using the build script

cd rp2350_serial_bridge
./build.sh

Method 2: Manual build with CMake

mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j4

After building, a rp2350_serial_bridge.uf2 file will be generated.

Flashing the Firmware

  1. Hold the BOOTSEL button on the RP2350 board and connect it to your PC via USB.
  2. The board will mount as a USB mass storage device.
  3. Copy the generated rp2350_serial_bridge.uf2 file to this drive.
  4. The board will reboot and start running the dual-mode USB bridge application.

You can now insert the BleuIO dongle into the USB-A port of the RP2350A board and begin communication from your PC.

Code Structure

  • main.c – Main entry point of the application.
  • serial_host_bridge.c/h – Handles the USB CDC host implementation.
  • tusb_config.h – TinyUSB configuration file.
  • CMakeLists.txt – Build configuration for CMake.

Getting Started with Your Own BLE App

This project is just a starting point. It demonstrates how to set up USB host/device mode and communicate with a CDC-based USB dongle like BleuIO. From here, you can extend the code to parse responses, interact with BLE devices, or trigger actions based on received data.

By combining the flexible RP2350A platform with BleuIO, developers can create their own powerful standalone BLE applications for IoT, sensors, or industrial control—without relying on a full computer.

Source Code

You can find the complete source code for this project on GitHub:

GitHub Repository: bleuio-rp2350

Share this post on :

Building a BLE-Powered Air Quality Dashboard with Grafana

Indoor air quality is an important factor in workplace health, home comfort, and industrial safety. Monitoring CO2, particulate matter, temperature, humidity, and other environmental metrics can help prevent health issues, optimize HVAC usage, and maintain compliance with building standards. In this tutorial, we’ll walk through building a complete air quality monitoring solution using the BleuIO USB dongle and the HibouAir BLE sensor. We’ll use Python to collect data, InfluxDB to store it, and Grafana to visualize it in real time.

This project works entirely offline, runs on both macOS and Windows, and is suitable for personal, educational, or professional use. Whether you want to monitor a home office or manage sensors in an industrial setup, this system is simple to deploy and easy to scale.

What You Need

You will need one BleuIO USB dongle and one HibouAir BLE sensor. These two devices form the core of the setup. The BleuIO dongle will connect via a serial port and act as a BLE Central, continuously scanning for BLE advertisement packets sent by the HibouAir sensor.

For software, you’ll need Python 3, InfluxDB, and Grafana, all of which are available for both macOS and Windows. You’ll also install a few Python packages (pyserial and influxdb) to allow your script to read data over the serial port and write it to the database.

About Grafana

Grafana is an open-source analytics and visualization platform designed to display time-series data. It integrates seamlessly with databases like InfluxDB, allowing users to create interactive and customizable dashboards. In this project, Grafana serves as the front end for visualizing live air quality data coming from the HibouAir sensor. Its intuitive interface and support for powerful queries make it ideal for monitoring environmental conditions over time, setting alerts, and gaining actionable insights from BLE sensor data.

macOS Installation

On macOS, it’s easiest to install tools using Homebrew. You can install everything with the following commands:

brew install influxdb@1
brew install grafana
brew install python

Start the services:

brew services start influxdb@1
brew services start grafana

Windows Installation

For Windows, download installers from official sites:

Make sure Python and pip are added to your system path. You can then install Python packages via:

pip install pyserial influxdb

Also, identify your BleuIO COM port by opening Device Manager and checking Ports (COM & LPT). You’ll see something like COM3, which you’ll use in the Python script.

Setting Up InfluxDB

After installing InfluxDB, you need to create a database that will store sensor readings. On macOS, open your terminal. On Windows, use Command Prompt or PowerShell.

To enter the InfluxDB CLI:

influx

Inside the CLI, run:

> CREATE DATABASE hibouair
> SHOW DATABASES
> EXIT

This creates a database called hibouair, which your Python script will use to log environmental data.

You don’t need to set up authentication for local testing. If you do secure your instance later, remember to update Grafana and the Python script with your username and password.

Configuring Grafana

Once Grafana is installed and running, open your browser and go to:

http://localhost:3000

The default username and password are:

Username: admin
Password: admin

After logging in, you’ll be asked to set a new password. Once you’re in the Grafana dashboard:

  1. Go to Settings → Data Sources
  2. Click Add data source
  3. Choose InfluxDB
  4. Set the following:
    • URL: http://localhost:8086
    • Database: hibouair
    • User and Password: leave empty unless you’ve enabled authentication
    • HTTP Method: GET or POST (default is fine)
  5. Click Save & Test

You should see a success message confirming Grafana can read from InfluxDB.

Connecting BleuIO via Serial Port

Insert your BleuIO USB dongle.

  • On macOS, find the port using: ls /dev/cu.usbmodem* Example: /dev/cu.usbmodem4048FDEBA6D01
  • On Windows, check Device Manager → Ports (COM & LPT) and find something like COM3 or COM4.

This port will be used in your Python script to open a serial connection to the dongle.

The Python Script

The Python script initializes the BleuIO dongle in Central mode, sends a scan command (AT+FINDSCANDATA), and reads the BLE advertisement packets from the HibouAir sensor. The data, which arrives in hexadecimal format, is decoded to extract values like temperature, humidity, CO2, VOC, PM1, PM2.5, PM10, light, noise, and pressure. The script then writes this data to InfluxDB with a timestamp.

To avoid storing duplicate values, the script only logs the first valid reading per scan cycle. It waits for a configurable period (e.g., 20 seconds) before scanning again.

You can view and copy the complete Python script from the GitHub repository linked below.

GitHub: bleuio-hibouair-influx-grafana

To run the script:

python3 bleuio_to_influx.py

Make sure to replace the serial port path and board ID in the script with your actual values.

Creating a Dashboard in Grafana

With data flowing into InfluxDB, it’s time to visualize it in Grafana. Start by creating a new dashboard:

  1. From the left menu, go to Dashboards → New → Add new panel
  2. In the query editor, select your InfluxDB data source
  3. Enter the following query to display CO2 levels: SELECT last("co2") FROM "air_quality" WHERE $timeFilter GROUP BY time($__interval)

This query fetches the latest CO2 readings from the air_quality measurement. You can change "co2" to "temp", "hum", "voc", "pm25" or any other field depending on what you want to display.

Grafana will automatically plot the data on a line chart. You can switch to a gauge, bar, or other visualizations from the panel settings. You can also set thresholds, value ranges, and colors to improve readability.

Repeat this process to add more panels for temperature, humidity, VOCs, PM2.5, PM10, and other values.

Once your dashboard is complete, click Apply, then Save the dashboard to reuse it later.

Output

How It Works

This system leverages BLE advertisement broadcasting from HibouAir and AT command-based serial communication with the BleuIO dongle. The dongle scans for a specific board ID using AT+FINDSCANDATA=<board_id>=3, and the Python script reads and decodes each advertisement received. The decoded data is immediately sent to InfluxDB using the official client library.

Grafana queries this time-series data and displays it in real time. Since all components are local, there is no dependency on external servers or cloud APIs. The system is also modular, so you can easily scale it to support multiple sensors, extend the decoder for other BLE formats, or forward data to cloud-based platforms in the future.

Use Cases

This setup is ideal for a range of applications. In an office environment, monitoring CO2 and humidity can help optimize ventilation and improve productivity. In factories or workshops, tracking particulate matter and VOCs ensures air quality compliance and safety. For researchers and students, this project provides a hands-on, local, and open-source method to study environmental changes. Even in residential settings, it can help detect poor air circulation or pollutant spikes. The simplicity of this solution makes it accessible to both developers and non-developers alike.

Extending the Project

There are several ways to expand this project:

  • Add support for additional BLE data types, such as HibouAir’s type 08 advertisements for NO₂
  • Set up Grafana alerts to notify you when CO2 exceeds 800 ppm or humidity drops below 30%
  • Export historical data from InfluxDB to CSV for offline analysis
  • Run the Python script as a background service on system boot
  • Connect multiple sensors to cover multiple rooms or areas

With minimal changes, you can also run this setup on a Raspberry Pi or Linux server.

With just a BleuIO dongle, a HibouAir sensor, and a few open-source tools, you’ve built a fully functional air quality monitoring solution. The system is flexible, lightweight, and runs entirely offline. It provides real-time insights into indoor environmental conditions and can be adapted for a variety of applications.

Share this post on :

Using BleuIO with Node-RED to Build a BLE Air Quality Dashboard

Node-RED, a flow-based development tool for visual programming, has become a favorite among IoT developers for rapidly building and testing automation workflows. In this article, we explore how to use BleuIO, a Bluetooth Low Energy (BLE) dongle, within Node-RED to interact with BLE devices like HibouAir—an affordable and reliable air quality monitoring sensor.

We will demonstrate:

  • How to send BLE commands to BleuIO and read back data
  • How to scan for BLE advertisements from a HibouAir device
  • How to decode and display real-time air quality data like CO2, temperature, and humidity on a live dashboard

By the end, you’ll have a working Node-RED BLE setup using BleuIO and a HibouAir sensor.

What is Node-RED?

Node-RED is an open-source, flow-based programming tool built on Node.js. It offers a browser-based visual interface that allows developers to connect hardware devices, APIs, and services using prebuilt “nodes.” Originally developed by IBM, Node-RED has grown into one of the most accessible platforms for prototyping and building IoT and automation solutions.

What makes Node-RED especially appealing is its simplicity. Without needing to write complex code, developers can drag and drop logic blocks and wire them together to build powerful flows. It supports a wide range of protocols, including MQTT, HTTP, and—through serial communication. With its real-time data handling, debugging tools, and powerful dashboard feature, Node-RED becomes an ideal choice for BLE-based IoT projects like this one.

What is HibouAir?

HibouAir is a compact and affordable air quality monitoring device developed by Smart Sensor Devices. Designed for both indoor and outdoor use, it transmits real-time environmental data over Bluetooth Low Energy (BLE), making it easy to integrate into any smart environment. The sensor measures key air quality parameters such as CO2, temperature, humidity, particulate matter (PM1.0, PM2.5, PM10), VOCs, light intensity, noise levels etc. This simplicity makes it a perfect fit for developers and system integrators working with platforms like Node-RED, where data can be read, decoded, and visualized in minutes.

What We Built

We built a flow in Node-RED that:

  1. Sends an AT command to put BleuIO in central role (AT+CENTRAL)
  2. Sends a scan command to search for HibouAir devices (AT+FINDSCANDATA)
  3. Reads the advertisement data from a known board ID (e.g., 220069)
  4. Decodes the BLE hex payload using a custom decoder function
  5. Extracts and displays live air quality values (CO2, temperature, humidity) in the dashboard

Requirements

To replicate this project and visualize air quality data using Node-RED and BleuIO, you’ll need the following hardware and software:

BleuIO Dongle

A plug-and-play USB Bluetooth Low Energy (BLE) dongle that supports AT commands over serial.
Get BleuIO

HibouAir Sensor

An affordable air quality monitoring device that broadcasts environmental data via BLE advertisements.
Get HibouAir

Node-RED

A low-code flow-based development tool to wire together devices, APIs, and services.
Node-RED Installation Guide

Tip: You can install Node-RED globally via npm:

npm install -g --unsafe-perm node-red

Node-RED Dashboard

An additional Node-RED module used to create UI dashboards.
Dashboard GitHub Repo
Install it with:

cd ~/.node-red  
npm install node-red-dashboard

HibouAir Decoder Script

A Node.js-based decoding script that extracts sensor values from BLE advertisement data.

// decoder.js
function advDataDecode(data) {
  let pos = data.indexOf('5B070');
  let dt = new Date();
  let currentTs =
    dt.getFullYear() +
    '/' +
    (dt.getMonth() + 1).toString().padStart(2, '0') +
    '/' +
    dt.getDate().toString().padStart(2, '0') +
    ' ' +
    dt.getHours().toString().padStart(2, '0') +
    ':' +
    dt.getMinutes().toString().padStart(2, '0') +
    ':' +
    dt.getSeconds().toString().padStart(2, '0');

  let tempHex = parseInt(
    '0x' +
      data
        .substr(pos + 22, 4)
        .match(/../g)
        .reverse()
        .join('')
  );
  if (tempHex > 1000) tempHex = (tempHex - (65535 + 1)) / 10;
  else tempHex = tempHex / 10;

  let noiseAdvValue = parseInt('0x' + data.substr(pos + 14, 4));
  noiseAdvValue = noiseAdvValue * -1 + 120;

  return {
    type: parseInt(data.substr(pos + 6, 2), 16),
    light: parseInt(
      '0x' +
        data
          .substr(pos + 14, 4)
          .match(/../g)
          .reverse()
          .join('')
    ),
    noise: noiseAdvValue,
    pressure:
      parseInt(
        '0x' +
          data
            .substr(pos + 18, 4)
            .match(/../g)
            .reverse()
            .join('')
      ) / 10,
    temp: tempHex,
    hum:
      parseInt(
        '0x' +
          data
            .substr(pos + 26, 4)
            .match(/../g)
            .reverse()
            .join('')
      ) / 10,
    voc: parseInt(
      '0x' +
        data
          .substr(pos + 30, 4)
          .match(/../g)
          .reverse()
          .join('')
    ),
    pm1:
      parseInt(
        '0x' +
          data
            .substr(pos + 34, 4)
            .match(/../g)
            .reverse()
            .join('')
      ) / 10,
    pm25:
      parseInt(
        '0x' +
          data
            .substr(pos + 38, 4)
            .match(/../g)
            .reverse()
            .join('')
      ) / 10,
    pm10:
      parseInt(
        '0x' +
          data
            .substr(pos + 42, 4)
            .match(/../g)
            .reverse()
            .join('')
      ) / 10,
    co2: parseInt('0x' + data.substr(pos + 46, 4)),
    vocType: parseInt('0x' + data.substr(pos + 50, 2)),
    ts: currentTs,
  };
}

module.exports = { advDataDecode };

Place the decoder file (hibouair-decoder.js) in your Node-RED user directory and reference it via functionGlobalContext in your settings.js:

functionGlobalContext: {
decoderLib: require('./hibouair-decoder.js')
}

How It Works

1. Send AT Commands to BleuIO

We use inject and function nodes to send a flush signal (\r\n) followed by an AT command to the BleuIO dongle via a serial out node.

let flush = Buffer.from('\r\n');
let command = Buffer.from('AT+CENTRAL\r\n');

node.send([{ payload: flush }, null]);
setTimeout(() => {
node.send([null, { payload: command }]);
}, 500);
return null;

2. Read BLE Advertisements

A serial in node reads back raw BLE advertisements. These are filtered and passed through a custom decoder only if they match a specific prefix like 5B0705 and contain the HibouAir board ID.

3. Decode Payload

We placed a hibouair-decoder.js script next to settings.js and loaded it globally using:

functionGlobalContext: {
decoderLib: require('./hibouair-decoder.js'),
}

The decoder function parses the hex payload into human-readable sensor values.

4. Show on Dashboard

Finally, we use dashboard gauge widgets to show live values:

  • CO2 in ppm
  • Temperature in °C
  • Humidity in %RH

The Node-RED dashboard UI gives a beautiful, real-time snapshot of your air quality.

Live Dashboard

Live readings of CO2, temperature, and humidity.

A snapshot of the working Node-RED flow using BleuIO and HibouAir.

Use Cases

This solution opens doors for a wide variety of applications. In smart classrooms, it ensures students learn in environments with healthy air quality, which can significantly affect concentration and wellbeing. In modern office spaces, monitoring CO2 and temperature helps facilities maintain optimal working conditions, improving both productivity and comfort.

For developers and researchers, this integration offers an easy way to prototype BLE applications, decode custom advertisements, and visualize data with minimal setup. Environmental agencies or facility managers can use this same setup for on-site testing and audits without needing cloud connectivity.

Even at home, you can deploy this as a DIY setup to monitor indoor air conditions and get real-time alerts when CO2 levels get high due to poor ventilation.

What You Can Do Next

Now that you have a live setup showing CO2, temperature, and humidity from HibouAir on a Node-RED dashboard, the possibilities for extending this flow are endless.

To store and track trends, you can add a chart node that logs values over time. This enables historical analysis of indoor air conditions, which is useful for compliance, optimization, or just awareness.

If you’re concerned about thresholds, consider adding a switch node that triggers alerts—say, if CO2 levels rise above 1000 ppm or the temperature exceeds 30°C. This could be used to turn on ventilation or send a mobile notification.

You might also want to persist data to a local SQLite database or forward readings to a cloud-based API for further processing or sharing. This transforms your flow into a powerful edge gateway.

Finally, you can export the complete flow as a template, allowing colleagues, customers, or community users to import it directly and start monitoring with their own HibouAir and BleuIO setup.

Try It Yourself

You can import the full Node-RED flow here and start using it with:

[
    {
        "id": "310d89a2ee784f54",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "27fc4a581ee7c31e",
        "type": "tab",
        "label": "BleuIO HibouAir Scanner",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "3e00bbe41084b2d0",
        "type": "serial-port",
        "name": "",
        "serialport": "/dev/tty.usbmodem4048FDEBA6D01",
        "serialbaud": "9600",
        "databits": "8",
        "parity": "none",
        "stopbits": "1",
        "waitfor": "",
        "newline": "\\r\\n",
        "bin": "false",
        "out": "char",
        "addchar": "",
        "responsetimeout": "10000"
    },
    {
        "id": "71fb9c335c790a65",
        "type": "ui_base",
        "theme": {
            "name": "theme-light",
            "lightTheme": {
                "default": "#0094CE",
                "baseColor": "#0094CE",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "edited": true,
                "reset": false
            },
            "darkTheme": {
                "default": "#097479",
                "baseColor": "#097479",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "edited": false
            },
            "customTheme": {
                "name": "Untitled Theme 1",
                "default": "#4B7930",
                "baseColor": "#4B7930",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
            },
            "themeState": {
                "base-color": {
                    "default": "#0094CE",
                    "value": "#0094CE",
                    "edited": false
                },
                "page-titlebar-backgroundColor": {
                    "value": "#0094CE",
                    "edited": false
                },
                "page-backgroundColor": {
                    "value": "#fafafa",
                    "edited": false
                },
                "page-sidebar-backgroundColor": {
                    "value": "#ffffff",
                    "edited": false
                },
                "group-textColor": {
                    "value": "#1bbfff",
                    "edited": false
                },
                "group-borderColor": {
                    "value": "#ffffff",
                    "edited": false
                },
                "group-backgroundColor": {
                    "value": "#ffffff",
                    "edited": false
                },
                "widget-textColor": {
                    "value": "#111111",
                    "edited": false
                },
                "widget-backgroundColor": {
                    "value": "#0094ce",
                    "edited": false
                },
                "widget-borderColor": {
                    "value": "#ffffff",
                    "edited": false
                },
                "base-font": {
                    "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
                }
            },
            "angularTheme": {
                "primary": "indigo",
                "accents": "blue",
                "warn": "red",
                "background": "grey",
                "palette": "light"
            }
        },
        "site": {
            "name": "Node-RED Dashboard",
            "hideToolbar": "false",
            "allowSwipe": "false",
            "lockMenu": "false",
            "allowTempTheme": "true",
            "dateFormat": "DD/MM/YYYY",
            "sizes": {
                "sx": 48,
                "sy": 48,
                "gx": 6,
                "gy": 6,
                "cx": 6,
                "cy": 6,
                "px": 0,
                "py": 0
            }
        }
    },
    {
        "id": "06150d2ac284223f",
        "type": "ui_tab",
        "name": "HibouAir",
        "icon": "dashboard",
        "order": 1,
        "disabled": false,
        "hidden": false
    },
    {
        "id": "fa02b7029c4379aa",
        "type": "ui_group",
        "name": "Sensor Values",
        "tab": "06150d2ac284223f",
        "order": 1,
        "disp": true,
        "width": 6,
        "collapse": false,
        "className": ""
    },
    {
        "id": "2cb6402037188267",
        "type": "inject",
        "z": "27fc4a581ee7c31e",
        "name": "Send AT+CENTRAL",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 150,
        "y": 80,
        "wires": [
            [
                "b17ce9f8843985f7"
            ]
        ]
    },
    {
        "id": "b17ce9f8843985f7",
        "type": "function",
        "z": "27fc4a581ee7c31e",
        "name": "Flush then CENTRAL",
        "func": "let flush = Buffer.from('');\nlet command = Buffer.from('AT+CENTRAL\\r\\n');\n\nnode.send([{ payload: flush }, null]);\nsetTimeout(() => {\n    node.send([null, { payload: command }]);\n}, 500);\n\nreturn null;",
        "outputs": 2,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 350,
        "y": 80,
        "wires": [
            [
                "871745e5fe30720c"
            ],
            [
                "871745e5fe30720c"
            ]
        ]
    },
    {
        "id": "4d092f4b416e0c3c",
        "type": "inject",
        "z": "27fc4a581ee7c31e",
        "name": "Send SCAN",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 150,
        "y": 140,
        "wires": [
            [
                "d56ecb6cd4d62906"
            ]
        ]
    },
    {
        "id": "d56ecb6cd4d62906",
        "type": "function",
        "z": "27fc4a581ee7c31e",
        "name": "Flush then SCAN",
        "func": "let flush = Buffer.from('');\nlet command = Buffer.from('AT+FINDSCANDATA=220069=3\\r\\n');\n\nnode.send([{ payload: flush }, null]);\nsetTimeout(() => {\n    node.send([null, { payload: command }]);\n}, 500);\n\nreturn null;",
        "outputs": 2,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 350,
        "y": 140,
        "wires": [
            [
                "871745e5fe30720c"
            ],
            [
                "871745e5fe30720c"
            ]
        ]
    },
    {
        "id": "871745e5fe30720c",
        "type": "serial out",
        "z": "27fc4a581ee7c31e",
        "name": "BleuIO Write",
        "serial": "3e00bbe41084b2d0",
        "x": 620,
        "y": 100,
        "wires": []
    },
    {
        "id": "4073fa55dc0171d7",
        "type": "serial in",
        "z": "27fc4a581ee7c31e",
        "name": "BleuIO Read",
        "serial": "3e00bbe41084b2d0",
        "x": 150,
        "y": 220,
        "wires": [
            [
                "a770c5735942970f"
            ]
        ]
    },
    {
        "id": "a770c5735942970f",
        "type": "function",
        "z": "27fc4a581ee7c31e",
        "name": "Clean Output",
        "func": "msg.payload = msg.payload.trim();\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 350,
        "y": 240,
        "wires": [
            [
                "229eb49191d8cc43"
            ]
        ]
    },
    {
        "id": "be5d9035305e24b0",
        "type": "debug",
        "z": "27fc4a581ee7c31e",
        "name": "BleuIO Response",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 850,
        "y": 180,
        "wires": []
    },
    {
        "id": "229eb49191d8cc43",
        "type": "function",
        "z": "27fc4a581ee7c31e",
        "name": "Decoder",
        "func": "const ADV_PREFIX = '5B0705';\nconst { advDataDecode } = global.get('decoderLib');\n\nconst line = msg.payload;\n\n// Look for advertising data with 5B0705\nif (typeof line === 'string' && line.includes(ADV_PREFIX)) {\n    const match = line.match(/([0-9A-F]{40,})/i);\n    if (match) {\n        const hexPayload = match[1];\n        const decoded = advDataDecode(hexPayload);\n        msg.payload = decoded;\n        return msg;\n    }\n}\n\n// Ignore other messages\nreturn null;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 240,
        "wires": [
            [
                "a445d2f694c3bde7",
                "d4b606693bb1244d",
                "0384ee99e42fdedd"
            ]
        ]
    },
    {
        "id": "a445d2f694c3bde7",
        "type": "function",
        "z": "27fc4a581ee7c31e",
        "name": "co2",
        "func": "msg.payload = msg.payload.co2;\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 730,
        "y": 320,
        "wires": [
            [
                "aed5ab0a3929659a"
            ]
        ]
    },
    {
        "id": "aed5ab0a3929659a",
        "type": "ui_gauge",
        "z": "27fc4a581ee7c31e",
        "name": "co2",
        "group": "fa02b7029c4379aa",
        "order": 0,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Co2",
        "label": "ppm",
        "format": "{{value}}",
        "min": "400",
        "max": "2000",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 890,
        "y": 320,
        "wires": []
    },
    {
        "id": "d4b606693bb1244d",
        "type": "function",
        "z": "27fc4a581ee7c31e",
        "name": "temperature",
        "func": "msg.payload = msg.payload.temp;\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 710,
        "y": 400,
        "wires": [
            [
                "4ee1b9d45026b3ea"
            ]
        ]
    },
    {
        "id": "4ee1b9d45026b3ea",
        "type": "ui_gauge",
        "z": "27fc4a581ee7c31e",
        "name": "temperature",
        "group": "fa02b7029c4379aa",
        "order": 1,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Temperature",
        "label": "°C",
        "format": "{{value}}",
        "min": 0,
        "max": "50",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 910,
        "y": 400,
        "wires": []
    },
    {
        "id": "0384ee99e42fdedd",
        "type": "function",
        "z": "27fc4a581ee7c31e",
        "name": "Humidity",
        "func": "msg.payload = msg.payload.hum;\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 680,
        "y": 480,
        "wires": [
            [
                "6743dd3a283e3219"
            ]
        ]
    },
    {
        "id": "6743dd3a283e3219",
        "type": "ui_gauge",
        "z": "27fc4a581ee7c31e",
        "name": "Humidity",
        "group": "fa02b7029c4379aa",
        "order": 1,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Humidity",
        "label": "%rH",
        "format": "{{value}}",
        "min": 0,
        "max": "100",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "diff": false,
        "className": "",
        "x": 900,
        "y": 480,
        "wires": []
    }
]
  • A BleuIO dongle plugged into your computer
  • A nearby broadcasting HibouAir sensor

Just install Node-RED, load this flow, and you’ll start seeing real-time air quality readings in your browser dashboard.

This tutorial shows how BleuIO seamlessly integrates with platforms like Node-RED to help developers quickly build BLE-powered applications. Combined with a device like HibouAir, this setup makes monitoring air quality simple, affordable, and accessible—without any advanced hardware or coding requirements.

We encourage you to extend this example, share your flows, or reach out to us with new ideas. BLE development doesn’t have to be hard. With BleuIO and Node-RED, it’s just a few clicks away.

Share this post on :

Build a BLE Environmental RSS Feed with BleuIO

Monitoring environmental data in real time is becoming increasingly important, whether you are tracking air quality in an office, monitoring home conditions, or running a smart city project. With the BleuIO dongle and a BLE-enabled sensor like the HibouAir, it is remarkably simple to gather this data and share it with the world in a format that is open, accessible, and easy to consume.

This project demonstrates how you can turn BLE advertisement data into a live-updating RSS feed. The result is a transparent, shareable, and continuously updated stream of environmental measurements that anyone can subscribe to, whether they are using a standard RSS reader, a smart home dashboard, or an industrial system.

Project Overview

At its core, this project uses the BleuIO dongle to scan for BLE advertising packets from a HibouAir environmental sensor. The HibouAir broadcasts valuable metrics such as temperature, humidity, pressure, and CO2 levels over BLE advertisements. Instead of relying on a local desktop application or complicated server-side setups, this project leverages the power of modern browsers using the Web Serial API to connect directly to the BleuIO dongle over the USB port.

Once connected, the user can trigger BLE scans from within the browser. The scan runs every 30 seconds, decoding the broadcasted data in real time. Each new data point is sent to a lightweight PHP script on the server, which stores and appends the data in an RSS feed (rss.xml). This RSS file is immediately accessible to any subscriber or reader, allowing people to see live updates on the environmental conditions around the sensor.

The entire system is designed to be simple and easy to maintain. The frontend is styled with Tailwind CSS to ensure a responsive, polished interface. The user can connect, start scanning, stop scanning, and instantly visualize decoded data in a clear, scrollable log area.

Requirements

To build and run this project, you will need the following:

How It Works

The project flow is straightforward. First, the user plugs the BleuIO dongle into their machine. Using the Web Serial API, the web page establishes a secure, permission-based connection with the dongle, sending standard AT commands to initiate scanning. The page decodes the advertising data using JavaScript, translating raw BLE packets into readable environmental measurements.

Every 30 seconds, a scan command is sent, retrieving the most recent advertising broadcasts. When a valid HibouAir packet is detected, the temperature, humidity, pressure, and CO2 values are extracted, timestamped, and then posted to the server using a simple JSON POST request. The PHP script running on the server receives these decoded values and appends them to the existing RSS feed. The result is a continually growing XML feed that any RSS reader can parse and display.

This approach removes the need for any heavy backend logic or traditional data pipelines. With the combination of the BLE advertisement protocol, the Web Serial API, and the simple power of RSS, the system remains robust, efficient, and fully open-standard.

Real-World Use Cases

There are many practical applications for this type of BLE-to-RSS feed system. In a smart building, it can provide real-time updates on air quality for facilities managers. Research labs can monitor and record environmental changes over time, sharing data with collaborators instantly. Homeowners could integrate the feed with smart thermostats or ventilation systems, optimizing air quality dynamically based on conditions broadcast by the HibouAir.

This approach is also perfect for educational settings, where students can learn about BLE communication, environmental sensors, and open-standard data sharing in a single, hands-on project. For communities or public-facing projects, the RSS feed provides a transparent, shareable view of conditions in a local area, encouraging citizen science or public environmental monitoring.

Why This Matters

Traditionally, BLE data collection has required local scripts, drivers, and other installation-heavy methods. Thanks to the Web Serial API, this entire solution can run in the browser, with no software to install apart from a modern web browser. The user grants permission to the serial port, connects the dongle, and can immediately start scanning, decoding, and sharing data.

By pushing this data into a simple RSS format, you gain compatibility with a huge ecosystem of readers and dashboards without reinventing the wheel. RSS is universal, easy to parse, and instantly shareable. This means a single HibouAir board and a BleuIO dongle can power an environmental feed accessible to anyone, anywhere.

Getting Started

This project is easy to set up. You only need a BleuIO dongle, a HibouAir board, a standard PHP-capable web server, and a modern web browser such as Chrome. The HTML page includes everything needed to connect to the dongle, issue scan commands, and decode results using JavaScript. Tailwind CSS ensures the user interface is polished and responsive.

After decoding, the environmental data is posted to a PHP script which appends it to an RSS feed file. From there, any visitor or RSS reader can subscribe to see the latest measurements. There is no complex backend, no cloud account, and no paid services — just a clean, standards-based solution that you can host yourself.

Source Code

The full project source code — including the HTML, JavaScript, PHP, and Tailwind-powered frontend — is available on GitHub. You can download, modify, or extend it as needed for your use case.

GitHub Repository

Feel free to clone the repository, fork it, and contribute improvements or suggestions.

Output

This project shows how BleuIO, in combination with Web Serial and simple RSS technology, can deliver an impressive, real-time environmental data feed. With minimal setup and a truly modern web-first architecture, you can build a robust, transparent, and shareable monitoring system in just a few hours.

If you are looking to explore BLE scanning, build a real-time environmental monitor, or simply share sensor data with the world in an open way, this project is a perfect starting point. Give it a try, and transform your local BLE sensors into a globally available data feed that anyone can use.

Share this post on :

BLE Device Proximity Alert System Using BleuIO

Looking to detect nearby Bluetooth devices based on how close they are? With the BleuIO USB dongle and a simple Python script, you can create a BLE proximity alert system that triggers a sound when a specific device is within signal range. This setup filters devices based on signal strength (RSSI), allowing you to monitor only those within a defined proximity.

This project is ideal for anyone exploring BLE applications such as access control, IoT automation, or even security demos.

Use Cases

The BLE Proximity Alert System can serve various real-world scenarios:

  • Secure Zone Monitoring
    By defining a signal strength threshold, the system only responds to devices that are physically close—perfect for protecting sensitive areas like server rooms or restricted workspaces.
  • Proximity-Based Device Interactions
    Trigger actions such as pairing or unlocking devices only when a known BLE tag or smartphone comes within a defined range, improving both UX and security.
  • Awareness and Educational Demos
    Demonstrate proximity-based alerts in classrooms or public exhibits to show how BLE technology can be used for social distancing or presence detection.
  • Smart Home or Office Automation
    Use proximity as a trigger to automate actions—such as turning on lights, sending notifications, or logging user presence—without needing additional sensors.

Requirements

To run this BLE proximity alert system, you’ll need:

  • BleuIO Dongle
    A USB Bluetooth Low Energy dongle that supports AT command communication and works with macOS, Windows, and Linux. This dongle will be used to scan for nearby BLE devices and filter based on signal strength.
  • Close Beacon Device
    In this project, we use a Close Beacon as the target BLE device. These compact beacons broadcast Bluetooth advertisements, making them ideal for proximity detection demos.
  • Python 3.x
    Ensure Python is installed on your system. The script uses Python to handle serial communication and control logic.
  • Sound File
    A short .wav or .mp3 audio file (e.g., beep.wav) placed in the same directory as the script. This sound will be played when the target BLE device is detected within the defined proximity.

Required Python Library

Only one external Python library is needed:

  • pyserial – for handling communication with the BleuIO dongle via the serial port.

You can install it using:

pip install pyserial

No additional audio libraries are required on macOS or Windows; the script uses the built-in afplay tool for macOS and winsound for Windows to play audio.

How It Works

When you launch the script, it establishes a serial connection with the BleuIO dongle. It then sets an RSSI filter using the AT+FRSSI command, which limits scan results to only those devices whose signal strength is above a certain threshold (i.e., closer in physical distance). For example, setting a filter of -76 means the scan will ignore devices with weaker signals (further away) and only show those nearby.

Every 30 seconds, the script initiates a BLE scan using AT+GAPSCAN=3. It parses the output in real time, looking for a specific MAC address. If the device is detected, the system immediately plays a sound to alert the user. This process repeats continuously, offering reliable detection based on proximity rather than just presence.

Important Notes

  • Serial Port
    Update the SERIAL_PORT variable in the script to match your operating system:
    • macOS: Typically /dev/cu.usbmodemXXXX
    • Windows: Usually COM3, COM4, etc.
  • MAC Address
    Replace the default value with the MAC address of your target BLE device.
  • RSSI Threshold
    Modify RSSI_FILTER_THRESHOLD at the top of the script to define how close the device must be to trigger the alert. Acceptable values range from -1 (very strong signal) to -99 (very weak).
  • Sound File
    Make sure the beep sound file (e.g., beep.wav) is present in the same folder as the script.

Source Code

The source code is available at

https://github.com/smart-sensor-devices-ab/ble-proximity-alert.git

You can find the complete Python script with configuration options and logic here:

import platform
import serial
import time
import subprocess
if platform.system() == "Windows":
    import winsound

# ==== CONFIGURATION ====
SERIAL_PORT = '/dev/cu.usbmodem4048FDEBA6D01'  # Update as needed
#SERIAL_PORT = "COM8"  # Update as needed
BAUD_RATE = 9600
TARGET_DEVICE_MAC = "D0:76:50:80:15:32"
SOUND_FILE = "beep.wav"  # Must exist in the same folder

# RSSI filter threshold: Acceptable range is -1 to -99
RSSI_FILTER_THRESHOLD = "60"  # You can input without '-' sign
# ========================


def play_beep():
    try:
        if platform.system() == "Windows":
            winsound.PlaySound(SOUND_FILE, winsound.SND_FILENAME)
        else:
            subprocess.call(["afplay", SOUND_FILE])
    except Exception as e:
        print(f"Failed to play sound: {e}")


def connect_to_bleuio():
    try:
        ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
        time.sleep(2)

        # Send basic AT to wake up
        ser.write(b"AT\r\n")
        time.sleep(0.5)

        # Ensure threshold has leading '-'
        rssi_value = RSSI_FILTER_THRESHOLD
        if not rssi_value.startswith("-"):
            rssi_value = "-" + rssi_value

        command = f"AT+CENTRAL\r\n".encode()
        ser.write(command)
        print(f"BleuIO set to central role\n")
        time.sleep(0.5)

        # Send AT+FRSSI command
        command = f"AT+FRSSI={rssi_value}\r\n".encode()
        ser.write(command)
        print(f"RSSI filter set to {rssi_value} dBm\n")
        time.sleep(0.5)

        return ser
    except Exception as e:
        print(f"Error connecting to BleuIO: {e}")
        return None


def scan_for_device(ser):
    ser.write(b"AT+GAPSCAN=3\r\n")
    end_time = time.time() + 5

    while time.time() < end_time:
        if ser.in_waiting:
            line = ser.readline().decode(errors="ignore").strip()
            print(line)
            if TARGET_DEVICE_MAC in line:
                print("Target device found! Triggering alert...")
                play_beep()

    print("Scan complete.\n")


def main():
    ser = connect_to_bleuio()
    if not ser:
        return

    print("Connected to BleuIO. Starting periodic scan...\n")

    while True:
        scan_for_device(ser)
        time.sleep(30)


if __name__ == "__main__":
    main()

Conclusion

This BLE proximity alert system is a great example of using signal strength (RSSI) to filter and detect nearby Bluetooth devices. It’s a lightweight, effective solution that can be easily customized for different applications—from home automation to enterprise access control. The source code is entirely open and flexible, allowing anyone to adapt it for their own use cases and expand it with additional features such as logging, notifications, or integration with cloud services.

This is an example project—but the building blocks can easily become part of something much bigger.

Share this post on :

BLE Star Topology Visualizer Using BleuIO

Monitoring nearby Bluetooth Low Energy (BLE) devices is essential for developers, testers, and engineers working with IoT, smart sensors, or proximity-based applications. To simplify this process, we’ve developed a visual tool—BLE Star Topology Visualizer—that connects to a BleuIO USB dongle and graphically maps nearby advertising BLE devices using RSSI-based distance estimation. This project not only provides a live BLE scan but also visualizes the proximity of devices in a central-node star topology.

What This Tool Does

The BLE Star Topology Visualizer is a web-based application that:

  • Connects to a BleuIO USB dongle via Web Serial API.
  • Performs a BLE GAP scan using AT+GAPSCAN=x to detect nearby BLE devices.
  • Displays detected devices as nodes around the central BleuIO dongle.
  • Uses RSSI (Received Signal Strength Indicator) to:
    • Color-code devices (green = strong signal, red = weak signal).
    • Adjust the visual distance from the center (closer = stronger signal).
  • Shows device MAC address, RSSI, and name (if available) in a tooltip on hover.

Requirements

To use this BLE Star Topology Visualizer, you’ll need a BleuIO USB dongle, which acts as the central scanning device. The dongle is responsible for performing GAP scans and returning nearby Bluetooth advertising data, including RSSI values and device names. No additional drivers or installations are needed—just plug in the BleuIO dongle, open the web app in a supported browser, and you’re ready to start scanning and visualizing.

Why This is Useful

BLE developers often depend on terminal-based command outputs to monitor advertising packets from nearby devices. While functional, this raw data can quickly become difficult to interpret—especially in environments with dozens of concurrent BLE signals. The BLE Star Topology Visualizer simplifies this challenge by transforming complex scan results into an interactive graphical map. It offers an immediate visual representation of proximity, density, and signal strength, helping developers, testers, and engineers better understand their BLE environment at a glance. Whether it’s estimating coverage, validating signal strength, or analyzing deployment patterns, this tool bridges the gap between data and insight.

Use Cases

This visualizer can serve many real-world needs. In a smart office or industrial IoT setup, it helps pinpoint where BLE beacons or sensors are broadcasting from, enabling better device placement and coverage optimization. During security audits, it offers a quick way to detect and identify all BLE devices within range, helping spot unauthorized transmitters. In educational settings, instructors can use it to demonstrate how RSSI reflects signal strength and how Bluetooth devices advertise themselves. Additionally, developers working on new BLE-enabled hardware can use this tool for field testing, validating how their products behave under various proximity and interference conditions.

How It Works

1. BLEUIO Setup

The script utilizes BleuIO AT commands. Once connected, the script sends the following:

  • AT+CENTRAL to switch to scanning mode.
  • AT+SHOWRSSI=1 to prepare BLEUIO to show RSSI in output.
  • AT+GAPSCAN=5 to scan for 5 seconds and return a list of advertising devices.

2. GAP Scan Output Parsing

The GAP scan output is parsed using a regex pattern that extracts:

  • RSSI value
  • MAC address
  • Optional device name (e.g., “(HibouAIR)”)

If the same MAC appears multiple times (with and without name), the version with the name is prioritized and stored.

3. Visualization with D3.js

Using D3.js, the central node (BleuIO) is placed in the center. Each nearby device:

  • Appears as a circle node around it.
  • Is connected with a line whose length is proportional to the RSSI (inverted and scaled).
  • Shows color-coded signal strength:
    • Green: Strong RSSI ≥ -50
    • Yellow: Medium RSSI > -70
    • Red: Weak RSSI ≤ -70

A tooltip displays full device info (MAC, RSSI, and name) when hovered.

How to Try

  1. Plug in your BleuIO dongle.
  2. visit this site to access the live script https://smart-sensor-devices-ab.github.io/ble-rssi-map/
  3. Click Connect and allow access to the BleuIO serial port.
  4. Click Scan to begin GAP scan.
  5. Watch as nearby BLE devices are discovered and plotted dynamically.

Output

Code Access

The full script is available at Github.

The BLE Star Topology Visualizer is a powerful yet simple tool that turns your BLE scan into an interactive experience. Whether you’re debugging, teaching, or optimizing device placement, this script offers a real-time glimpse into the invisible world of Bluetooth communication.

Share this post on :

Understanding BLE Advertising and How BleuIO Makes It Easy

Bluetooth Low Energy (BLE) has become the backbone of modern wireless communication for low-power IoT devices. One of its core features — advertising — allows BLE devices to broadcast information without the need for pairing or a connection. From contact tracing to proximity marketing and sensor broadcasting, BLE advertising powers many real-world applications.

What Is BLE Advertising?

BLE advertising is a mechanism where a device sends out small packets of data at regular intervals to announce its presence. These packets are picked up by nearby BLE-capable central devices (e.g., smartphones, gateways, computers).

A typical BLE advertisement packet contains:

  • Flags: Indicating capabilities like discoverability
  • Local Name: Device name (optional)
  • Service UUIDs: Describing the services the device supports
  • Manufacturer Specific Data: Custom payload defined by the manufacturer
  • Other fields: e.g., TX Power, service data, etc.

The total payload size is limited to 31 bytes, which includes all AD (advertising data) fields. Crafting this data properly is crucial to avoid errors and ensure compatibility with BLE scanners.

BLE Advertising with BleuIO and AT Commands

Traditionally, crafting BLE advertising packets required working with complex SDKs or embedded C libraries. But with BleuIO, things are much simpler — thanks to its powerful AT command set.

Here are two useful AT commands for advertising:

  • AT+ADVDATA= sets the advertising payload using a colon-separated hex format
  • AT+ADVSTART starts broadcasting the advertising packet

Example:

AT+ADVDATA=07:FF:5B:07:01:02:03:04
AT+ADVSTART

This advertises a Manufacturer Specific Data block:

  • 07: Total length of this AD field
  • FF: Type = Manufacturer Specific
  • 5B07: Manufacturer ID (Smart Sensor Devices)
  • 01020304: Custom payload

Using this simple format, you can test different advertising payloads, simulate sensor broadcasts, or even debug BLE scanning applications.

A Web Tool for BLE Advertising

To make things even easier, we’ve created a web-based BLE advertising builder that works right inside your browser.

Try it here:
https://smart-sensor-devices-ab.github.io/ble_advertising_payload/

What It Can Do:

  • Lets you connect to your BleuIO dongle directly from the browser
  • Offers two modes:
    • Builder Mode: Enter data by field — length, type, manufacturer ID, and custom payload
    • Raw Mode: Paste a full, colon-separated advertising packet directly
  • Automatically sends AT+ADVDATA and AT+ADVSTART commands
  • Displays real-time feedback from BleuIO (e.g., OK, INVALID PARAMETER)
  • Includes built-in validation so you don’t exceed the 31-byte BLE limit

Use Cases

Whether you’re a developer, educator, or BLE hobbyist, this tool is perfect for:

  • ✅ Rapid testing of advertising formats
  • ✅ Simulating BLE sensor outputs (like HibouAir)
  • ✅ Learning how BLE advertising packets are structured
  • ✅ Teaching BLE fundamentals without needing mobile apps or complex tools

BLE advertising is a critical piece of the Bluetooth ecosystem, and mastering it opens up a world of possibilities. With BleuIO’s simple AT interface and our new browser-based payload builder, creating, testing, and learning BLE advertising has never been easier.

So plug in your BleuIO dongle, open the tool in your browser, and start crafting BLE packets — one byte at a time.

Share this post on :