Build a BLE Signal Strength Analyzer with Live Charts Using BleuIO, Python and Plotly

RSSI analysis becomes much easier when you can see the signal changing instead of watching a stream of numbers in a terminal. A chart makes it possible to notice movement, sudden drops, periods of instability, and differences between devices almost immediately.

In this project, we use a BleuIO USB dongle with Python and Plotly Dash to build a live BLE signal strength analyzer. The application scans nearby BLE advertisers, lets us select a device, and follows its RSSI over time. It also provides a quick overview of every device currently being discovered.

This is intended as a starting point rather than a complete positioning or ranging system. If you already work with RSSI, the dashboard gives you a practical base for collecting observations, testing an environment, and extending the analysis for your own use case.

What the analyzer provides

The dashboard is organized around one selected BLE device. Its current RSSI, smoothed RSSI, recent trend, and signal stability appear directly above a live time-series chart. The raw readings remain visible, so the processed line never hides what the dongle actually received.

Below the main analysis, the Discovered Now section compares the latest RSSI values from nearby devices. An RSSI distribution chart shows how the selected device’s readings are spread across the recent observation window.

This combination is useful when you want to answer practical questions such as whether a signal is consistently weak, whether it is changing direction, or whether an apparently stable device is producing occasional large variations.

Hardware and software

You will need:

The application uses pySerial to communicate with BleuIO and Plotly Dash for the local dashboard. BleuIO handles the BLE radio operation, so the Python application only needs to work with its serial AT-command interface.

Download and install the project

Clone the repository and move into the project directory:


git  clone  https://github.com/smart-sensor-devices-ab/bleuio-rssi-analyzer.git

cd  bleuio-rssi-analyzer

On macOS or Linux, create a virtual environment and install the dependencies:


python3  -m  venv  .venv

source  .venv/bin/activate

python  -m  pip  install  -r  requirements.txt

On Windows PowerShell, use:


py -m venv .venv

.venv\Scripts\Activate.ps1

python -m pip install -r requirements.txt

Connect the BleuIO dongle before starting the application. BleuIO briefly exposes its bootloader after power-up, so allow approximately 15 seconds for the normal application serial port to appear.

Start the analyzer:


python  app.py

Then open http://127.0.0.1:8050 in a browser.

Start scanning

The serial-port menu only shows ports identified as BleuIO devices. On macOS, this normally looks like /dev/cu.usbmodem.... On Windows, the dongle may appear as a generic USB Serial Device (COMx), so the application also recognizes BleuIO by its USB vendor and product identifiers.

Select the dongle and click Start scan. The application places BleuIO in central role and repeatedly performs a short timed scan using AT+GAPSCAN=1. Repeating the timed scan gives the dashboard a continuing series of fresh RSSI observations instead of a single discovery result. The command behavior is documented in the BleuIO AT command reference.

As advertisements are received, nearby addresses appear in the BLE device menu. Select the device you want to examine, and the analysis area updates immediately.

Follow a selected signal

The Current RSSI card displays the most recent reading. This value is useful for immediate feedback, but normal radio variation can make it jump even when neither device has deliberately moved.

The Smoothed RSSI view makes the overall movement easier to follow. The project first applies a short median filter to reduce isolated spikes, followed by an exponentially weighted moving average that gives more importance to recent observations. The original samples remain visible as dots, while the smoothed result appears as the blue line.

This processing is deliberately lightweight. It is enough to make the live chart readable, but it is also easy to replace if your experiment needs a Kalman filter, a different smoothing window, or no filtering at all.

Read the trend and stability indicators

The Recent Trend card compares the latest smoothed value with the recent signal history. A change of at least 4 dB is described as Getting stronger or Getting weaker. Smaller changes are shown as Stable. The card also displays the measured change, making the label easy to interpret rather than presenting it as an unexplained classification.

The Signal Stability card looks at the variation among the latest raw readings. A tightly grouped signal is labeled Steady. Greater variation is reported as Variable or Unstable. Strength and stability are treated separately: a weak signal can still be steady, and a strong signal can fluctuate considerably.

These thresholds are sensible defaults for exploring a signal, not universal BLE rules. They are kept in a small analysis module so they can be adjusted for the advertising interval, environment, antenna, and behavior of the device being studied.

Compare nearby devices

The Discovered Now chart provides context for the focused analysis. It shows the latest RSSI from every address found during the scan, ordered from stronger to weaker signals. This is useful when choosing a target or checking how crowded the local BLE environment is.

The distribution beside it belongs only to the selected device. It counts how often recent readings occurred within each RSSI range. A narrow group of bars usually accompanies a steadier signal, while a wider spread reveals more variation. The chart can be especially useful when comparing two locations or antenna orientations that have a similar average RSSI but different consistency.

Try a simple observation

Choose a BLE advertiser that sends packets regularly and leave it in a fixed position. Let the analyzer collect readings for a short period, then change one condition at a time: rotate the device, move it farther away, place an obstacle between it and BleuIO, or walk through the signal path.

The raw samples show the immediate radio response. The smoothed line makes the larger movement easier to see, while the trend and stability cards provide a compact summary. The distribution shows whether the new condition changed only the average signal or also made it more variable.

The goal is not to turn RSSI into an exact ruler. Bluetooth radio measurements are affected by the environment, reflections, antenna orientation, and the human body. The Bluetooth SIG notes that newer Bluetooth Channel Sounding is more reliable for accurate ranging than approaches that use RSSI as a distance proxy. The Bluetooth Low Energy primer provides useful background on that distinction.

For this project, RSSI is used for what it does well: observing relative signal behavior, detecting meaningful changes, and building an experiment around real measurements.

Take the project further

The source is intentionally compact so it can be adapted. A next version could save readings to CSV, compare multiple selected devices, add experiment markers, calculate packet reception rates, or load a calibration profile for a known environment. The analysis functions can also be replaced without changing the BleuIO serial scanner or the dashboard structure.

You can find the complete application, installation instructions, and tests in the BleuIO RSSI Analyzer repository.

With a live view of both raw and processed RSSI, the project turns BleuIO into a useful signal exploration tool—and a practical foundation for more focused BLE research.

Share this post on :

Scanning HibouAir Sensors with CircuitPython, BleuIO and the Cytron MOTION 2350 Pro

In this tutorial we will use CircuitPython to turn a Cytron MOTION 2350 Pro into a HibouAir scanner. The board’s RP2350 has no Bluetooth radio of its own. A BleuIO dongle in the board’s USB-A host port provides Bluetooth Low Energy, and a short CircuitPython program talks to it with ordinary AT commands.

The program scans for air quality monitoring device HibouAir advertisements, decodes them, and keeps the latest readings in global variables. One of them, the PM10 value from a HibouAir PM sensor, is ready to be used by the rest of your program. As an example, the program turns a hobby servo on one of the board’s servo ports into an analog PM10 gauge.

Everything runs on the board. No computer is needed once the code is in place, and the HibouAir sensors do not need to be paired.

Requirements

Hardware

Software

  • CircuitPython 10.x for the Cytron MOTION 2350 Pro, from circuitpython.org. The tutorial uses 10.3.1.
  • Two libraries from the Adafruit CircuitPython Library Bundle for version 10.x, copied into the lib folder on the CIRCUITPY drive:
    • asyncio (the whole folder)
    • adafruit_ticks.mpy, which asyncio needs

With circup installed on your computer, one command copies both:

circup install asyncio

Everything else the program uses is built into CircuitPython: usb_host, usb.core, json, binascii, microcontroller, pwmio and time. The servo is driven with pwmio directly, so it needs no extra library. No boot.py is needed.

What Does This Project Do?

1. The board becomes a USB host

The MOTION 2350 Pro’s USB-A port is driven by the RP2350’s PIO. CircuitPython starts it with usb_host.Port on GPIO24 and GPIO25. The board definition has no named pins for this port, so the program uses the raw pins.

2. CircuitPython finds BleuIO

The program waits until a device with vendor ID 0x2DCF and product ID 0x6002 appears, which is BleuIO. It selects the dongle’s configuration and sets DTR and RTS, so that BleuIO starts sending its output.

3. The dongle is brought into a known state

BleuIO remembers what it was doing, so a scan from an earlier run may still be going. The program first sends Ctrl-C three times to stop it. It then throws away everything the dongle sends until it has been quiet for 0.3 seconds. After that it sends:

  • ATE0, which turns echo off
  • ATV1, which turns verbose mode on, so every reply is one line of JSON

4. BleuIO scans for HibouAir only

