บทเรียน 1.6 — แกะแอป Edge AI: ทะเบียนโมเดล verdict และ action

จาก verdict สู่ action

โมดูล 1 — เริ่มต้น: รันของจริงแล้วแกะดูข้างใน

Onboarding — กลับด้าน (แกะแอป Edge AI)

คาถาประจำบทเรียน: "โมเดลบอกว่า 'เจออะไร' เป็นแค่ครึ่งเดียว — Edge AI ที่ใช้งานได้จริงต้อง 'ลงมือทำอะไรต่อ' เมื่อคำตอบเข้าเงื่อนไข"

MicroPython บนบอร์ด BENTO (PSoC Edge · Cortex-M55 + Ethos-U55 NPU)

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

เปิดบทเรียนด้วยของจริงก่อน

ยังใช้แนว กลับด้าน เหมือนสองชุดบทเรียนแรก — ชุดบทเรียนนี้เราจะรันแอป Edge AI ที่ทำงานได้จริงก่อน แล้วค่อยแกะกายวิภาคของมัน แล้ว remix เป็นของเราเอง

รันแอปจริงก่อน เมนู 12 / Motion 13 แกะกายวิภาค select→result→action remix เอง สลับโมเดล + action อยากสร้างต่อ โมดูล 6 (Apps)

นี่คือขั้น Investigate → Modify ของ PRIMM: เราเห็นแอปทำงานแล้ว (Run) ชุดบทเรียนนี้จะเปิดฝาดูข้างใน (Investigate) แล้วดัดแปลง (Modify) ให้เป็นแอปของเรา

บทเรียน 1.1–1.3 เราเห็นเมนูรู้จำท่าทาง บทเรียน 1.4–1.5 เราแกะแอปเซนเซอร์ — ชุดบทเรียนนี้รวมสองอย่าง: แกะ "แอป Edge AI" ให้ขาด แล้วต่อยอดจาก "อ่านผล" ไปสู่ "สั่งการ"

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

เป้าหมายของชุดบทเรียนนี้

จบชุดบทเรียนนี้เราจะเดินครบ 4 เรื่อง แล้วปิดท้ายด้วยการ remix แอป Edge AI ของเราเอง:

  1. กายวิภาคของแอป Edge AI ตัวเดียว — 3 จังหวะ: select → result → action
  2. ทะเบียนโมเดล (registry) — เลือกโมเดลจากชื่อด้วย find_model() ไม่ hard-code index
  3. เส้นทาง verdict → screen — คำตอบของโมเดลกลายเป็นภาพบนจอได้ยังไง
  4. verdict → action — เพิ่ม "การกระทำจริง" เมื่อคำตอบเข้าเงื่อนไข + รู้จัก on_result()
  5. ลงมือ: remix s03_anatomy_edgeai.py — สลับโมเดล + สั่งการเมื่อเจอคลาสเป้าหมาย

ปลายทางของวันนี้: แอปเฝ้าจับ "โมเดลเดียว 1 คลาส" ที่พอเจอคลาสเป้าหมายเกินเกณฑ์ บี๊บ + ขึ้นแบนเนอร์ เอง

วันนี้เราไม่ได้แค่ "อ่านผล" (บทเรียน 1.1–1.3) แต่ทำให้ผลนั้น สั่งการอะไรบางอย่างได้ — นี่คือประตูสู่ โมดูล 6 (Apps)

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ชุดบทเรียนนี้อยู่ตรงไหนของคอร์ส

บทเรียน 1.6–1.7 เป็นบทเรียน ปิดกล่อง Onboarding — สองชุดบทเรียนก่อนกลับด้าน "แอปเซนเซอร์" และตอนนี้เรากลับด้าน "แอป Edge AI"

บทเรียน 1.1–1.3 รันเมนู 6 โมเดล บทเรียน 1.4–1.5 แกะแอปเซนเซอร์ บทเรียน 1.6–1.7 (วันนี้) แกะแอป Edge AI + action บทเรียน 2.1–2.2+ Pillar 1: DAQ
  • บทเรียน 1.1–1.3 ให้เราเห็นปลายทาง (เมนูรู้จำท่าทาง) และรู้จัก 4 คำสั่ง models/select/result/stop
  • บทเรียน 1.6–1.7 ต่อยอด: จากอ่านผลเฉยๆ ไปสู่ "อ่านผลแล้วลงมือ" — และจากเมนูหลายโมเดล ไปสู่แอปโฟกัสโมเดลเดียว
  • จบชุดบทเรียนนี้ Onboarding ครบ แล้วบทเรียน 2.1–2.2 เราเริ่มลงลึกเก็บข้อมูลเซนเซอร์ของเราเอง (DAQ)

