SDK for TESAIoT Dev Kit
API reference & tutorials (ModusToolbox)
Loading...
Searching...
No Matches
E1 — Select, confirm, start: REQUESTED versus ACTIVE
Variant
mtb-mpy and mtb-only

MicroPython examples in this chapter run on mtb-mpy only (there is no REPL on mtb-only). The C-side sequence and the on-screen observables apply to both variants.

Note
Credit: the Ready Models are Infineon's. The Siren, Cough and Factory Alarm models this engine can run — siren_lib_eval.a, cough_lib_eval.a, alarm_lib_eval.a — are DEEPCRAFT™ Ready Models authored by Imagimob AB, an Infineon Technologies company, and published by Infineon: https://www.infineon.com/design-resources/embedded-software/deepcraft-edge-ai-solutions/deepcraft-ready-models They are why this kit can demonstrate real audio Edge AI at all. TESAIoT references them under the Imagimob AI Model Evaluation License Agreement and abides by it — the use here is research and teaching, we hold no rights in them and pass none on, and the archives are not redistributed. Evaluating them on your own board is permitted for 60 days (§2.1); shipping a product that links them, redistributing them, or any commercial use is not (§2.2(a), §2.2(c)). For production, buy the non-evaluation model from Imagimob/Infineon, or train your own in DEEPCRAFT™ Studio: https://www.infineon.com/design-resources/embedded-software/deepcraft-edge-ai-solutions/deepcraft-studio A model you train yourself runs on this same API unchanged, and its licensing metrics are not the Ready Model ones. Full credit, the clause citations and our disclosed per-model symbol renaming: THIRD_PARTY_NOTICES.md §2.4.
Credit: the models this firmware ships are Infineon's too. The motion, audio and radar models — proj_cm55/modules/ai_models/model_motion.c, model_audio.c and model_radar.c — are DEEPCRAFT™ Studio exports, generated by Infineon's Edge AI tool and copyright Imagimob AB, an Infineon Technologies company. The line at the top of each file reads "Copyright © 2023- Imagimob AB, All Rights Reserved." TESAIoT did not train them, did not author them and does not own them; what is ours is the engine around them — the model registry, the sensor feed router and the run-time loader. Anything generated by DEEPCRAFT™ Studio, or derived from a DEEPCRAFT™ model, is Imagimob's and Infineon's. Those headers reserve all rights and grant nothing, so nothing here licenses them on, and the Apache-2.0 grant on this project's own code does not reach inside those files. The use here is research and teaching, not commercial deployment: to put these models, or anything derived from them, into a product, settle it with Infineon and Imagimob first. Train your own at https://www.infineon.com/design-resources/embedded-software/deepcraft-edge-ai-solutions/deepcraft-studio Full entry: THIRD_PARTY_NOTICES.md §2.2 and §4.3.

Learning goal

After this chapter you can explain why the Edge AI engine keeps two model indices — the one you requested and the one that is active — why they disagree for up to 15 s after every select, and why the firmware confirms a select against the index it sent rather than the number you typed. You will also reproduce the documented promise: edge_ai.select(13) is answered by edge_ai.active() == 253.

Note
Where the code lives. template/ contains zero .c call sites for any ai_engine_* function. Every caller shown here is compiled into libbento_cm55.a (built from deepcraft_task.c, page_edge_ai.c and tesaiot_display.c, per template/lib/cm55_core/PROVENANCE.txt), and the MicroPython binding modedgeai.c is compiled into libbento_mpy.a. The excerpts below are lifted verbatim from those archived sources; each states its origin (file:lines in the source-of-truth tree — compiled into the archive, not shipped as source). The excerpt files are a documentation-build input and are not distributed in this package. MicroPython reaches the engine only over the IPC model link — it cannot call ai_engine_* at all.
Thin evidence — no shipped example program. No working edge_ai.* MicroPython program ships under template/. The working J6 programs live outside the shipped tree. This chapter therefore teaches against the REPL surface, one call at a time; nothing here asks you to run a file you do not have.

Real firmware sequence

Bring-up. The display controller creates the inference task after the display is confirmed up (tesaiot_display.c:467, after display_ok = true), so a fault in the ML runtime can never cost the screen. ai_engine_init() is idempotent (ai_engine.c:1735-1738) and tries a stack ladder of {2048, 1536} words; on total failure ai_engine_stack_words() returns 0, which is the gate every control tests first.

Origin
Lifted from tesaiot_display.c:462-468 (compiled into the prebuilt archive; not shipped as source).

The MicroPython path re-calls init before every start. The comment in this excerpt is the whole chapter in nine lines — read it before anything else:

Origin
Lifted from deepcraft_task.c:631-641 (compiled into the prebuilt archive; not shipped as source).

Select. edge_ai.select(n) (modedgeai.c:492-497, bounds 0..255) first translates the legacy indices — model_link_resolve_set() at modedgeai.c:503, definition ipc_model_link_defs.h:135-143: 13→INTRUDER (253), 14→ROOM (254), 15→MIC (255). Below 16 the index rides the legacy opcode band; at 16 and above it goes as SELECT_IDX with the index in the payload (modedgeai.c:507-509). The confirm that follows compares against what was sent, not what was typed (modedgeai.c:513-516).

CM55 receives it and performs a second, independent translation (deepcraft_task.c:785-787), then ai_engine_init() (idempotent) immediately before ai_engine_start(sel):

Origin
Lifted from deepcraft_task.c:790-807 (compiled into the prebuilt archive; not shipped as source).

The two translations are kept consistent by the _Static_assert block at ai_engine.h:121-126, whose comment records what happened when they drifted: every Sound Watch select reported "not confirmed" on a board that had in fact switched.

Confirm. modedgeai.c:373-393 runs a two-query loop against the wire: Q_ACTIVE == want means loaded. Otherwise it polls Q_REQUESTED; once requested == want has been seen, a later requested < 0 can only mean the model's init() failed on ai_task, so it returns False immediately instead of waiting the ceiling. Budget: EDGE_AI_CONFIRM_TRIES 750 × EDGE_AI_CONFIRM_DELAY_MS 20 = 15 s (modedgeai.c:107-108). The reason it is that long: a cold TFLite-Micro + Ethos-U55 bring-up runs synchronously inside the model's init(), and s_current flips only after it returns (modedgeai.c:89-105).

The two queries on the CM55 side:

Origin
Lifted from deepcraft_task.c:301-311 (compiled into the prebuilt archive; not shipped as source).

Step by step

Step 1 — Confirm the engine exists (both variants)

Navigate Home → Edge AI. The page's Load button is a no-op while ai_engine_stack_words() == 0:

Origin
Lifted from page_edge_ai.c:348-369 (compiled into the prebuilt archive; not shipped as source).

What you should observe. The chip badge on the page reads one of CHIP_RUNNING / CHIP_LOADING / CHIP_FAIL / CHIP_STOPPED. CHIP_FAIL is selected at page_edge_ai.c:1338 when stack_words() is 0 — the engine never started. Anything else means the inference task exists. There is no UART line for this: CM55 prints nothing (proj_cm55/main.c:9-10).

Step 2 — First REPL contact (mtb-mpy only)

>>> import edge_ai
>>> edge_ai.models()

What you should observe. A list whose length equals the CM55 registry count. Compiled-in models come first; staged (uploaded) models append later (Chapter E3).

Step 3 — Select a single model and watch the two indices diverge

>>> edge_ai.select(0)
True
>>> edge_ai.active()
0

What you should observe. select() blocks until confirmed — up to 15 s on a cold load — and returns True. Immediately afterwards active() equals the index you selected. If you could read requested mid-load you would see it lead active by the whole cold-init window; that is the REQUESTED-vs-ACTIVE distinction, and it is the reason the next step exists.

On screen: the badge goes CHIP_LOADING during the window, then CHIP_RUNNING once ai_engine_snapshot() has something to show (page_edge_ai.c:1409-1411).

Step 4 — The documented promise: select(13) reports 253

>>> edge_ai.select(13)
True
>>> edge_ai.active()
253

What you should observe. active() reports 253, not 13. 13 is the legacy alias for the Intruder Watch set; both translation points map it to AI_PARALLEL_INTRUDER (253), exactly as ai_engine.h:94-96 promises. A program that compares active() against the number it typed will call this a failure. Compare against the resolved index instead. This requires a build with EDGE_AI_HAS_MIC (ai_engine.c:1850-1859); on a motion-only image the set index is refused and select() returns False after the engine parks s_active (ai_engine.c:1843-1849 records that this once burned two 15 s budgets).

Step 5 — Read the diagnostics dictionary

>>> d = edge_ai.diag()
>>> d['ml_state'], d['npu_cycles'], d['stale_drops']

What you should observe. A dict (modedgeai.c:438-489) answered from the IPC callback, so it still arrives when ai_task is wedged (deepcraft_task.c:356-395). The full reading guide is Chapter E4; for now note only that ml_state alone is not trusted (modedgeai.c:432-437).

Traps

Warning
Never gate a fallback on ai_engine_active(). active() returns s_current (LOADED) and lags the request by a whole cold-init. The recorded bug (ai_engine.c:1868-1873, again at deepcraft_task.c:634-640): a page tap set s_active = <sel> and posted START; a guard that read s_current (still −1 that tick) fired ai_engine_start(0) and clobbered the selection back to model 0 — "select Radar, Load, get Motion". Guard on ai_engine_requested(); it also drops to −1 on init failure, which is what lets select() fail fast.
Check the return of ai_engine_start() before touching rate or state. In the shipped caller the sensor rate is asserted only inside the success branch (deepcraft_task.c:804-806); a bad index leaves state alone and the Q_ACTIVE confirm surfaces it.
ai_engine_init() before ai_engine_start(), always. With s_task == NULL, start() returns false silently (ai_engine.c:1835). The shipped idiom re-calls the idempotent init at every start site.
A field-name trap, preserved from history. An earlier revision of template/proj_cm55/modules/ai_models/README.md showed an example reading r.valid, r.label, r.confidence — fields ai_result_t (ai_engine.h:73-83) has never had, so that example did not compile. The README shipped in this release is fixed (its example now uses ai_engine_snapshot() + ai_result_top_positive(), README.md:33-40). The lesson stands: ai_result_t carries raw scores[] plus top_class, not a label or a validity flag — the real reduction is ai_result_top_positive() (ai_engine.h:249-265); Chapter E2 shows it in use.

Variant box

mtb-mpy mtb-only
Drive side edge_ai.select()/active()/diag() from the REPL On-screen Load button only (page_edge_ai.c:353-368), or your own CM55 C code calling ai_engine_start()
Confirm select() blocks on the 15 s two-query loop Watch the chip badge: CHIP_LOADING → CHIP_RUNNING
Engine-alive check edge_ai.diag() Badge ≠ CHIP_FAIL; C-side ai_engine_stack_words() != 0
Console [MPY] boot line only; nothing per select [HB] t=lus tasks=u only — no Edge AI UART output exists on either variant