- 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 included in this package. 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, model sets 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 reports 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. The engine ships as libbento_edge_ai.a. Its on-board callers — the Edge AI page, the model-link task and the display bring-up — are compiled into libbento_cm55.a, and the MicroPython binding is compiled into libbento_mpy.a. This chapter therefore describes everything by what you call and what you observe. The C calls it names have compilable examples under template/proj_cm55/examples/edge_ai/ (01_first_inference.c, 08_deepcraft_link.c). MicroPython reaches the engine only over the IPC model link — it cannot call ai_engine_* at all.
-
No shipped MicroPython example program. No 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.
Firmware sequence
Bring-up. The display bring-up creates the inference task after the display is confirmed up, so a fault in the ML runtime can never cost the screen. ai_engine_init() is idempotent; if the task cannot be created, ai_engine_stack_words() returns 0, which is the gate every control tests first.
Re-call init before every start. Every shipped caller calls the idempotent ai_engine_init() immediately before ai_engine_start(), because start() returns false on an engine that never started. Your own code should do the same.
Select. edge_ai.select(n) (bounds 0..255) first translates the legacy indices with model_link_resolve_set(), defined in ipc_model_link_defs.h: 13→INTRUDER (253), 14→ROOM (254), 15→MIC (255). Below 16 the index rides the legacy opcode band (SELECT_BASE + n); at 16 and above it goes as SELECT_IDX with the index in the payload. The confirm that follows compares against what was sent, not what was typed.
CM55 receives it, applies the same translation, and calls ai_engine_init() immediately before ai_engine_start(sel). Both sides use the one definition in ipc_model_link_defs.h, and ai_engine.h static-asserts that its AI_PARALLEL_* constants match it, so both sides always compare the same number.
Confirm. edge_ai.select() 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, so it returns False immediately instead of waiting out the ceiling. The budget is about 15 s. The reason it is that long: a cold TFLite-Micro + Ethos-U55 bring-up runs inside the model's init(), and ai_engine_active() changes only after it returns.
On the CM55 side, Q_ACTIVE answers with ai_engine_active() and Q_REQUESTED with ai_engine_requested().
Step by step
Step 1 — Confirm the engine exists (both variants)
Navigate Home → Edge AI. The page's Load button does nothing while ai_engine_stack_words() == 0.
What you should observe. The chip badge on the page reads one of Running / Loading / Failed / Stopped. It reads Failed 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 for 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 Loading during the window, then Running once ai_engine_snapshot() has something to show.
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 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 at least one microphone model; on a motion-only image ai_engine_start() refuses the set index and select() returns False.
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 that edge_ai.diag() answers even while inference is stalled. The full reading guide is Chapter E4; for now note only that ml_state alone is not trusted.
Traps
- Warning
- Never gate a fallback on ai_engine_active(). active() reports the model that has finished loading, and lags the request for a whole cold-init. A fallback that tests active() fires while a perfectly good start is still in flight and clobbers the selection back to the default model — "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. Set the sensor rate only after start() returned true; a bad index leaves state alone and the Q_ACTIVE confirm surfaces it.
-
ai_engine_init() before ai_engine_start(), always. On an engine that never started, start() returns false silently. 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 (in ai_engine.h) 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()). 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(); 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, or your own CM55 C code calling ai_engine_start() |
| Confirm | select() blocks on the two-query loop for up to about 15 s | Watch the chip badge: Loading → Running |
| Engine-alive check | edge_ai.diag() | Badge ≠ Failed; 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 |