AT+FINDSCANDATA=FF5B07

This reports only advertisements that contain FF5B07. FF is the AD type for manufacturer-specific data, and 5B 07 is HibouAir’s company ID, 0x075B, as it appears in the packet. Each result arrives as a line such as:

{"SF":127,"addr":"D9:63:8F:8E:01:0D","type":0,"data":"0201061BFF5B070504220095AE009627CF00AC014200000000000000019902"}

5. The board decodes the advertisement

The program walks the advertisement’s AD structures until it finds the HibouAir manufacturer data. This works whether or not the flags field comes first. Counting from the company ID, the sensor beacon (beacon type 0x05) holds:

OffsetFieldFormat
2Beacon type0x05 for sensor readings
3Board type0x03 is the PM sensor
4–6Sensor IDas shown in the HibouAir app, e.g. 22026A
9Pressure16-bit little-endian, tenths of hPa
11Temperature16-bit little-endian, signed, tenths of °C
13Humidity16-bit little-endian, tenths of %rh
17PM1.016-bit little-endian, tenths of µg/m³
19PM2.516-bit little-endian, tenths of µg/m³
21PM1016-bit little-endian, tenths of µg/m³
23CO216-bit big-endian, ppm

6. The readings are kept in global variables

  • pm10: the latest PM10 value in µg/m³, or None until a PM sensor has been heard
  • pm10_sensor: the ID of the sensor it came from
  • pm10_time: time.monotonic() when it arrived, so that a stale value can be recognised
  • sensors: a dictionary with everything the last beacon of every HibouAir sensor said, keyed by sensor ID

If you have more than one PM sensor, set PM_SENSOR_ID to the one pm10 should follow. If nothing has been heard for 15 seconds, the program starts the scan again.

Showing PM10 on a Servo Gauge

The MOTION 2350 Pro has eight servo ports, on GP0 to GP7, each with a PWM channel of its own. The program drives a servo on GP0 as a needle: 0 µg/m³ at 0 degrees, and 100 µg/m³ or more at 180 degrees.

With that full scale, the PM10 limits the HibouAir app uses fall at simple angles. That is where to draw the bands on a paper scale behind the servo arm:

PM10LevelNeedle
0–20 µg/m³Good0–36°
20–50 µg/m³Fair36–90°
50–100 µg/m³Poor90–180°
100 µg/m³ and aboveBad180°

The gauge is its own asyncio task, beside the reader and the command logic:

  • It sends a 50 Hz servo signal, from 500 µs at 0 degrees to 2500 µs at 180.
  • It moves the needle 2 degrees every 20 ms, so the arm swings to a new reading instead of jumping.
  • At start it sweeps once across the whole scale. A servo that is not connected is then noticed at once, instead of being taken for clean air.
  • Once the needle has been still for half a second, the pulses stop. A servo told to hold a position holds it actively, and at 0 degrees, the commonest reading of all, it pushes against its end stop and hums. Without pulses an ordinary hobby servo goes quiet and stays where it is.
  • The pulses start again when a new reading moves the needle by at least one degree, so readings of 0.0 and 0.1 µg/m³ taking turns do not wake it.

Connect the servo to the GP0 servo header, matching the servo cable to the markings next to the header. Check Cytron’s datasheet for how the servo ports are supplied before connecting a larger servo.

The settings are at the top of code.py:

  • SERVO_PIN: the port, board.GP0 to board.GP7
  • PM10_FULL_SCALE: the PM10 value at 180 degrees
  • SERVO_MIN_US and SERVO_MAX_US: the pulse widths at the ends. Narrow them if your servo strains at its end stops.
  • SERVO_STEP_DEG: how fast the needle moves
  • SERVO_REST_S and SERVO_DEADBAND_DEG: how long the needle is still before it rests, and how far a reading must move it to wake it

The board’s four DC motor drivers, on GP8 to GP15 (board.M1A to board.M4B in CircuitPython), would suit a different use of the same value, such as running a fan faster as PM10 rises.

Why the Flush After Ctrl-C Matters

Ctrl-C stops the scan, but whatever BleuIO had already queued still arrives afterwards. Often the last piece is a line cut off in the middle, sometimes with a byte that is not valid UTF-8.

In early versions of this program, a reader that tried to decode that line before removing it from its buffer stuck on it. Every later piece of data was appended behind the same broken line, and the program heard nothing more, in about half of all restarts.

The program now does two things:

  • It removes each line from the buffer before decoding it, so a broken line costs that line only.
  • It discards everything until the dongle has been quiet before sending the next command.

With both in place, the start-up worked every time in repeated tests, whether the dongle was fresh (echo on, verbose off) or left in verbose mode by an earlier run.

How to Run the Project

Step 1: Install CircuitPython

Download the CircuitPython UF2 for the Cytron MOTION 2350 Pro and put the board into its UF2 bootloader mode. On RP2350 boards this is done by holding BOOT while pressing RESET, or while connecting the USB cable. Copy the UF2 to the RP2350 drive that appears. The board restarts as a drive named CIRCUITPY.

Step 2: Add the libraries

Run circup install asyncio, or copy the asyncio folder and adafruit_ticks.mpy from the library bundle into CIRCUITPY/lib.

Step 3: Copy the program

Save the code at the end of this post as code.py on the CIRCUITPY drive. CircuitPython starts it as soon as the file is saved.

Step 4: Connect BleuIO

Insert the BleuIO dongle into the board’s USB-A port. Do not connect BleuIO to the computer for this setup.

Step 5: Connect the servo (optional)

Plug the servo into the GP0 servo header. It sweeps once from 0 to 180 degrees and back when the program starts, then follows PM10, and goes quiet whenever the needle is still.

Step 6: Watch the serial console

Open the board’s serial console at 115200 baud. Use Mu, Thonny or the CircuitPython web workflow, or on macOS and Linux:

screen /dev/cu.usbmodem2101 115200

The device name differs from computer to computer. If the console shows raw REPL; CTRL-B to exit, an editor has left it in raw REPL mode: press Ctrl-B, then Ctrl-D to start code.py.

Expected Output

BleuIO dongle found! (0x2dcf:0x6002)
CDC Control Lines (DTR/RTS) enabled.
Starting background USB reader task...
Starting the HibouAir scan...
[Sending]: ATE0
[Sending]: ATV1
[Sending]: AT+FINDSCANDATA=FF5B07
[HibouAir 22026A] PM10 0.1 ug/m3  PM2.5 0.1  PM1 0.0  21.8 C
[HibouAir 22008C] board 0x04, 21.5 C, 39.8 %rh, CO2 459 ppm
[HibouAir 220095] board 0x04, 20.8 C, 42.9 %rh, CO2 411 ppm
[HibouAir 22005A] board 0x04, 19.6 C, 44.2 %rh, CO2 721 ppm
[HibouAir 220265] board 0x09, 19.8 C, 45.5 %rh, CO2 533 ppm

Every HibouAir sensor in range appears with its ID. Lines from the PM sensor show the particle readings; the others show temperature, humidity and CO2. Each new PM line also updates pm10.

The Code

Source code is available to download from

https://github.com/smart-sensor-devices-ab/hibouair-with-circuitpython

This project is a practical example of how BleuIO can bring Bluetooth Low Energy connectivity to development boards without built-in Bluetooth support. Using the Cytron MOTION 2350 Pro and CircuitPython, we demonstrated how to scan for HibouAir sensors, read real-time data, and use that data to control a servo motor.

The complete source code is available for developers to explore, modify, and integrate into their own projects. Whether you’re building sensor monitoring systems, automation solutions, or custom IoT applications, this example provides a starting point for using BleuIO with compatible development boards.

Share this post on :

Send BLE Air Quality Data to Cloudflare D1 with Python and BleuIO

In this tutorial, we will build a simple BLE-to-cloud air quality gateway using HibouAir, BleuIO, Python, Cloudflare Workers, and Cloudflare D1.

A HibouAir sensor will advertise its air quality measurements over Bluetooth Low Energy. BleuIO, connected to a computer through USB, will scan for a specific HibouAir device and provide the BLE advertising data to a Python application through the serial port. The Python application will decode CO2, temperature, and humidity, display the measurements in the terminal, and upload them to a Cloudflare Worker. The Worker will then store the measurements in a Cloudflare D1 database.

This project is intentionally kept simple. It demonstrates how BleuIO can act as the Bluetooth interface for a desktop or gateway application without requiring the application itself to manage a Bluetooth stack.

Why Cloudflare D1?

