← All projects

IoT sensor network

CPE Lyon, team of 4, 2025 to 2026

A complete IoT chain built by four students for the Embedded Development & IoT module: micro:bit sensors, a custom secured radio protocol, a Python server, Google Firestore and an Android app. My part was the gateway firmware.

Goal

  • Collect temperature, humidity, pressure and light from a connected object and send them over a secured radio link.
  • Let an operator reconfigure the object's display remotely from an Android app.
  • Cover the whole chain, from embedded firmware to cloud and mobile. Each of us owned one component: the object firmware, the gateway firmware (mine), the Python server and the Android app.

Architecture

Architecture of the IoT sensor networkA micro:bit object with sensors talks over 2.4 GHz radio to a micro:bit gateway. The gateway talks over USB serial to a Python server. The server reads and writes Google Firestore, which the Android app listens to in real time. Every link works in both directions: measurements go up, configuration goes down.Objectmicro:bit, BME280Gatewaymicro:bit (my part)ServerPython 3Firestorecloud databaseAndroid appKotlin, Compose2.4 GHzUSB serialSDKliveArchitecture of the IoT sensor networkA micro:bit object with sensors talks over 2.4 GHz radio to a micro:bit gateway. The gateway talks over USB serial to a Python server. The server reads and writes Google Firestore, which the Android app listens to in real time. Every link works in both directions: measurements go up, configuration goes down.Objectmicro:bit, BME280Gatewaymicro:bit (my part)ServerPython 3Firestorecloud databaseAndroid appKotlin, Compose2.4 GHzUSB serialSDKlive

Every link works both ways: measurements go up, configuration orders go down.

My part: the gateway firmware

  • Two-way relay

    Measurements go up to the server over USB serial, configuration frames go down to the object over 2.4 GHz.

  • Frame filtering

    Anything too short or missing VER=1 and ;CRC= is dropped: the 2.4 GHz band is full of stray packets.

  • CRC-8 check

    Every radio frame is checked, and a rejected frame gets an ERR frame with a real, valid checksum.

  • Truncated frames fixed

    The micro:bit's serial printf() buffer was too small: replaced with snprintf() and a send of the full ManagedString.

  • Build system

    The yotta project is split into firmware/gateway and firmware/object, selected by one field in module.json.

How it works

Protocol and security

  • Readable protocol

    KEY=VALUE fields separated by semicolons: easy to debug in a serial terminal, simple to parse on a micro:bit and in Python.

  • Integrity, CRC-8

    A CRC-8 (polynomial 0x07) on the body of every frame.

  • Authentication

    An AUTH field: a CRC-8 over the shared key, the object ID and the frame counter.

  • Anti-replay

    The server rejects any frame whose counter is not greater than the last valid one for that object.

Shared keys live in a keys.json file kept out of Git.

Data path

  1. 1

    Object ↔ Gateway2.4 GHz

    The object reads a BME280 sensor, shows the values on an OLED screen and sends a frame every 5 seconds.

  2. 2

    Gateway ↔ ServerUSB serial

    The gateway filters and checks each frame and relays it to the PC; configuration frames travel the other way.

  3. 3

    Server ↔ FirestoreSDK

    The Python server validates the CRC, the AUTH field and the counter, acknowledges the frame and stores the measurements in Firestore.

  4. 4

    Firestore ↔ Android applive

    The Android app (Kotlin, Jetpack Compose) listens to Firestore in real time. Confirming a new display order writes it to Firestore, the server turns it into a CONFIG frame, and the gateway relays it to the object.

A measurement frame (example values)
VER=1;SRC=OBJ;DST=GTW;ID=01;TYPE=DATA;PAYLOAD=T:22,L:150,H:45,P:1013;TS=0042;AUTH=5F;CRC=BF

Problems we solved

ProblemFix
Truncated serial frames: the serial printf() buffer was too small.Replaced with snprintf() and a full-string send.
Endless BADCRC errors with no active object: stray radio packets reached the checks.Length and marker filters.
ERR frames with an invalid checksum: the CRC was hardcoded to 00.It is now computed for real.
No radio frame received at all: the default packet limit (38 bytes) was below our 75 to 85 byte frames.Raised in config.json.
BADAUTH after every firmware update: the AUTH scheme had drifted between firmware and server.The object must be reflashed after any change to it.

Result

  • Validated live

    In a live demonstration: four measurements, authenticated radio transmission, cloud storage and remote reconfiguration of the display.

  • Beyond the brief

    A complete application protocol, a multi-threaded server with a Firestore listener, and an Android interface with charts, history and drag and drop.

What we'd improve next

  • Persist the anti-replay counter across server restarts.
  • Authenticate the CONFIG frames sent from the server to the object (they currently carry AUTH=00).
  • Move from CRC-8 to CRC-16 for better robustness.
  • Add end-to-end encryption on the radio link, which was out of scope for this academic project.
All the details

My part: the gateway firmware

  • Two-way relay between the 2.4 GHz radio and the USB serial link to the PC: measurements go up to the server, configuration frames go down to the object.
  • Frame filtering. The 2.4 GHz band is full of stray packets, so the gateway drops anything too short or missing the expected markers (VER=1 and ;CRC=).
  • CRC-8 check on every radio frame, and ERR frames carrying a real, valid checksum when a frame is rejected.
  • Fixed truncated serial frames: the micro:bit's serial printf() buffer was too small, so I replaced it with snprintf() and a send of the full ManagedString.
  • Build system: I split the yotta project into firmware/gateway and firmware/object, selected by a single field in module.json, so nobody has to move files between builds.

Protocol and security

  • A readable text protocol (KEY=VALUE fields separated by semicolons): easy to debug in a serial terminal and simple to parse on a micro:bit and in Python.
  • Integrity: a CRC-8 (polynomial 0x07) on the body of every frame.
  • Authentication: an AUTH field computed with a CRC-8 over the shared key, the object ID and the frame counter.
  • Anti-replay: the server rejects any frame whose counter is not greater than the last valid one for that object.
  • Shared keys live in a keys.json file kept out of Git.

Data path

  • The object reads a BME280 sensor, shows the values on an OLED screen and sends a frame every 5 seconds.
  • The Python server validates the CRC, the AUTH field and the counter, acknowledges the frame and stores the measurements in Firestore.
  • The Android app (Kotlin, Jetpack Compose) listens to Firestore in real time. Confirming a new display order writes it to Firestore, the server turns it into a CONFIG frame, and the gateway relays it to the object.

Problems we solved

  • Truncated serial frames: the serial printf() buffer was too small. Fixed with snprintf() and a full-string send.
  • Endless BADCRC errors with no active object: stray radio packets reached the checks. Fixed with length and marker filters.
  • ERR frames with an invalid checksum: the CRC was hardcoded to 00. It is now computed for real.
  • No radio frame received at all: the default packet limit (38 bytes) was below our 75 to 85 byte frames. Raised in config.json.
  • BADAUTH after every firmware update: the AUTH scheme had drifted between firmware and server. The object must be reflashed after any change to it.

Result

  • Validated in a live demonstration: four measurements, authenticated radio transmission, cloud storage and remote reconfiguration of the display.
  • We went beyond the brief with a complete application protocol, a multi-threaded server with a Firestore listener, and an Android interface with charts, history and drag and drop.

Contact

Email meLinkedIn

Based in Lyon, France

Last updated

Download CVContact