"กลับด้าน" ยังทำงานเหมือนเดิม: เห็นของสำเร็จก่อน แล้วย้อนเข้าใจ — พอถึง โมดูล 6 (Apps) คุณจะกลับมามองแอปวันนี้แล้วต่อยอดเป็นระบบจริงได้

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ทบทวนเร็ว — 4 คำสั่งจากบทเรียน 1.1–1.3

ก่อนแกะแอป เรียก 4 คำสั่งของ edge_ai ที่เจอในบทเรียน 1.1–1.3 กลับมาในหัวก่อน ชุดบทเรียนนี้เราจะต่อยอดจากตรงนี้พอดี:

คำสั่ง ทำอะไร
edge_ai.models() คืน list ของ dict — ทะเบียนโมเดลทั้งหมด {index, name, sensor, labels}
edge_ai.select(n) สั่งให้ CM55 รันโมเดลหมายเลข n (ส่งคำสั่งแล้วรอยืนยัน)
edge_ai.result() ผลอนุมานล่าสุดเป็น dict หรือ None — มี label/conf/scores/seq/latency_ms
edge_ai.stop() หยุดเครื่องยนต์ให้ว่าง (idle)
edge_ai.CONF_FLOOR ค่าคงที่ 0.50 — ต่ำกว่านี้ถือว่า "ยังไม่ชัวร์"

ชุดบทเรียนนี้เพิ่มอีกสองแนวคิดบนฐานนี้: เลือกโมเดลจากชื่อ (แทน index) และ on_result() (แทนการ poll เอง)

ถ้ายังไม่แม่น เปิด s01_first_inference.py อ่านทวนก่อน — ชุดบทเรียนนี้ยืนบนคำสั่งเดิมเป๊ะ แค่ประกอบมันเป็น "แอปที่ลงมือทำ" ได้

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

จากเมนู 6 โมเดล สู่ "แอปตัวเดียว"

บทเรียน 1.1–1.3 ใช้ 12_edge_ai_menu.py — เมนูสลับได้ทั้ง 6 โมเดล เหมาะกับ "สำรวจว่ามีอะไรบ้าง" แต่แอปใช้งานจริงมักโฟกัส โมเดลเดียว แล้วทำงานให้ดีที่สุด

12 · เมนู 6 โมเดล (บทเรียน 1.1–1.3) dropdown สลับได้ทุกตัว "มีโมเดลอะไรให้เล่นบ้าง?" เหมาะกับสำรวจ / เดโม 13 → s03 · แอปโมเดลเดียว (ชุดบทเรียนนี้) โฟกัสโมเดลเดียว + ลงมือ "เจอ shaking แล้วทำอะไรต่อ?" เหมาะกับสินค้า / งานจริง
  • 13_edge_ai_motion.py คือฝาแฝดโมเดลเดียวของ 12 — เลือก Motion ด้วย find_model() แล้ววนอ่านผล
  • แอปชุดบทเรียนนี้ (s03_anatomy_edgeai.py) ต่อยอดจาก 13: เพิ่ม "สั่งการเมื่อเจอคลาสเป้าหมาย" เข้าไป

โลกจริงส่วนใหญ่ไม่ได้ให้ผู้ใช้เลือกโมเดลเอง — นาฬิกาตรวจการล้ม รันโมเดลเดียวตลอด แล้วลงมือเมื่อเจอ นี่คือรูปแบบที่เรากำลังแกะ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

รันของจริงก่อน — เห็นแอปทำงานเลย

ก่อนแกะโค้ด รันสองแอปอ้างอิงให้เห็นของจริงในมือก่อน (ทำได้ทั้ง Emulator และบอร์ด):

  1. เปิด ide.tesaiot.dev หรือ BENTO IDE เสียบบอร์ด
  2. รัน 13_edge_ai_motion.py — โมเดล Motion โมเดลเดียว
  3. เขย่าบอร์ด / วาดวงกลม / วางนิ่ง ดูคลาสที่ชนะ (shaking/circle/idle) + แถบคะแนน + latency เปลี่ยนสด
  4. สังเกต: มัน รู้จำท่าของคุณได้แล้ว ตั้งแต่ยังไม่แตะโค้ด — นั่นแหละคือของที่เราจะแกะ
  5. ลอง 12_edge_ai_menu.py เทียบด้วย เห็นว่า "โมเดลเดียว" กับ "เมนู" ต่างกันตรงไหน

