Skip to content

Hands-on: send the fused event over MQTT

Module 6 — Edge AI apps · Slides: slides.md · Module overview · Course page

Fill five points in s17_fusion_iot.py to select the model, read the verdict, read the raw gyro, AND the two signals together, and publish each confirmed event as JSON to an MQTT broker over WiFi, once per event, with one codebase that falls back to a [SIM] mode by itself when there’s no network.

By the end of this lesson, you will:

  1. Fill the five points in practice/s17_fusion_iot.py until each fused event is published to the topic once per shake, and confirm the message on the receiving side (for example mosquitto_sub or a web MQTT client).
  2. Find a motion where the model reports the target class but the raw gate fails (or the reverse), and confirm no event is sent.
  3. Explain graceful degradation with try/except ImportError and is_connected(), and why only the summarised event with its raw evidence should go in the payload.

You’ve been through lesson 6.5, and understand the fused condition and the edge trigger. Prepare an MQTT-receiving program, such as mosquitto_sub -h test.mosquitto.org -t "tesaiot/edge-ai/s17/#" -v, or a web MQTT client that can connect to the same broker.

  • Hardware: a TESAIoT Dev Kit board already flashed with BENTO’s MicroPython firmware, or the BENTO Emulator inside BENTO IDE — the emulator simulates WiFi connecting successfully, and sends MQTT to a real public broker over WebSocket (falling back to a simulated broker if it can’t connect), but the gyro on the emulator stays near zero, so practising on the emulator needs the gate temporarily switched to the accelerometer. On the board, you need to enter your own WiFi name and password.
  • Prior lesson: lesson 6.5 — Sensor fusion: a model’s verdict with a raw sensor

The whole file reads as one sentence: connect to the network → find a model and start it running → loop reading the verdict and the raw gyro → once the two signals agree, publish → stop on exit. The networking part is already provided: import wifi, mqtt sits inside try/except ImportError (set HAVE_NET = False if the modules are missing); wifi.connect(WIFI_SSID, WIFI_PASS) returns True/False and blocks until connected or timed out; wifi.ip() reports the IP; mqtt.connect(BROKER, PORT, client_id=CLIENT_ID), then check mqtt.is_connected() — failing to connect enters [SIM] mode, which prints the payload to the console instead. The five points to fill in are: (1) edge_ai.select(model['index']) (2) r = edge_ai.result() (3) ax, ay, az, gx, gy, gz = sensors.bmi270.motion() (4) fused = model_hit and raw_ok, and (5) mqtt.publish(TOPIC, payload) inside publish_event(), which wraps try/except OSError, since publish throws OSError when the connection drops.

MQTT is publish/subscribe through a broker — the sender doesn’t need to know who’s listening. Our payload is a short piece of JSON, {"event": ..., "conf": ..., "gyro": ..., "ts": ...}, carrying both the verdict and the raw evidence, so the receiving end can audit the decision afterward. It sends only the summarised event, not a raw stream, saving bandwidth and protecting privacy. The fired flag makes one event equal one message, so it doesn’t spam the public broker. Public brokers are heavily shared, so change CLIENT_ID and TOPIC to have your own name appended — if a client_id collides with someone else’s, the broker will kick the old connection out — and never send private data to a public broker. On the emulator, temporarily switch the gate to gmag = abs(ax) + abs(ay) + abs(az - 9.81) with MOTION_FLOOR = 5.0, since the HW panel only moves the accelerometer; pressing Shake on a real board still uses the gyro gate as before.

s17_fusion_iot_full.py lets you pick between two gates with GATE_MODE: "imu" (the raw gyro, corroboration-style) or "radar" (is someone present — multi-modal, a different sensor from the model; on the emulator, the Shake button makes presence true). It counts events, automatically reconnects to the broker when it drops, and shows RSSI from wifi.status()["rssi"].

File What this file teaches
examples/s17_fusion_iot_full.py Fusion + IoT, polished version