Cloudflare D1 is a serverless SQL database built on SQLite. It works particularly well with Cloudflare Workers, allowing us to create a small HTTP API without running our own server.

For a project like this, there are several advantages:

  • No database server to install or maintain
  • No local web server required
  • HTTPS is handled automatically
  • Cloudflare Workers can communicate directly with D1
  • D1 supports regular SQL
  • A useful free tier is available
  • The database remains accessible even when the Computer running the BLE gateway is offline

This makes it convenient for prototypes, BLE gateways, environmental monitoring projects, demonstrations, and small IoT applications.

For this example, the Computer only needs to run the Python gateway application. Everything after the HTTP request is handled in the cloud.

Requirements

For this project, you will need:

Hardware

Software and services

  • Python 3
  • A free Cloudflare account
  • pyserial
  • requests

For this example, the HibouAir sensor has the board ID:

220069

and BleuIO appears on the Mac at:

/dev/cu.usbmodem4048FDEBA6D01

Your serial port depends on operating system and HibouAir board ID may be different.

Step 1: Create a Cloudflare D1 Database

First, log in to your Cloudflare account.

From the Cloudflare dashboard, go to the D1 database section and create a new database.

For this tutorial, we will call it:

hibouair-db

Once the database has been created, open its SQL Console.

Run the following SQL:

CREATE TABLE IF NOT EXISTS readings (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    board_id TEXT NOT NULL,
    measured_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
    co2 INTEGER NOT NULL,
    temperature REAL NOT NULL,
    humidity REAL NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_readings_board_time
ON readings(board_id, measured_at);

This creates a simple table containing:

  • Database ID
  • HibouAir board ID
  • Timestamp
  • CO₂
  • Temperature
  • Humidity

The measured_at field is generated automatically by D1 when the reading is inserted.

A stored record will look conceptually like this:

id          1
board_id    220069
measured_at 2026-10-05 14:42:30
co2         623
temperature 24.1
humidity    43.7

Step 2: Create the Cloudflare Worker

Next, create a Cloudflare Worker.

Now go to:

Workers & Pages → Create application

Create a basic Worker. ( you can choose “Start with Hello world”)

The Worker will sit between our Python application and the D1 database.

For this project, we can name it:

hibouair-api

and then press deploy.

After creating the Worker, open the code editor and replace the default code with the following :

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // API status
    if (url.pathname === "/" && request.method === "GET") {
      return Response.json({
        service: "HibouAir Cloud API",
        status: "online"
      });
    }

    // Store a new sensor reading
    if (url.pathname === "/api/readings" && request.method === "POST") {
      const suppliedKey = request.headers.get("X-API-Key");

      if (!suppliedKey || suppliedKey !== env.API_KEY) {
        return Response.json(
          {
            success: false,
            error: "Unauthorized"
          },
          {
            status: 401
          }
        );
      }

      let data;

      try {
        data = await request.json();
      } catch {
        return Response.json(
          {
            success: false,
            error: "Invalid JSON"
          },
          {
            status: 400
          }
        );
      }

      const boardId = String(data.board_id || "").trim();
      const co2 = Number(data.co2);
      const temperature = Number(data.temperature);
      const humidity = Number(data.humidity);

      if (!boardId) {
        return Response.json(
          {
            success: false,
            error: "board_id is required"
          },
          {
            status: 400
          }
        );
      }

      if (
        !Number.isFinite(co2) ||
        !Number.isFinite(temperature) ||
        !Number.isFinite(humidity)
      ) {
        return Response.json(
          {
            success: false,
            error: "Invalid sensor values"
          },
          {
            status: 400
          }
        );
      }

      const result = await env.DB
        .prepare(`
          INSERT INTO readings (
            board_id,
            co2,
            temperature,
            humidity
          )
          VALUES (?, ?, ?, ?)
        `)
        .bind(
          boardId,
          co2,
          temperature,
          humidity
        )
        .run();

      return Response.json({
        success: true,
        message: "Reading stored",
        board_id: boardId,
        co2: co2,
        temperature: temperature,
        humidity: humidity,
        meta: result.meta
      });
    }

    // Return the latest reading
    if (
      url.pathname === "/api/readings/latest" &&
      request.method === "GET"
    ) {
      const boardId =
        url.searchParams.get("board_id") || "220069";

      const reading = await env.DB
        .prepare(`
          SELECT
            id,
            board_id,
            measured_at,
            co2,
            temperature,
            humidity
          FROM readings
          WHERE board_id = ?
          ORDER BY id DESC
          LIMIT 1
        `)
        .bind(boardId)
        .first();

      if (!reading) {
        return Response.json(
          {
            success: false,
            error: "No readings found"
          },
          {
            status: 404
          }
        );
      }

      return Response.json({
        success: true,
        reading: reading
      });
    }

    return Response.json(
      {
        success: false,
        error: "Not found"
      },
      {
        status: 404
      }
    );
  }
};

Deploy the Worker.

At this stage, the Worker exists, but it is not yet connected to the D1 database.

Step 3: Bind the D1 Database to the Worker

Open the settings for the hibouair-api Worker.

Add a D1 database binding.

Use:

Variable name: DB
Database: hibouair-db

The variable name must be:

DB

because the Worker accesses the database through:

env.DB

For example:

await env.DB
    .prepare("SELECT * FROM readings")
    .all();

Once the binding has been added, deploy the Worker again if necessary.

Step 4: Add an API Key

Our Python application will be publicly connected to the Cloudflare Worker over HTTPS.

We therefore do not want anybody who discovers the Worker URL to be able to insert arbitrary sensor measurements.

We can protect the write endpoint using a simple API key.

On the Computer, generate a random key:

python3 -c "import secrets; print(secrets.token_hex(32))"

This will generate a long random value.

Copy it.

Now create a Worker secret environment variable named:

API_KEY

and paste the generated value as its value.

The Python application will include this key in every request using:

X-API-Key

The Worker checks:

const suppliedKey =
    request.headers.get("X-API-Key");

and compares it against:

env.API_KEY

Step 5: Test the Worker

Cloudflare will provide a Worker URL similar to:

https://hibouair-api.your-account.workers.dev

Open the base Worker URL in a browser.

You should receive something similar to:

{
  "service": "HibouAir Cloud API",
  "status": "online"
}

If you see this response, the Worker is running.

Our Python application will send measurements to:

https://hibouair-api.your-account.workers.dev/api/readings

The latest stored reading can later be retrieved from:

https://hibouair-api.your-account.workers.dev/api/readings/latest

Step 6: Download the Python Project

The source code for the BleuIO Python gateway is available on GitHub:

GitHub: https://github.com/smart-sensor-devices-ab/bleuio-cloudflare-d1-database

Clone the repository:

git clone https://github.com/smart-sensor-devices-ab/bleuio-cloudflare-d1-database

Then enter the project directory:

cd bleuio-cloudflare-d1-database

The project structure is intentionally small:

bleuio-cloudflare-d1/
│
├── main.py
├── info.example.txt
├── requirements.txt
└── .gitignore

The Python application handles:

  1. Connecting to BleuIO
  2. Sending BleuIO AT commands
  3. Searching for the HibouAir board ID
  4. Reading BLE advertising data
  5. Decoding CO2, temperature, and humidity
  6. Displaying the measurement in the terminal
  7. Sending the measurement to Cloudflare
  8. Repeating the process at a configurable interval

Step 7: Install the Python Dependencies

It is recommended to create a Python virtual environment.

Run:

python3 -m venv .venv

Activate it:

source .venv/bin/activate

Then install the required libraries:

pip install -r requirements.txt

The application only requires:

pyserial
requests

pyserial is used to communicate with BleuIO through the USB serial port.

requests is used to send the decoded measurements to the Cloudflare Worker over HTTPS.

Step 8: Configure the Project

The repository includes:

info.example.txt

Copy it:

cp info.example.txt info.txt

Then open:

info.txt

The configuration looks like this:

BLEUIO_PORT=/dev/cu.usbmodem4048FDEBA6D01
HIBOUAIR_SENSOR_ID=220069

SCAN_INTERVAL_SECONDS=30

CLOUDFLARE_API_URL=https://hibouair-api.YOUR-SUBDOMAIN.workers.dev/api/readings
CLOUDFLARE_API_KEY=YOUR_REAL_API_KEY

You need to update the values for your own environment.

BLEUIO_PORT

On our Mac, BleuIO is available at:

/dev/cu.usbmodem4048FDEBA6D01

Your port may be different.

You can normally find USB serial devices with:

ls /dev/cu.*

Look for the device corresponding to BleuIO.

