Taking an edge AI app apart: the registry, the verdict and the action
Module 1 — Getting started: run the real thing, then take it apart · Slides: slides.md · Module overview · Course page
Take a single-model edge AI app apart into three beats — select → result → action — pick a model by name instead of number, see where conf comes from (softmax and argmax), and add an action that fires once when a confident target class appears.
Objectives
Section titled “Objectives”By the end of this lesson, you will:
- Write find_model() to look a model up by a word in its name via edge_ai.models(), pass the returned dict’s index to select(), and explain why this is sturdier than selecting a fixed number.
- Compute the softmax of a small set of raw scores, and state that label comes from the argmax and conf from the max of the same scores.
- Write an action condition that needs both the target label and conf ≥ CONF_FLOOR, with a fired flag so the action fires once per detection (edge-triggered).
- Compare polling with result() against on_result(cb) — who drives it, whether seq must be checked, and what each suits.
Before you start
Section titled “Before you start”You’ve been through lessons 1.1–1.5, and remember the four calls models, select, result, stop and the four-beat skeleton. Open 13_edge_ai_motion.py in BENTO IDE, and if you can, open 12_edge_ai_menu.py to compare.
- Hardware: a TESAIoT Dev Kit board already flashed with BENTO’s MicroPython firmware, or the BENTO Emulator inside BENTO IDE — the emulator has no Push Detection, and model scores are simulated.
- Prior lesson: lesson 1.5 — Hands-on: remix it into your own Tilt Monitor
See it work first
Section titled “See it work first”Run 13_edge_ai_motion.py before touching any code, then shake the board, draw circles, or hold it still — the winning class, the score bars, and the latency all update live. This app is the single-model twin of the 12_edge_ai_menu.py menu — try running them side by side, and notice how “one model” differs from “a menu.”
Concepts
Section titled “Concepts”A single-model edge AI app reads as three beats: (1) select — pick a model from the registry; (2) result — read the verdict and draw the screen; (3) action — act when the verdict matches a condition. Lessons 1.1–1.3 stopped at beat 2; this lesson adds beat 3, which is what makes edge AI “actually useful.” Most real work never lets the user choose a model themselves — a fall-detection clock runs one model continuously and acts the moment it finds something.
The registry from edge_ai.models() is the app’s source of truth. Instead of select(0) with a number that might shift when the firmware changes, we write find_model(keyword), which searches by name and returns the whole dict — giving both the index to pass to select, and labels to set the target class. Change one keyword, and you’ve remixed the app for a different model.
scores comes from softmax, which squeezes raw scores (logits) $z_i$ into probabilities that add up to 1: $\text{softmax}(z)_i = e^{z_i} / \sum_j e^{z_j}$. From there, label is the argmax (the position of the highest score), and conf is the max (the highest score’s value). For example, scores = [0.03, 0.05, 0.92] gives label = shaking and conf = 0.92. CONF_FLOOR has meaning precisely because the scores have already been normalized.
An action must fire when it’s both the target class and confident enough (r['conf'] >= edge_ai.CONF_FLOOR), and fire once per detection with a fired flag (edge-triggered) — not every frame while the class keeps matching (level-triggered). Keeping “the decision” (the hit condition) separate from “the act” (fire_action(), which beeps with ui.tone and shows a banner) lets you remix the action without touching the detection logic. The other approach is edge_ai.on_result(cb), which has the firmware call our function whenever there’s a verdict (immediately on a class change, and repeating the same class roughly once a second), so you don’t have to check seq yourself — though on the BENTO Emulator, the callback only fires when the program itself calls edge_ai.result() or edge_ai.active().
Worked example
Section titled “Worked example”11_edge_ai_motion.py does the same job as 13, but uses the older module (deepcraft), which only gives the winning class. 13 uses edge_ai, so it also gets every class’s score and the latency. Open both files side by side to see what the model registry adds.
| File | What this file teaches |
|---|---|
| examples/11_edge_ai_motion.py | Edge AI: an AI model classifying motion (runs on the board, no network needed) |
| examples/13_edge_ai_motion.py | Edge AI: Motion (IMU) with the new edge_ai API — seeing every class’s score plus latency |
This lesson’s slides also reference files in other lessons:
- m01-onboarding/l02-edge-ai-module/examples/12_edge_ai_menu.py — Edge AI Menu: select and run any AI model in one firmware image (no network needed)
- m01-onboarding/l03-first-inference-lab/solution/s01_first_inference.py — running our first edge AI model
- m01-onboarding/l07-verdict-action-lab/examples/s03_anatomy_edgeai_full.py — watching for a class with on_result, then acting (full version)
- m01-onboarding/l07-verdict-action-lab/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)
Check your understanding
Section titled “Check your understanding”The same questions are in quiz.yaml for automated checking.
-
Why is find_model(“Motion”) better than edge_ai.select(0) in an app meant to work across several boards? (single choice · objective 1)
- a) Because select() can’t accept a number
- b) Because model order can differ by firmware or board — searching by name always gets the model you want, and also hands you its labels to use
- c) Because find_model() runs faster on the NPU
- d) Because model names never change, but the index changes on every boot
Solution
b — on the Dev Kit, Cough sits at 3, but on the emulator it’s at 2, since there’s no Push. Querying the registry by name makes the code resilient to this difference.
-
Raw scores z = [0, 0, 0] for three classes, run through softmax. What do you get? (single choice · objective 2)
- a) [0, 0, 0]
- b) [1, 1, 1]
- c) roughly [0.33, 0.33, 0.33], so conf is below CONF_FLOOR
- d) [1, 0, 0]
Solution
c — e⁰ = 1 for every class, divided by a total of 3 gives about 0.33 for each, all equal. argmax still picks one winner, but a conf of 0.33 is below 0.50, so it shouldn’t be trusted.
-
scores = [0.10, 0.70, 0.20] for labels [‘idle’, ‘circle’, ‘shaking’]. What are top, label and conf? (single choice · objective 2)
- a) top = 1, label = circle, conf = 0.70
- b) top = 0.70, label = circle, conf = 1
- c) top = 2, label = shaking, conf = 0.20
- d) top = 1, label = idle, conf = 0.10
Solution
a — argmax gives position 1 (top), matching the name circle; max gives the value 0.70, which is conf.
-
You shake the board continuously for three seconds and the model answers shaking every frame. With no fired flag, what happens? (single choice · objective 3)
- a) The action still fires only once, same as before
- b) The action fires every frame it’s still detected, beeping repeatedly and annoyingly (level-triggered)
- c) The action never fires at all
- d) The model stops working
Solution
b — the fired flag makes it fire once on the rising edge, then resets once you leave the target class. This is the same kind of edge-trigger as one button press giving exactly one event.
-
Which of these are true about on_result(cb) compared with polling using result()? (select every correct answer) (multiple choice · objective 4)
- a) The firmware calls cb for us, so we never have to check seq ourselves
- b) cb fires immediately when the class changes, and can also be called repeatedly with the same class roughly once a second, so you still need a fired flag
- c) Polling sees every beat inside one single loop, which suits early learning
- d) on_result means you never need to call edge_ai.stop() at the end
Solution
a, b, c — both approaches are valid; they differ in who’s asking. At the end, you still need to remove the callback with on_result(None) and always call stop().
- Run
13_edge_ai_motion.pyand note in your learning log which class and roughly what conf each motion gives. - In the REPL, try
find_model("Cough")and print theindexandlabelsyou get, comparing against your board or emulator. - Compute the softmax of z = [2.0, 3.1, 1.2] with a calculator, and state which class wins and with what conf.
Going further
Section titled “Going further”In lesson 1.7, we’ll fill in the s03_anatomy_edgeai.py file until the app beeps and shows a banner by itself when it finds the target class, then remix it into our own.
Next lesson: lesson 1.7 — Hands-on: from a verdict to an action on the board
Reflect
Section titled “Reflect”- At home or at work, what job “knows what it found” but that isn’t enough — it also needs to “act on it”? Should that action fire once, or repeatedly?
- If your action was calling in an emergency, would you set CONF_FLOOR high or low, and why?
Review questions
Answer on your own first, then open the answer.
-
Why is find_model("Motion") better than edge_ai.select(0) in an app meant for several boards? (Objective 1)
- เพราะ select() รับเลขไม่ได้
- เพราะลำดับโมเดลอาจต่างกันตามเฟิร์มแวร์หรือบอร์ด การค้นจากชื่อจึงได้โมเดลที่ต้องการเสมอ และได้ labels มาใช้ต่อด้วย
- เพราะ find_model() รันเร็วกว่าบน NPU
- เพราะชื่อโมเดลไม่เคยเปลี่ยน แต่ index เปลี่ยนทุกครั้งที่บูต
Show answer
Answer: B. เพราะลำดับโมเดลอาจต่างกันตามเฟิร์มแวร์หรือบอร์ด การค้นจากชื่อจึงได้โมเดลที่ต้องการเสมอ และได้ labels มาใช้ต่อด้วย
บน Dev Kit Cough อยู่ที่ 3 แต่บน Emulator อยู่ที่ 2 เพราะไม่มี Push การถามทะเบียนด้วยชื่อทำให้โค้ดทนต่อความต่างนี้
-
Raw scores z = [0, 0, 0] for three classes. What does softmax give? (Objective 2)
- [0, 0, 0]
- [1, 1, 1]
- [0.33, 0.33, 0.33] โดยประมาณ conf จึงต่ำกว่า CONF_FLOOR
- [1, 0, 0]
Show answer
Answer: C. [0.33, 0.33, 0.33] โดยประมาณ conf จึงต่ำกว่า CONF_FLOOR
e⁰ = 1 ทุกคลาส หารด้วยผลรวม 3 ได้ราว 0.33 เท่ากันหมด argmax ยังเลือกผู้ชนะได้หนึ่งตัว แต่ conf 0.33 ต่ำกว่า 0.50 จึงไม่ควรเชื่อ
-
scores = [0.10, 0.70, 0.20] for labels ['idle', 'circle', 'shaking']. What are top, label and conf? (Objective 2)
- top = 1, label = circle, conf = 0.70
- top = 0.70, label = circle, conf = 1
- top = 2, label = shaking, conf = 0.20
- top = 1, label = idle, conf = 0.10
Show answer
Answer: A. top = 1, label = circle, conf = 0.70
argmax ให้ตำแหน่ง 1 (top) ซึ่งตรงกับชื่อ circle ส่วน max ให้ค่า 0.70 คือ conf
-
You shake the board for three seconds and every frame says shaking. Without the fired flag, what happens? (Objective 3)
- action ยิงครั้งเดียวเหมือนเดิม
- action ยิงทุกเฟรมที่ยังเจอ บี๊บรัวจนน่ารำคาญ (level-triggered)
- action ไม่ยิงเลย
- โมเดลหยุดทำงาน
Show answer
Answer: B. action ยิงทุกเฟรมที่ยังเจอ บี๊บรัวจนน่ารำคาญ (level-triggered)
ธง fired ทำให้ยิงตอนขอบขาขึ้นครั้งเดียว แล้วรีเซ็ตเมื่อออกจากคลาสเป้าหมาย นี่คือ edge-trigger แบบเดียวกับการกดปุ่มหนึ่งครั้งได้ event หนึ่งครั้ง
-
Which statements about on_result(cb) compared with polling result() are correct? (select all that apply) (Objective 4)
- เฟิร์มแวร์เป็นผู้เรียก cb ให้ เราจึงไม่ต้องเช็ก seq เอง
- cb ถูกเรียกทันทีที่คลาสเปลี่ยน และอาจถูกเรียกซ้ำด้วยคลาสเดิมราววินาทีละครั้ง จึงยังต้องมีธง fired
- การ poll เห็นทุกจังหวะในลูปเดียว เหมาะกับตอนเริ่มเรียน
- on_result ทำให้ไม่ต้องเรียก edge_ai.stop() ตอนจบ
Show answer
Answer: A. เฟิร์มแวร์เป็นผู้เรียก cb ให้ เราจึงไม่ต้องเช็ก seq เอง · B. cb ถูกเรียกทันทีที่คลาสเปลี่ยน และอาจถูกเรียกซ้ำด้วยคลาสเดิมราววินาทีละครั้ง จึงยังต้องมีธง fired · C. การ poll เห็นทุกจังหวะในลูปเดียว เหมาะกับตอนเริ่มเรียน
ทั้งสองวิธีถูกต้อง ต่างกันที่ใครถาม ตอนจบยังต้องถอน callback ด้วย on_result(None) แล้ว stop() เสมอ
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.
"Taking an edge AI app apart: the registry, the verdict and the action" 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: "แกะแอป Edge AI: ทะเบียนโมเดล verdict และ action" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
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