Skip to content

Hands-on: from verdict to action on the board

Module 1 — Getting started: run the real thing, then take it apart · Slides: slides.md · Module overview · Course page

Fill the four blanks in the s03_anatomy_edgeai.py file until the single-model watcher beeps and shows a banner on its own when a confident target class appears, then remix the model, the target class and the action without touching the detection logic.

By the end of this lesson, you will:

  1. Fill in all four blanks of practice/s03_anatomy_edgeai.py (select, result, verdict.text, fire_action) so that performing the target class or making the target sound gives one beep and a banner, and leaving that class turns the banner back to “waiting…”.
  2. Remix the app by changing MODEL_KEYWORD and TARGET_CLASS to another model using a different sensor, setting the target class from the labels actually reported, and confirm the action fires on the new class.
  3. Find a case where the target class wins but conf is below CONF_FLOOR, and explain why the action doesn’t fire, and what the fired flag prevents.

You’ve been through lesson 1.6, and understand the hit condition (a matching label and conf ≥ CONF_FLOOR) and the fired flag. If you don’t remember the four calls from lesson 1.3 well, open the s01_first_inference.py solution to review first, since blanks 1–3 here are the same ones.

The whole file reads as one sentence: find the model by name → tell it to run → loop reading results onto the screen roughly every 180 ms → once a confident target class appears, act → stop on exit. Blanks 1–3 repeat what you already did in lesson 1.3 (select(model['index']) inside a try, r = edge_ai.result(), verdict.text(...)). Blank 4 is new: fire_action(r['conf']), inside the condition if hit and not fired:. The left half of the loop (sensor → model) happens automatically on the CM55; the right half (result → screen → action) is entirely our own Python code on the CM33.

There are two layers to remix. The first sits in two lines at the top of the file: MODEL_KEYWORD swaps both the model and the sensor (IMU → MIC → RADAR), while TARGET_CLASS must spell exactly the same as a name in that model’s labels (readable from the console line or from edge_ai.models()) — for instance, "Cough" pairs with "cough". The second layer is fire_action(), editable in one place: change ui.tone’s note, use ui.sfx(ui.SFX_UI_SELECT), beep at two levels depending on confidence, or count occurrences — none of which affects what the app detects.

At the end, the finally block always stops the engine. If you use on_result (as in the full version), you must remove the callback with edge_ai.on_result(None) before calling stop(). Success in this lesson isn’t just seeing the banner — you should be able to say how this app differs from lesson 1.3, and what your remix changed.

s03_anatomy_edgeai_full.py moves to on_result, colours things using CONF_FLOOR, counts actions, and shows latency. Read it once your practice file is working, and notice the main loop now only handles buttons, because reading results has moved into the callback.

File What this file teaches
examples/s03_anatomy_edgeai_full.py Watching for a class with on_result, then acting (full version)

Fill it in this order: 1) edge_ai.select(model['index']) after find_model 2) r = edge_ai.result() 3) verdict.text(r['label'] or '-') 4) fire_action(r['conf']) inside the hit condition. Choose MODEL_KEYWORD/TARGET_CLASS to try first (starting with Motion and shaking works well). If the screen stays stuck at ---, blank 1 is still empty. If the class shows correctly but there’s no sound or banner, blank 4 is still empty.

Practice file Topic
practice/s03_anatomy_edgeai.py Taking an edge AI app apart, then remixing it: swap the model, act on a class (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/s03_anatomy_edgeai.py practice/s03_anatomy_edgeai.py

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

  1. You’ve filled in the whole file. The screen correctly shows the shaking class, but there’s no beep and no banner at all. Which blank is still empty? (single choice · objective 1)

    • a) Blank 1, edge_ai.select(model[‘index’])
    • b) Blank 2, r = edge_ai.result()
    • c) Blank 4, fire_action(r[‘conf’])
    • d) None — it’s because the emulator has no speaker
    Solution

    c — the class showing on screen means blanks 1–3 are working. Blank 4 is where a verdict becomes an action. If it’s empty, the app “reports” but never “acts.”

  2. You want to change the app into a cough detector. How should you set the two lines at the top of the file? (single choice · objective 2)

    • a) MODEL_KEYWORD = “Cough”, TARGET_CLASS = “cough”
    • b) MODEL_KEYWORD = “cough”, TARGET_CLASS = “Cough Detection”
    • c) MODEL_KEYWORD = “Motion”, TARGET_CLASS = “cough”
    • d) MODEL_KEYWORD = 3, TARGET_CLASS = 1
    Solution

    a — find_model searches for a word in the name, case-insensitively, but TARGET_CLASS must match the model’s labels exactly — for Cough Detection, those are unlabelled and cough.

  3. The result is {‘label’: ‘shaking’, ‘conf’: 0.42} and TARGET_CLASS = ‘shaking’. What does the action do? (single choice · objective 3)

    • a) Fires right away, since the class matches
    • b) Doesn’t fire, because hit also needs conf ≥ CONF_FLOOR (0.50), and fired gets reset to False
    • c) Fires twice
    • d) The program throws OSError
    Solution

    b — hit is False because conf hasn’t reached the threshold, so the code takes the elif not hit branch, which resets fired and shows the “waiting” banner. This is what guards against a false positive.

  4. Put the sequence of events in order when a user shakes the board twice (shake, stop, shake), with the target set to shaking (ordering · objective 3)

    • a) hit is False, so fired resets to False
    • b) hit is True and fired is False, so the action fires and sets fired = True
    • c) hit is True again, fired is False, so the action fires a second time
    • d) hit is still True but fired is True, so it doesn’t fire again
    Solution

    b → d → a → c — edge-triggered: it fires the moment the class is entered, doesn’t fire again while still shaking, resets once you leave the class, and is ready to fire again once you enter it a second time.

