Skip to content

Hands-on: an action pipeline that resists false positives

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

Fill the four pipeline stages in s16_action_pipeline.py — read the result, filter, debounce plus cooldown, and fire the action — until the light on screen goes blue, yellow, red, with a sound and a log line, once the target class has held long enough. Then tune NEED_HITS and COOLDOWN_MS, and prove the pipeline genuinely rejects fake signals.

By the end of this lesson, you will:

  1. Fill the four points in practice/s16_action_pipeline.py so that the target class held continuously for NEED_HITS fires the red light, a sound, and one log line, while a single-frame signal doesn’t fire.
  2. Tune NEED_HITS to at least three values (for example, 1, 3, 6), record the false positives and false negatives of each, and choose a value for a given job with reasons.
  3. Point to which stage blocked a fake signal you created, and explain what happens if last_fire = now is forgotten.

You’ve been through lesson 6.3, and understand the four-stage pipeline, EMA, and firing on the rising edge. Keep your learning log ready to record a NEED_HITS tuning table.

The whole file reads as one sentence: select a model → loop reading results → does this frame hit the target? → has debounce and cooldown both been satisfied? → fire the action. The practice file already prepares the light-card widget, fire_action() and set_light(). Four points remain, following the flow: (1) r = edge_ai.result() — leave it empty, and the screen never shows a class, and the pipeline never runs (2) hit = (r['label'] == TARGET_CLASS and r['conf'] >= edge_ai.CONF_FLOOR) — leave it empty, and streak never moves (3) should_fire = ready and cooled, where ready = streak >= NEED_HITS and cooled comes from the already-prepared ticks_diff, and (4) fire_action(r['conf']); last_fire = now; streak = 0 — triggering the action, resetting the cooldown clock, and clearing the counter. Forget last_fire = now, and cooldown never restarts, so the pipeline fires repeatedly while the event holds.

The engineering heart of this is tuning — there’s no single correct value. A high NEED_HITS gives fewer false alerts but misses brief events. A high CONF_FLOOR only trusts very confident results. A long COOLDOWN_MS avoids repeated disturbance, but two real events back to back get seen as one. Measure the result by counting false positives (firing when there’s no event) against false negatives (an event happens but nothing fires), then ask which kind of mistake is more costly for this job. Success isn’t just a light turning on — you must show that the pipeline rejects a fake signal.

s16_action_pipeline_full.py adds dsp.EMA smoothing at EMA_ALPHA = 0.35, a four-state light, on_result that logs only on a class change, and a blocked counter that counts how many times cooldown stopped a repeated firing. Try coughing or shaking for a long time and watch blocked increase.

File What this file teaches
examples/s16_action_pipeline_full.py A full action pipeline: filter + smooth + debounce + action (full version)

The # TODO: comments are at lines 120 (reading the result), 134 (filter), 152 (debounce + cooldown), and 157 (firing the action). If a single brief cough fires it, stage 2 or 3 isn’t working yet. If holding a shake fires repeatedly, check last_fire = now at point 157.

Practice file Topic
practice/s16_action_pipeline.py From a verdict to a real action: RGB + sound + a log (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/s16_action_pipeline.py practice/s16_action_pipeline.py

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

  1. The light stays blue forever, even while shaking is held a long time, and streak never moves at all. Which point is still empty? (single choice · objective 1)

    • a) Point 2, hit is still False
    • b) Point 4
    • c) Point 3
    • d) Nothing is wrong
    Solution

    a — streak only increases when hit is true. If hit is still stuck at its starting value False, the pipeline resets every frame.

  2. The light turns yellow at 1/3 and 2/3 as you shake, but never red, with no sound or log. Which point is still empty? (single choice · objective 1)

    • a) Point 1
    • b) Point 2
    • c) Point 3 or 4 (should_fire is still False, or fire_action is never called)
    • d) COOLDOWN_MS is zero
    Solution

    c — streak moving means stages 1 and 2 are working. The end of the pipeline that never fires is the should_fire condition, or the fire_action call.

  3. NEED_HITS = 1. What would the test results likely look like? (single choice · objective 2)

    • a) It never fires
    • b) The fastest to respond, but with the most false positives, since even a single frame passing the filter stage fires it
    • c) It misses real events the most often
    • d) Same as NEED_HITS = 6
    Solution

    b — debouncing at 1 frame is the same as having no debounce at all. A high value like 6, on the other hand, misses real brief events more often.

  4. You forget last_fire = now at point 4, then hold a shake for a long time. What happens? (single choice · objective 3)

    • a) It fires once, normally
    • b) Cooldown never restarts, and the pipeline fires again every time streak completes
    • c) It never fires at all
    • d) The program errors out
    Solution

    b — cooled stays true forever, since last_fire never changes, so the cooldown stage has no effect.

