Files
MSE-PI-E2EEDA-Plein-de-eeee…/gateway
DjeAvd 2a7546fe8b feat(gateway): implement BLE-to-MQTT gateway
Implement Gateway class that discovers Nordic Thingy:52 nodes via BLE
and publishes sensor data to MQTT broker on each notification received.

- Automatic node discovery via BLE service UUID (ef680100)
- GATT notifications for temperature, humidity and CO2
- Publish immediately on reception to {gateway_id}/{mac}/update
- Connection timeout to avoid blocking on unreachable nodes
- Disconnection detection and automatic reintegration into scan
- Logging with DEBUG/INFO/WARNING/ERROR levels

Assisted-by: Claude:claude-sonnet-4-6 — debugging BLE parallel connections (BlueZ InProgress error), GATT UUID discovery (ef680100 vs ef680200), byte decoding for temperature/humidity/CO2, async connection timeout implementation
2026-06-04 12:32:45 +02:00
..
2026-03-19 12:32:17 +01:00

Gateway — BLE to MQTT

This component runs on a Raspberry Pi 4 and acts as the communication bridge between the Nordic Thingy:52 sensor nodes and the rest of the system. It reads environmental data from the nodes over BLE and publishes it to a local MQTT broker (Mosquitto) in a structured JSON format.

Role in the architecture

Thingy:52 nodes  -->  (BLE)  -->  Raspberry Pi  -->  (MQTT)  -->  Database / ML / Notifications

Dependencies

Install the required Python libraries:

pip3 install bleak paho-mqtt --break-system-packages

Install and start the Mosquitto MQTT broker:

sudo apt install -y mosquitto mosquitto-clients
sudo systemctl enable mosquitto
sudo systemctl start mosquitto

Usage

python3 gateway.py

At startup, the script asks for the room ID (e.g. C1, A2, B5). It then automatically discovers all Thingy:52 nodes in range by filtering BLE advertising packets on the Nordic Configuration service UUID (ef680100). Each detected node is assigned a name based on the room ID and a counter (e.g. C1_thingy1, C1_thingy2).

To run the gateway in the background and keep it running after closing SSH:

nohup python3 gateway.py > log.txt 2>&1 &

MQTT topic structure

classroom/{room_id}/{node_id}

Example: classroom/C1/C1_thingy1

Message format

Each message is published as a JSON object with the following structure:

{
  "timestamp": "2026-03-26T13:24:05.176072+00:00",
  "room_id": "C1",
  "node_id": "C1_thingy1",
  "sensors": {
    "co2_ppm": 400,
    "temperature_c": 25.37,
    "humidity_pct": 44
  }
}
Field Type Description
timestamp string (ISO 8601 UTC) Time of measurement, added by the gateway
room_id string Identifier of the monitored room
node_id string Identifier of the Thingy:52 node
co2_ppm integer eCO2 concentration in parts per million
temperature_c float Indoor air temperature in degrees Celsius
humidity_pct integer Relative humidity in percent

Output files

For each session, two local files are created in the gateway directory:

  • data_{room_id}.csv — comma-separated file for local analysis
  • data_{room_id}.json — JSON file following the database team format

Publishing interval

The default publishing interval is 5 minutes (300 seconds). This can be adjusted by modifying the INTERVAL variable in gateway.py.

Utility scripts

  • scan.py — scans for nearby BLE devices and prints their name and MAC address
  • check_uuid.py — connects to Thingy:52 nodes and prints their advertised service UUIDs

Notes on the CO2 sensor

The Thingy:52 uses a CCS811 sensor which measures eCO2 (equivalent CO2), estimated from volatile organic compound (VOC) levels rather than directly measuring CO2 concentration. Values should be interpreted as indicative trends rather than precise measurements, particularly during the first 24 to 48 hours of operation while the sensor calibrates.

Overnight test results

An overnight test was conducted with 2 Thingy:52 nodes placed in two separate rooms (windows closed, 7h session, 5 minute interval).

  • Room 1: 4 occupants (2 adults, 2 children)
  • Room 2: unoccupied

Overnight measurements

CO2 rose progressively from 400 ppm to a peak of 1071 ppm in the occupied room, consistent with human respiration in a confined space. The unoccupied room remained stable between 400 and 465 ppm, confirming that variations in Room 1 are directly linked to human presence.

Note: the CCS811 sensor measures eCO2 estimated from VOC levels, not direct CO2. Occasional spikes (e.g. 877 ppm at 02:45, 1071 ppm at 03:05) may be caused by factors such as perspiration or movement near the sensor and should be interpreted with caution.