|
SDK for TESAIoT Dev Kit
API reference & tutorials (ModusToolbox)
|
Send a framed payload, emit an event, acknowledge a verb, cross to CM55 through the shared-memory bridge, and know which parts of the exported surface are real and which are dead. 77 of the 87 exported symbols have no caller in template/; only three ble_nus .c files ship as source (bento_time.c, character_lottie.c, character_lottie_default.c). Everything else is in libbento_secure.a; the excerpts marked archived are lifted verbatim from those sources and each states its origin (file:lines — compiled into libbento_secure.a, not shipped as source).
Framing a send. The shipped exemplar is the time-sync ack: caller frames the JSON with a trailing \n, bounds-checks snprintf, passes an explicit length, and (void)-casts the result:
ble_nus_send returns −1 unless init succeeded, state is CONNECTED and the peer enabled TX notifications (ble_nus.c:878-885); fragmentation is internal (MTU−3, capped at 180 B); the library mallocs and copies, so a stack buffer is legal (:893-905). A weak stub at ble_nus.c:65 makes the symbol safe to reference before init. Its true fan-in is ~80+ through the NUS_SEND macro.
Polling state. The firmware deliberately polls instead of trusting the on_state callback — the bonded-pair fast-resume path was observed to miss GATT_CONNECTION_STATUS_EVT (2026-05-10):
ble_nus_get_state() is a lock-free plain read, safe from any task and safe before init (weak stub returns BLE_NUS_STATE_OFF).
Emitting an event. nus_emit_event(json) takes NUL-terminated JSON with no trailing newline (it appends one); strlen(json) + 2 <= 256 or it returns −1 and drops — no truncation; −1 also covers NULL and link-down. The shipped caller (mod_dualband.c:196-213, mtb-mpy source; declared via a local extern at :44-46) formats {"evt":"bento.net.down","n":lu,"reason":"s"}, checks the snprintf bound, then raises OSError("nus_emit_event failed (d)") on non-zero.
Acknowledging a verb — archived caller, with the sensor_stream_start interval clamp to [10, 5000]:
The verb is hard-truncated at 32 chars; ack_cmd_len == 0 means strlen; ok is derived (both NULL ⇒ true; a non-NULL error flips it false and appends "error"); void — a send failure is invisible; runs on the NUS RX dispatch task, not arbitrary context.
Crossing to CM55. ipc_bento_buddy_send writes one static .cy_shared_socmem message — not reentrant; data_len silently clamped to IPC_DATA_MAX_LEN; bounded retry 50 × 1 ms so task only, never ISR; pass len+1 so the receiver gets the NUL and the excl-NUL count in value:
Radio state on the wire — radio_mode_str returns a static literal ("invalid" out of range, so s is unconditionally safe); vocabulary unknown | ble_adv | ble_paired | switching_to_wifi | wifi_active | switching_to_ble | wifi_failed:
Devmode. The secret is RAM-only and regenerated every boot — desktops must re-provision after every reboot; bento_devmode_init is idempotent and self-called by every public entry:
What this challenge/response actually protects. The HMAC-SHA256 step itself is implemented correctly — TTL'd, one-shot nonce, plus a failed-attempt rate limiter. The secret, however, is handed out in plaintext: bento_devmode_emit_provision() sends {"evt":"bento.devmode.provision","secret":"<64 hex>"} to the first peer that connects (ble_nus_lazy.c:99-109) over the unencrypted TX characteristic.
The secret also comes from an xorshift32 seeded with the FreeRTOS tick, a stack address and one constant (bento_devmode.c:54-66) — under 32 bits of entropy, not the OPTIGA RNG. Together that means the gate keeps out a peer that connects second and no one else. Treat it as a deterrent, not an enforcement boundary.
Agent state is a single in-flight ask owned by one NUS RX task:
Connect from Bento Desktop Buddy. Advertising is open and the device accepts any central, so the connection needs no pairing first. If the desktop does initiate pairing it is Just Works (BTM_IO_CAPABILITIES_NONE + BTM_LE_AUTH_REQ_SC_BOND, no MITM bit): no passkey appears on the screen and there is no MITM protection. The DisplayOnly 6-digit passkey screen is not yet implemented.
What you should observe. The topbar BLE glyph changes within 500 ms of the connection — that is the poll cadence above.
Pairing is optional, not required. The shipped GATT database sets no GATTDB_PERM_AUTH_* bit on RX, TX or the CCCD (checked in libbento_secure.a member bento_core_11.o, 2026-08-29 — see GATT data). An unbonded peer writes RX and enables notifications normally and does not get GATT_INSUF_AUTHENTICATION. Treat the link as unauthenticated.
Let the desktop send bento.time.sync.
What you should observe on the desktop: a JSON line {"ack":"bento.time.sync","ok":true,"n":0,"synced":true,"boot_epoch_ms":…,"uptime_ms_at_sync":…} — the exact frame the snippet builds.
Trigger a mode change (bento_buddy.stop() on mtb-mpy, or the CM55 page button).
What you should observe on the desktop: {"event":"bento.radio.state","mode":"…","ssid":"…","ipv4":"…","ble_paired":…,"wifi_fail_count":…} with mode drawn from the vocabulary above.
Send bento.sensor.stream.start with an unknown id.
What you should observe. An ack with "ok":false,"error":"unknown_id"; with a valid id and interval_ms outside [10, 5000], the stream runs at the clamped interval.
These are exported from libbento_secure.a with no caller in either tree; their examples are authored for the documentation (no shipped call site) and labelled as such: NUS_UUID_CHAR_TX, NUS_UUID_CHAR_RX (present only as raw bytes inside nus_gatt_database[], nus_gatt_db.c:83), voice_capture_is_running, radio_scheduler_set_boot_mode, radio_scheduler_get_boot_mode, nus_protocol_tick, nus_protocol_send_permission, nus_fp_is_active, nus_events_pending_ack_count, nus_agent_note_ask (body at nus_agent.c:122-133 — a definition, not a call), nus_agent_buffer_len, fw_hash_prefix8, bento_buddy_auto_start_install (superseded; main.c:344-347). A page that shows any of them as part of the shipping protocol path is wrong.
The five overridable.txt symbols are the opposite case — WEAK hooks you implement, defined in wifi_init.c: :193 app_wifi_connect_direct, :248 app_wifi_disconnect, :256 app_wifi_get_ipv4, :261 lfs_save_wifi_creds, :304 lfs_load_wifi_creds:
| mtb-mpy | mtb-only | |
|---|---|---|
| Protocol handlers | identical (archived) | identical |
| nus_emit_event caller shown | mod_dualband.c (shipped in this package) | not shipped; the contract is the same |
| Topbar state push | sensor_auto_task.c:1440-1459 | same file ships in both packages |