On windows , look for COM port on device manager.

HIBOUAIR_SENSOR_ID

For this project, our HibouAir board ID is:

220069

Replace this with the board ID of the HibouAir device you want to monitor.

SCAN_INTERVAL_SECONDS

This determines how often the application starts a new HibouAir scan.

For this tutorial:

SCAN_INTERVAL_SECONDS=30

means approximately one scan every 30 seconds.

You can later change this without modifying the Python source code.

For example:

SCAN_INTERVAL_SECONDS=60

for approximately one reading per minute.

Or:

SCAN_INTERVAL_SECONDS=300

for approximately one reading every five minutes.

CLOUDFLARE_API_URL

Replace this with the URL of the Worker created earlier:

CLOUDFLARE_API_URL=https://hibouair-api.YOUR-SUBDOMAIN.workers.dev/api/readings

CLOUDFLARE_API_KEY

Enter the same secret that was configured in the Cloudflare Worker:

CLOUDFLARE_API_KEY=YOUR_REAL_API_KEY

The info.txt file should not be committed to GitHub because it contains the API key.

Step 9: How the HibouAir Data Is Read

BleuIO communicates with our Python application using simple AT commands over the serial port.

The application first connects to BleuIO and places it in central mode.

It then searches for our HibouAir board ID using:

AT+FINDSCANDATA=220069=5

Here:

220069

is the HibouAir board ID, and:

5

defines the scan duration.

BleuIO handles the Bluetooth Low Energy scanning and returns the matching BLE advertising information over serial.

This means the Python application does not need to directly communicate with the operating system’s native Bluetooth API.

BleuIO then returns the advertising data to Python.

Step 10: Decode the HibouAir Advertisement

The Python application searches the advertising payload for the HibouAir data section:

5B070

For the HibouAir advertisement used in this example, the application extracts the three measurements needed for this project. Temperature, Humidity and CO2. The result becomes a normal Python structure similar to:

{
    "co2": 623,
    "temperature": 24.1,
    "humidity": 43.7
}

The application also performs basic validation before uploading the measurements.

For example, obviously invalid temperature, humidity, or CO2 values are rejected instead of being stored in the database.

Step 11: Sending the Measurement to Cloudflare

Once a valid advertisement has been decoded, Python prepares a JSON request.

For example:

{
  "board_id": "220069",
  "co2": 623,
  "temperature": 24.1,
  "humidity": 43.7
}

This is sent using an HTTP POST request to:

/api/readings

The API key is included in the request header:

X-API-Key

The Cloudflare Worker validates the key and inserts the measurement into D1.

The Python application therefore never connects directly to the database.

Step 12: Run the Application

Before running the project, make sure no other program is currently using the BleuIO serial port.

For example, close any active BleuIO connection in:

  • BleuIO application
  • screen
  • serial monitor
  • terminal program
  • other Python application

Then run:

python3 main.py

The application should connect to BleuIO.

You should see output similar to:

BleuIO + HibouAir + Cloudflare D1
---------------------------------

BleuIO port : /dev/cu.usbmodem4048FDEBA6D01
HibouAir ID : 220069
Scan interval: 30 seconds

Trying BleuIO on /dev/cu.usbmodem4048FDEBA6D01 at 115200 baud...

BleuIO connected successfully.

Setting BleuIO to central mode...
Ready.

The application will then start scanning:

Scanning for HibouAir 220069...

Command: AT+FINDSCANDATA=220069=5

When a valid HibouAir advertisement is received and decoded, the terminal will display something similar to:

============================================
          HIBOUAIR AIR QUALITY
============================================

  CO2                623 ppm
  Temperature       24.1 °C
  Humidity          43.7 %RH

============================================

Uploading to Cloudflare D1...
✓ Reading stored successfully.

Next scan in 23 seconds...

The application continues running automatically.

There is no need to restart it for each measurement.

To stop the application, press:

Ctrl + C

Step 13: Check the Data in Cloudflare D1

Now return to the Cloudflare D1 database console.

Run:

SELECT *
FROM readings
ORDER BY id DESC
LIMIT 10;

You should see the measurements coming from HibouAir.

For example:

id  board_id  measured_at          co2  temperature  humidity

12  220069    2026-10-05 14:58:20  618  24.1         43.8
11  220069    2026-10-05 14:57:50  621  24.1         43.8
10  220069    2026-10-05 14:57:20  623  24.1         43.7

You can also explore the data from your database.

Step 14: Check the Latest Reading Through the API

We also created a simple read endpoint.

Open:

https://YOUR-WORKER.workers.dev/api/readings/latest

The Worker will query D1 and return the latest measurement.

For example:

{
  "success": true,
  "reading": {
    "id": 12,
    "board_id": "220069",
    "measured_at": "2026-10-05 14:58:20",
    "co2": 618,
    "temperature": 24.1,
    "humidity": 43.8
  }
}

Building a Dashboard Later

This tutorial intentionally stops at the database and API level.

The objective is to demonstrate the complete BLE-to-cloud data path while keeping the example easy to understand.

At this point, the data is already available in the cloud.

A developer could later build a dashboard showing:

Current CO₂
Current Temperature
Current Humidity

CO₂ history
Temperature history
Humidity history

A dashboard could request historical readings through additional Worker API endpoints.

It could also display thresholds, warnings, averages, minimum/maximum measurements, or other analytics.

Those features are deliberately outside the scope of this example because the main goal is to demonstrate how easily BLE data can be brought into a serverless cloud architecture using BleuIO.

We kept the project intentionally simple so that the individual parts are easy to understand and modify.

From here, the project can be expanded in many directions. Multiple HibouAir sensors could be monitored, additional air quality parameters could be stored, historical API endpoints could be created, and a complete cloud dashboard could be built on top of the existing data.

Share this post on :

Running a HibouAir BLE Air Quality Scanner on Adafruit Fruit Jam with BleuIO

In this project, we will look at how the BleuIO SSD025 Bluetooth Low Energy USB dongle can add BLE communication to an Adafruit Fruit Jam powered by the RP2350.

The project takes an unusual software approach. Instead of running the HibouAir scanner as part of the native firmware, the Fruit Jam runs a custom real-time operating system called myRTOS .

A myRTOS application module, hibouair.c, written in C, communicates with the BleuIO dongle over USB, scans for nearby HibouAir air-quality sensors, decodes their Bluetooth Low Energy advertisements and displays the latest measurements in real time in a terminal window.

The result is a compact embedded BLE air-quality gateway running on the RP2350.

Requirements

For this project, we use:

The Fruit Jam is particularly suitable for this project because it has a built-in USB hub with two exposed USB host ports.

What does this project do?

1. HibouAir broadcasts sensor data

2. BleuIO receives the BLE advertisements

3. Fruit Jam receives the data over USB

4. myRTOS decodes and displays the measurements

The HibouAir sensor periodically broadcasts its measurements using BLE advertising packets. The SSD025 receives these packets and makes the information available to the RP2350 over USB.

The myRTOS application then scans for HibouAir devices, interprets the received information and maintains a live list of detected sensors.

The terminal provides an easy way to observe the air-quality information as it arrives.

Hardware

The project is built around three main pieces of hardware.

Adafruit Fruit Jam with the BleuIO SSD025 connected directly to the USB Type-A host port.

Adafruit Fruit Jam

The Fruit Jam is a compact computer based on the RP2350B microcontroller. Adafruit provides two USB Type-A host ports through an onboard USB hub, making it possible to connect USB peripherals directly to the board.

The board provides considerably more functionality than is required for this simple gateway. In addition to the RP2350B, it includes flash storage, PSRAM, USB host capability, microSD, DVI output, audio hardware and an ESP32-C6 for wireless connectivity.

For this project, however, the important feature is the USB host interface .

BleuIO SSD025

The BleuIO SSD025 is used as the Bluetooth Low Energy interface.

This means the RP2350 application does not need to implement the Bluetooth Low Energy stack itself.

The dongle supports Bluetooth 5.2 and USB Full Speed, can operate in Bluetooth central or peripheral roles, and is controlled through its USB interface.

The application can therefore access BLE functionality through BleuIO’s USB interface while leaving the Bluetooth communication to the dongle.

HibouAir SSD002

The HibouAir SSD002 is the wireless air-quality sensor.

HibouAir’s documentation describes the SSD002/2B as a CO2 air-quality monitor using an NDIR CO2 sensor, with wireless Bluetooth connectivity. The sensor family can provide environmental parameters including CO2, temperature, humidity, particulate matter and VOC-related measurements depending on the model.

www.hibouair.com

Why use BleuIO?

The BleuIO acts as the bridge between the USB host implemented by the Fruit Jam and the Bluetooth Low Energy devices in the surrounding environment.

The application processor can concentrate on:

  • USB communication
  • RTOS scheduling
  • data parsing
  • terminal output

while the BleuIO handles the Bluetooth side.

BleuIO’s own HibouAir gateway example similarly uses BleuIO to provide BLE communication to RP2040/RP2350 development boards through USB.

The Software Architecture

The software in this project is based on myRTOS , a small real-time operating system environment running on the Fruit Jam.

The HibouAir application is started from the RTOS command interface:

myrtos:/> hibouair

Once started, the application enters scanning mode:

scanning; ctrl-C to stop

The application then continuously processes incoming BLE data.

Conceptually, the software pipeline looks like this:

HibouAir → BLE advertisements → BleuIO SSD025 → USB → Fruit Jam / myRTOS → HibouAir packet decoding → Terminal display

The important point is that this is not simply a raw BLE packet monitor.

The application turns the incoming wireless data into a human-readable sensor table .

How to Run the Project

The complete project is available on GitHub:

https://github.com/smart-sensor-devices-ab/Adafruit-Fruit-Jam-with-BleuIO

  1. Connect the board: Connect the Adafruit Fruit Jam to your computer using a USB-C cable and turn the power switch ON.
  2. Hold down Button #1 (the UF2 BOOT button located on GPIO0), press and release the reset button while continuing to hold Button #1, and release Button #1 only after the bootloader drive appears.
  3. Drag the project’s .uf2 file onto the board’s bootloader drive. The image includes myRTOS and the hibouair.c application module.
  4. Press the reset button to reboot the board.
  5. Connect the BleuIO SSD025: Plug the BleuIO SSD025 into one of the Fruit Jam’s USB Type-A host ports.
  6. Power the HibouAir sensor: Make sure one or more HibouAir sensors are powered on and within Bluetooth range of the BleuIO dongle.

Button Locations on the Adafruit Fruit Jam

To enter UF2 bootloader mode, this project uses two buttons on the Fruit Jam:

  • Button #1 (UF2 BOOT) — located on GPIO0 towards the bottom-right edge of the board.
  • Reset button — located on the left edge of the board.

The image below shows the location of both buttons.

After rebooting, open a terminal window and connect to the board.

Windows

After rebooting, open Device Manager and identify the COM port assigned to the Fruit Jam. Connect to this port using a serial terminal such as PuTTY.

macOS

To find the Fruit Jam’s serial port, open a terminal and run:

ls /dev/cu.usbmodem*

The command should return a device similar to:

/dev/cu.usbmodem2351xxxx

Connect to it using:

screen /dev/cu.usbmodem2351xxxx

Real-Time HibouAir Scanning

After connecting to the Fruit Jam, start the HibouAir application from the myRTOS command interface:

myrtos:/> hibouair
scanning; ctrl-C to stop

The program then prints a table containing the detected HibouAir devices.

This gives the user an immediate overview of all the HibouAir sensors currently being received.

Expected Output

For example, the output below shows several HibouAir devices detected during a scan. The table shows each detected device’s board identifier, Bluetooth address, sensor type and latest environmental measurements.

Where Could This Go Next?

The current application concentrates on the essential task: receiving HibouAir advertisements and presenting the decoded measurements in real time.

Logging the data to the Fruit Jam’s microSD card would also make it possible to create long-term air-quality datasets without requiring a permanently connected computer.

The Fruit Jam’s USB host capability, microSD storage and RP2350B processor make this type of expansion practical.

Conclusion

This project demonstrates how the Adafruit Fruit Jam, BleuIO SSD025 and HibouAir SSD002 can be combined to create a compact RP2350-based Bluetooth Low Energy air-quality gateway.

HibouAir sensors broadcast their environmental measurements over BLE. BleuIO receives these advertisements and transfers the data to the Fruit Jam over USB, where the myRTOS hibouair application decodes the sensor data and presents the latest measurements in real time.

The project shows how BleuIO can add BLE communication to an RP2350-based embedded system while keeping the Bluetooth interface separate from the main application.

Share this post on :

Build a Cloud Air Quality Dashboard with HibouAir, BleuIO, and TagoIO

Air quality and environmental monitoring are common IoT use cases where Bluetooth Low Energy sensors can provide useful real-time information without requiring complex infrastructure.

In this project, we will use HibouAir as our air quality monitoring sensor, BleuIO as the Bluetooth Low Energy interface, and a Python application to collect and decode the advertised sensor data. The decoded measurements will then be sent to TagoIO, where we can store and visualize the data using an online dashboard.

The goal is to keep the project simple. There is no need to install an IoT server, MQTT broker, database, or dashboard software locally. BleuIO handles the BLE communication, the Python application processes the sensor data collected from HibouAir, and TagoIO handles the cloud side.

Why TagoIO?

TagoIO is an IoT platform that provides tools for connecting devices, storing sensor data, and creating dashboards for visualization.

For this project, one of the main reasons for choosing TagoIO is that it is easy to get started. It provides a free plan, which is suitable for experimenting with a small number of devices and building proof-of-concept IoT applications.

This makes it particularly useful for projects such as environmental monitoring where we simply want to send sensor measurements to the cloud and visualize them without setting up our own backend infrastructure.

If the project grows later, TagoIO also provides paid subscription options with additional resources and capabilities. This means we can start with the free option while developing and testing the project, and move to a larger plan later if necessary.

Another useful feature is its dashboard builder. Once our sensor variables reach TagoIO, we can create cards, displays, gauges, and time-series charts directly from the web interface.

For our HibouAir project, this allows us to visualize measurements such as:

  • Temperature
  • Relative humidity
  • CO2

Project Requirements

For this project, we need the following hardware and software.

Hardware

BleuIO

BleuIO is a USB Bluetooth Low Energy dongle that allows us to control BLE communication using simple AT commands over a serial connection.

In this project, BleuIO scans for the BLE advertisements transmitted by HibouAir and passes the received advertisement data to our Python application.

HibouAir

HibouAir is the environmental BLE sensor used in this example.

For this project, we are using a HibouAir device advertising with the board ID:

220069

Software Requirements

Install Python 3 if it is not already available.

The project uses the following Python packages:

pyserial
requests

Install them using:

python3 -m pip install pyserial requests

You will also need a free TagoIO account.

Create an account and then create a new device in the TagoIO console.

For example:

Device Name: HibouAir Air Quality Sensor

After creating the device, TagoIO generates a Device Token.

The Device Token is used by the Python application to authenticate when sending measurements to TagoIO.

Keep this token private and do not publish it in a public GitHub repository.

How the Project Works

The project consists of three main stages.

1. Collect BLE Advertisements from HibouAir

HibouAir continuously broadcasts environmental data using Bluetooth Low Energy advertisements.

BleuIO is connected to the computer through USB and appears as a serial device.

On macOS, for example, the BleuIO dongle may appear as:

/dev/cu.usbmodemxxxxxxx

The Python application opens this serial port and communicates with BleuIO.

To locate our specific HibouAir device, the application asks BleuIO to search for advertisements containing the HibouAir board ID:

220069

Conceptually, the command looks like:

AT+FINDSCANDATA=220069=3

BleuIO scans for matching BLE advertisements and returns the advertisement data to the Python application.

The data received from BleuIO contains the raw BLE advertisement in hexadecimal format.

The Python application identifies the HibouAir environmental advertisement and extracts the relevant bytes from the payload.

The values are then decoded into useful environmental measurements.

For example:

Temperature : 24.6 °C
Humidity    : 48.2 %
CO2         : 612 ppm

Sending the Data to TagoIO

Once the HibouAir advertisement has been decoded, the Python application prepares the measurements in the format expected by TagoIO.

For example:

[
  {
    "variable": "temperature",
    "value": 24.6,
    "unit": "°C"
  },
  {
    "variable": "humidity",
    "value": 48.2,
    "unit": "%"
  },
  {
    "variable": "co2",
    "value": 612,
    "unit": "ppm"
  }
]

The application then sends the data to the TagoIO API using HTTPS.

The TagoIO Device Token is included with the request so that TagoIO knows which device the measurements belong to.

After the first successful upload, the variables automatically become available for the device in TagoIO.

In this example, the variables are:

temperature
humidity
co2

The application continues scanning for new HibouAir advertisements and periodically uploads new measurements.

Creating the TagoIO Dashboard

Once the sensor data starts appearing in TagoIO, we can create a dashboard.