The MVP for lessons 6.3–6.4: a debounced action pipeline where the target class held continuously triggers a real action, while a brief flicker of the signal is guarded against.

  • All four points in the practice file are filled in. Practise on the emulator with Motion and the Shake button, then try it with real coughing on the board.
  • Tune NEED_HITS = 1, 3, 6. Run the same test ten times per value, and record a table of false positives and false negatives in your learning log.
  • Create a signal that should be blocked (a brief tap of Shake, or a single cough), confirm it doesn’t fire, and say which stage blocked it.
  • Choose a value for two jobs (a fire alert versus a burglar alert), with reasons.

In the next pair of lessons (6.5–6.6), we’ll combine a verdict with a raw sensor (sensor fusion), then send an event out over the network with WiFi and MQTT.

Next lesson: lesson 6.5 — Sensor fusion: a model’s verdict with a raw sensor

  • How do the values you’d choose for a burglar alert differ from a fire alert, and how would you explain that to a customer?
  • If your log only ever showed ALERT, with no record of blocked firings, how would you know the guard is actually working?

Review questions

Answer on your own first, then open the answer.

  1. The light stays cyan even with a long shake and the streak never moves. Which point is empty? (Objective 1)

    1. จุดที่ 2 hit ยังเป็น False
    2. จุดที่ 4
    3. จุดที่ 3
    4. ไม่มีจุดใดผิด
    Show answer

    Answer: A. จุดที่ 2 hit ยังเป็น False

    streak เพิ่มเมื่อ hit เป็นจริงเท่านั้น ถ้า hit ยังเป็นค่าเริ่มต้น False ท่อจะรีเซ็ตทุกเฟรม

  2. The light goes amber 1/3 and 2/3 as you shake, but never turns red and there is no sound or log. Which point is empty? (Objective 1)

    1. จุดที่ 1
    2. จุดที่ 2
    3. จุดที่ 3 หรือ 4 (should_fire ยังเป็น False หรือยังไม่เรียก fire_action)
    4. COOLDOWN_MS เป็นศูนย์
    Show answer

    Answer: C. จุดที่ 3 หรือ 4 (should_fire ยังเป็น False หรือยังไม่เรียก fire_action)

    streak ขยับแปลว่าด่าน 1 และ 2 ทำงาน ปลายท่อที่ไม่ยิงอยู่ที่เงื่อนไข should_fire หรือการเรียก fire_action

  3. With NEED_HITS = 1, what result do you expect? (Objective 2)

    1. ไม่เคยยิง
    2. ไวที่สุดแต่ false positive มากที่สุด เพราะเฟรมเดียวที่ผ่านด่านกรองก็ยิง
    3. พลาดเหตุการณ์จริงบ่อยที่สุด
    4. เหมือน NEED_HITS = 6
    Show answer

    Answer: B. ไวที่สุดแต่ false positive มากที่สุด เพราะเฟรมเดียวที่ผ่านด่านกรองก็ยิง

    debounce ที่ 1 เฟรมเท่ากับไม่มี debounce ส่วนค่าสูงอย่าง 6 จะพลาดเหตุการณ์สั้นของจริงบ่อยขึ้น

  4. You forget last_fire = now at point 4 and hold a long shake. What happens? (Objective 3)

    1. ยิงครั้งเดียวตามปกติ
    2. cooldown ไม่เริ่มนับใหม่ ท่อยิงซ้ำทุกครั้งที่ streak ครบ
    3. ไม่ยิงเลย
    4. โปรแกรม error
    Show answer

    Answer: B. cooldown ไม่เริ่มนับใหม่ ท่อยิงซ้ำทุกครั้งที่ streak ครบ

    cooled ยังเป็นจริงตลอดเพราะ last_fire ไม่ขยับ ด่าน cooldown จึงไม่มีผล

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: an action pipeline that resists false positives" 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: "ลงมือทำ: action pipeline ที่กัน false positive" จาก 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/l04-action-pipeline-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