The MVP for lessons 1.6–1.7: genuinely remix s03_anatomy_edgeai.py — swap the model (change MODEL_KEYWORD and TARGET_CLASS), and trigger an action when a matching verdict occurs.

  • All four blanks in the practice file are filled in; performing the motion or sound gives one beep and shows the banner.
  • Remix it to at least one other model (for example, Motion → Cough), and confirm the action fires on the new class.
  • Find a motion or sound where the target class wins but conf is below CONF_FLOOR, and note in your learning log why the action doesn’t fire.
  • Be able to explain why both label and conf must be checked, and why the fired flag is needed.

In the next module, we open pillar 1 (DAQ), starting to collect our own sensor data into CSV files as raw material for training models in module 5.

Next lesson: lesson 2.1 — Sampling to match the model: Nyquist rate, windows and CSV schema

  • Should the action you chose in your remix fire once per detection, or repeatedly at intervals? If repeatedly, how would you design the flag or timer for that?
  • If you had to bring this app to a model whose target class isn’t in the same position, what parts of your code would need to change?

Review questions

Answer on your own first, then open the answer.

  1. The file is filled, the screen correctly shows shaking, but there is no beep and no banner. Which blank is still empty? (Objective 1)

    1. ช่อง 1 edge_ai.select(model['index'])
    2. ช่อง 2 r = edge_ai.result()
    3. ช่อง 4 fire_action(r['conf'])
    4. ไม่มีช่องไหนว่าง เป็นเพราะ Emulator ไม่มีลำโพง
    Show answer

    Answer: C. ช่อง 4 fire_action(r['conf'])

    คลาสขึ้นจอแปลว่าช่อง 1–3 ทำงานแล้ว ช่อง 4 คือจุดที่ verdict กลายเป็น action ถ้าว่าง แอปจะ "รายงาน" แต่ไม่ "ลงมือ"

  2. You want the app to catch coughs. How do you set the two lines at the top of the file? (Objective 2)

    1. MODEL_KEYWORD = "Cough", TARGET_CLASS = "cough"
    2. MODEL_KEYWORD = "cough", TARGET_CLASS = "Cough Detection"
    3. MODEL_KEYWORD = "Motion", TARGET_CLASS = "cough"
    4. MODEL_KEYWORD = 3, TARGET_CLASS = 1
    Show answer

    Answer: A. MODEL_KEYWORD = "Cough", TARGET_CLASS = "cough"

    find_model ค้นคำในชื่อโดยไม่สนตัวพิมพ์ แต่ TARGET_CLASS ต้องตรงกับ labels ของโมเดลเป๊ะ ซึ่งของ Cough Detection คือ unlabelled กับ cough

  3. The result is {'label': 'shaking', 'conf': 0.42} and TARGET_CLASS = 'shaking'. What happens to the action? (Objective 3)

    1. ยิงทันทีเพราะคลาสตรง
    2. ไม่ยิง เพราะ hit ต้องการ conf ≥ CONF_FLOOR (0.50) ด้วย และ fired ถูกรีเซ็ตเป็น False
    3. ยิงสองครั้ง
    4. โปรแกรมโยน OSError
    Show answer

    Answer: B. ไม่ยิง เพราะ hit ต้องการ conf ≥ CONF_FLOOR (0.50) ด้วย และ fired ถูกรีเซ็ตเป็น False

    hit เป็น False เพราะ conf ยังไม่ถึงเกณฑ์ โค้ดจึงเข้า elif not hit ซึ่งรีเซ็ต fired และขึ้นแบนเนอร์ "รอจับ" นี่คือตัวกัน false positive

  4. Order the events when the user shakes the board twice (shake, stop, shake) with shaking as the target. (Objective 3)

    1. hit เป็น False จึงรีเซ็ต fired = False
    2. hit เป็น True และ fired เป็น False จึงยิง action แล้วตั้ง fired = True
    3. hit เป็น True อีกครั้ง fired เป็น False จึงยิง action รอบที่สอง
    4. hit ยังเป็น True แต่ fired เป็น True จึงไม่ยิงซ้ำ
    Show answer

    Correct order: B. hit เป็น True และ fired เป็น False จึงยิง action แล้วตั้ง fired = True → D. hit ยังเป็น True แต่ fired เป็น True จึงไม่ยิงซ้ำ → A. hit เป็น False จึงรีเซ็ต fired = False → C. hit เป็น True อีกครั้ง fired เป็น False จึงยิง action รอบที่สอง

    edge-trigger ยิงตอนเพิ่งเข้าคลาส ไม่ยิงซ้ำระหว่างที่ยังเขย่าค้าง รีเซ็ตเมื่อออกจากคลาส แล้วพร้อมยิงใหม่เมื่อเข้าคลาสอีกครั้ง

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: from verdict to action on the board" 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: "ลงมือทำ: จาก verdict สู่ action บนบอร์ด" จาก 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/m01-onboarding/l07-verdict-action-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