In the TagoIO console, go to:

Dashboards

Create a new dashboard and give it a name such as:

HibouAir Air Quality Dashboard

We can then add widgets using the variables sent by our Python application.

For example, we can create current-value widgets for:

Temperature
Humidity
CO2

We can also add line charts to display how each measurement changes over time.

Because TagoIO stores timestamps with the measurements, the line-chart widgets can display the sensor data as a time series.

This gives us a simple cloud-based air-quality monitoring dashboard without having to maintain our own database or web application.

Source Code

The complete source code for this project is available on GitHub:

GitHub:
https://github.com/smart-sensor-devices-ab/BleuIO-hibouair-tagoio

Configure the Application

Before running the application, update the configuration with your BleuIO serial port and TagoIO Device Token.

For example:

BLEUIO_PORT = "/dev/cu.usbmodem4048FDE52CF21"

HIBOUAIR_ID = "220069"

You will also need your TagoIO Device Token.

For development, the token can be supplied through the application configuration.

The TagoIO API endpoint also needs to match the region used by your TagoIO account.

Running the Application

Make sure:

  1. BleuIO is connected to the computer.
  2. HibouAir is powered on and advertising.
  3. Your computer has an Internet connection.
  4. Your TagoIO Device Token has been configured.

Then run:

python3 script.py

The application should first connect to BleuIO.

You should see output similar to:

BleuIO + HibouAir + TagoIO
==========================

BleuIO port: /dev/cu.usbmodem4048FDE52CF21
BleuIO responded OK

Looking for HibouAir 220069

When a valid HibouAir advertisement is received, the decoded sensor measurements will be displayed:

----------------------------------
HibouAir detected
----------------------------------

Temperature   : 24.6 °C
Humidity      : 48.2 %
CO2           : 612 ppm

----------------------------------

TagoIO upload: OK

The application will continue scanning and uploading new readings until it is stopped.

Use:

Ctrl + C

to stop the application.

Output

After the application has been running for a while, the sensor measurements will be available from the TagoIO device page and dashboard.

The dashboard can display the latest HibouAir measurements as well as their historical changes.

In this project, we created a simple cloud-connected environmental monitoring system using HibouAir, BleuIO, Python, and TagoIO.

This project should be considered a starting point rather than a finished monitoring product.

The code can be modified or expanded in many ways. Feel free to modify, extend, or reuse the code in your own BLE and IoT applications as needed.

Share this post on :

Turn an RP2040 or RP2350 Board into a HibouAir Gateway with BleuIO

In this tutorial, we will look at how the BleuIO USB dongle can add Bluetooth Low Energy communication to an RP2040 or RP2350 development board.

The project uses the open-source Pico I/O Bridge firmware. It allows a supported development board to work as a USB host for BleuIO. BleuIO scans nearby HibouAir sensors, and the board displays the latest sensor data through a simple browser interface.

The board also creates a small USB network connection with the computer. This means the dashboard can be opened directly in a web browser without installing a desktop application or connecting the board to Wi-Fi. The firmware is written in Rust and exposes its interfaces through a local .local address.

This project is a useful example of how BleuIO can provide BLE communication to development boards that do not have built-in Bluetooth. The board handles USB hosting and networking, while BleuIO handles Bluetooth scanning.

Requirements

You will need:

What Does This Project Do?

The project creates a simple connection between HibouAir sensors, BleuIO and your computer.

1. HibouAir broadcasts sensor data

HibouAir sensors regularly broadcast their measurements using Bluetooth Low Energy advertising packets.

Depending on the HibouAir model, these broadcasts can contain parameters such as: Temperature, Humidity, Pressure, CO2, VOC, Noise, PM1.0, PM2.5, PM10, Ambient light

Because the project reads BLE advertising data, the HibouAir sensors do not need to be paired with the development board.

2. BleuIO receives the BLE advertisements

BleuIO is connected to the USB-A host port of the development board.

After BleuIO is detected, the firmware manages the scanning process automatically. BleuIO searches for nearby HibouAir advertisements and sends the received data to the board.

This is an important part of the project: the RP2040 or RP2350 board does not need its own Bluetooth radio. BleuIO provides the Bluetooth Low Energy functionality through USB.

3. The board decodes the HibouAir data

The firmware reads the BLE scan results from BleuIO and decodes the HibouAir manufacturer data.

It identifies the HibouAir sensor ID and sensor type before extracting the measurements supported by that particular model.

The firmware keeps the latest readings for up to eight HibouAir sensors in memory. When no sensor filter has been configured, sensors are discovered automatically.

4. The results appear in a web browser

The development board appears on the computer as a small USB Ethernet device. It provides its own local IP network and advertises a unique .local hostname.

The browser dashboard shows: BleuIO connection status, USB transfer status, Number of HibouAir sensors discovered, HibouAir sensor IDs, Sensor types, Latest measurements, Time since the last advertisement, Number of reports received

The dashboard updates automatically while BleuIO continues scanning.

How to Run the Project

The complete project is available on GitHub:

Pico I/O Bridge GitHub repository

There are two ways to run it:

  1. Flash a pre-built firmware file.
  2. Build the firmware from the Rust source code.

Using a pre-built firmware file is the easiest method.

Method 1: Use the Pre-Built Firmware

Step 1: Download the firmware

Open the project’s release page:

Pico I/O Bridge v0.2.0 release

The release contains pre-built UF2 firmware files for the supported board profiles, together with a checksum file.

Download the UF2 file that matches your board.

For this project, select the firmware for either:

Adafruit Feather RP2040 USB Host

or:

Waveshare RP2350 USB-A

Do not flash firmware intended for a different board model.

Step 2: Put the board into bootloader mode

Disconnect the board from the computer.

Put the board into its normal UF2 or BOOTSEL flashing mode. The exact button combination depends on the board, so follow the normal bootloader procedure for your development board.

When the board enters bootloader mode, it should appear on the computer as a removable USB drive.

Step 3: Flash the UF2 file

Copy the downloaded UF2 file to the board’s removable drive.

The board should restart automatically after the file has been copied.

Wait a few seconds for the firmware to start and for the USB network connection to become available.

Step 4: Connect BleuIO

Insert the BleuIO USB dongle into the USB-A host port of the development board.

The physical connection should look like this:

Do not connect BleuIO directly to the computer for this setup. It needs to be connected to the USB host port on the development board.

Step 5: Power the HibouAir sensors

Make sure your HibouAir sensors are powered on and located within Bluetooth range of BleuIO.

No Bluetooth pairing process is required. The sensors simply need to be broadcasting their normal BLE advertisement data.

Step 6: Find the board hostname

Each board creates a unique .local hostname.

For the Adafruit Feather RP2040 USB Host, it will follow this format:

pico-io-usb-host-xxxxxx.local

For the Waveshare RP2350 USB-A, it will follow this format:

pico-io-waveshare-rp2350-xxxxxx.local

Replace xxxxxx with the unique identifier assigned to your board.

On macOS, the board can also be discovered through the built-in dns-sd utility:

dns-sd -B _http._tcp

The project advertises its web interface through mDNS and DNS Service Discovery.

On Windows, the optional mDNS Discovery utility can be used to view available mDNS and DNS-SD services.

Step 7: Open the web interface

Open a browser and enter the board hostname.

For example:

http://pico-io-usb-host-244c29.local/usb-host.html

Use your board’s actual hostname rather than the example above.

The USB-host-enabled profiles expose the USB status page at:

/usb-host.html

The web interface and the USB-host API are included whenever the firmware is built with the pio-usb-host feature.

Step 8: Wait for BleuIO to become ready

After opening the page, check the USB section.

You should see a status similar to:

BleuIO Ready

You should also see:

BleuIO HibouAir scanner ready

Once BleuIO is ready, the managed HibouAir scan begins automatically.

Nearby HibouAir sensors should start appearing as individual cards. The first results may take a few seconds, depending on the sensors’ advertisement intervals.

Expected Output

The page should show each discovered HibouAir sensor with its sensor ID, sensor model and latest measurements.

Method 2: Build the Firmware from Source

Developers can also clone the complete project and build the firmware using Rust.

Clone the repository:

git clone https://github.com/ulso/pico-io-bridge.git
cd pico-io-bridge

Build for the Adafruit Feather RP2040 USB Host

cargo build --locked --release \
  --no-default-features \
  --features board-adafruit-rp2040-usb-host

Build for the Waveshare RP2350 USB-A

cargo build --locked --release \
  --target thumbv8m.main-none-eabihf \
  --no-default-features \
  --features board-waveshare-rp2350-usb-a

