SDK สำหรับ TESAIoT Dev Kit
คู่มืออ้างอิง API และ Tutorial (MTB & µPython)
Loading...
Searching...
No Matches
I2 — ส่วนที่เรียกใช้ได้ของโปรโตคอล NUS
variant ที่ใช้ได้
mtb-mpy และ mtb-only
Warning
สิ่งที่ต้องอ่านก่อนทั้งกลุ่ม — อ่านก่อนสิ่งอื่นใด โมดูล Bento Buddy / BLE ทั้งหมดคอมไพล์ออกจาก build ปริยาย: ENABLE_PAGE_BENTO_BUDDY ?= 0 (proj_cm33_ns/Makefile:64, :305) และบล็อก BLE ทั้งบล็อกรวมถึงข้อความ [boot] อยู่ภายใน #if ENABLE_PAGE_BENTO_BUDDY (main.c:36, :325) ขั้นแรกคือ make getlibs ใน proj_cm33_ns (ดึง btstack และเฟิร์มแวร์ BT) แล้วจึงสร้างใหม่ด้วย ENABLE_PAGE_BENTO_BUDDY=1 หากไลบรารีขาดหาย build จะหยุดที่ $(error) ของ Makefile ที่ :353-359 — ตัว error นั้นเองคือสิ่งที่สังเกตได้ ซึ่งหัวข้อนี้ใช้แสดงสถานะที่ยังไม่มีสิ่งที่ต้องมีก่อน: ENABLE_PAGE_BENTO_BUDDY=1 requires btstack-integration. Run 'make getlibs' to fetch the AIROC BLE host stack. ไม่มีสิ่งใดในบทนี้หรือใน I1 — การ bring-up BLE และกฎวิทยุเดียว ที่สังเกตได้บน build ปริยาย

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

ส่ง payload ที่ประกอบเป็นเฟรมแล้ว ปล่อย event ตอบรับคำสั่ง ข้ามไปยัง CM55 ผ่านสะพานหน่วยความจำร่วม และรู้ว่าส่วนใดของ API ที่ส่งออกมานั้นใช้งานได้จริงและส่วนใดตายแล้ว symbol ที่ส่งออก 77 จาก 87 ตัวไม่มีผู้เรียกใน template/ และมีไฟล์ .c ของ ble_nus เพียง 3 ไฟล์ที่ส่งมอบเป็นซอร์ส (bento_time.c, character_lottie.c, character_lottie_default.c) ที่เหลือทั้งหมดอยู่ใน libbento_secure.a ผู้เรียกที่อยู่ใน archive จะอธิบายเป็นข้อความ เพราะซอร์สนั้นไม่ได้ส่งมอบมา

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

การประกอบเฟรมเพื่อส่ง ตัวอย่างที่มีอยู่จริงในของที่ส่งมอบคือ ack ของการซิงก์เวลา: ผู้เรียกประกอบ JSON โดยปิดท้ายด้วย \n ตรวจขอบเขตของ snprintf ส่งความยาวไปอย่างชัดแจ้ง และแคสต์ผลลัพธ์เป็น (void):

/* ...context: inside the bento.time.sync ack emitter ... */
int w = snprintf(tx, sizeof(tx),
"{\"ack\":\"bento.time.sync\",\"ok\":true,\"n\":0,"
"\"synced\":true,\"boot_epoch_ms\":%s,\"uptime_ms_at_sync\":%s}\n",
boot_buf, up_buf);
if (w > 0 && w < (int)sizeof(tx)) {
(void)ble_nus_send((const uint8_t *)tx, (size_t)w);
}

ble_nus_send คืนค่า −1 เว้นแต่ init สำเร็จ สถานะเป็น CONNECTED และอุปกรณ์คู่เปิดการแจ้งเตือน TX แล้ว (ประกาศใน ble_nus.h) การแบ่งชิ้นข้อมูลจัดการอยู่ภายใน (MTU−3 จำกัดสูงสุดที่ 180 B) ไลบรารีจองหน่วยความจำแล้วคัดลอกข้อมูลเอง บัฟเฟอร์บน stack จึงใช้ได้ stub ชนิด weak ภายใน archive ทำให้อ้างถึง symbol นี้ได้อย่างปลอดภัยก่อน init จำนวนผู้เรียกจริงของมันคือราว 80 ขึ้นไปผ่านมาโครส่งข้อมูล

การวนถามสถานะ เฟิร์มแวร์เลือกวนถามโดยเจตนา แทนที่จะไว้ใจ callback on_state — พบว่าเส้นทางกลับมาทำงานเร็วของคู่อุปกรณ์ที่ผูกไว้แล้วพลาด GATT_CONNECTION_STATUS_EVT (2026-05-10):