The # TODO: comments are at lines 127 (select), 142 (result), 151 (reading motion()), 162 (fused), and 115 (mqtt.publish inside publish_event). If the gyro stays stuck at 0 on the board, point 151 is still empty. If you see a MQTT TX line but the receiving side sees nothing, check point 115 and the topic name on both sides.

Practice file Topic
practice/s17_fusion_iot.py Combining a model’s verdict with a raw sensor, then streaming it to the cloud (the fill-in-the-code version)

Open the solution after trying on your own at least once, and read how to use the solutions first.

Solution Pairs with
solution/s17_fusion_iot.py practice/s17_fusion_iot.py

The same questions are in quiz.yaml for automated checking.

  1. One hard shake, but the receiving side sees more than ten messages. Which part of the code is missing? (single choice · objective 1)

    • a) try/except OSError
    • b) The fired flag that fires only on the rising edge of fused
    • c) wifi.ip()
    • d) The gyro gate
    Solution

    b — fused stays true across several consecutive frames during a shake. Without fired, every frame gets published.

  2. On the board, the on-screen gyro stays stuck at 0 the whole time, and no event is ever sent. Which point is still empty? (single choice · objective 1)

    • a) Point 3, reading sensors.bmi270.motion()
    • b) Point 5, mqtt.publish
    • c) Point 1, select
    • d) Nothing is wrong
    Solution

    a — the default values gx = gy = gz = 0.0 make gmag equal 0, so the raw gate never opens, and fused is never true.

  3. You pick the board up quickly. The model answers shaking at 0.7, but gmag = 25 with MOTION_FLOOR = 40. What happens? (single choice · objective 2)

    • a) The event is sent, because the model is confident
    • b) Nothing is sent, because the raw gate fails — fusion filters this false positive out
    • c) The app crashes
    • d) It sends in [SIM] mode
    Solution

    b — AND requires both stages to pass. This is exactly the kind of false positive fusion exists to filter out.

  4. Why is import wifi, mqtt wrapped in try/except ImportError? (single choice · objective 3)

    • a) To make the import faster
    • b) So one codebase still runs even when the firmware has no networking module, falling back to offline mode instead of crashing right at the import line
    • c) To hide the WiFi password
    • d) It isn’t necessary
    Solution

    b — graceful degradation means checking before using a part that requires the network. The rest of the app still works and still demonstrates the fusion mechanism.

  5. Which payload best fits this lesson’s edge-to-cloud principle? (single choice · objective 3)

    • a) Streaming raw IMU data 50 times a second
    • b) {“event”:“shaking”,“conf”:0.92,“gyro”:180,“ts”:123456}, once per event
    • c) A raw audio file every second
    • d) A screenshot every frame
    Solution

    b — think at the edge, then send only the summary with the necessary raw evidence. It saves bandwidth and protects privacy.

The MVP for lessons 6.5–6.6: a fused decision (verdict AND a raw gate) is genuinely published to MQTT, once per event, while gentle motion is never sent.

  • Set CLIENT_ID and TOPIC to include your name. Fill in all five points of the practice file, then run it on the board or the emulator (switching to the accelerometer gate on the emulator).
  • Open a receiving program subscribed to your topic, shake until you see a message on the receiving side, and note a sample payload in your learning log.
  • Find a motion where one stage fails, and confirm no message is sent.
  • Disconnect the network (or enter the wrong WiFi name), and confirm the app falls back to [SIM] mode without crashing.

In the next module (under the hood), we’ll dig into the real Edge AI stack — the tri-core setup, ai_engine, the IPC model link, and TFLite-Micro on the NPU — to see how the verdicts we’ve used throughout the course actually happen.

Next lesson: lesson 7.1 — The Edge AI stack: tri-core, ai_engine, the IPC model link, and TFLite-Micro

  • If a hundred boards sent events to the same topic, how would you design the topic name and payload so the receiving end can tell them apart?
  • What kind of data should never go to a public broker, and where would you send it instead?

Review questions