ความรู้สึก "มันทำงานได้แล้ว ฉันอยากรู้ว่าทำไม" คือเชื้อเพลิงของชุดบทเรียนนี้ — จับความสงสัยนั้นไว้ แล้วเราจะเปิดฝาดูข้างในกันทีละส่วน

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

จอ Emulator ที่รันได้จริง

ไม่มีบอร์ดในมือก็เริ่มได้ — นี่คือหน้า Edge AI บน BENTO Emulator ที่รันในเบราว์เซอร์ล้วนๆ ทุกคนได้ลองมือพร้อมกัน:

หน้า Edge AI บน BENTO Emulator: dropdown เลือกโมเดล Motion Detection ปุ่ม Load และ Stop คลาสที่ชนะ idle เวลาอนุมาน และแถบความมั่นใจของ idle circle shaking

จอ emulator ที่รันได้จริง — หน้า Edge AI ของ BENTO (เลือกโมเดล ดู verdict + แถบคะแนน + conf สดๆ)

  • ทุกจังหวะที่เราจะแกะในชุดบทเรียนนี้ เห็นได้บนหน้านี้เลย: เลือกโมเดล (select) → คลาสที่ชนะตัวใหญ่กลางจอ (result) → แถบ scores ทุกคลาส + conf % ด้านข้าง
  • ตรงที่ยังว่างในภาพคือช่องที่เราจะเติมวันนี้ — จังหวะ action เมื่อ verdict เข้าเงื่อนไข

Emulator กับบอร์ดจริงใช้โค้ดชุดเดียวกันเป๊ะ — เขียนบน Emulator ให้เข้าใจก่อน แล้วยกไฟล์เดิมไปลงบอร์ดได้ทันที ไม่ต้องแก้อะไร

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

กายวิภาคของแอป Edge AI ตัวเดียว

แอป Edge AI ที่โฟกัสโมเดลเดียว อ่านเป็น 3 จังหวะ เสมอ — จำโครงนี้ไว้ แล้วทุกแอปในคอร์สจะเข้าใจง่ายขึ้น:

1 · select เลือกโมเดลจากทะเบียน find_model → select() 2 · result อ่าน verdict + วาดจอ result() → verdict.text 3 · action สั่งการเมื่อเข้าเงื่อนไข fire_action() ← ของใหม่ เลือก ──▶ อ่าน ──▶ ลงมือ บทเรียน 1.1–1.3 หยุดที่จังหวะ 2 · ชุดบทเรียนนี้เติมจังหวะ 3 เข้าไป

จังหวะ 1–2 คือของเดิมจากบทเรียน 1.1–1.3 (เลือกโมเดล อ่านผลขึ้นจอ) จังหวะ 3 — action — คือหัวใจใหม่ที่ทำให้ Edge AI "ใช้งานได้จริง" ไม่ใช่แค่โชว์ตัวเลข

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ทะเบียนโมเดล (registry) — หน้าตาข้างใน

edge_ai.models() คืน ทะเบียน ที่เฟิร์มแวร์ถืออยู่ แต่ละแถวคือโมเดลหนึ่งตัว การแกะแอปเริ่มที่เข้าใจโครงสร้างนี้:

edge_ai.models() → list ของ dict index 0 name: "Motion Detection" sensor: 0 (IMU) labels: [idle, circle, shaking] index 1 name: "Baby Cry Detection" sensor: 2 (MIC) labels: [unlabelled, baby_cry] index 2 name: "Push Detection" sensor: 1 (RADAR) labels: [unlabelled, Push] ... รวม 6 แถวบน Dev Kit · index ใช้ตอน select() · labels คือคลาสที่ result() จะตอบ
  • แต่ละ dict มี 4 คีย์: index (ใช้ select), name (โชว์/ค้นหา), sensor (0=IMU,1=RADAR,2=MIC), labels (คลาสที่ตอบได้)
  • labels สำคัญมากชุดบทเรียนนี้ — คลาสเป้าหมายที่เราจะดักจับต้องเป็นชื่อจากลิสต์นี้เป๊ะ

ทะเบียนคือ "แหล่งความจริง" ของแอป — เราไม่เดาว่าโมเดลไหนคลาสอะไร เราอ่านจากที่นี่ ถ้าเฟิร์มแวร์เพิ่มโมเดล แอปเห็นเองทันที

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

select() — เลือกจากชื่อ ไม่ใช่ index

บทเรียน 1.1–1.3 เราส่ง index ตรงๆ (select(0)) แต่ index อาจสลับได้ถ้าเฟิร์มแวร์เปลี่ยน แอปที่ทนทานกว่าคือ ค้นหาโมเดลจากชื่อ แล้วค่อยเอา index ไป select:

def find_model(keyword):
    ms = edge_ai.models()
    for m in ms:
        if keyword.lower() in m['name'].lower():
            return m           # เจอชื่อที่ตรง คืน dict ทั้งก้อน
    return ms[0]               # ไม่เจอ ใช้ตัวแรกกันแอปพัง

model = find_model("Motion")   # remix: เปลี่ยน keyword = เปลี่ยนแอป
edge_ai.select(model['index']) # เอา index จาก dict ไปสั่งจริง
  • find_model() ยืมมาจาก 13_edge_ai_motion.py — คืน dict ทั้งก้อน เราจึงได้ทั้ง index และ labels มาใช้ต่อ
  • เปลี่ยนแค่ keyword เดียว แอปก็ remix เป็นโมเดลอื่นได้ — นี่คือจุด remix แรกของชุดบทเรียน

นิสัยเดิมจากบทเรียน 1.1–1.3 ยังอยู่: ถามฮาร์ดแวร์ก่อน อย่าเดา แต่รอบนี้เราถามด้วย "ชื่อ" ที่มนุษย์อ่านออก แทนตัวเลข index ที่จำยาก

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

result() — verdict dict (ทบทวน)

result() คืน dict ก้อนเดิมจากบทเรียน 1.1–1.3 ชุดบทเรียนนี้เราจะใช้ 2 คีย์เป็นหลักในการตัดสิน "จะสั่งการไหม": label กับ conf

r = edge_ai.result()
# r = {'label': 'shaking',     # คลาสที่ชนะ (ข้อความ)  ← ใช้เทียบเป้าหมาย
#      'top': 2,               # index ของคลาสที่ชนะ
#      'conf': 0.92,           # ความมั่นใจ 0..1        ← ใช้เทียบ CONF_FLOOR
#      'scores': [.03,.05,.92],# คะแนนทุกคลาส → ทำแถบ
#      'latency_ms': 4.1,
#      'seq': 137, 'running': True}
  • label = "เจออะไร" · conf = "มั่นใจแค่ไหน" — สองตัวนี้รวมกันคือเงื่อนไขของ action
  • seq เพิ่มทุกครั้งที่มีผลใหม่ — ยังใช้กันวาดจอซ้ำเหมือนบทเรียน 1.1–1.3
  • result() เป็น pull ถามเมื่อไรก็ได้ในลูป คืนผลล่าสุด ไม่บล็อกรอ

บทเรียน 1.1–1.3 เราเอา label ขึ้นจอเฉยๆ ชุดบทเรียนนี้เราจะ เอา label + conf ไปตัดสินใจ ว่าจะลงมือทำหรือไม่ — ความต่างอยู่ตรงนี้

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

เบื้องหลัง conf — softmax เปลี่ยนคะแนนดิบเป็นความน่าจะเป็น

scores กับ conf ที่ result() คืนมาไม่ได้โผล่มาลอยๆ — มันมาจากสูตรชื่อ softmax โมเดลปล่อย "คะแนนดิบ" (logits) ziz_i ของแต่ละคลาสออกมาก่อน แล้ว softmax บีบให้เป็นความน่าจะเป็นที่รวมกันได้ 1:

softmax(z)i=ezi∑j=1Kezj\text{softmax}(z)_i = \frac{e^{z_i}}{\displaystyle\sum_{j=1}^{K} e^{z_j}}

อ่านทีละตัวแบบไม่ต้องกลัวสูตร:

  • ziz_i = คะแนนดิบของคลาสที่ ii (logit) ที่โมเดลปล่อยออกมา ค่าติดลบก็ได้
  • KK = จำนวนคลาสทั้งหมด (เช่น Motion มี 3 คลาส: idle, circle, shaking)
  • ezie^{z_i} = ยกกำลัง exponential ทำให้ค่าบวกเสมอ และดันคะแนนที่สูงให้เด่นขึ้น
  • ตัวส่วน ∑jezj\sum_j e^{z_j} = ผลรวมของทุกคลาส ใช้ "หาร normalize" ให้ทุกคลาสรวมกันได้พอดี 1

ผลลัพธ์ softmax(z)i\text{softmax}(z)_i คือ ความน่าจะเป็นของคลาส ii — นี่แหละคือค่าที่ไปโผล่ในทุกช่องของ r['scores'] (แต่ละแถบที่เห็นบนจอ)

ทำไมไม่ใช้คะแนนดิบตรงๆ? เพราะเราอยากได้เลข 0..1 ที่ตีความเป็น "มั่นใจกี่เปอร์เซ็นต์" ได้จริง — และเส้น CONF_FLOOR = 0.50 จะมีความหมายก็ต่อเมื่อคะแนนถูก normalize แล้วเท่านั้น

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

argmax หาป้าย · max หา conf — ที่มาของ label กับ conf

จาก scores ที่ softmax ให้มา เฟิร์มแวร์คำนวณสองค่าที่เราเอาไปตัดสินใจ ด้วยตัวดำเนินการคู่กัน:

y^=arg⁡max⁡isoftmax(z)iconf=max⁡isoftmax(z)i\hat{y} = \arg\max_i \text{softmax}(z)_i \qquad\qquad \text{conf} = \max_i \text{softmax}(z)_i

  • arg⁡max⁡\arg\max = ตำแหน่ง (index) ของคลาสที่คะแนนสูงสุด → กลายเป็น r['top'] แล้วแปลงเป็นชื่อ r['label']
  • max⁡\max = ค่า คะแนนสูงสุดเอง (ไม่ใช่ตำแหน่ง) → กลายเป็น r['conf'] ความมั่นใจ 0..1

ลองแทนตัวเลขจริงของโมเดล Motion 3 คลาส:

scores=[ 0.03, 0.05, 0.92 ]  ⟹  y^=2 (shaking),conf=0.92\text{scores} = [\,0.03,\ 0.05,\ 0.92\,] \;\Longrightarrow\; \hat{y}=2\ (\text{shaking}),\quad \text{conf}=0.92

  • คลาส index 2 คะแนนสูงสุด → label = "shaking", top = 2
  • ค่าสูงสุด 0.92 → conf = 0.92 = มั่นใจ 92%
  • 0.92≥CONF_FLOOR (0.50)0.92 \geq \text{CONF\_FLOOR}\ (0.50) → เงื่อนไข hit เป็น True → สั่งการ

นี่คือสะพานเชื่อมสูตรกับโค้ดของเราตรงๆ: r['label'] มาจาก argmax, r['conf'] มาจาก max ของ scores ก้อนเดียวกัน — สองบรรทัดเงื่อนไข hit ในชุดบทเรียนนี้ (label == TARGET และ conf >= CONF_FLOOR) ก็คือ argmax คู่กับ max นั่นเอง

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

เส้นทาง verdict → screen

ก่อนต่อ action มาปิดเส้นทางเดิมให้ขาดก่อน: verdict หนึ่งก้อนกลายเป็นภาพบนจอได้ยังไง นี่คือ "จังหวะ 2" แบบละเอียด

result() dict หรือ None seq เปลี่ยน? มีผลใหม่จริงไหม verdict.text คลาสที่ชนะ (Seg7) แถบ scores br.value ทุกคลาส conf % ความมั่นใจ
  • เช็ก seq ก่อน → วาดเฉพาะตอนมีผลใหม่ (ไม่รัดจอ) · แล้วกระจายไป verdict.text + แถบ scores + conf
  • scores คือคะแนน ทุกคลาส เอาไปทำแถบ คลาสที่ i == top ระบายเขียว ผู้ใช้เห็นทั้งคำตอบและความสูสี

นี่คือ "จังหวะ 2" ที่คุณเติมมาแล้วในบทเรียน 1.1–1.3 — ชุดบทเรียนนี้เราต่อ "จังหวะ 3" (action) จากจุดเดียวกันนี้ หลังวาดจอเสร็จ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

ของใหม่ชุดบทเรียนนี้ — verdict → ACTION

จนถึงบทเรียน 1.1–1.3 เราหยุดที่ "เอาคำตอบขึ้นจอ" แต่แอป Edge AI จริงต้อง ลงมือทำอะไรต่อ เมื่อคำตอบเข้าเงื่อนไข นี่คือก้าวจาก inference ไปสู่ application

verdict "shaking" 92% เข้าเงื่อนไข? label + conf ACTION บี๊บ · แบนเนอร์ · แจ้งเตือน yes
  • ในบทเรียนนี้ action = บี๊บ + แบนเนอร์บนจอ (ทำได้ทั้ง Emulator และบอร์ด)
  • ในงานจริง action เดียวกันนี้อาจเป็น: เปิดไฟ · ส่ง MQTT แจ้งเตือน · บันทึกเหตุการณ์ลงไฟล์ · สั่งมอเตอร์

"โมเดลรู้ว่าเจออะไร" เป็นแค่ครึ่งทาง — คุณค่าจริงเกิดตอน การกระทำ ที่ตามมา นี่คือสิ่งที่ โมดูล 6 (Apps) ทั้งบล็อกจะขยายให้ลึก

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

เงื่อนไขของ action — label + conf

action ไม่ควรยิงทุกครั้งที่โมเดลตอบ — ต้องยิงเฉพาะเมื่อ ใช่คลาสที่เราสนใจ และ มั่นใจพอ สองเงื่อนไขนี้คู่กันเสมอ:

hit = (r['label'] == TARGET_CLASS            # 1) ใช่คลาสเป้าหมายไหม
       and r['conf'] >= edge_ai.CONF_FLOOR)  # 2) มั่นใจถึงเกณฑ์ไหม (>= 0.50)

if hit and not fired:
    fire_action(r['conf'])                   # ลงมือ
    fired = True
  • เช็ก label อย่างเดียวไม่พอ — ถ้าโมเดลตอบ "shaking" แต่ conf แค่ 30% แปลว่ามัน "เดา" เราไม่ควรลงมือ
  • CONF_FLOOR = 0.50 คือเส้นที่เฟิร์มแวร์แนะนำ (จากบทเรียน 1.1–1.3) — เป็นตัวกัน false positive

นี่คือบทเรียน Edge AI สำคัญ: action ต้องตั้งอยู่บนความมั่นใจ ไม่ใช่แค่ป้ายคลาส เส้น CONF_FLOOR คือปุ่มที่คุณจะปรับจริงจังในชุดบทเรียน Apps (บทเรียน 6.3–6.4)

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

edge-trigger — ยิงครั้งเดียวต่อการเจอ

ปัญหา: ถ้าคุณเขย่าค้าง 3 วินาที โมเดลตอบ "shaking" ทุกเฟรม — ถ้ายิง action ทุกเฟรม เสียงจะบี๊บรัวจนน่ารำคาญ ทางแก้คือ จำว่ายิงไปแล้ว

if hit and not fired:      # เพิ่งเข้าคลาสเป้าหมาย → ยิงครั้งเดียว
    fire_action(r['conf'])
    fired = True
elif not hit:              # ออกจากคลาสเป้าหมายแล้ว → รีเซ็ต
    fired = False
  • fired เป็นธง: ตั้ง True ตอนยิง เคลียร์ False ตอนออกจากคลาส — action จึงยิง "ตอนขอบขาขึ้น" ครั้งเดียว
  • นี่เรียกว่า edge-triggered (ยิงตอนเปลี่ยนสถานะ) ตรงข้ามกับ level-triggered (ยิงตลอดที่ยังเจอ)

pattern เดียวกับปุ่มกดในงาน embedded: เรากด หนึ่งครั้ง ได้ event หนึ่งครั้ง ไม่ใช่ event รัวตราบที่ยังกดค้าง — action ที่ดีต้องคุมจังหวะการยิงเสมอ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

fire_action() — action จริงบนบอร์ด

fire_action() คือที่ที่ verdict กลายเป็นการกระทำจริง ในบทเรียนนี้เราให้มันบี๊บ + ขึ้นแบนเนอร์ + จดคอนโซล:

def fire_action(conf_val):
    banner.text("! เจอ %s !" % TARGET_CLASS)
    banner.color(GREEN)
    if hasattr(ui, "tone"):
        ui.tone(72, ui.WAVE_SINE, 120, 150)   # โน้ต MIDI 72, sine, ดัง 120, 150 ms
    lcd.console('<span class=ok> ACTION: เจอ %s (conf %.0f%%)</span>'
                % (TARGET_CLASS, conf_val * 100))
  • ui.tone(note, wave, vol, ms) เล่นเสียงผ่าน SFX mixer ฝั่ง CM55 — note เป็น MIDI (60 = โดกลาง)
  • ห่อด้วย hasattr(ui, "tone") เผื่อพื้นผิวที่ไม่มีลำโพง — แอปไม่พังถ้าเล่นเสียงไม่ได้
  • แยก fire_action() เป็นฟังก์ชัน = remix ง่าย: อยากเปลี่ยน action แก้ที่เดียว

เก็บ "การตัดสินใจ" (เงื่อนไข hit) แยกจาก "การลงมือ" (fire_action) — โครงนี้ทำให้เปลี่ยน action โดยไม่แตะ logic ตรวจจับได้ นี่คือนิสัยออกแบบที่ดี

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

on_result() — วิธี event-driven

การ poll เอง (result() ในลูป + เช็ก seq) ใช้ได้ดี แต่ edge_ai มีทางที่สะอาดกว่า: ให้เฟิร์มแวร์ เรียกฟังก์ชันของเราให้ เมื่อมีคำตัดสิน (ทันทีที่คลาสเปลี่ยน และทวนคลาสเดิมราววินาทีละครั้ง)

poll เอง (บทเรียน 1.1–1.3 + ฝึก) while: r = result() ถ้า seq เปลี่ยน → วาด/สั่งการ เราถามเอง เช็ก seq เอง คุมจังหวะได้ตรงไปตรงมา on_result(cb) (ฉบับเต็ม) on_result(on_change) เฟิร์มแวร์เรียก cb ให้เมื่อมีคำตัดสิน ไม่ต้องเช็ก seq เอง ลูปเหลือแค่รับปุ่ม
def on_change(r):          # เฟิร์มแวร์เรียกให้เมื่อมีคำตัดสิน (scheduler context — ปลอดภัยกับ UI)
    verdict.text(r['label'] or '-')
    if r['label'] == TARGET_CLASS and r['conf'] >= edge_ai.CONF_FLOOR:
        fire_action(r['conf'])
edge_ai.on_result(on_change)   # ลงทะเบียนครั้งเดียว

on_result ยิง cb ให้เราทันทีที่คลาสเปลี่ยน (และทวนคลาสเดิมราววินาทีละครั้ง) เราจึงไม่ต้องเช็ก seq เอง แต่ยังต้องมีธง fired กันยิง action ซ้ำ — s03_anatomy_edgeai_full.py ใช้วิธีนี้ ทำให้ลูปหลักเหลือแค่รับปุ่ม back

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0

poll กับ on_result — เลือกยังไง

สองวิธีให้ผลเดียวกัน ต่างที่ "ใครถาม" — เข้าใจข้อแลกเปลี่ยนแล้วเลือกใช้ให้เหมาะกับงาน:

ประเด็น poll เอง (result()) on_result(cb)
ใครขับ โค้ดเราถามเองในลูป เฟิร์มแวร์เรียก cb ให้
เช็ก seq ต้องเช็กเอง ไม่ต้อง (ยิงตอนคลาสเปลี่ยน และทวนคลาสเดิมราววินาทีละครั้ง)
คุมจังหวะ ตรงไปตรงมา เห็นทั้งลูป logic กระจายไปอยู่ใน cb
เหมาะกับ เริ่มเรียน · อยากเห็นทุกจังหวะ แอปที่ action ต้องไวและสะอาด
  • ในไฟล์ฝึก (practice/) เราใช้ poll เพราะเห็นทั้ง 3 จังหวะในลูปเดียว เข้าใจง่าย
  • บน BENTO Emulator callback ของ on_result จะทำงานก็ต่อเมื่อโปรแกรมเรียก edge_ai.result() หรือ edge_ai.active() ถ้าลูปมีแค่ ui.poll() บน Emulator จะไม่เห็นผลเลย (บนบอร์ดไม่มีข้อจำกัดนี้)
  • ในฉบับเต็ม (examples/) เราใช้ on_result เพราะแอปโตขึ้น — ย้าย logic ไปที่ cb ทำให้ลูปสะอาด

ทั้งคู่ถูกต้อง ไม่มีผิด — เริ่มจาก poll ให้เข้าใจกลไกก่อน แล้วค่อยยกไป on_result เมื่อแอปต้องการความสะอาด (ลองเป็นโจทย์ต่อยอดดูได้)

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก Edge AI Developer (รศ.วิรุฬห์ ศรีบริรักษ์, BUU) · CC BY-NC 4.0