/* ...context: inside the sensor auto-push loop ... */
#if defined(BENTO_HAS_BLE_NUS) && (BENTO_HAS_BLE_NUS == 1)
/* BLE NUS host-link state poll (every 5th cycle = ~500ms).
* Why poll instead of using on_state callback: the callback
* registration window depends on the AIROC stack delivering
* GATT_CONNECTION_STATUS_EVT, which we observed was missing on
* the bonded-pair fast-resume path on 2026-05-10 — the desktop
* was actively serving fw.query verbs over the GATT link but
* ble_nus_get_state() still read ADVERTISING. Polling closes
* the loop deterministically: whatever the stack actually
* thinks the state is, the LCD topbar will reflect it within
* 500 ms of any change. */
if ((now_ms - last_ble_ms) >= 500u) {
last_ble_ms = now_ms;
static int8_t last_pushed_ble = -1; /* −1 = uninitialized */
int8_t now_connected = (ble_nus_get_state() == BLE_NUS_STATE_CONNECTED) ? 1 : 0;
if (now_connected != last_pushed_ble) {
sensor_auto_push_ble_state(now_connected != 0);
last_pushed_ble = now_connected;
}
}

ble_nus_get_state() เป็นการอ่านธรรมดาแบบไม่ใช้ lock ปลอดภัยจากทุก task และปลอดภัยก่อน init (stub ชนิด weak คืน BLE_NUS_STATE_OFF)

การปล่อย event nus_emit_event(json) รับ JSON ที่ปิดท้ายด้วย NUL และ ไม่มีการขึ้นบรรทัดใหม่ต่อท้าย (ตัวมันเติมให้เอง) ต้องมี strlen(json) + 2 <= 256 มิฉะนั้นคืน −1 แล้ว ทิ้ง ข้อมูลนั้นไม่มีการตัดสั้น ค่า −1 ยังครอบคลุมกรณี NULL และลิงก์ล่มด้วย ผู้เรียกที่มีอยู่จริงในของที่ส่งมอบ (mod_dualband.c:196-213 ซอร์สฝั่ง mtb-mpy ประกาศผ่าน extern ในไฟล์ที่ :44-46) จัดรูป {"evt":"bento.net.down","n":lu,"reason":"s"} ตรวจขอบเขตของ snprintf แล้วโยน OSError("nus_emit_event failed (d)") เมื่อค่าที่ได้ไม่เป็นศูนย์

การตอบรับคำสั่ง — ผู้เรียกที่เก็บอยู่ใน archive หนีบช่วงเวลาของ sensor_stream_start ไว้ที่ [10, 5000]

ตัวคำสั่งถูกตัดตายตัวที่ 32 อักขระ ack_cmd_len == 0 หมายถึงให้ใช้ strlen ค่า ok เป็นค่าที่คำนวณขึ้น (ถ้าเป็น NULL ทั้งคู่ ⇒ true ส่วน error ที่ไม่ใช่ NULL จะพลิกเป็น false และต่อท้ายด้วย "error") เป็น void — ความล้มเหลวในการส่งจึงมองไม่เห็น และทำงานบน task ที่กระจายงาน NUS RX ไม่ใช่ context ใดก็ได้

การข้ามไปยัง CM55 ipc_bento_buddy_send เขียนข้อความ .cy_shared_socmem แบบ static เพียงชุดเดียว — ไม่ reentrant ค่า data_len ถูกหนีบเป็น IPC_DATA_MAX_LEN อย่างเงียบ ๆ มีการลองใหม่แบบมีขอบเขต 50 × 1 ms จึง เรียกได้จาก task เท่านั้น ห้ามจาก ISR ให้ส่ง len+1 เพื่อให้ฝั่งรับได้ NUL ไปด้วย และได้จำนวนที่ไม่รวม NUL ใน value

สถานะวิทยุบนสาย — radio_mode_str คืนค่าเป็นสตริงคงที่แบบ static (คืน "invalid" เมื่ออยู่นอกช่วง s จึงปลอดภัยเสมอ) คำศัพท์ที่ใช้คือ unknown | ble_adv | ble_paired | switching_to_wifi | wifi_active | switching_to_ble | wifi_failed

Devmode ค่า secret อยู่ใน RAM เท่านั้นและสร้างใหม่ทุกครั้งที่บูต เครื่องเดสก์ท็อปจึงต้องจัดเตรียม (provisioning) ใหม่หลังการรีบูตทุกครั้ง bento_devmode_init เป็น idempotent และจุดเข้าใช้งานสาธารณะทุกจุดเรียกมันเอง

ขอบเขตที่ challenge/response นี้ปกป้องได้จริง ขั้นตอน HMAC-SHA256 ถูกนำไปใช้อย่างถูกต้อง (nonce มี TTL ใช้ได้ครั้งเดียว และมีตัวจำกัดอัตราการลองผิด) แต่ตัว secret เองถูกส่งออกไปในรูป plaintext: bento_devmode_emit_provision() ส่งเฟรม {"evt":"bento.devmode.provision","secret":"<64 hex>"} ให้ peer ตัวแรกที่เชื่อมต่อสำเร็จ (เรียกเมื่อสถานะเปลี่ยนเป็น CONNECTED ดู bento_devmode.h) ผ่าน TX ที่ยังไม่ได้เข้ารหัส

นอกจากนี้ secret มาจาก xorshift32 ที่ seed ด้วย tick ของ FreeRTOS, แอดเดรสบน stack และค่าคงที่หนึ่งค่า จึงเป็นเอนโทรปีระดับต่ำกว่า 32 บิต ไม่ใช่ค่าจาก RNG ของ OPTIGA ผลรวมคือ gate นี้กัน peer ที่มาทีหลังได้ แต่กัน peer ที่เชื่อมต่อเป็นรายแรกไม่ได้ ให้ถือว่าเป็นเครื่องกีดขวาง ไม่ใช่ขอบเขตการบังคับใช้สิทธิ์

สถานะของ agent คือคำถามที่กำลังดำเนินอยู่เพียงรายการเดียว ซึ่ง NUS RX task เดียวเป็นเจ้าของ

ทีละขั้น

ขั้นที่ 1 — จับคู่แล้วเฝ้าดูแถบบน

เชื่อมต่อจาก Bento Desktop Buddy การโฆษณา (advertising) เปิดรับ central ทุกตัว จึงเชื่อมต่อได้โดยไม่ต้องจับคู่ก่อน หากเครื่องเดสก์ท็อปขอจับคู่ จะเป็นแบบ Just Works (BTM_IO_CAPABILITIES_NONE + BTM_LE_AUTH_REQ_SC_BOND ไม่มีบิต MITM) ไม่มี passkey แสดงบนหน้าจอ และไม่มีการป้องกัน MITM ส่วนหน้าจอ passkey 6 หลักแบบ DisplayOnly นั้นยังไม่ได้พัฒนา

สิ่งที่ควรสังเกต glyph BLE บนแถบบนเปลี่ยนภายใน 500 ms หลังการเชื่อมต่อ — นั่นคือจังหวะการวนถามที่กล่าวไว้ข้างต้น

การจับคู่เป็นทางเลือก ไม่ใช่ข้อบังคับ ฐานข้อมูล GATT ที่ส่งมอบจริงไม่ได้ตั้งบิต GATTDB_PERM_AUTH_* บน RX, TX หรือ CCCD เลย (ตรวจใน libbento_secure.a สมาชิก bento_core_11.o เมื่อ 2026-08-29 ดู ข้อมูล GATT) อุปกรณ์คู่ที่ยังไม่ได้ผูกจึงเขียน RX และเปิด notification ได้ตามปกติ และจะไม่ได้รับ GATT_INSUF_AUTHENTICATION ให้ปฏิบัติต่อ link นี้เสมือนยังไม่ผ่านการพิสูจน์ตัวตน

ขั้นที่ 2 — ack ของการซิงก์เวลา

ให้เครื่องเดสก์ท็อปส่ง bento.time.sync

สิ่งที่ควรสังเกต บนเครื่องเดสก์ท็อป: บรรทัด JSON {"ack":"bento.time.sync","ok":true,"n":0,"synced":true,"boot_epoch_ms":…,"uptime_ms_at_sync":…} — เป็นเฟรมที่ snippet สร้างขึ้นพอดี

ขั้นที่ 3 — event สถานะวิทยุ

กระตุ้นให้เปลี่ยนโหมด (bento_buddy.stop() บน mtb-mpy หรือปุ่มบนหน้า CM55)

สิ่งที่ควรสังเกต บนเครื่องเดสก์ท็อป: {"event":"bento.radio.state","mode":"…","ssid":"…","ipv4":"…","ble_paired":…,"wifi_fail_count":…} โดย mode มาจากคำศัพท์ที่ระบุไว้ข้างต้น

ขั้นที่ 4 — สตรีมและ ack

ส่ง bento.sensor.stream.start พร้อม id ที่ไม่รู้จัก

สิ่งที่ควรสังเกต ack ที่มี "ok":false,"error":"unknown_id" ส่วนกรณีที่ id ถูกต้องและ interval_ms อยู่นอกช่วง [10, 5000] สตรีมจะทำงานที่ช่วงเวลาที่หนีบไว้แล้ว

ส่วนที่ส่งออกมาแล้วตาย — 13 รายการ ตัวอย่างเขียนขึ้นเอง

รายการต่อไปนี้ส่งออกจาก libbento_secure.a โดย ไม่มีผู้เรียกในซอร์สทั้ง 2 ชุด ตัวอย่างของแต่ละรายการเขียนขึ้นเองเพื่อเอกสารนี้ (ไม่มี call site ในของที่ส่งมอบ) และกำกับไว้เช่นนั้น ได้แก่ NUS_UUID_CHAR_TX, NUS_UUID_CHAR_RX (ปรากฏเป็นไบต์ดิบภายใน nus_gatt_database[] เท่านั้น), 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 (ใน archive มีเพียงนิยามของมัน ไม่มีการเรียก), nus_agent_buffer_len, fw_hash_prefix8, bento_buddy_auto_start_install (ถูกแทนที่ไปแล้ว main.c:344-347) หน้าใดที่แสดงรายการเหล่านี้เป็นส่วนหนึ่งของเส้นทางโปรโตคอลที่ส่งมอบจริงคือหน้าที่ผิด

symbol 5 ตัวใน overridable.txt เป็นกรณีตรงข้าม — เป็น hook ชนิด WEAK ที่ผู้ใช้ไลบรารี ต้องนิยามเอง และนิยามไว้ใน 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:

int lfs_save_wifi_creds(const char *ssid, const char *password, const char *security)
{
if (!lfs_wifi_creds_ready()) return -1;
if (count < 0) count = 0;
/* Upsert: find existing slot for this SSID, else append (LRU eviction
* when full). */
int idx = -1;
for (int i = 0; i < count; i++) {
if (strncmp(saved[i].ssid, ssid, sizeof(saved[i].ssid)) == 0) {
idx = i;
break;
}
}
if (idx < 0) {
idx = (count < QSPI_WIFI_CREDS_MAX) ? count : 0;
if (count < QSPI_WIFI_CREDS_MAX) count++;
}
memset(&saved[idx], 0, sizeof(qspi_wifi_entry_t));
strncpy(saved[idx].ssid, ssid, sizeof(saved[idx].ssid) - 1);
if (password != NULL) {
strncpy(saved[idx].password, password, sizeof(saved[idx].password) - 1);
}
/* security field is uint8_t — store the low byte of the cy_wcm enum so
* we stay binary-compatible with modwifi.c's saved entries (which also
* writes the truncated enum). The low byte distinguishes the common
* cases: 0=OPEN, 4=WPA2_AES (also matches WPA3_SAE's low byte — WPA3
* is treated as WPA2 on the load path for now; explicit WPA3 storage
* requires bumping the struct format). */
bool is_open = (security != NULL && strcmp(security, "OPEN") == 0)
|| (password == NULL || password[0] == '\0');
saved[idx].security = is_open
? (uint8_t)(CY_WCM_SECURITY_OPEN & 0xFFu)
: (uint8_t)(CY_WCM_SECURITY_WPA2_AES_PSK & 0xFFu);
saved[idx].flags = 0x01; /* auto_connect */
return lfs_wifi_creds_write(saved, count) ? 0 : -2;
}

กับดัก

Warning
ผู้เรียก ipc_bento_buddy_send 2 รายพร้อมกันทำให้ข้อมูลของกันและกันเสียหาย — เพราะมีข้อความร่วมเพียงชุดเดียว
nus_emit_event ทิ้ง payload ที่ใหญ่เกินไปอย่างเงียบ ๆ โดยไม่ส่งออกสาย ให้ตรวจค่าที่มันคืนมา
lfs_save_wifi_creds ไม่จับ wifi_creds_lock — มันทำ read-modify-write ลงไฟล์ LFS โดยตรง และชิงกับ lfs_wifi_creds_write ที่ทำงานพร้อมกันได้ (ภาคผนวก X #18)
การเรียก ble_nus_init จาก task อื่นที่ไม่ใช่ task ที่จ่ายไฟให้ชิป ล้มเหลวด้วย state=ERROR

กล่อง variant

mtb-mpy mtb-only
handler ของโปรโตคอล เหมือนกัน (อยู่ใน archive) เหมือนกัน
ผู้เรียก nus_emit_event ที่แสดงไว้ mod_dualband.c (ส่งมอบในแพ็กเกจนี้) ไม่ได้ส่งมอบ แต่ข้อกำหนดการเรียกใช้เหมือนกัน
การดันสถานะขึ้นแถบบน sensor_auto_task.c:1440-1459 ไฟล์เดียวกันนี้ส่งมอบในทั้ง 2 แพ็กเกจ