The RP2040 and RP2350 profiles use different Rust target configurations, so it is important to use the command intended for your board.

With the board in BOOTSEL mode, the project can also be built and flashed using the appropriate cargo run command. The repository contains the full toolchain and flashing details for developers who want to modify the firmware.

The complete source code, build instructions and examples are available in the Pico I/O Bridge GitHub repository. Additional project screenshots can be found in the Pico I/O Bridge Wiki.

Share this post on :

Send HibouAir Temperature and Humidity Data to openSenseMap Using BleuIO

In this project, we are going to build a simple air quality data-sharing system using a BleuIO USB dongle, a HibouAir sensor, Python, and openSenseMap.

The goal of this project is to collect temperature and humidity data from a HibouAir sensor over Bluetooth Low Energy and publish the values online using openSenseMap. Once the data is uploaded to openSenseMap, it can be viewed publicly and shared with others.

This is a minimal project designed to show how BleuIO can be used as a BLE gateway for collecting environmental sensor data and sending it to an online platform. The same idea can later be expanded to include other air quality parameters such as CO2, VOC, particulate matter, pressure, or other sensor values.

What is openSenseMap?

openSenseMap is an open data platform for environmental sensor data. It allows users to create a virtual sensor station, called a senseBox, and upload measurements from different sensors.

By using openSenseMap, we can make our air quality data public and easy to access. This is useful for projects related to environmental monitoring, smart cities, classrooms, research, and IoT demonstrations.

In this project, we will create a senseBox on openSenseMap and add two sensors:

  • Temperature
  • Relative humidity

The Python script will read these values from the HibouAir BLE advertisement and upload them to the correct openSenseMap sensor IDs.

Project Requirements

To complete this project, we need the following hardware and software:

No extra microcontroller, BLE pairing, database, or web server is required. BleuIO connects directly to the computer through USB and is controlled using AT commands from the Python script.

Setting Up openSenseMap

First, create an account on openSenseMap. After logging in, create a new senseBox. This senseBox will represent the sensor station where our HibouAir measurements will be uploaded.

Inside the senseBox, create two sensors:

  • Temperature with unit °C
  • Relative humidity with unit %RH

After creating the senseBox and sensors, openSenseMap will provide different IDs. We need to copy and save the following values:

  • senseBox ID
  • Temperature sensor ID
  • Humidity sensor ID

These IDs are important because the Python script uses them to know where to upload each measurement. The senseBox ID identifies the box, while each sensor ID identifies the individual measurement field.

Configuring the Project

The project uses an info.txt file to store the required IDs and configuration values.

If info.txt does not already exist, copy the example file:

cp info.example.txt info.txt

Then add the openSenseMap IDs and the HibouAir board ID:

OPENSENSEMAP_BOX_ID=your_box_id_here
SENSOR_ID_TEMPERATURE=your_temperature_sensor_id_here
SENSOR_ID_HUMIDITY=your_humidity_sensor_id_here
HIBOUAIR_SENSOR_ID=220069

# Optional on macOS:
# BLEUIO_PORT=/dev/cu.usbmodemXXXX

The HIBOUAIR_SENSOR_ID is the board ID printed on the HibouAir sensor. The script uses this ID to filter BLE advertisements and make sure it is reading data from the correct device.

Normally, the script automatically finds the BleuIO serial port on macOS by checking /dev/cu.usbmodem* and /dev/cu.usbserial*. If needed, the port can also be added manually in info.txt.

The info.txt file is excluded from Git using .gitignore, so private box IDs and sensor IDs are not committed to the repository.

Installing Dependencies

Open Terminal, go to the project directory, and run:

python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt

The project uses only two Python packages:

  • pyserial
  • requests

pyserial is used to communicate with the BleuIO dongle over the serial port, and requests is used to upload data to openSenseMap.

How the Project Works

The BleuIO USB dongle is connected directly to the Mac. The Python script opens the BleuIO serial port and sends AT commands to scan for BLE advertisements from the HibouAir sensor.

The script uses the HibouAir board ID to filter the scan result. For example:

AT+FINDSCANDATA=220069=5

This command asks BleuIO to search for advertised data containing the board ID 220069 for five seconds.

When the scan response is received, the script decodes the latest valid HibouAir advertisement. It extracts only two values. Temperature and Relative humidity,

After decoding the values, the script uploads them to the configured openSenseMap senseBox using the temperature and humidity sensor IDs.

The upload process repeats every 60 seconds.

Only temperature and humidity are uploaded. Bluetooth addresses, raw advertising payloads, and other sensor values are not sent to openSenseMap or saved to a file.

Running the Script

Connect BleuIO to the Computer and run:

python3 main.py

Press Ctrl+C to stop the logger.

Example output:

## BleuIO HibouAir openSenseMap Logger

Using port: /dev/cu.usbmodem4048FDE52CF21
Checking BleuIO...
BleuIO responded OK

Scanning for HibouAir sensor ID: 220069
Scan command: AT+FINDSCANDATA=220069=5
Waiting for BleuIO scan response...

Decoded HibouAir data:
Temperature: 24.3 °C
Humidity: 42.8 %RH

Uploading to openSenseMap...
Temperature uploaded successfully
Humidity uploaded successfully

Waiting 60 seconds before next scan...

The script reads the BleuIO response, decodes the temperature and humidity values, uploads them to openSenseMap, and then waits before starting the next scan.

Outputs

Source Code

The full source code for this project is available on GitHub.

Users can download the project, configure their own openSenseMap IDs, add their HibouAir board ID, and run the script on their own system.

GitHub URL:

https://github.com/smart-sensor-devices-ab/bleuio_opensensemap

Expanding the Project

This project currently uploads only temperature and humidity, but it can be expanded easily.

Users can modify the script to decode and upload more environmental parameters, such as: CO2, VOC, Particulate matter, Air pressure, Light level etc.

Additional sensors can also be created in openSenseMap, and their sensor IDs can be added to the configuration file. This makes the project flexible for different air quality monitoring and IoT applications.

With only a BleuIO USB dongle, a HibouAir sensor, and a Python script, we can collect temperature and humidity data and make it publicly available. The project is easy to set up, requires no BLE pairing, and can be extended for more advanced air quality monitoring applications.

Share this post on :

Build a BLE RSSI Heatmap Visualizer Using BleuIO

Bluetooth Low Energy devices are everywhere, from smart sensors and beacons to IoT devices and proximity-based applications. When working with BLE, developers often need a quick way to scan nearby advertising devices and understand their signal strength.

In this project, we build a simple BLE RSSI Heatmap Visualizer using the BleuIO USB dongle. The application runs directly in the browser and connects to BleuIO through the serial port. It performs a BLE scan, reads nearby advertising devices, extracts their RSSI values, and displays them in a visual heatmap.

This is helpful because raw BLE scan output can be difficult to read, especially when many devices are advertising at the same time. With this visualizer, developers can quickly see which devices have strong, medium, or weak signal strength.

BleuIO makes this project simple because it handles the BLE scanning through easy AT commands. Instead of writing low-level Bluetooth code, we can send commands from JavaScript and focus on building the visualization.

Requirements

To try this project, you need:

How It Works

The application connects to the BleuIO dongle using the browser’s Web Serial API. Once connected, it sends BleuIO AT commands to put the dongle into central scanning mode and start a BLE GAP scan.

The main commands used are:

AT+CENTRAL
AT+SHOWRSSI=1
AT+GAPSCAN=5

AT+CENTRAL prepares BleuIO for scanning. AT+SHOWRSSI=1 enables RSSI values in the scan output. AT+GAPSCAN=5 scans nearby BLE devices for 5 seconds.

The browser app then reads the serial output and looks for device information such as MAC address, RSSI value, and device name if available. Each device is grouped based on RSSI strength.

The signal categories are simple:

Strong signal: RSSI >= -50
Medium signal: RSSI between -51 and -70
Weak signal: RSSI <= -71

The heatmap places BleuIO at the center and displays nearby BLE devices around it. Stronger devices appear closer to the center, while weaker devices appear farther away. The device bubbles are also color-coded, making it easier to understand the BLE environment at a glance.

A table below the heatmap shows the detected devices with MAC address, RSSI, name, signal category, and last seen time.

GitHub Project Code

The full source code is available here:

https://github.com/smart-sensor-devices-ab/ble-rss-heatmap-visualizer

You can download or clone the project and open it in a supported browser.

Live Testing

You can also try the live version here:

https://smart-sensor-devices-ab.github.io/ble-rss-heatmap-visualizer/

To test it:

  1. Plug in the BleuIO USB dongle.
  2. Open the live page in your browser.
  3. Click Connect.
  4. Select the BleuIO serial port.
  5. Click Scan.
  6. View the BLE RSSI heatmap and detected device table.

Output Screenshot

This is an example project showing how BleuIO can be used with the Web Serial API to build a practical BLE scanning and visualization tool. Developers can use the source code as a starting point and modify it for their own use cases.

For example, the project can be extended to scan for a longer period of time, change the RSSI threshold values, filter specific devices, highlight known BLE sensors, or integrate the scan results into a larger web dashboard.

Because BleuIO works with simple AT commands over serial, it is easy to integrate with different programming languages, platforms, and browser-based tools like this one.

Share this post on :

Using BleuIO with Fortran to Scan and Decode BLE Advertising Data

Bluetooth Low Energy development is often associated with languages like C, Python, JavaScript, or mobile frameworks. But with BleuIO, BLE communication becomes accessible from almost any programming language that can work with a serial port. In this tutorial, we will build a simple Fortran terminal application that connects to a BleuIO USB dongle, sends AT commands, scans for nearby HibouAir BLE advertisements, decodes the advertising data, and prints the sensor values directly in the terminal.

This project is intentionally small and beginner-friendly. The goal is not to build a full production BLE application, but to demonstrate how Fortran can communicate with BLE devices through BleuIO using simple serial communication.

Why Fortran for a BLE Example?

Fortran is one of the oldest programming languages still actively used today, especially in scientific computing, engineering, numerical modelling, and high-performance applications. While Fortran is not normally the first language people think of for BLE development, this example shows an interesting advantage of using BleuIO.

What We Are Building

The application does the following:

It connects to a BleuIO USB dongle using serial terminal. Then it configures the serial port and sends BleuIO AT commands. After that, it starts a filtered BLE scan for HibouAir advertisements using the command:

AT+FINDSCANDATA=FF5B07

The program then reads the incoming scan data, extracts the advertising payload, decodes the HibouAir manufacturer data, and prints values such as sensor ID, light or noise, pressure, temperature, humidity, CO2, and particulate matter values where available. The README also notes that the program automatically searches for a BleuIO dongle using VID 0x2dcf and PID 0x6002.

Project Requirements

Before running the project, install the required packages on macOS using Homebrew:

brew install gcc libserialport pkg-config

You will also need the following hardware:

1. BleuIO USB dongle
2. HibouAir CO2 Sensor

Getting the Project

The full source code is available on GitHub:

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

Building the Project

Build the project with:

make

This creates the executable:

./bleuio_fortran_scanner

The README confirms that the project is built using make and creates the bleuio_fortran_scanner binary.

Running the Project

Run the scanner from the terminal:

./bleuio_fortran_scanner

Once started, the program searches for the connected BleuIO dongle, opens the serial port, prepares the dongle, and starts scanning for HibouAir BLE advertisements.

You can stop the program at any time using:

Ctrl-C

How It Works

The project is split into small Fortran source files to keep the example easy to understand.

The libserialport_bindings.f90 file contains the small ISO_C_BINDING interface required to call libserialport. The serial_utils.f90 file handles finding, opening, configuring, reading from, writing to, and closing the serial port. The bleuio_commands.f90 file contains helper procedures for sending BleuIO AT commands. The simple_json.f90 file extracts the predictable fields needed from the BleuIO scan response. The hibouair_decoder.f90 file decodes HibouAir manufacturer data and prints the sensor values. Finally, main.f90 contains the main scanner flow.

The main idea is simple. BleuIO receives BLE advertising data and returns it through the serial port. The Fortran application reads each line, checks whether it contains valid scan data, extracts the advertisement payload, and decodes the bytes according to the HibouAir data format.

Because BleuIO handles the BLE communication, the Fortran code does not need to manage Bluetooth scanning directly. It only sends AT commands and reads the response.

Example Output

When a HibouAir CO₂ sensor is found, the terminal output looks like this:

BleuIO Fortran HibouAir Scanner
--------------------------------
Searching for BleuIO dongle...
Connected: /dev/tty.usbmodemXXXX

Sensor ID: 123ABC
Light: 52 Lux
Pressure: 1012.4 hPA
Temperature: 23.6 C
Humidity: 45.1 %rh
CO2: 612 ppm

For a PM sensor board, the output includes particulate matter values:

  Sensor ID: 456DEF
Light: 38 Lux
Pressure: 1011.8 hPA
Temperature: 22.9 C
Humidity: 47.2 %rh
PM 1.0: 2.1 ug/m3
PM 2.5: 4.8 ug/m3
PM 10: 7.3 ug/m3
CO2: 0 ppm

These sample outputs are also included in the project README.

Output Screenshot

Add a screenshot here showing the terminal running the Fortran application and printing decoded HibouAir sensor values.

Further Development

This project is a simple example showing how BleuIO can be used with Fortran when building BLE applications. It demonstrates the basic workflow: connect to BleuIO through a serial port, send AT commands, scan for BLE advertisements, read the response, and decode the data.

Developers can use this example as a starting point and expand it for their own projects. For example, the script could be extended to save sensor readings to a file, export data as CSV, monitor multiple HibouAir devices, trigger alerts when CO₂ or PM values are high, or integrate the data with scientific and engineering workflows where Fortran is already used.

Share this post on :

Building a BLE Application with Zig and BleuIO: Reading Sensor Data from HibouAir

In this tutorial, we will build a lightweight command-line application that interacts with Bluetooth Low Energy (BLE) devices using Zig and a BleuIO USB dongle. The application will scan for nearby BLE advertisements and decode sensor data broadcast by a HibouAir device. While HibouAir is used here as a test case, the main goal is to demonstrate how BleuIO simplifies BLE application development and how Zig can be used to build efficient, low-level tools for working with wireless data. By the end of this guide, you will have a working example of a BLE-powered CLI tool that reads, processes, and displays real-time data directly in your terminal.

About ZipLang (Zig)

Zig is a modern systems programming language designed with a focus on simplicity, performance, and control over hardware resources. It is particularly well-suited for applications that require direct interaction with devices, such as serial communication and embedded systems. In this project, Zig is used to communicate with the BleuIO dongle over a serial interface, process incoming BLE scan data, and decode raw payloads into meaningful values. Its minimal runtime and explicit design make it a strong choice for building reliable BLE tools without unnecessary abstraction.

Project Requirements

Hardware

Software

  • Zig installed
  • libserialport
  • pkg-config

How It Works

The application uses BleuIO as a bridge between the computer and BLE devices. When the program starts, it automatically detects the connected BleuIO dongle by matching its vendor and product ID, then opens a serial connection configured for communication. Once the connection is established, the program sends AT commands to initialize the dongle and begin scanning for BLE advertisement data that matches a specific identifier used by HibouAir devices.

As BLE data is received, the Zig application continuously reads the serial output, interprets the incoming JSON responses, and extracts the relevant payload. This payload is then decoded into human-readable sensor values such as temperature, humidity, pressure, particulate matter, and CO2 levels. The decoded information is printed directly in the terminal, allowing real-time monitoring without requiring any additional tools or manual decoding steps. When the program is stopped, it gracefully sends commands to halt scanning and reset the dongle before closing the connection.

Source Code

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

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

Install and Run the Project

Follow the readme file inside the project to install and run. Once dependencies are installed, follow these steps:

Build the project

zig build

Run the application

zig build run

Make sure your BleuIO dongle is connected before running the program.

Example Output

When the application is running, it prints decoded BLE sensor data directly to the terminal. Each detected broadcast from a matching device is processed and displayed in a readable format, making it easy to observe environmental data in real time.

About BleuIO

BleuIO plays a central role in this project by abstracting the complexity of BLE communication. Instead of implementing a full Bluetooth stack within the application, developers can interact with BLE devices using simple AT commands over a serial interface. This approach allows BLE functionality to be integrated into applications written in virtually any programming language and run on any major operating system. By handling scanning, filtering, and communication at the hardware level, BleuIO enables developers to focus on application logic, making BLE development faster and more accessible.

This tutorial demonstrates how Zig and BleuIO can be combined to build a practical BLE application with minimal overhead. While HibouAir is used here as a sample device for testing, the same approach can be applied to a wide range of BLE-enabled sensors and devices. The combination of a lightweight programming language and a simple BLE interface creates a powerful development workflow that is both efficient and easy to extend for real-world use cases.

Share this post on :