Answer on your own first, then open the answer.

  1. One hard shake gives the receiver more than ten messages. What is missing from the code? (Objective 1)

    1. try/except OSError
    2. ธง fired ที่ยิงเฉพาะขอบขาขึ้นของ fused
    3. wifi.ip()
    4. ประตู gyro
    Show answer

    Answer: B. ธง fired ที่ยิงเฉพาะขอบขาขึ้นของ fused

    fused เป็นจริงหลายเฟรมติดกันระหว่างเขย่า ถ้าไม่มี fired ทุกเฟรมจะถูก publish

  2. On the board the gyro value stays at 0 and no event is ever sent. Which point is empty? (Objective 1)

    1. จุดที่ 3 อ่าน sensors.bmi270.motion()
    2. จุดที่ 5 mqtt.publish
    3. จุดที่ 1 select
    4. ไม่มีจุดใดผิด
    Show answer

    Answer: A. จุดที่ 3 อ่าน sensors.bmi270.motion()

    ค่าเริ่มต้น gx = gy = gz = 0.0 ทำให้ gmag เป็น 0 ประตูดิบจึงไม่เคยเปิด fused ก็ไม่เคยจริง

  3. You lift the board quickly; the model says shaking 0.7 but gmag = 25 with MOTION_FLOOR = 40. What happens? (Objective 2)

    1. ส่งเหตุการณ์ เพราะโมเดลมั่นใจ
    2. ไม่ส่ง เพราะประตูดิบไม่ผ่าน fusion กรอง false positive นี้ไว้
    3. แอปพัง
    4. ส่งแบบ [SIM]
    Show answer

    Answer: B. ไม่ส่ง เพราะประตูดิบไม่ผ่าน fusion กรอง false positive นี้ไว้

    AND ต้องผ่านทั้งสองด่าน นี่คือตัวอย่างของ false positive ที่ fusion มีไว้กรอง

  4. Why wrap import wifi, mqtt in try/except ImportError? (Objective 3)

    1. ให้ import เร็วขึ้น
    2. ให้โค้ดชุดเดียวรันได้แม้เฟิร์มแวร์ไม่มีโมดูลเน็ต โดยถอยเป็นโหมด offline แทนการพังตั้งแต่บรรทัด import
    3. เพื่อซ่อนรหัส WiFi
    4. ไม่จำเป็น
    Show answer

    Answer: B. ให้โค้ดชุดเดียวรันได้แม้เฟิร์มแวร์ไม่มีโมดูลเน็ต โดยถอยเป็นโหมด offline แทนการพังตั้งแต่บรรทัด import

    degrade อย่างสง่างามคือเช็กก่อนใช้ส่วนที่ต้องมีเน็ต ส่วนที่เหลือของแอปยังทำงานและแสดงกลไก fusion ได้

  5. Which payload best fits the edge-to-cloud principle of this lesson? (Objective 3)

    1. สตรีม IMU ดิบ 50 ครั้งต่อวินาที
    2. {"event":"shaking","conf":0.92,"gyro":180,"ts":123456} ครั้งเดียวต่อเหตุการณ์
    3. ไฟล์เสียงดิบทุกวินาที
    4. ภาพหน้าจอทุกเฟรม
    Show answer

    Answer: B. {"event":"shaking","conf":0.92,"gyro":180,"ts":123456} ครั้งเดียวต่อเหตุการณ์

    คิดที่ขอบแล้วส่งเฉพาะข้อสรุปพร้อมหลักฐานดิบที่จำเป็น ประหยัด bandwidth และรักษาความเป็นส่วนตัว

Cite this lesson

If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.

"Hands-on: send the fused event over MQTT" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

Thai attribution: "ลงมือทำ: ส่งเหตุการณ์ที่ fuse แล้วขึ้น MQTT" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

Lesson link: https://tesaiot.github.io/tesa-qualification-program/en/courses/edge-ai-developer/m06-apps/l06-fusion-iot-lab/

Full guide: how to cite TESA

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA