ข้ามไปยังเนื้อหา

ท่อสั่งการ: CONF_FLOOR, debounce, cooldown และ on_result

โมดูล 6 — แอป Edge AI · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

เปลี่ยนการนับเป็นการสั่งการจริงอย่างมีวินัยด้วยท่อสี่ด่าน คือกรองด้วย CONF_FLOOR, debounce ที่ต้องจับต่อเนื่อง, cooldown ที่เว้นช่วงกันยิงรัว และ action ผ่านไฟ RGB เสียงและ log เสริมด้วย EMA smoothing คณิตของการยิงตอนขอบขึ้น และการเลือกระหว่าง poll กับ on_result

เมื่อจบบทเรียนนี้ คุณจะ:

  1. อธิบายท่อสี่ด่าน (กรอง → debounce → cooldown → action) และบอกได้ว่าแต่ละด่านกัน false positive แบบใด
  2. คำนวณ EMA หนึ่งก้าวด้วยมือจาก y_t = αx_t + (1 − α)y_{t−1} และอธิบายว่าทำไมมันกดยอดแหลมเดี่ยวได้โดยเก็บสถานะแค่ค่าเดียว
  3. ใช้เงื่อนไขยิง fire = [c_t ≥ H] ∧ [c_{t−1} < H] ∧ [t − t_last ≥ T_cool] กับลำดับเฟรมที่กำหนด และอธิบายว่าทำไมต้องวัดเวลาด้วย ticks_diff
  4. เลือกระหว่าง poll ด้วย result() กับ callback ด้วย on_result() ให้งานที่กำหนด โดยรู้ว่า callback มาเมื่อคลาสเปลี่ยนและทวนคลาสเดิมราววินาทีละครั้ง

ผ่านชุดบทเรียน 6.1–6.2 มาแล้ว มีแอปนับที่ใช้ CONF_FLOOR และขอบขาขึ้น และจำ dsp.EMA จากบทเรียน 4.1 ได้ เปิดตัวอย่าง 20_confirmed_alert.py ไว้ใน BENTO IDE

  • อุปกรณ์: บอร์ด TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE — บน Emulator ใช้โมเดล Motion กับปุ่ม Shake ซ้อมท่อได้ครบ (โมเดลเสียงบน Emulator เป็นค่าจำลองที่คลาสเหตุการณ์ไม่ชนะ) และ callback ของ on_result จะมาเมื่อโปรแกรมเรียก result() หรือ active()
  • เรียนมาก่อน: บทเรียน 6.2 — ลงมือทำ: แอปโฟกัสของเราเอง

รัน s16_action_pipeline_full.py (อยู่ในบทเรียน 6.4) เลือกโมเดล Cough แล้วลองไอ ไฟบนจอไล่จากฟ้า (เฝ้าดู) เป็นเหลือง (เจอแต่ยังไม่ชัวร์) แล้วค่อยเป็นแดง (ยิง) สังเกตว่าไอแวบเดียวสั้น ๆ ไม่ยิงทันที แล้วถามว่ามันรู้ได้อย่างไรว่าอันไหนของจริง

verdict ดิบเชื่อทั้งดุ้นไม่ได้ โมเดลกระพริบเกินเส้นชั่ววูบได้เสมอ ถ้ายิงทุกเฟรมที่ label == "cough" จะเตือนผิดบ่อยจนคนเลิกสนใจ ปัญหาเดียวกับ switch bounce ของปุ่มกด เราจึงวาง ท่อสี่ด่าน: (1) กรอง hit = r['label'] == TARGET_CLASS and r['conf'] >= edge_ai.CONF_FLOOR ตัดคำตอบที่ไม่มั่นใจ (2) debounce streak += 1 เมื่อ hit และ streak = 0 ทันทีที่หลุด ต้องครบ NEED_HITS จึงพร้อม ยอดแหลมเดี่ยวผ่านไม่ได้เพราะไม่ค้าง (3) cooldown time.ticks_diff(now, last_fire) >= COOLDOWN_MS กันยิงรัวตอนเหตุการณ์ค้างยาว ใช้ ticks_diff เสมอเพราะตัวนับ ms วนกลับได้ (4) action ใน fire_action() ที่แยกไว้ต่างหาก: การ์ดสีบนจอแทนไฟ RGB (ฟ้า เหลือง แดง), ui.tone(note, wave, velocity, dur_ms) หรือ ui.sfx(...) ห่อด้วย hasattr และ log ผ่าน lcd.console พร้อมเวลาและความมั่นใจ

เกราะเสริมคือ EMA $y_t = \alpha x_t + (1-\alpha) y_{t-1}$ ที่ $\alpha = 0.35$ ถ้า $y_{t-1} = 0.20$ แล้วยอดแหลม $x_t = 0.90$ โผล่มา ได้ $y_t = 0.445$ ยังไม่ถึง 0.50 ต้องสูงจริงหลายเฟรมจึงไต่ข้ามเส้น และเก็บสถานะแค่ค่าเดียว debounce นับจำนวนเฟรม ส่วน smoothing กดขนาดของค่า ใช้คู่กันยิ่งแน่น เขียนเป็นคณิตได้ว่า $c_t = c_{t-1} + 1$ เมื่อ hit และ $c_t = 0$ เมื่อพลาด แล้วยิงเมื่อ $[c_t \ge H] \wedge [c_{t-1} < H] \wedge [t - t_{last} \ge T_{cool}]$ คือยิงตอนความพร้อมเพิ่งครบ ครั้งเดียวต่อหนึ่งเหตุการณ์ แบบเดียวกับ edge-triggered interrupt

การรับผลมีสองแบบ poll คือเรียก result() เองทุกรอบ ได้ทุกเฟรมจึงนับ streak ได้ ส่วน edge_ai.on_result(cb) ให้เฟิร์มแวร์เรียกเราทันทีที่คลาสที่ชนะเปลี่ยน และทวนคลาสเดิมราววินาทีละครั้ง จึงเหมาะกับการ log การเปลี่ยนคลาส (จำคลาสล่าสุดไว้แล้วข้ามรอบที่ทวน) แต่ไม่เหมาะกับ debounce งานที่ต้องนับเฟรมหรือเวลาใช้ poll งานที่แค่ตอบสนองต่อเหตุการณ์ใช้ callback และถอนด้วย edge_ai.on_result(None) ตอนจบเสมอ

19_motion_verdict_action.py ใช้ป้าย ความมั่นใจ และเวลาอนุมานพร้อมกันก่อนสั่งเสียง (เกณฑ์ CONF = 0.70) และ 20_confirmed_alert.py ยืนยัน CONFIRM_N = 4 รอบติดกันก่อนเชื่อ แล้วเว้น ALERT_GAP_MS = 8000 ก่อนเตือนซ้ำ ซึ่งคือท่อแบบเดียวกันด้วยตัวเลขอีกชุด (ทั้งสองไฟล์ย้ายมาจากคอร์ส AIoT ของผู้เขียน)

ไฟล์ ไฟล์นี้สอน
examples/19_motion_verdict_action.py ท่าทางสั่งงาน และราคาของแต่ละคำตัดสิน
examples/20_confirmed_alert.py ให้โมเดลเป็นแหล่งค่า แล้วยืนยันก่อนเตือน

สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นหรือใน shared/ ด้วย:

คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ

  1. คลาส cough กระโดดขึ้นเหนือ CONF_FLOOR เพียงเฟรมเดียวแล้วตกลง ด่านใดกันไม่ให้ยิง (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) ด่านกรอง
    • ข) ด่าน debounce เพราะ streak ไม่ถึง NEED_HITS แล้วรีเซ็ตเป็น 0
    • ค) ด่าน cooldown
    • ง) ด่าน action
    เฉลย

    ข — ยอดแหลมที่สูงพอผ่านด่านกรองได้ แต่ไม่ค้างต่อเนื่อง streak จึงไม่ครบ ส่วน cooldown มีไว้กันการยิงรัวตอนเหตุการณ์จริงค้างยาว

  2. EMA ที่ α = 0.35 ค่าเดิม y = 0.20 เจอค่าใหม่ x = 0.90 ได้ y ใหม่เท่าไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) 0.90
    • ข) 0.55
    • ค) 0.445
    • ง) 0.315
    เฉลย

    ค — 0.35 × 0.90 + 0.65 × 0.20 = 0.315 + 0.130 = 0.445 ยอดแหลมถูกกดจนยังไม่ข้ามเกณฑ์ 0.50

  3. NEED_HITS = 3 และพ้น cooldown แล้ว ลำดับเฟรม hit, hit, hit, hit, hit จะยิงกี่ครั้ง (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) 0
    • ข) 1 ครั้ง ที่เฟรมที่สาม
    • ค) 3 ครั้ง
    • ง) 5 ครั้ง
    เฉลย

    ข — ยิงเมื่อ c_t ถึง 3 เป็นครั้งแรก (c_{t−1} = 2 < 3) เฟรมถัดไป c_{t−1} ครบแล้วจึงไม่ยิงซ้ำ และ cooldown เริ่มนับใหม่ด้วย

  4. ทำไมต้องใช้ time.ticks_diff(now, last_fire) แทน now − last_fire (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) เร็วกว่า
    • ข) ตัวนับ ticks_ms วนกลับได้ ticks_diff จัดการการวนกลับให้ผลต่างยังถูก
    • ค) ได้หน่วยวินาที
    • ง) ไม่มีความต่าง
    เฉลย

    ข — เมื่อตัวนับเต็มแล้ววนกลับ การลบตรง ๆ จะได้ค่าติดลบหรือผิด ticks_diff ออกแบบมาเพื่อกรณีนี้

  5. งานใดเหมาะกับ edge_ai.on_result(cb) มากที่สุด (เลือกหนึ่งข้อ · เป้าหมายข้อ 4)

    • ก) นับ streak ต่อเฟรมเพื่อ debounce
    • ข) log เมื่อคลาสที่ชนะเปลี่ยน โดยจำคลาสล่าสุดไว้เพื่อข้ามรอบที่เฟิร์มแวร์ทวนคลาสเดิม
    • ค) วัด latency ทุกเฟรม
    • ง) smoothing ด้วย EMA ทุกเฟรม
    เฉลย

    ข — callback มาตอนคลาสเปลี่ยนและทวนราววินาทีละครั้ง ไม่ได้มาทุกเฟรม งานที่ต้องนับเฟรมจึงควรใช้ poll

  • รัน 20_confirmed_alert.py บน Emulator ด้วยโมเดล Motion กดปุ่ม Shake สั้น ๆ กับค้างยาว แล้วจดว่าป้ายสถานะที่ยืนยันแล้วเปลี่ยนเมื่อไร
  • คำนวณ EMA ต่อจากตัวอย่างอีกสามเฟรมเมื่อ x = 0.90 ค้างไว้ แล้วดูว่าเฟรมใดข้าม 0.50
  • เขียนลำดับ hit/miss สิบเฟรมที่มีทั้งยอดแหลมเดี่ยวและเหตุการณ์จริง แล้วทำเครื่องหมายเฟรมที่ยิงเมื่อ NEED_HITS = 3

บทเรียน 6.4 เราจะเติมสี่ด่านของท่อใน s16_action_pipeline.py แล้วจูน NEED_HITS กับ COOLDOWN_MS จนกัน false positive ได้จริง

บทเรียนถัดไป: บทเรียน 6.4 — ลงมือทำ: action pipeline ที่กัน false positive

  • งานไหนที่การพลาดของจริง (false negative) แพงกว่าการเตือนผิด และคุณจะจูนท่อไปทางไหน
  • ถ้าใช้ on_result ทำ debounce แทน poll จะเกิดปัญหาอะไร

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. คลาส cough กระโดดขึ้นเหนือ CONF_FLOOR เพียงเฟรมเดียวแล้วตกลง ด่านใดกันไม่ให้ยิง (เป้าหมายข้อ 1)

    1. ด่านกรอง
    2. ด่าน debounce เพราะ streak ไม่ถึง NEED_HITS แล้วรีเซ็ตเป็น 0
    3. ด่าน cooldown
    4. ด่าน action
    ดูเฉลย

    คำตอบ: B. ด่าน debounce เพราะ streak ไม่ถึง NEED_HITS แล้วรีเซ็ตเป็น 0

    ยอดแหลมที่สูงพอผ่านด่านกรองได้ แต่ไม่ค้างต่อเนื่อง streak จึงไม่ครบ ส่วน cooldown มีไว้กันการยิงรัวตอนเหตุการณ์จริงค้างยาว

  2. EMA ที่ α = 0.35 ค่าเดิม y = 0.20 เจอค่าใหม่ x = 0.90 ได้ y ใหม่เท่าไร (เป้าหมายข้อ 2)

    1. 0.90
    2. 0.55
    3. 0.445
    4. 0.315
    ดูเฉลย

    คำตอบ: C. 0.445

    0.35 × 0.90 + 0.65 × 0.20 = 0.315 + 0.130 = 0.445 ยอดแหลมถูกกดจนยังไม่ข้ามเกณฑ์ 0.50

  3. NEED_HITS = 3 และพ้น cooldown แล้ว ลำดับเฟรม hit, hit, hit, hit, hit จะยิงกี่ครั้ง (เป้าหมายข้อ 3)

    1. 0
    2. 1 ครั้ง ที่เฟรมที่สาม
    3. 3 ครั้ง
    4. 5 ครั้ง
    ดูเฉลย

    คำตอบ: B. 1 ครั้ง ที่เฟรมที่สาม

    ยิงเมื่อ c_t ถึง 3 เป็นครั้งแรก (c_{t−1} = 2 < 3) เฟรมถัดไป c_{t−1} ครบแล้วจึงไม่ยิงซ้ำ และ cooldown เริ่มนับใหม่ด้วย

  4. ทำไมต้องใช้ time.ticks_diff(now, last_fire) แทน now − last_fire (เป้าหมายข้อ 3)

    1. เร็วกว่า
    2. ตัวนับ ticks_ms วนกลับได้ ticks_diff จัดการการวนกลับให้ผลต่างยังถูก
    3. ได้หน่วยวินาที
    4. ไม่มีความต่าง
    ดูเฉลย

    คำตอบ: B. ตัวนับ ticks_ms วนกลับได้ ticks_diff จัดการการวนกลับให้ผลต่างยังถูก

    เมื่อตัวนับเต็มแล้ววนกลับ การลบตรง ๆ จะได้ค่าติดลบหรือผิด ticks_diff ออกแบบมาเพื่อกรณีนี้

  5. งานใดเหมาะกับ edge_ai.on_result(cb) มากที่สุด (เป้าหมายข้อ 4)

    1. นับ streak ต่อเฟรมเพื่อ debounce
    2. log เมื่อคลาสที่ชนะเปลี่ยน โดยจำคลาสล่าสุดไว้เพื่อข้ามรอบที่เฟิร์มแวร์ทวนคลาสเดิม
    3. วัด latency ทุกเฟรม
    4. smoothing ด้วย EMA ทุกเฟรม
    ดูเฉลย

    คำตอบ: B. log เมื่อคลาสที่ชนะเปลี่ยน โดยจำคลาสล่าสุดไว้เพื่อข้ามรอบที่เฟิร์มแวร์ทวนคลาสเดิม

    callback มาตอนคลาสเปลี่ยนและทวนราววินาทีละครั้ง ไม่ได้มาทุกเฟรม งานที่ต้องนับเฟรมจึงควรใช้ poll

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"ท่อสั่งการ: CONF_FLOOR, debounce, cooldown และ on_result" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "The action pipeline: CONF_FLOOR, debounce, cooldown and on_result" 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

ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/edge-ai-developer/m06-apps/l03-action-pipeline/

วิธีอ้างอิง TESA ฉบับเต็ม

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

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA