SDK สำหรับ TESAIoT Dev Kit
คู่มืออ้างอิง API และ Tutorial (MTB & µPython)
Loading...
Searching...
No Matches
E2 — Parallel set และการวนถามผลลัพธ์
variant ที่ใช้ได้
mtb-mpy และ mtb-only

ตัวอย่าง MicroPython ใช้ได้บน mtb-mpy เท่านั้น ส่วนการยุบรวมผลฝั่ง C และสิ่งที่สังเกตได้บนหน้าจอใช้ได้ทั้งสอง variant โค้ดฝั่ง C ในบทนี้เขียนขึ้นจาก API สาธารณะใน ai_engine.h ตัวอย่างที่สมบูรณ์อยู่ที่ template/proj_cm55/examples/edge_ai/03_parallel_set_run.c และ 04_set_membership.c

Note
เครดิต: โมเดล Edge AI เป็นของ Infineon ไม่ใช่ของ TESAIoT โมเดล motion, audio และ radar ที่เฟิร์มแวร์นี้ส่งมอบ (proj_cm55/modules/ai_models/model_*.c) เป็นผลลัพธ์ที่ export จาก DEEPCRAFT™ Studio และมีลิขสิทธิ์ของ Imagimob AB ซึ่งเป็นบริษัทในเครือ Infineon Technologies ส่วนโมเดล Siren, Cough และ Factory Alarm เป็น DEEPCRAFT™ Ready Model ของผู้สร้างรายเดียวกัน เผยแพร่โดย Infineon อยู่ภายใต้ Imagimob AI Model Evaluation License Agreement และไม่ได้แจกจ่ายต่อในแพ็กเกจนี้ TESAIoT ไม่ได้ฝึกและไม่ได้เป็นเจ้าของโมเดลใดเลย สิ่งที่เป็นของ TESAIoT คือ engine ที่ครอบอยู่ ซอร์สของโมเดลที่ส่งมอบมาระบุเพียง All Rights Reserved โดยไม่มีการให้สิทธิ์ใด ๆ เขียนไว้ในไฟล์ ที่นี่จึงเป็นการให้เครดิต ไม่ใช่การส่งต่อสิทธิ์ การใช้งานที่นี่เป็นไปเพื่อการวิจัยและการอบรม ไม่ใช่การใช้เชิงพาณิชย์ ผู้ที่ต้องการสิทธิ์การใช้งานต้องขอจาก Infineon และ Imagimob โดยตรง ผู้ที่ต้องการฝึกโมเดลของตนเองเริ่มได้ที่ https://www.infineon.com/design-resources/embedded-software/deepcraft-edge-ai-solutions/deepcraft-studio รายละเอียดฉบับเต็มและการอ้างอิงข้อสัญญาอยู่ที่ THIRD_PARTY_NOTICES.md §2.2, §2.4 และ §4.3

เป้าหมายของหัวข้อนี้

สั่งให้หลายโมเดลทำงานพร้อมกันในฐานะ set อ่านผลตัดสินจาก set โดยไม่ทำให้การตรวจจับหายไป และรู้จักช่วงตั้งตัว (settle) ที่ผลตัดสินถูกกันไว้ไม่กี่วินาทีหลังจาก set เริ่มทำงาน ประโยคที่สำคัญที่สุดเพียงประโยคเดียว: ai_engine_snapshot() ใช้ไม่ได้ภายใน set

ลำดับการทำงานของเฟิร์มแวร์

ค่าคงที่ของ set (ai_engine.h): MIC = 255, ROOM = 254, INTRUDER = 253, ALL = 252 และ AI_PARALLEL_FIRST = ALL ส่วนชื่อเรียกเดิม 13/14/15 แปลงเป็น 253/254/255 (บท E1) เครื่องยนต์จะยอมรับดัชนีของ set ก็ต่อเมื่ออิมเมจนั้น build พร้อมโมเดลไมโครโฟนอย่างน้อยหนึ่งตัว มิฉะนั้น ai_engine_start() คืนค่า false

สมาชิกของ set ai_engine_set_models() คืนรายชื่อสมาชิกที่มีผลอยู่: หากเคยกำหนดสมาชิกภาพแบบชัดแจ้งด้วย ai_engine_set_define() รายการนั้นจะใช้แทนรายการที่คอมไพล์มา มิฉะนั้นจึงใช้รายการที่คอมไพล์มา โมเดลที่ init() ล้มเหลวไปครั้งหนึ่งไม่ถูกตัดออกจาก set ตลอดรอบการบูต การ start set ครั้งถัดไปจะลองโมเดลนั้นอีกครั้ง

สมาชิกภาพที่กำหนดเอง — edge_ai.set_define(set, [i, j, ...]) แปลงดัชนีเดิมก่อน แล้วส่ง SET_BEGIN, SET_ADD หนึ่งครั้งต่อหนึ่งสมาชิก และ SET_COMMIT จากนั้นอ่านกลับด้วย Q_SET — การอ่านกลับนี้เป็นสิ่งเดียวที่จับได้ว่ามี ADD ตัวใดหล่นหาย ฝั่ง CM55 นำคำนิยามไปใช้ทั้งชุดตอน SET_COMMIT ฝั่ง C ทำสิ่งเดียวกันด้วยการเรียก 2 ครั้ง โดยการอ่านกลับเป็นการตอบรับเพียงอย่างเดียวที่มี:

static const uint8_t members[] = { 0u, 2u }; /* registry indices */
/* refused whole: a bad index, 0 or more than 8 members, or AI_PARALLEL_ALL */
}
uint8_t got[8];
/* n == 2 and got[] == members[] is the confirmation */
#define AI_PARALLEL_INTRUDER
Definition ai_engine.h:105
uint32_t ai_engine_set_members_defined(uint32_t set_index, uint8_t *out, uint32_t max)
สมาชิกภาพแบบชัดแจ้งหากมีการนิยามไว้ มิฉะนั้นได้ 0 — เป็น ack เดียวของ set_define
int ai_engine_set_define(uint32_t set_index, const uint8_t *members, uint32_t n)
กำหนดสมาชิกภาพแบบชัดแจ้งให้ set ณ เวลาทำงาน; ต้องอยู่ใน task context และเป็นแบบทั้งหมดหรือไม่เอาเลย

การยุบรวมผลภายใน set — เลือกสมาชิกที่เป็นผลบวกซึ่งมีค่าความเชื่อมั่นสูงสุด ผ่าน ai_engine_set_members() + ai_engine_snapshot_model() + ai_result_top_positive():

uint8_t idx[8];
uint32_t n = ai_engine_set_members(idx, 8u);
int best = -1;
uint8_t best_model = 0u, best_class = 1u;
for (uint32_t i = 0u; i < n; i++) {
if (!ai_engine_snapshot_model(idx[i], &r)) {
continue; /* never published yet */
}
uint8_t cls;
int pct = ai_result_top_positive(&r, &cls); /* 0..100, class 0 excluded */
if (pct > best) { best = pct; best_model = idx[i]; best_class = cls; }
}
bool ai_engine_snapshot_model(uint32_t index, ai_result_t *out)
ผลตัดสินล่าสุดของโมเดลหนึ่งตัว — เป็นตัวอ่านที่ต้องใช้ภายใน set
uint32_t ai_engine_set_members(uint8_t *idx, uint32_t max)
ดัชนีใน registry ของสมาชิกใน set ที่ ACTIVE อยู่; ต้องตรวจเซนเซอร์ของสมาชิกแต่ละราย
Definition ai_engine.h:74

ผ่านการเชื่อมโมเดล คะแนนแต่ละคลาสถูกรายงานเป็นช่วง 0..100 (score_pct ในคำตอบของ Q_RESULT) ส่วน class_count == 0 หมายความว่า "ยังไม่มีผลตัดสิน" และ edge_ai.result() คืนค่า None

ผลข้างเคียงเรื่องอัตราเซนเซอร์ การ start set ที่มีสมาชิกเป็นโมเดล IMU อย่างน้อยหนึ่งตัวจะยกอัตราของ accelerometer ขึ้นเป็น 50 Hz ตลอดช่วงการทำงานนั้น ส่วน ai_engine_model() คืนค่า NULL สำหรับดัชนีเทียมของ set ดังนั้นให้แยกกิ่งด้วย idx >= AI_PARALLEL_FIRST ก่อนเป็นอันดับแรก:

const int a = ai_engine_active();
if (a >= (int)AI_PARALLEL_FIRST) {
const char *set_name = ai_engine_set_name((uint32_t)a); /* a set */
} else if (a >= 0) {
const ai_model_desc_t *d = ai_engine_model((uint32_t)a); /* one model */
}
#define AI_PARALLEL_FIRST
Definition ai_engine.h:114
int ai_engine_active(void)
โมเดลที่ LOADED แล้ว หรือ -1; ตามหลัง requested อยู่หนึ่งช่วง cold-init
const ai_model_desc_t * ai_engine_model(uint32_t index)
รายการใน registry คืน NULL หากอยู่นอกช่วง — และคืน NULL สำหรับดัชนีเทียมของ set
const char * ai_engine_set_name(uint32_t set_index)
ชื่อที่มนุษย์อ่านได้ของ set; คืน NULL สำหรับดัชนีที่ไม่ใช่ set
Definition ai_engine.h:45

ทีละขั้น

ขั้นที่ 1 — ดูรายชื่อ set ที่หน้าจอมีให้ (ทั้งสอง variant)

บนหน้าจอ ให้เปิดดรอปดาวน์เลือกโมเดลบนหน้า Edge AI แถวของ set จะถูกเพิ่มเข้ามาก็ต่อเมื่อ set นั้นมีสมาชิกอย่างน้อย 2 ตัว และ ai_engine_set_name() คืนชื่อที่ไม่ใช่ NULL

สิ่งที่ควรสังเกต แถวของ set (Sound Watch, Room Presence, Intruder Watch, All Models) ปรากฏต่อจากโมเดลเดี่ยวที่คอมไพล์มา ส่วน set ที่มีสมาชิกน้อยกว่า 2 ตัวในอิมเมจนี้จะไม่ปรากฏเลย ซึ่งเป็นข้อเท็จจริงเรื่องเนื้อหาของ build ไม่ใช่ความผิดพลาด

ขั้นที่ 2 — เลือก set หนึ่งชุดแล้วดูช่วงตั้งตัว

>>> edge_ai.select(255) # MIC set; 15 is the legacy alias
True
>>> edge_ai.result()
>>> edge_ai.result()

สิ่งที่ควรสังเกต ในช่วงไม่กี่วินาทีแรก result() คืนค่า None — เพราะ class_count == 0 — ระหว่างที่ข้อมูลนำเข้าของ set กำลังตั้งตัว บนหน้าจอ ป้ายผลตัดสินจะอ่านว่า filling windows lu%% โดยตัวเลขร้อยละไต่ขึ้น และ badge แสดง Loading ฝั่ง C ใช้ ai_engine_mic_settling() / ai_engine_mic_settle_pct():

/* verdicts are withheld: show progress rather than silent zero bars */
uint32_t pct = ai_engine_mic_settle_pct(); /* 0..100 */
}
uint32_t ai_engine_mic_settle_pct(void)
ความคืบหน้าของการ settle 0..100; มีความหมายเฉพาะภายใน set เท่านั้น (นอกนั้นได้ 100)
bool ai_engine_mic_settling(void)
เป็น true ในช่วงไม่กี่วินาทีหลังจาก set เริ่มทำงาน ขณะที่ข้อมูลนำเข้ากำลังตั้งตัว; ผลตัดสินจะถูกระงับ...

ให้แสดงข้อมูลวินิจฉัยต่อไป ระหว่าง ช่วงตั้งตัวด้วย บรรทัดสถิติของหน้า Edge AI อัปเดตต่อไปในช่วงนี้ เพื่อให้การที่ NPU ค้างในช่วงตั้งตัวยังมองเห็นได้ ส่วน mic_settle_pct() มีความหมายเฉพาะเมื่ออยู่ภายใน set เท่านั้น กรณีอื่นมันคืนค่า 100

ขั้นที่ 3 — วนถามผลลัพธ์

>>> r = edge_ai.result()
>>> r

สิ่งที่ควรสังเกต เมื่อตั้งตัวเสร็จแล้ว จะได้ผลลัพธ์ที่ฟิลด์ต่าง ๆ มาจากสมาชิกที่เป็นผลบวกซึ่งมีค่าความเชื่อมั่นสูงสุด แต่ละแถวของสมาชิกบนหน้าจอมีตัวจับเวลาความเงียบของตัวเอง ซึ่งนับจากเวลาที่ seq ของสมาชิกนั้นไม่เปลี่ยน:

if (r.seq != last_seq[i]) { last_seq[i] = r.seq; last_change_ms[i] = now_ms; }
uint32_t silent_ms = now_ms - last_change_ms[i];
}
uint32_t seq
Definition ai_engine.h:83

ai_engine_snapshot_model() ที่คืนค่า false หมายความว่า "ไม่เคยเผยแพร่ผลเลย" ซึ่งไม่เหมือนกับ "เงียบ" และดัชนีที่ส่งเข้าไปต้องเป็นดัชนีจริงใน registry ห้ามเป็นดัชนีเทียมของ set เด็ดขาด

ขั้นที่ 4 — กำหนดสมาชิกภาพเอง

>>> edge_ai.set_define(253, [0, 2])
>>> edge_ai.select(253)
True

สิ่งที่ควรสังเกต set_define คืนค่าหลังจากที่การอ่านกลับด้วย Q_SET ยืนยันสมาชิกครบทุกตัวแล้ว ADD ที่หล่นหายจะปรากฏเป็นรายการที่สั้นลง และคำนิยามทั้งชุดจะถูกปฏิเสธหากมีดัชนีใดไม่ถูกต้อง (ได้หมดหรือไม่ได้เลย) สมาชิกภาพรับได้ 1..8 รายการ และ set_define(252, ...) ล้มเหลวเสมอ เพราะ AI_PARALLEL_ALL คือโมเดลทุกตัวที่ลงทะเบียนไว้โดยนิยาม จึงกำหนดใหม่ไม่ได้ กำหนดได้เฉพาะ INTRUDER, ROOM และ MIC

ขั้นที่ 5 — อ่าน active() เพียงครั้งเดียวต่อหนึ่งรอบเรนเดอร์ (ฝั่ง C)

const int a = ai_engine_active(); /* read ONCE per render */
uint8_t idx[8];
uint32_t n = (a >= (int)AI_PARALLEL_FIRST)
? ai_engine_set_models((uint32_t)a, idx, 8u) : 0u;
/* size the rows from n and label the layout from a -- the same a */
uint32_t ai_engine_set_models(uint32_t set_index, uint8_t *idx, uint32_t max)
สมาชิกของ set ใด ๆ ตามลำดับใน registry; สมาชิกภาพที่กำหนดชัดแจ้งใช้แทนรายการที่คอมไพล์มา

สิ่งที่ควรสังเกต ไม่มีอะไรให้เห็น เพราะนี่คือวินัยที่กันไม่ให้ป้ายเลย์เอาต์กับจำนวนแถวไม่ตรงกัน ในตอนที่การเชื่อมโมเดลสั่ง start หรือ stop เครื่องยนต์จาก task อื่นกลางรอบเรนเดอร์

กับดัก

Warning
อันตราย (hazard) จากการใช้ snapshot ภายใน set ai_engine_snapshot() คืนผลตัดสินล่าสุดจากโมเดลตัวใดก็ได้ ภายใน set การตรวจจับเสียงไอที่ 0.94 จะถูกตามด้วยผล "unlabelled" จากสมาชิกอีกสามตัวก่อนที่ผู้อ่านที่วนถามที่ 10 Hz จะเห็นมัน — การตรวจจับนั้นไม่ได้มาช้า แต่หายไปเลย ให้ใช้ ai_engine_snapshot_model() ทีละสมาชิกแล้วยุบรวมผลเอง ดัชนีของ set คือทุกค่าในช่วง [AI_PARALLEL_FIRST, AI_PARALLEL_MIC] ให้ทดสอบทั้งช่วง ไม่ใช่ == AI_PARALLEL_MIC ซึ่งครอบคลุมเพียงหนึ่งในสี่ set
set_define เรียกได้ใน task context เท่านั้น ห้ามเรียกจาก ISR หรือจาก callback ของ pipe
ai_engine_model_count() นำหน้าจำนวนแถว ai_engine_model(i) ยังคืนค่า NULL ได้สำหรับดัชนีที่ไม่มีแถวอยู่ ให้ continue ห้ามยกเลิกทั้งลูป:
for (uint32_t i = 0u; i < ai_engine_model_count(); i++) {
if (d == NULL) {
continue; /* no row here: skip it, keep going */
}
/* ... use d ... */
}
uint32_t ai_engine_model_count(void)
มีโมเดลลงทะเบียนไว้กี่ตัว; ค่านับนำหน้าแถวจริง

กล่อง Variant

mtb-mpy mtb-only
เลือก set edge_ai.select(252..255) แถวในดรอปดาวน์ แล้วกด Load
กำหนดสมาชิกภาพ edge_ai.set_define() ทำได้จากโค้ด CM55 ที่เขียนเองเท่านั้น โดยเรียก ai_engine_set_define() จาก task แล้วอ่านกลับด้วย ai_engine_set_members_defined()
สิ่งที่สังเกตได้ระหว่างตั้งตัว result() เป็น None; หน้าจอแสดง filling windows lu%% หน้าจอแสดง filling windows lu%%
ผลตัดสิน dict จาก edge_ai.result() ป้ายผลตัดสิน บวกตัวจับเวลาความเงียบรายแถว