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.
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.
Try it now:Live demo on GitHub Pages — plug in BleuIO, and press Connect. Make sure to pass correct HibouAir Board ID
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.
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.
We are excited to announce the release of BleuIO Firmware v2.7.85. This update brings important fixes and improvements to ensure a smoother and more reliable experience when working with your BleuIO dongle.
What’s New in v2.7.85
This release focuses on bug fixes and performance improvements:
AT+CANCELCONNECT fix – Resolved an issue where the command would incorrectly return an error.
Improved JSON compatibility – Event message codes in verbose mode now include quotes, ensuring proper JSON format for easier parsing.
Better disconnection handling – Fixed a bug where AT+GAPDISCONNECTALL would sometimes fail to disconnect all connected devices.
Multi-device connection fix – Corrected an error where AT+GAPCONNECT returned “Connection limit exceeded” incorrectly when attempting to connect to the 8th device.
By upgrading to the latest firmware, you ensure your BleuIO dongle works at its best with improved stability and compatibility. If you are using BleuIO for projects that involve multiple device connections, JSON parsing, or handling connections/disconnections dynamically, this update is especially important.
Get the Latest Firmware
We recommend all users download and install the v2.7.85 firmware to take advantage of these improvements. Download the latest firmware here
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.
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.
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.
This tutorial shows how to build a small, browser-based monitor that reads only the advertised noise level from a nearby HibouAir sensor using a BleuIO USB BLE dongle. There is no pairing, no audio recording, and no microphone access. The page simply listens for Bluetooth Low Energy (BLE) advertisements, decodes a numeric noise value emitted by the sensor, and renders it as a color bar between 40–80 dBSPL. When the value exceeds a threshold you choose, a plain “shhhh” banner appears as a gentle cue to keep things quiet.
Why a Noise-Only, Privacy-Centric Monitor?
The goal is awareness, not surveillance. Many environments benefit from real-time feedback about loudness—libraries, classrooms, shared offices, and homes—yet microphones introduce privacy concerns and data-handling obligations. This project avoids all of that by reading a single numeric value that the HibouAir computes internally and broadcasts publicly. Because BLE advertisements are brief, connectionless, and contain no personally identifiable information or audio, the solution is both practical and privacy-preserving. It gives occupants a clear indication of ambient noise without storing, transmitting, or analyzing conversations.
What You’ll Build
You’ll create a single HTML file that talks to the BleuIO dongle through the Web Serial API. The page switches BleuIO to a central role, periodically runs a targeted scan for your HibouAir device ID, and parses the manufacturer-specific bytes in each advertisement to extract the noise reading. The value is then mapped to a 40–80 dB display range and presented as a horizontal color bar. If the measured level crosses your threshold, the banner appears. Everything runs locally in your browser; there is no backend server and nothing leaves your machine.
Google Chrome or Microsoft Edge on desktop (Web Serial enabled)
Any text editor to save and edit index.html
How It Works
BLE devices periodically broadcast short advertisement packets. These packets can include a manufacturer-specific data (MSD) field where vendors store compact sensor values. Because advertisements are public and unidirectional, you can read them without pairing or maintaining a connection, which makes them ideal for low-overhead telemetry and privacy-first designs.
What We Read
HibouAir encodes a noise metric inside its MSD block. This project looks only for that metric. The page filters scan results to a specific board ID so you capture advertisements from your own sensor. Each time the dongle reports an advertisement line, the page extracts the longest hex payload, finds the MSD anchor, and reads two bytes that represent the noise value.
The BLE Flow
When you click Connect, the browser opens a serial session to BleuIO. The page sends AT+CENTRAL once to set the dongle into scanning mode. Every few seconds, it issues AT+FINDSCANDATA=<BOARD_ID>=3 to perform a three-second targeted scan and then reads the output until the dongle prints “SCAN COMPLETE.” This cadence repeats continuously so your display stays current without spamming the serial interface.
Save the page as index.html and open it with Chrome or Edge on your desktop. Click Connect BleuIO and select the dongle’s serial port. Verify the Board ID matches your HibouAir unit, then click Start. You should see the serial log print “SCANNING…” followed by advertisement lines and “SCAN COMPLETE.” As new packets arrive, the color bar moves within the 40–80 dB range and the banner appears when the measured level meets or exceeds your threshold. The app continues scanning at a steady cadence without manual intervention.
Output
Use Cases
In a library or reading room, the bar provides a quiet, visual nudge to maintain a calm atmosphere without recording anyone. Classrooms can use it to keep group work from spilling into disruption while reassuring students and parents that no audio is captured. Open offices gain a neutral reference during focus periods, with the banner serving as a gentle reminder rather than an alarm. At home, the display helps keep late-night activities considerate of sleeping children or neighbors. Across all of these contexts, the design sidesteps privacy concerns by never collecting voice or content—only a simple loudness number the sensor already computes.
Accuracy and Limitations
The 40–80 dB window is a display choice that covers common indoor scenarios; this is not a calibrated sound level meter. BLE advertisements arrive periodically, so the value updates in small steps rather than continuously. Placement matters: keep the HibouAir and BleuIO within reasonable proximity to reduce missed packets. If the environment is unusually quiet or loud, you can shift the threshold or adjust the visual window to suit your space.
This project demonstrates how to execute a Python script wirelessly from your mobile phone using a BleuIO dongle. By sending simple text commands over Bluetooth Low Energy (BLE) through the Serial Port Service (SPS), the Python script can trigger actions. In our case, the script will retrieve and display CO2, temperature, and humidity values from a nearby BLE air quality monitoring sensor called HibouAir.
This approach opens up many possibilities for interactive projects, sensor monitoring, and remote control applications — all without the need for internet connectivity.
Begin by plugging your BleuIO dongle into the computer. Open a BLE terminal such as the BleuIO Web Terminal or any serial monitor and enable the Serial Port Service. You can do this by entering:
ATASPS1 AT+ADVSTART
This enables the SPS service, ensures incoming data is displayed as ASCII text, and starts advertising so your phone can connect.
The Python Script
The Python script connects to the BleuIO dongle over a serial port and listens for incoming BLE messages. When it receives one of four possible commands — CO2, TEMP, HUM, or ALL — it triggers a scan using the AT+FINDSCANDATA=220069=3 command. Here 220069 is the boardID of HibouAir sensor which we will use to retrieve air quality data in real time. This scans for nearby BLE devices broadcasting the matching advertising data. From the latest packet received, the script decodes and extracts the relevant sensor values.
Below is the complete script. Update the port variable to match your BleuIO’s serial port location.
import serial
import time
import re
def send_and_wait(ser, at_cmd):
ser.write((at_cmd + '\r\n').encode())
time.sleep(2)
output = []
while ser.in_waiting:
line = ser.readline().decode('utf-8', errors='ignore').strip()
output.append(line)
return output
def extract_last_adv_hex(lines):
adv_lines = [l for l in lines if "Device Data [ADV]:" in l]
if not adv_lines:
return None
last_line = adv_lines[-1]
match = re.search(r'([0-9A-Fa-f]{40,})$', last_line)
if match:
return match.group(1)
return None
def parse_co2_temp_hum(adv):
pos = adv.find("5B0705")
if pos == -1:
return None
def parse_val(offset, reverse=True, scale=1.0, signed=False):
raw = adv[pos + offset : pos + offset + 4]
if reverse:
raw = ''.join(reversed([raw[i:i+2] for i in range(0, 4, 2)]))
val = int(raw, 16)
if signed and val > 32767:
val -= 65536
return val / scale
temp_raw = parse_val(22, scale=1, signed=True)
temp = temp_raw / 10
hum = parse_val(26, scale=10)
co2 = int(adv[pos + 46 : pos + 50], 16)
return {
"temp": round(temp, 1),
"hum": round(hum, 1),
"co2": co2
}
# --- Main Loop ---
port = "/dev/cu.usbmodem4048FDEBA6D01"
baudrate = 115200
try:
with serial.Serial(port, baudrate, timeout=1) as ser:
print("Listening for BLE SPS command...")
while True:
line = ser.readline().decode('utf-8', errors='ignore').strip()
if line:
#print(f" Received: {line}")
cmd = line.replace("[Received]:", "").strip().upper()
if cmd in ["CO2", "TEMP", "HUM", "ALL"]:
print(" Scanning for environmental data...")
results = send_and_wait(ser, "AT+FINDSCANDATA=0504220069=3")
adv = extract_last_adv_hex(results)
if adv:
data = parse_co2_temp_hum(adv)
if data:
if cmd == "CO2":
print(f" CO2: {data['co2']} ppm")
elif cmd == "TEMP":
print(f" Temp: {data['temp']} °C")
elif cmd == "HUM":
print(f" Hum: {data['hum']} %")
elif cmd == "ALL":
print(f" Temp: {data['temp']} °C")
print(f" Hum: {data['hum']} %")
print(f" CO2: {data['co2']} ppm")
else:
print(" Could not decode sensor values.")
else:
print(" No advertising data found.")
except serial.SerialException as e:
print("Serial error:", e)
How It Works
Once the script is running, it keeps the serial connection to the BleuIO open and listens for BLE SPS messages sent from the phone. When a command is received, it is cleaned to remove any extra text added by the dongle, then matched against one of the four recognized keywords. If a match is found, the script asks the dongle to scan for nearby advertising packets from a device matching our filter. The script extracts the CO2, temperature, and humidity values, then displays the requested data based on the command.
For example, sending CO2 from the phone will only display the CO2 value, while sending ALL will show all three measurements.
Testing the Project
Run the script on your computer, then use the nRF Connect app on your phone to connect to the BleuIO dongle. Locate the SPS write characteristic (UUID ending in ...5CBA) and send a command in Text mode. Try sending CO2, TEMP, HUM, and ALL to see the different outputs. You should see the values appear in your terminal almost instantly after each command is sent.
Output
Use Cases and Expanding the Idea
This setup goes beyond simply reading CO2, temperature, and humidity. At its core, it is a BLE command-to-script bridge — meaning you can trigger any Python action from your phone without needing Wi-Fi or internet access.
In real-world use, this could be applied to:
Remote environmental monitoring: Trigger on-demand scans for sensor readings in greenhouses, offices, or laboratories.
IoT control panels: Send commands to control connected devices, such as turning fans or air purifiers on when CO2 levels rise.
Data logging systems: Store sensor readings in a file or database only when requested, helping conserve storage and processing power.
Event-based automation: Pair with external hardware like Raspberry Pi GPIO pins to trigger physical actions when commands are received.
Because the commands are just plain text, the possibilities for customization are endless. You could add more commands to control different hardware, run different scripts, or even communicate with multiple BLE devices. This same approach can also integrate with web dashboards, cloud services, or data visualization tools to make the results more accessible and actionable.
This project shows how simple it is to control Python scripts and interact with BLE sensor data using BleuIO and your phone. By combining the SPS profile with AT commands, you can create flexible, interactive tools that can be extended for various IoT applications — from remote environmental monitoring to interactive control systems.