เป้าหมายของหัวข้อนี้
เมื่อจบบทนี้ ผู้อ่านจะระบุได้ว่าข้อมูลรับรอง WiFi แต่ละชุดบนบอร์ดอยู่ในที่เก็บใด คอร์ใดเป็นผู้เขียน และจะอยู่รอดข้ามการรีบูตหรือไม่
ที่เก็บข้อมูลรับรองมี 2 แห่ง และการสับสนระหว่าง 2 แห่งนี้คือความผิดพลาดที่มีโอกาสเกิดมากที่สุดใน Tutorial ทั้งชุดของ SDK นี้:
| ที่เก็บบูต LFS | ที่เก็บฝั่ง UI ของ OPTIGA |
| ตระกูล API | lfs_wifi_creds_* | wifi_saved_* |
| อยู่ที่ใด | ไฟล์ LittleFS /.wifi_creds บนหน่วยความจำแฟลช QSPI | NVM ของ OPTIGA Trust M, data object แบบ Type 3, slot 5, 6, 8, 9, 10, 11 (wifi_saved.h:5-10) |
| คอร์ | CM33_NS | CM55 (เข้าถึงผ่าน IPC CRED_READ/WRITE/ERASE) |
| ใครอ่านตอนบูต | wifi_boot_auto_connect() — บทนี้ | ไม่มีใครอ่าน ที่เก็บนี้ไม่ได้อยู่บนเส้นทางการบูต |
| ใครแสดงบนหน้าจอ | ไม่มี | รายการเครือข่ายที่บันทึกไว้ของหน้า WiFi Connect — บท C2 |
| รูปแบบข้อมูล | magic "WIFI", version 1, สูงสุด 6 × รายการละ 100 ไบต์, CRC32 (lfs_wifi_creds_c.c:10-18) | หนึ่งรายการขนาด 106 ไบต์ต่อหนึ่ง OPTIGA object, การขับออกแบบ LRU |
เครือข่ายที่เพิ่มจากจอ LCD จะลงในที่เก็บ OPTIGA และ ลงในที่เก็บ LFS ด้วย (ผ่าน worker บน CM33) ส่วนเครือข่ายที่เพิ่มจาก Python ด้วย wifi.connect() จะลงในที่เก็บ LFS เท่านั้น บอร์ดเชื่อมต่ออัตโนมัติจากที่เก็บ LFS ให้จำประโยคนี้ไว้ตลอดทุกขั้นด้านล่าง
ลำดับการทำงานจริงของเฟิร์มแวร์
เจ้าของเส้นทางนี้คือ WiFiIPC worker task (sensor_auto_task.c, wifi_ipc_worker_task) ไม่ใช่ task ที่ตั้งขึ้นเฉพาะสำหรับ WiFi สิ่งแรกที่ task นี้ทำก่อนเข้าสู่ลูปคิวของตนคือ wifi_boot_auto_connect() โดยเรียกแบบ inline เพราะการแยกเป็นอีก task หนึ่งจะกินแรม 4 KB (คอมเมนต์ที่ sensor_auto_task.c:1101-1104)
การเชื่อมต่ออัตโนมัติตอนบูต
static void wifi_boot_auto_connect(void)
{
#if BENTO_HAS_MPY
printf("[WiFi-Boot] Waiting for credentials from MicroPython...\r\n");
for (int wait = 0; wait < 100; wait++) {
if (g_boot_wifi_creds_count != 0) break;
vTaskDelay(pdMS_TO_TICKS(100));
}
#else
{
if (n > 0) g_boot_wifi_creds_count = n;
}
#endif
int count = (int)g_boot_wifi_creds_count;
if (count <= 0) {
printf("[WiFi-Boot] No saved credentials — skipping\r\n");
return;
}
printf("[WiFi-Boot] %d saved network(s)\r\n", count);
if (!wifi_ensure_ready()) {
printf("[WiFi-Boot] SDIO/WCM init failed (0x%08lX)\r\n",
(unsigned long)s_wifi_last_error);
return;
}
for (int i = 0; i < count; i++) {
char ssid_local[33];
char pass_local[65];
bool slot_empty;
wifi_creds_lock();
slot_empty = (g_boot_wifi_creds[i].ssid[0] == '\0');
if (!slot_empty) {
strncpy(ssid_local, g_boot_wifi_creds[i].ssid,
sizeof(ssid_local) - 1);
ssid_local[sizeof(ssid_local) - 1] = '\0';
strncpy(pass_local, g_boot_wifi_creds[i].password,
sizeof(pass_local) - 1);
pass_local[sizeof(pass_local) - 1] = '\0';
}
wifi_creds_unlock();
if (slot_empty) continue;
static wifi_scan_ctx_t boot_scan;
memset(&boot_scan, 0, sizeof(boot_scan));
boot_scan.done = xSemaphoreCreateBinary();
bool found = false;
if (boot_scan.done != NULL) {
memset(&filter, 0, sizeof(filter));
filter.
mode = CY_WCM_SCAN_FILTER_TYPE_SSID;
strncpy((
char *)filter.
param.SSID, ssid_local,
sizeof(filter.
param.SSID) - 1);
cy_rslt_t sr = cy_wcm_start_scan(wifi_scan_callback, &boot_scan, &filter);
if (sr == CY_RSLT_SUCCESS) {
if (xSemaphoreTake(boot_scan.done, pdMS_TO_TICKS(8000)) == pdTRUE) {
found = (boot_scan.count > 0);
} else {
cy_wcm_stop_scan();
found = (boot_scan.count > 0);
}
}
vSemaphoreDelete(boot_scan.done);
boot_scan.done = NULL;
}
if (!found) {
printf("[WiFi-Boot] '%s' not found in scan — skipped\r\n",
ssid_local);
continue;
}
printf("[WiFi-Boot] '%s' found (RSSI=%d) — connecting...\r\n",
ssid_local,
(boot_scan.count > 0) ? boot_scan.entries[0].rssi : 0);
cy_wcm_connect_params_t params;
cy_wcm_ip_address_t ip;
memset(¶ms, 0, sizeof(params));
memset(&ip, 0, sizeof(ip));
strncpy((char *)params.ap_credentials.SSID, ssid_local,
sizeof(params.ap_credentials.SSID) - 1);
strncpy((char *)params.ap_credentials.password, pass_local,
sizeof(params.ap_credentials.password) - 1);
params.ap_credentials.security = (pass_local[0] == '\0')
: CY_WCM_SECURITY_WPA3_WPA2_PSK;
cy_rslt_t r = wifi_connect_robust(¶ms, &ip, "WiFi-Boot");
if (CY_RSLT_SUCCESS == r) {
#ifdef BOOT_VERBOSE
uint32_t v4 = ip.ip.v4;
printf("[WiFi-Boot] Connected to '%s'! IP=%lu.%lu.%lu.%lu\r\n",
ssid_local,
(unsigned long)(v4 & 0xFF), (unsigned long)((v4 >> 8) & 0xFF),
(unsigned long)((v4 >> 16) & 0xFF), (unsigned long)((v4 >> 24) & 0xFF));
#endif
s_wifi_state.mode = AUTO_WIFI_MODE_STA;
s_wifi_state.connected = true;
strncpy(s_wifi_state.ssid, ssid_local,
sizeof(s_wifi_state.ssid) - 1);
wifi_update_ip_from_wcm();
push_wifi_state_to_cm55(true);
if (ntp_sync_rtc()) {
s_ntp_synced = true;
push_time_to_cm55();
tesaiot_bridge_ntp_synced();
#ifdef BOOT_VERBOSE
printf("[WiFi-Boot] NTP synced + pushed to CM55\r\n");
#endif
}
return;
ให้อ่านทางแยกที่ต้นฟังก์ชันนั้นอย่างละเอียด lfs_wifi_creds_read() เป็นตัวอ่านใน ทั้งสอง variant สิ่งที่ต่างกันคือ ใครเป็นผู้เรียก และ implementation ใดเป็นผู้ตอบ:
mtb-mpy — MicroPython task เป็นผู้เรียกจาก mpy_main.c หลังเมานต์ VFS แล้ว (ที่เก็บนี้เรียก open() ของ Python อยู่ภายใน จึงต้องใช้ task ของ VM และต้องมี GC heap ว่างราว 16 KB) จากนั้นเติมค่าลง g_boot_wifi_creds ส่วน WiFi worker จะรอนานสุด 10 s (100 × 100 ms) ให้ g_boot_wifi_creds_count มีค่าไม่เป็นศูนย์
การอ่านตอนบูตอยู่ที่ mpy_main.c:633-661 marker [mpy_lfs_wifi_creds_boot_read] (มีเฉพาะใน zip ของ mtb-mpy — ไฟล์นี้ไม่อยู่ในแพ็กเกจ mtb-only) หลังจาก gc_collect() และ lfs_wifi_creds_init() แกนของมันคือ:
wifi_creds_lock();
g_boot_wifi_creds_count = n;
int lfs_wifi_creds_read(qspi_wifi_entry_t *entries, int max_entries)
เติมอาร์เรย์ของผู้เรียก; คืนค่าจำนวนรายการ และคืน 0 เมื่อการตรวจความถูกต้องล้มเหลวไม่ว่ากรณีใด
#define QSPI_WIFI_CREDS_MAX
Definition wifi_creds_types.h:23
- mtb-only — ไม่มี VM ดังนั้น WiFi worker จึงเรียก lfs_wifi_creds_read() เอง และ implementation คือที่เก็บฝั่ง C storage_c/lfs_wifi_creds_c.c (:69) ซึ่งวางอยู่บน bento_storage (บท G1) header เดียวกัน ชื่อเดียวกัน และไบต์บนแฟลชชุดเดียวกัน
if (entries == NULL || max_entries <= 0) return 0;
if (!bento_storage_ready()) return 0;
int n = bento_storage_read_file(CREDS_FILE, s_buf, sizeof(s_buf));
if (n < CREDS_TOTAL_SIZE) return 0;
uint32_t magic; memcpy(&magic, &s_buf[0], 4);
uint16_t version; memcpy(&version, &s_buf[4], 2);
uint16_t count; memcpy(&count, &s_buf[6], 2);
uint32_t stored; memcpy(&stored, &s_buf[CREDS_HEADER_SIZE + CREDS_ENTRIES_SIZE], 4);
if (stored != calc_crc32(s_buf, CREDS_HEADER_SIZE + CREDS_ENTRIES_SIZE)) {
if (stored != calc_xor32(s_buf, CREDS_HEADER_SIZE + CREDS_ENTRIES_SIZE)) return 0;
}
int out = (count < (uint16_t)max_entries) ? count : max_entries;
for (int i = 0; i < out; i++) {
entries[i].ssid[32] = '\0';
entries[i].password[64] = '\0';
}
return out;
}
if (count > 0 && entries == NULL) return false;
if (!bento_storage_ready()) return false;
memset(s_buf, 0, sizeof(s_buf));
uint16_t cnt = (uint16_t)count;
memcpy(&s_buf[0], &magic, 4);
memcpy(&s_buf[4], &version, 2);
memcpy(&s_buf[6], &cnt, 2);
if (count > 0) {
memcpy(&s_buf[CREDS_HEADER_SIZE], entries,
}
uint32_t crc = calc_crc32(s_buf, CREDS_HEADER_SIZE + CREDS_ENTRIES_SIZE);
memcpy(&s_buf[CREDS_HEADER_SIZE + CREDS_ENTRIES_SIZE], &crc, 4);
return bento_storage_write_file(CREDS_FILE, s_buf, sizeof(s_buf));
}
จากนั้นทำทีละ SSID ที่บันทึกไว้: คัดลอกรายการนั้นเป็น snapshot ภายใต้ wifi_creds_lock() (ผู้เขียนอีกรายบน BLE worker หรือใน IPC handler ต้องไม่ทำให้รหัสผ่านขาดกลางระหว่างคัดลอก — sensor_auto_task.c:989-994) แล้วสแกนแบบ เจาะจง เฉพาะ SSID นั้นด้วย timeout 8 s — ช่อง 5 GHz โดยเฉพาะ hotspot ของ iPhone ตอบ probe ที่ระบุปลายทางช้า (:981-986) — ข้าม SSID นั้นไปหากไม่พบในผลสแกน แล้วจึงค่อยพยายามเข้าร่วมเครือข่าย
ตัวการเข้าร่วมเครือข่าย
static bool wifi_ensure_ready(void)
{
if (app_wifi_is_ready()) {
return true;
}
cy_rslt_t r = app_wifi_init();
if (r != CY_RSLT_SUCCESS) {
s_wifi_last_error = r;
return false;
}
s_wifi_last_error = CY_RSLT_SUCCESS;
return true;
}
#define WIFI_CONNECT_MAX_ATTEMPTS (6)
#define WIFI_CONNECT_SETTLE_MS (1500)
static cy_rslt_t wifi_connect_robust(cy_wcm_connect_params_t *params,
cy_wcm_ip_address_t *ip, const char *tag)
{
cy_rslt_t r = CY_RSLT_TYPE_ERROR;
for (int attempt = 1; attempt <= WIFI_CONNECT_MAX_ATTEMPTS; attempt++) {
r = cy_wcm_connect_ap(params, ip);
if (CY_RSLT_SUCCESS == r || cy_wcm_is_connected_to_ap()) {
return CY_RSLT_SUCCESS;
}
printf("[%s] attempt %d/%d failed (0x%08lX)\r\n",
tag, attempt, WIFI_CONNECT_MAX_ATTEMPTS, (unsigned long)r);
if (attempt < WIFI_CONNECT_MAX_ATTEMPTS) {
cy_wcm_disconnect_ap();
vTaskDelay(pdMS_TO_TICKS(WIFI_CONNECT_SETTLE_MS));
}
}
return r;
}
พยายาม 6 ครั้ง ห่างกันครั้งละ 1500 ms และคั่นด้วย cy_wcm_disconnect_ap() ทุกครั้ง คอมเมนต์เหนือฟังก์ชันบันทึกเหตุผลไว้: ในการบูตเย็น (cold boot) พูล RX buffer ของ WHD ยังมีไม่พอ WLC_E_SET_SSID จึงถูกทิ้ง และการเข้าร่วมครั้งแรกคืนค่า 0x020003FF ห้าม "แก้" ลูปนี้ให้เหลือครั้งเดียว
การบันทึกข้อมูลรับรอง — จุดที่สอง variant แยกทางกัน
เส้นทางการบันทึกสองเส้นเขียนไฟล์ /.wifi_creds ไฟล์เดียวกัน แต่จังหวะเวลาตรงข้ามกัน
mtb-only: เขียนทันที จาก WiFi worker
#if !BENTO_HAS_MPY
{
int n;
wifi_creds_lock();
n = (int)g_boot_wifi_creds_count;
wifi_creds_unlock();
wifi_creds_lock();
g_boot_wifi_creds_dirty = false;
wifi_creds_unlock();
printf("[WiFiIPC] Credentials persisted (%d entries)\r\n", n);
} else {
printf("[WiFiIPC] ERROR: credential save failed — kept dirty\r\n");
}
}
#endif
คัดลอกเป็น snapshot ภายใต้ lock แล้วเขียน นอก lock — การเขียนแฟลชใช้เวลาระดับมิลลิวินาที และ BLE worker ใช้ตัวแปร global ชุดเดียวกันนี้ร่วมด้วย
mtb-mpy: เลื่อนไปให้ flusher ซึ่งทำงานเฉพาะตอน REPL ว่าง
บน mtb-mpy worker เขียนเองไม่ได้ เพราะ lfs_wifi_creds_write() ต้องอยู่ใน task context ของ MicroPython (sensor_auto_task.c:799) worker จึงเพียงปรับค่า g_boot_wifi_creds และตั้ง g_boot_wifi_creds_dirty ส่วนการเขียนจริงเกิดใน wifi_creds_flush_if_dirty() ที่ mpy_main.c:169-192 marker [mpy_lfs_wifi_creds_flush_if_dirty] (มีเฉพาะใน zip ของ mtb-mpy — ไฟล์นี้ไม่อยู่ในแพ็กเกจ mtb-only) ส่วนท้ายของฟังก์ชัน ได้แก่ การเขียน นโยบายของแฟล็ก และการปลด lock:
g_boot_wifi_creds_dirty = false;
} else {
}
wifi_creds_unlock();
bool lfs_wifi_creds_write(const qspi_wifi_entry_t *entries, int count)
เขียนทับ /.wifi_creds ด้วย CRC32; ทำ snapshot ภายใต้ lock แล้วเขียนนอก lock
wifi_creds_flush_if_dirty() ถูกเรียกจาก 2 จุดเท่านั้นใน mpy_main.c คือต้นลูป REPL และขั้นตอนรื้อระบบก่อน soft reboot (:680, :699 ตามไฟล์ที่ส่งมอบจริง) ข้อสังเกต: ที่นี่การเขียนทั้งหมดอยู่ ภายใน lock ซึ่งเป็นนโยบายตรงข้ามกับเส้นทาง mtb-only ข้างต้น ทั้ง 2 แบบถูกต้องตามบริบทของตน
- Warning
- อันตราย (hazard) เรื่องข้อมูลรับรองสูญหายเมื่อ /main.py วนไม่รู้จบ (เฉพาะ mtb-mpy) หาก /main.py ทำงานไม่สิ้นสุด ไม่ว่าจะเป็นลูปอ่านเซนเซอร์ เกม หรือ while True: VM จะไม่กลับไปที่พรอมป์ REPL เลย flusher จึงไม่ได้ทำงาน และข้อมูลรับรองที่บันทึกจากหน้า WiFi บนจอ LCD จะไม่ถูกเขียนลงจริง บอร์ดเชื่อมต่อได้ในตอนนั้น แล้วลืมเครือข่ายนั้นในการตัดไฟแล้วจ่ายไฟใหม่ครั้งถัดไป mtb-only ไม่มีอันตรายข้อนี้เพราะเขียนทันที ถ้า /main.py ของผลิตภัณฑ์วนไม่รู้จบ ให้เลือกอย่างใดอย่างหนึ่ง: กลับไปที่ REPL เป็นระยะ หรือบันทึกข้อมูลรับรองจาก Python ด้วย wifi.connect() ซึ่งเขียนไฟล์โดยตรง (ดูกับดักท้ายบท) (ภาคผนวก X ข้อ 11)
ทีละขั้น
ขั้นที่ 1 — ใส่ข้อมูลรับรองลงในที่เก็บ LFS
เลือกทำ อย่างใดอย่างหนึ่ง ต่อไปนี้ และต้องรู้ว่าเพิ่งเขียนลงที่เก็บใด:
- จากจอ LCD (ได้ทั้งสอง variant): Home → WiFi → scan → เลือกเครือข่าย → ใส่รหัสผ่าน → Connect วิธีนี้เขียนที่เก็บฝั่ง UI ของ OPTIGA (บท C2) และ ส่งรายการ LFS ต่อให้ worker บน CM33 เตรียมไว้
- จาก Python (เฉพาะ mtb-mpy): import wifi; wifi.connect("ssid", "password") วิธีนี้เขียนที่เก็บ LFS โดยตรง โดยไม่จับ lock และไม่แตะ g_boot_wifi_creds (modwifi.c:261-285)
สิ่งที่ควรสังเกต
- mtb-mpy เส้นทาง Python บน UART: [WiFi] Connecting to 's'... แล้วตามด้วย [WiFi] Connected! IP=lu.lu.lu.lu แล้ว [WiFi] Credentials saved to QSPI (d entries) (modwifi.c:243, :253, :285 — ไฟล์ที่ยังพิมพ์จริง ไม่ถูกปิดเสียง)
- ทั้งสอง variant บนหน้าจอ: glyph WiFi บน topbar เลิกซ่อน ตัวขับคือ IPC_CMD_WIFI_STATE_PUSH จาก push_wifi_state_to_cm55(true) ของ worker และฝั่ง CM55 อ่านด้วย ipc_sensorhub_wifi_connected() พร้อมการตรวจจับขอบสัญญาณ (sensorhub_ui.c:114-124)
- ทั้งสอง variant บนหน้าจอ ในอีกไม่กี่วินาทีถัดมา: นาฬิกาบน topbar ปรากฏขึ้นเมื่อ NTP ลงตัวแล้ว (ntp_sync_rtc() → push_time_to_cm55()) จัดรูปแบบเป็น Thu 28 Aug 14:06 โดย sensorhub_ui.c:127-142
ขั้นที่ 2 — ตรวจให้แน่ว่าการเขียนถึงแฟลชจริง
- mtb-only: ไม่ต้องทำอะไร การเขียนเกิดขึ้นแล้วใน worker
- mtb-mpy: ถ้า /main.py กำลังทำงานอยู่ ให้หยุดแล้วกลับไปที่พรอมป์ REPL (กด Ctrl-C จากคอนโซลอนุกรม (serial console) หรือปล่อยให้จบเอง) flusher ทำงานที่ต้นลูป REPL และ ไม่มี บรรทัด UART สำหรับการ flush นี้ เพราะข้อความ [WiFiIPC] Credentials persisted ในไฟล์นั้นถูกคอมไพล์ทิ้งไป (ดูกับดัก)
สิ่งที่ควรสังเกต ไม่มีอะไรบน UART หลักฐานอยู่ที่ขั้นที่ 3
ขั้นที่ 3 — ตัดไฟแล้วจ่ายไฟใหม่ แล้วดูการเชื่อมต่ออัตโนมัติ
ถอดสายบอร์ดแล้วเสียบกลับ (การรีเซ็ตจากดีบักเกอร์ไม่ใช่การตัดไฟแล้วจ่ายไฟใหม่ — บท A1/A2) การเชื่อมต่ออัตโนมัติตอนบูตเริ่มทันทีที่ WiFi worker task ได้ทำงาน
สิ่งที่ควรสังเกต
- mtb-only: [HB] t=lus tasks=u ทุก 10 s (proj_cm33_ns/main.c:107-109) เป็นหลักฐานว่า CM33_NS ยังจัดคิวงานอยู่ ภายในราว 8–20 s glyph WiFi บน topbar จะปรากฏตามด้วยนาฬิกา
- mtb-mpy: [MPY] GC heap u KB @ p in s (mpy_main.c:552-554) เป็นหลักฐานว่า VM ขึ้นแล้ว WiFi worker รอให้ VM เติมค่าตัวแปร global อยู่ จากนั้นจึงเห็น glyph และนาฬิกาเช่นเดียวกัน
- ทั้งสอง variant หากตัวที่สั่งเชื่อมต่อคือส่วนเชื่อม BLE/เดสก์ท็อปแทน (build ที่ตั้ง ENABLE_PAGE_BENTO_BUDDY=1 และมีการสลับคลื่นวิทยุจากเดสก์ท็อป บท I1 — เป็นเส้นทางเดียวที่เรียก app_wifi_connect_direct()): [wifi-glue] connecting SSID=s sec=d → [wifi-glue] connected, IP=lu.lu.lu.lu หรือ [wifi-glue] attempt d/3 failed (0x%08lX) (wifi_init.c:228, :237, :244) ใน build ปริยายบรรทัดเหล่านี้ไม่พิมพ์เลย
- ความล้มเหลวตอนเปิดใช้ฮาร์ดแวร์นั้นดังและไม่ถูกปิดเสียง: [WiFi] ERROR: SDIO interrupt init failed, [WiFi] ERROR: SDIO setup failed (0x%08lX), [WiFi] ERROR: Host wake interrupt init failed, [WiFi] ERROR: WCM init failed (0x%08lX) (wifi_init.c:84, :94-95, :122, :146-147)
หาก glyph ไม่ปรากฏเลยและไม่มีบรรทัด ERROR ใด ๆ พิมพ์ออกมา แสดงว่าที่เก็บ LFS ว่าง (ข้ามขั้นที่ 2 ไปบน mtb-mpy) หรือไม่พบ SSID นั้นในการสแกนแบบเจาะจง
ขั้นที่ 4 — พิสูจน์ว่าอ่านจากที่เก็บใด
ย้ายบอร์ด ตัวเดิม ข้าม variant: แฟลช mtb-only ทับอิมเมจ mtb-mpy ที่เคยบันทึกเครือข่ายไว้ บอร์ดจะเชื่อมต่อกลับได้ ที่เก็บฝั่ง C และที่เก็บฝั่ง VM อ่านและเขียนไบต์ชุดเดียวกัน (lfs_wifi_creds_c.c:18 — "so a board can move between variants and keep its saved networks") ส่วนรายการฝั่ง UI ของ OPTIGA ไม่ได้รับผลกระทบจากการแฟลชไม่ว่าทางใด เพราะอยู่ใน secure element (ชิปนิรภัยแยกส่วน)
กับดัก
- กับดัก 1 — ทุกบรรทัด [WiFi-Boot] และ [WiFiIPC] ตายหมด
- sensor_auto_task.c:36 นิยาม printf(...) ให้เป็น ((void)0) ทั้งไฟล์ ดังนั้น [WiFi-Boot] 2 saved network(s), [WiFi-Boot] 's' not found in scan — skipped, [WiFiIPC] Credentials persisted (d entries), [WiFiIPC] ERROR: credential save failed — kept dirty และบรรทัดพี่น้องทั้งหมดถูกคอมไพล์ทิ้งไป Tutorial หรือตั๋วสนับสนุนใดที่บอกให้เฝ้าดูบรรทัดเหล่านี้คือคำแนะนำที่ผิด หากต้องการเห็นระหว่างเปิดใช้ระบบของตนเอง ให้คอมเมนต์ sensor_auto_task.c:36 ทิ้ง สร้างใหม่ แล้วใส่กลับก่อนส่งมอบ — การปิดเสียงนี้มีอยู่เพราะการแย่งใช้ UART ทำให้เฟรมข้อมูลไบนารีเสียหาย
- กับดัก 2 — wifi_saved_* ไม่ใช่ที่เก็บสำหรับบูต
- wifi_saved_load(), wifi_saved_load_all(), wifi_saved_add() อยู่บน CM55 และคุยกับ slot ของ OPTIGA ไม่มีอะไรบนเส้นทางการบูตเรียกฟังก์ชันเหล่านี้ ถ้าผลิตภัณฑ์ "บันทึกเครือข่าย" ผ่าน wifi_saved_add() เพียงอย่างเดียว (เช่น จากหน้า CM55 ที่เขียนขึ้นเอง) บอร์ดจะแสดงเครือข่ายนั้นในรายการ และจะไม่เชื่อมต่ออัตโนมัติไปยังเครือข่ายนั้นเลย
- กับดัก 3 — ผู้เขียน 2 รายไม่จับ lock
- modwifi.c:261-285 (wifi.connect() ของ Python) และ wifi_init.c:261-302 (lfs_save_wifi_creds() ซึ่งเป็นส่วนเชื่อม BLE) ทำ read-modify-write กับ /.wifi_creds โดยตรง และไม่แตะ g_boot_wifi_creds เลย ทั้งสองยังคงปฏิบัติตามข้อกำหนดใน header (sensor_auto_task.h:92-95 — ให้จับ lock ทุกครั้งที่เข้าถึงตัวแปร global 3 ตัวนั้นแบบหลายไบต์) ด้วยการไม่แตะตัวแปร global แต่ยังเกิด race condition กับตัว ไฟล์ ได้อยู่ดีเมื่อมี lfs_wifi_creds_write() ทำงานพร้อมกัน ไม่มี lock ระดับไฟล์อยู่นอก bs_lock() ของ bento_storage.c และตัวนั้นมีเฉพาะบน mtb-only (ภาคผนวก X ข้อ 18)
- กับดัก 4 — เมื่อเขียนล้มเหลว แฟล็ก dirty ถูกคงไว้โดยเจตนา
- wifi_creds_flush_if_dirty() ล้าง g_boot_wifi_creds_dirty ต่อเมื่อ lfs_wifi_creds_write() คืนค่า true เท่านั้น การเขียนที่ล้มเหลวจะถูกลองใหม่ในรอบที่ REPL ว่างครั้งถัดไป ห้ามเพิ่มบรรทัดที่ "ล้างแฟล็กทิ้งไปเลย"
- กับดัก 5 — lfs_wifi_creds_init() ต้องใช้ VM และ heap (mtb-mpy)
- ฟังก์ชันนี้ต้องทำงานจาก MicroPython task หลังเมานต์ VFS แล้ว และโค้ดที่ส่งมอบเรียก gc_collect() ทันทีก่อนหน้านั้น บน mtb-only ชื่อเดียวกันนี้เป็น no-op (lfs_wifi_creds_c.c:60) เพราะ bento_storage_init() เป็นเจ้าของการเปิดใช้งานจาก main()
Variant
- variant ที่ใช้ได้
- mtb-mpy และ mtb-only
| mtb-mpy | mtb-only |
| ใครเรียก lfs_wifi_creds_read() ตอนบูต | MicroPython task, mpy_main.c | WiFi worker, sensor_auto_task.c:961 |
| Implementation | libbento_mpy.a (อาศัย VFS ของ Python) | storage_c/lfs_wifi_creds_c.c |
| จังหวะการบันทึก | เลื่อนไปที่ flusher — ตอน REPL ว่าง หรือตอน soft reboot | เขียนทันทีใน worker |
| นโยบาย lock ขณะเขียน | เขียนทั้งหมดภายใน wifi_creds_lock() | คัดลอก snapshot ภายใน เขียนภายนอก |
| /main.py ที่วนไม่รู้จบทำให้การบันทึกหาย | ใช่ | ไม่เกี่ยวข้อง |
| ฟอร์แมตวอลุมเมื่อเมานต์ไม่สำเร็จ | ใช่ (mpy_main.c, except: เปล่า → mkfs) | ไม่เลย (bento_storage.c ปล่อยวอลุมไว้อย่างเดิม) |
| ข้อความ [WiFi] … จากเส้นทาง Python | มี | ไม่มี Python จึงไม่มีข้อความเหล่านี้ |
| ข้อความ [wifi-glue] … | คอมไพล์รวมไว้ แต่มีเฉพาะการเชื่อมต่อที่ขับด้วย BLE บน build ที่ตั้ง ENABLE_PAGE_BENTO_BUDDY=1 เท่านั้นที่ไปถึง | เหมือนกัน |
ฟังก์ชัน lfs_wifi_creds_* ทั้ง 6 ตัวปรากฏใน mpy_secure/api.txt แต่บน mtb-only ตัวที่ให้บริการคือซอร์ส C ที่ส่งมอบมา ไม่ใช่ archive เพราะ libbento_mpy.a ไม่ถูกลิงก์ภายใต้ BENTO_HAS_MPY=0 (proj_cm33_ns/Makefile:458-462)