Hands-on: your own focused app
Module 6 — Edge AI apps · Slides: slides.md · Module overview · Course page
Fill four points in s15_apps.py to make a focused cough-counting app: select the target model before the loop, read the result, show the winning class, and a detection condition gated by CONF_FLOOR. Then retarget it to Alarm or Siren by editing just two lines at the top of the file, giving you your own family of per-model apps.
Objectives
Section titled “Objectives”By the end of this lesson, you will:
- Fill the four points in practice/s15_apps.py until the app shows the winning class, a bar for every class, the latency, and a counter that rises once per target event.
- Retarget the app to at least one other model by editing only TARGET_KEYWORDS and TARGET_CLASS, checking the class name in edge_ai.models() first (Siren’s class is sirens).
- Temporarily remove the conf ≥ CONF_FLOOR condition, and use what you see to explain why the counter then collects false positives.
Before you start
Section titled “Before you start”You’ve been through lesson 6.1, and understand find_model, CONF_FLOOR, and counting on the rising edge. Prepare a coughing clip, an alarm sound, or a siren to play near the board.
- Hardware: a TESAIoT Dev Kit board already flashed with BENTO’s MicroPython firmware, or the BENTO Emulator inside BENTO IDE — on the emulator, the audio models are simulated values where the event class never beats unlabelled, so the counter never moves. You can still test the counting logic on the emulator using the Motion model with the Shake button; counting real coughs needs the board.
- Prior lesson: lesson 6.1 — Six models and the edge_ai API: an app focused on one model
Concepts
Section titled “Concepts”The whole file reads as one sentence: target the cough model → create the card → start it running → loop reading results, showing the class on screen, counting on detection → stop on exit. The settings panel at the top of the file is TARGET_KEYWORDS = ("cough",), TARGET_CLASS = "cough", and TARGET_SENSOR = edge_ai.SENSOR_MIC, keeping the model’s name separate from the class’s name, since they aren’t always the same. The four points we fill in are: (1) edge_ai.select(model['index']) before the loop, inside a try, since select() can throw OSError if the switch isn’t confirmed (2) r = edge_ai.result() (3) verdict.text(r['label'] or '-') when seq changes, and (4) is_target = r['label'] == TARGET_CLASS and r['conf'] >= edge_ai.CONF_FLOOR, which is the genuinely new part of this lesson.
The counter rises when is_target and not was_target — one per cough. If you see the number jump several times for one sound, the rising-edge condition is missing. If the result freezes and seq never moves, that’s the evaluation Ready Model’s ceiling, not broken code. Retargeting takes less than a minute: ("alarm",) with "alarm", or ("siren",) with "sirens" (note the s), then save as a new file. The rest of the file’s skeleton doesn’t need touching, because find_model() finds the model for you, and the bars adjust to whichever labels it returns. Success is being able to say when your action trusts the verdict, and how it guards against overcounting.
Worked example
Section titled “Worked example”s15_apps_full.py adds colour based on CONF_FLOOR, light debouncing (counting only after two consecutive detections), a label showing the time of the last detection, and a Reset button that clears the counter.
| File | What this file teaches |
|---|---|
| examples/s15_apps_full.py | An edge AI app “focused on one model” (full version) |
This lesson’s slides also reference a file in another lesson:
- m06-apps/l01-focused-apps/examples/16_edge_ai_sound_events.py — Edge AI: Sound Events (Cough / Alarm / Siren) — 3 mic models in one dropdown
Practice
Section titled “Practice”The # TODO: comments are at lines 87 (select), 96 (result), 101 (verdict.text), and 112 (is_target). If the card fully shows but never any results, check point 87 first. If the bars move but the large text never changes, check point 101.
| Practice file | Topic |
|---|---|
| practice/s15_apps.py | An edge AI app “focused on one model” (the fill-in-the-code version) |
Solution
Section titled “Solution”Open the solution after trying on your own at least once, and read how to use the solutions first.
| Solution | Pairs with |
|---|---|
| solution/s15_apps.py | practice/s15_apps.py |
Check your understanding
Section titled “Check your understanding”The same questions are in quiz.yaml for automated checking.
-
The app’s card fully shows, but there’s never any result at all, and the counter never moves. Which point is most likely still empty? (single choice · objective 1)
- a) Point 1, edge_ai.select(model[‘index’])
- b) Point 3, verdict.text(…)
- c) Point 4, is_target
- d) Nothing is wrong
Solution
a — without selecting the target model, no model runs, so there’s nothing for result() to return.
-
One cough, but the counter rises three to four times. Which part of the condition is missing? (single choice · objective 1)
- a) r[“conf”] >= edge_ai.CONF_FLOOR
- b) not was_target (counting on the rising edge only)
- c) r[“label”] == TARGET_CLASS
- d) edge_ai.stop()
Solution
b — one cough spans several inference results. Without remembering the previous round’s state, every matching result gets counted.
-
You retarget to siren with TARGET_KEYWORDS = (“siren”,) and TARGET_CLASS = “siren”, but the counter never moves. Why? (single choice · objective 2)
- a) The Siren model isn’t on the board
- b) The real class name is sirens (with an s), so label == TARGET_CLASS is never true
- c) CONF_FLOOR is too high
- d) find_model can’t find it
Solution
b — a model’s name and its class’s name aren’t always the same. Check
labelsfrom edge_ai.models() in the REPL before retargeting. -
You remove the conf condition, and the counter rises even with no one coughing. Why? (single choice · objective 3)
- a) The microphone is broken
- b) argmax always gives a winner, even when cough wins narrowly, 0.51 to 0.49 — that still gets counted, becoming a false positive
- c) seq never changes
- d) The model switched to Motion
Solution
b — CONF_FLOOR is the gate that says to trust a result only once it’s confident enough. Without that gate, every narrow win turns into an action.
The MVP for lessons 6.1–6.2: a single-model focused app with a clean UI, targeting a model with find_model(), showing the verdict, every class’s bar, and latency, with a counter that genuinely fires when the target class crosses CONF_FLOOR, retargetable to at least two models.
- All four points in the practice file are filled in. Practise on the emulator, then count real coughs on the board.
- Retarget to Alarm or Siren, save as a new file, and confirm the counter works.
- Temporarily remove
and r['conf'] >= edge_ai.CONF_FLOOR, play other sounds, and note in your learning log how the counter differs from before. Then put it back. - Be able to explain why
CONF_FLOORmust be checked before counting, and why counting must happen only on the rising edge.
Going further
Section titled “Going further”In the next pair of lessons (6.3–6.4), we’ll turn counting into a stronger action, such as an RGB light, sound, and a log, with full debounce and cooldown.
Next lesson: lesson 6.3 — An action pipeline: CONF_FLOOR, debounce, cooldown and on_result
Reflect
Section titled “Reflect”- If a cough-counting app overcounts in a room where people are talking, would you adjust CONF_FLOOR or debounce first?
- How does separating TARGET_KEYWORDS from TARGET_CLASS help whoever maintains your code after you?
Review questions
Answer on your own first, then open the answer.
-
The cards appear but no result ever shows and the counter never moves. Which point is most likely empty? (Objective 1)
- จุดที่ 1 edge_ai.select(model['index'])
- จุดที่ 3 verdict.text(...)
- จุดที่ 4 is_target
- ไม่มีจุดใดผิด
Show answer
Answer: A. จุดที่ 1 edge_ai.select(model['index'])
ถ้าไม่ select โมเดลเป้าหมาย ก็ไม่มีโมเดลรัน จึงไม่มีผลให้ result() คืน
-
One cough makes the counter jump by three or four. Which part of the condition is missing? (Objective 1)
- r["conf"] >= edge_ai.CONF_FLOOR
- not was_target (การนับเฉพาะขอบขาขึ้น)
- r["label"] == TARGET_CLASS
- edge_ai.stop()
Show answer
Answer: B. not was_target (การนับเฉพาะขอบขาขึ้น)
ไอหนึ่งครั้งกินหลายผลอนุมาน ถ้าไม่จำสถานะรอบก่อน ทุกผลที่เข้าเป้าจะถูกนับหมด
-
You retarget with TARGET_KEYWORDS = ("siren",) and TARGET_CLASS = "siren" and the counter never moves. Why? (Objective 2)
- โมเดล Siren ไม่มีบนบอร์ด
- ชื่อคลาสจริงคือ sirens (มี s) เงื่อนไข label == TARGET_CLASS จึงไม่เคยจริง
- CONF_FLOOR สูงเกินไป
- find_model หาไม่เจอ
Show answer
Answer: B. ชื่อคลาสจริงคือ sirens (มี s) เงื่อนไข label == TARGET_CLASS จึงไม่เคยจริง
ชื่อโมเดลกับชื่อคลาสไม่เหมือนกันเสมอ ให้ตรวจ labels จาก edge_ai.models() ใน REPL ก่อนรีทาร์เก็ต
-
Without the conf condition the counter rises even when nobody coughs. Why? (Objective 3)
- ไมค์เสีย
- argmax ให้ผู้ชนะเสมอ แม้ cough ชนะแบบเฉียด 0.51 ต่อ 0.49 ก็ถูกนับ กลายเป็น false positive
- seq ไม่เปลี่ยน
- โมเดลเปลี่ยนเป็น Motion
Show answer
Answer: B. argmax ให้ผู้ชนะเสมอ แม้ cough ชนะแบบเฉียด 0.51 ต่อ 0.49 ก็ถูกนับ กลายเป็น false positive
CONF_FLOOR คือประตูที่บอกว่าเชื่อผลก็ต่อเมื่อมั่นใจถึงเกณฑ์ ถ้าไม่มีประตู ทุกครั้งที่ชนะเฉียดก็กลายเป็นการกระทำ
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: your own focused app" 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: "ลงมือทำ: แอปโฟกัสของเราเอง" จาก 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/l02-focused-app-lab/
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