เป้าหมายของหัวข้อนี้
2 เรื่อง ซึ่งนำไปใช้ต่อได้ไกลเกินกว่าเรื่อง WiFi:
- สำนวน IPC แบบ msg.value พาพอยน์เตอร์ของคำตอบไปด้วย CM55 วางที่อยู่ของ struct คำตอบไว้ในข้อความ (message) ที่ส่งผ่าน pipe จากนั้น CM33_NS เติมค่าลง struct แล้วตั้ง ready = 1 หลัง barrier ทางหน่วยความจำ ส่วน CM55 วนถาม (polling) ค่า ready คำสั่ง IPC_CMD_WIFI_* ทุกตัวใช้สำนวนนี้
- กลไก slot ของ OPTIGA แบบ ไม่ประสานเวลา (async) ที่อยู่เบื้องหลังรายการเครือข่ายที่บันทึกไว้ — wifi_saved_probe_* แล้วตามด้วย wifi_saved_read_* — และข้อเท็จจริงที่สำคัญที่สุด: ดัชนีของรายการบน UI ไม่ใช่ ดัชนี slot ของที่เก็บ
บทนี้คือครึ่งหลังฝั่งที่เก็บ OPTIGA ของเรื่องที่บท C1 เริ่มไว้
ลำดับการทำงานจริงของเฟิร์มแวร์
Scan: CM55 ถาม CM33_NS ตอบ
ปุ่ม Scan ของหน้า WiFi Connect ตั้งสถานะเป็น WIFI_STATE_SCANNING แล้วเลื่อนออกไป 50 ms เพื่อให้ LVGL วาดจอเสร็จก่อนจะแตะ pipe (wifi_connect_native.c:406-412) callback ที่ถูกเลื่อนนั้นคือทั้งกระบวนการ scan/connect ของหน้านี้:
static void wifi_native_deferred_scan_cb(lv_timer_t *timer)
{
lv_timer_delete(timer);
if (s_ctx.ui == NULL) return;
if (!wifi_manager_scan_start()) {
aic_wifi_set_state(s_ctx.ui, WIFI_STATE_ERROR);
aic_wifi_show_error(s_ctx.ui, WIFI_ERR_SCAN_FAILED, "Scan failed");
return;
}
lv_timer_create(wifi_native_scan_poll_cb, 100, NULL);
}
static void wifi_native_on_scan(void)
{
if (s_ctx.ui == NULL) return;
aic_wifi_set_state(s_ctx.ui, WIFI_STATE_SCANNING);
lv_timer_create(wifi_native_deferred_scan_cb, 50, NULL);
}
static void wifi_native_on_select(int index, const char *ssid)
{
(void)index;
(void)ssid;
}
static void wifi_native_deferred_connect_cb(lv_timer_t *timer)
{
lv_timer_delete(timer);
if (s_ctx.ui == NULL) {
return;
}
bool ok = wifi_manager_connect(s_ctx.pending_ssid, s_ctx.pending_password);
if (!ok) {
ok = wifi_manager_connect(s_ctx.pending_ssid, s_ctx.pending_password);
}
if (!ok) {
aic_wifi_set_state(s_ctx.ui, WIFI_STATE_ERROR);
aic_wifi_show_error(s_ctx.ui, WIFI_ERR_AUTH_FAILED, "Connect failed");
return;
}
wifi_saved_add(s_ctx.pending_ssid, s_ctx.pending_password, s_ctx.pending_security);
wifi_native_refresh_saved();
wifi_native_scan_and_update();
}
static void wifi_native_on_connect(const char *ssid, const char *password, uint8_t security)
{
(void)security;
if (s_ctx.ui == NULL) {
return;
}
strncpy(s_ctx.pending_ssid, ssid, sizeof(s_ctx.pending_ssid) - 1);
s_ctx.pending_ssid[sizeof(s_ctx.pending_ssid) - 1] = '\0';
strncpy(s_ctx.pending_password, password, sizeof(s_ctx.pending_password) - 1);
s_ctx.pending_password[sizeof(s_ctx.pending_password) - 1] = '\0';
aic_wifi_set_state(s_ctx.ui, WIFI_STATE_CONNECTING);
lv_timer_create(wifi_native_deferred_connect_cb, 50, NULL);
}
wifi_manager_scan_start() เป็นฝั่งผู้ส่ง ข้อสังเกต: ไฟล์นี้อยู่ใต้ bento_libs/claw/common/modules/wifi_manager/ ไม่ได้อยู่ใต้ proj_cm55/ ดังนั้นการ grep หา IPC_CMD_WIFI_SCAN เฉพาะใน proj_cm55/ จะไม่พบอะไรเลย และดูเหมือนว่าเส้นทางนี้หายไป ซึ่งไม่จริง
bool wifi_manager_scan_start(void)
{
wifi_manager_ensure_ipc();
memset((void *)&s_wifi_ipc_resp, 0, sizeof(s_wifi_ipc_resp));
s_wifi_ipc_resp.ready = 0;
memset(&s_wifi_ipc_msg, 0, sizeof(s_wifi_ipc_msg));
s_wifi_ipc_msg.client_id = CM33_IPC_SENSOR_CTRL_CLIENT_ID;
s_wifi_ipc_msg.intr_mask = CY_IPC_CYPIPE_INTR_MASK_EP1;
s_wifi_ipc_msg.cmd = IPC_CMD_WIFI_SCAN;
s_wifi_ipc_msg.value = (uint32_t)&s_wifi_ipc_resp;
int retries = WIFI_IPC_SEND_RETRIES;
cy_en_ipc_pipe_status_t send_status;
do {
send_status = Cy_IPC_Pipe_SendMessage(
CM33_IPC_PIPE_EP_ADDR, CM55_IPC_PIPE_EP_ADDR,
(void *)&s_wifi_ipc_msg, NULL);
if (send_status == CY_IPC_PIPE_SUCCESS) {
break;
}
Cy_SysLib_DelayUs(WIFI_IPC_RETRY_DELAY_US);
} while (--retries > 0);
if (retries <= 0) {
s_last_error = WIFI_ERR_IPC_SEND_TIMEOUT;
return false;
}
s_last_error = CY_RSLT_SUCCESS;
return true;
}
bool wifi_manager_scan_ready(void)
{
__DMB();
return (s_wifi_ipc_resp.ready != 0);
}
พอยน์เตอร์ของคำตอบเดินทางไปใน msg.value บรรทัดเดียวนั้นคือข้อกำหนดการเรียกใช้ของคำสั่งทั้งตระกูล
บน CM33_NS callback ของ pipe ทำงานใน ISR context และทำเพียงอย่างเดียว คือดึงพอยน์เตอร์ของคำตอบออกมา แล้วส่งคำขอเข้าคิวให้ worker
if (msg->cmd >= IPC_CMD_WIFI_SCAN && msg->cmd <= IPC_CMD_WIFI_SOFTAP) {
ipc_response_t *resp = (ipc_response_t *)msg->value;
if (s_wifi_req_queue == NULL) {
wifi_ipc_reply_error_isr(resp, msg->cmd, AUTO_WIFI_ERR_QUEUE_FULL);
return;
}
wifi_ipc_req_t req;
memset(&req, 0, sizeof(req));
req.cmd = msg->cmd;
req.value = msg->value;
req.resp = resp;
memcpy(req.data, msg->data, sizeof(req.data));
BaseType_t hpw = pdFALSE;
if (xQueueSendFromISR(s_wifi_req_queue, &req, &hpw) != pdTRUE) {
wifi_ipc_reply_error_isr(resp, msg->cmd, AUTO_WIFI_ERR_QUEUE_FULL);
}
portYIELD_FROM_ISR(hpw);
return;
}
worker ดึงงานออกจากคิว และสำหรับการ scan จะทำตามลำดับนี้: wifi_ensure_ready() (เริ่มใช้งาน WiFi แบบเลื่อนเวลา — ฮาร์ดแวร์ไม่ถูกแตะจนกว่าจะใช้ครั้งแรก) กรอง RSSI ที่ −90 dBm, cy_wcm_start_scan(), ตัดรายการซ้ำตาม SSID โดยเก็บตัวที่ RSSI แรงที่สุด, เรียงลำดับแบบ insertion sort ตาม RSSI, ตัดให้เหลือ IPC_WIFI_SCAN_MAX_ENTRIES (6) แล้วตอบกลับ
static void wifi_ipc_handle_request(const wifi_ipc_req_t *req)
{
uint8_t status_blob[AUTO_WIFI_STATUS_DATA_LEN];
uint32_t err = 0;
if (req == NULL || req->resp == NULL) {
return;
}
switch (req->cmd) {
case IPC_CMD_WIFI_SCAN: {
printf("[WiFiIPC] SCAN cmd received\r\n");
if (!wifi_ensure_ready()) {
err = (uint32_t)s_wifi_last_error;
printf("[WiFiIPC] SCAN: wifi not ready, err=0x%08lX\r\n", (unsigned long)err);
wifi_ipc_reply(req->resp, req->cmd, 1, &err, 4);
break;
}
printf("[WiFiIPC] SCAN: wifi ready, starting scan...\r\n");
static wifi_scan_ctx_t scan_ctx;
memset(&scan_ctx, 0, sizeof(scan_ctx));
scan_ctx.done = xSemaphoreCreateBinary();
if (scan_ctx.done == NULL) {
err = AUTO_WIFI_ERR_BAD_ARG;
wifi_ipc_reply(req->resp, req->cmd, 2, &err, 4);
break;
}
memset(&filter, 0, sizeof(filter));
filter.
param.rssi_range = -90;
cy_rslt_t r = cy_wcm_start_scan(wifi_scan_callback, &scan_ctx, &filter);
if (r != CY_RSLT_SUCCESS) {
s_wifi_last_error = r;
err = (uint32_t)r;
printf("[WiFiIPC] SCAN: start_scan failed, err=0x%08lX\r\n", (unsigned long)err);
vSemaphoreDelete(scan_ctx.done);
wifi_ipc_reply(req->resp, req->cmd, 3, &err, 4);
break;
}
if (xSemaphoreTake(scan_ctx.done, pdMS_TO_TICKS(AUTO_WIFI_SCAN_TIMEOUT_MS)) != pdTRUE) {
(void)cy_wcm_stop_scan();
err = AUTO_WIFI_ERR_TIMEOUT;
printf("[WiFiIPC] SCAN: semaphore timeout (%d ms)\r\n", AUTO_WIFI_SCAN_TIMEOUT_MS);
vSemaphoreDelete(scan_ctx.done);
wifi_ipc_reply(req->resp, req->cmd, 4, &err, 4);
break;
}
vSemaphoreDelete(scan_ctx.done);
printf("[WiFiIPC] SCAN: collected %u raw networks\r\n", (unsigned)scan_ctx.count);
for (uint8_t i = 0; i < scan_ctx.count; i++) {
for (uint8_t j = i + 1; j < scan_ctx.count; ) {
if (strncmp(scan_ctx.entries[i].ssid, scan_ctx.entries[j].ssid, 32) == 0) {
if (scan_ctx.entries[j].rssi > scan_ctx.entries[i].rssi) {
scan_ctx.entries[i] = scan_ctx.entries[j];
}
scan_ctx.entries[j] = scan_ctx.entries[scan_ctx.count - 1];
scan_ctx.count--;
} else {
j++;
}
}
}
for (uint8_t i = 1; i < scan_ctx.count; i++) {
ipc_wifi_scan_entry_t tmp = scan_ctx.entries[i];
uint8_t j = i;
while (j > 0 && scan_ctx.entries[j - 1].rssi < tmp.rssi) {
scan_ctx.entries[j] = scan_ctx.entries[j - 1];
j--;
}
scan_ctx.entries[j] = tmp;
}
uint8_t send_count = scan_ctx.count;
if (send_count > IPC_WIFI_SCAN_MAX_ENTRIES) {
send_count = IPC_WIFI_SCAN_MAX_ENTRIES;
}
printf("[WiFiIPC] SCAN: sending top %u of %u unique networks\r\n",
(unsigned)send_count, (unsigned)scan_ctx.count);
uint16_t data_len = (uint16_t)(send_count * sizeof(ipc_wifi_scan_entry_t));
wifi_ipc_reply(req->resp, req->cmd, 0, scan_ctx.entries, data_len);
break;
ตัว barrier อยู่ในจังหวะตอบกลับนี้เอง:
static void wifi_ipc_reply(ipc_response_t *resp, uint32_t cmd, uint8_t status,
const void *data, uint16_t data_len)
{
if (resp == NULL) {
return;
}
memset((void *)resp, 0, sizeof(*resp));
resp->cmd = (uint8_t)cmd;
resp->status = status;
if (data != NULL && data_len > 0) {
if (data_len > IPC_RESPONSE_DATA_MAX) {
data_len = IPC_RESPONSE_DATA_MAX;
}
memcpy(resp->data, data, data_len);
resp->data_len = data_len;
}
__DMB();
resp->ready = 1;
}
__DMB() ก่อน แล้วจึง resp->ready = 1 ฝั่งผู้อ่านทำสลับกันเป็นเงาสะท้อน — wifi_manager_scan_ready() เรียก __DMB() แล้วจึงอ่าน ready (wifi_manager.c:294-298) ถ้าถอด barrier ตัวใดตัวหนึ่งออก หน้าบน CM55 จะอ่านรายการที่เขียนค้างอยู่ครึ่งเดียวเป็นครั้งคราว
Connect: การแพ็ก payload การลองใหม่ และการบันทึก
bool wifi_manager_connect(const char *ssid, const char *password)
{
if (ssid == NULL) {
s_last_error = WIFI_ERR_INVALID_ARG;
return false;
}
uint8_t payload[IPC_DATA_MAX_LEN];
memset(payload, 0, sizeof(payload));
size_t ssid_len = strnlen(ssid, 32);
memcpy(payload, ssid, ssid_len);
payload[ssid_len] = '\0';
const char *pass = (password != NULL) ? password : "";
size_t pass_len = strnlen(pass, 63);
memcpy(&payload[ssid_len + 1], pass, pass_len);
payload[ssid_len + 1 + pass_len] = '\0';
size_t payload_len = ssid_len + 1 + pass_len + 1;
if (!wifi_manager_ipc_request(IPC_CMD_WIFI_CONNECT, payload, payload_len,
WIFI_IPC_RESPONSE_TIMEOUT_CONN_MS)) {
return false;
}
(void)wifi_manager_ipc_request(IPC_CMD_WIFI_STATUS, NULL, 0,
WIFI_IPC_RESPONSE_TIMEOUT_MS);
wifi_manager_decode_status(&s_wifi_ipc_resp, &s_status);
return true;
}
ssid\0password\0 ถูกแพ็กลงใน payload ของข้อความ จากนั้นรอค่า ready แบบบล็อก โดยมี timeout แยกตามคำสั่ง:
while (!s_wifi_ipc_resp.ready && timeout > 0) {
vTaskDelay(pdMS_TO_TICKS(1));
timeout--;
}
__DMB();
if (!s_wifi_ipc_resp.ready) {
cm55_ipc_pipe_drain_release();
g_wifi_ipc_pipe_recoveries++;
s_last_error = WIFI_ERR_IPC_RESP_TIMEOUT;
return false;
}
บน CM33_NS กรณี connect จะแกะ payload ออก เข้าร่วมเครือข่ายด้วย wifi_connect_robust() ที่พยายาม 6 ครั้งจากบท C1 ตอบกลับ ผลักสถานะ WiFi ไปยัง CM55 ซิงก์ NTP หนึ่งครั้ง แล้วจึงเตรียมหรือเขียนข้อมูลรับรองลง LFS (ทางแยกของบท C1)
case IPC_CMD_WIFI_CONNECT: {
printf("[WiFiIPC] CONNECT cmd received\r\n");
const char *ssid = (const char *)req->data;
size_t ssid_len = strnlen(ssid, 32);
const char *pass = (const char *)&req->data[ssid_len + 1];
size_t pass_len = strnlen(pass, 63);
if (ssid_len == 0U) {
err = AUTO_WIFI_ERR_BAD_ARG;
wifi_ipc_reply(req->resp, req->cmd, 1, &err, 4);
break;
}
if (!wifi_ensure_ready()) {
err = (uint32_t)s_wifi_last_error;
wifi_ipc_reply(req->resp, req->cmd, 2, &err, 4);
break;
}
cy_wcm_connect_params_t params;
cy_wcm_ip_address_t ip;
memset(¶ms, 0, sizeof(params));
memset(&ip, 0, sizeof(ip));
memcpy(params.ap_credentials.SSID, ssid, ssid_len);
memcpy(params.ap_credentials.password, pass, pass_len);
params.ap_credentials.security = (pass_len == 0U)
: CY_WCM_SECURITY_WPA3_WPA2_PSK;
cy_rslt_t r = wifi_connect_robust(¶ms, &ip, "WiFiIPC");
if (r != CY_RSLT_SUCCESS) {
s_wifi_last_error = r;
err = (uint32_t)r;
wifi_ipc_reply(req->resp, req->cmd, 3, &err, 4);
break;
}
s_wifi_state.mode = AUTO_WIFI_MODE_STA;
s_wifi_state.connected = true;
s_wifi_state.rssi = 0;
memset(s_wifi_state.ssid, 0, sizeof(s_wifi_state.ssid));
strncpy(s_wifi_state.ssid, ssid, sizeof(s_wifi_state.ssid) - 1);
wifi_update_ip_from_wcm();
s_wifi_last_error = CY_RSLT_SUCCESS;
wifi_ipc_reply(req->resp, req->cmd, 0, NULL, 0);
push_wifi_state_to_cm55(true);
if (!s_ntp_synced) {
if (ntp_sync_rtc()) {
s_ntp_synced = true;
push_time_to_cm55();
tesaiot_bridge_ntp_synced();
}
}
wifi_creds_lock();
{
bool already_saved = false;
for (int i = 0; i < g_boot_wifi_creds_count; i++) {
if (strncmp(g_boot_wifi_creds[i].ssid, ssid, 32) == 0) {
memset(g_boot_wifi_creds[i].password, 0, 65);
strncpy(g_boot_wifi_creds[i].password, pass, 64);
g_boot_wifi_creds[i].flags = 0x01;
already_saved = true;
break;
}
}
if (!already_saved) {
int idx;
if (g_boot_wifi_creds_count < QSPI_WIFI_CREDS_MAX) {
idx = g_boot_wifi_creds_count;
g_boot_wifi_creds_count = idx + 1;
} else {
idx = 0;
}
memset(&g_boot_wifi_creds[idx], 0, sizeof(qspi_wifi_entry_t));
strncpy(g_boot_wifi_creds[idx].ssid, ssid, 32);
strncpy(g_boot_wifi_creds[idx].password, pass, 64);
g_boot_wifi_creds[idx].flags = 0x01;
}
g_boot_wifi_creds_dirty = true;
printf("[WiFiIPC] Credential staged for QSPI save (%d entries)\r\n",
(int)g_boot_wifi_creds_count);
}
wifi_creds_unlock();
#if !BENTO_HAS_MPY
{
qspi_wifi_entry_t snap[QSPI_WIFI_CREDS_MAX];
int n;
wifi_creds_lock();
n = (int)g_boot_wifi_creds_count;
if (n > QSPI_WIFI_CREDS_MAX) n = QSPI_WIFI_CREDS_MAX;
memcpy(snap, g_boot_wifi_creds, (size_t)n * sizeof(qspi_wifi_entry_t));
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
break;
กลับมาที่ CM55 หน้านี้ลอง connect ซ้ำ เพียงครั้งเดียวเท่านั้น — cy_wcm_connect_ap() มักล้มเหลวในครั้งแรกหลังการ scan เพราะการเปลี่ยนสถานะภายในของ WCM (wifi_connect_native.c:429-430) — และจะเรียก wifi_saved_add() ต่อเมื่อยืนยันแล้ว ว่าเข้าร่วมเครือข่ายสำเร็จเท่านั้น:
bool ok = wifi_manager_connect(s_ctx.pending_ssid, s_ctx.pending_password);
if (!ok) {
ok = wifi_manager_connect(s_ctx.pending_ssid, s_ctx.pending_password);
}
if (!ok) {
aic_wifi_set_state(s_ctx.ui, WIFI_STATE_ERROR);
aic_wifi_show_error(s_ctx.ui, WIFI_ERR_AUTH_FAILED, "Connect failed");
return;
}
wifi_saved_add(s_ctx.pending_ssid, s_ctx.pending_password, s_ctx.pending_security);
wifi_native_refresh_saved();
wifi_native_scan_and_update();
}
wifi_saved_add() ไม่เคยถูกเรียกแบบเผื่อไว้ก่อน ผู้เรียกไม่ต้องเติมค่าใด ๆ ลงในรายการล่วงหน้า เพราะการตัดรายการซ้ำ แฟล็ก last_used และการปิดท้ายด้วย NUL เป็นเรื่องภายในทั้งหมด (wifi_saved.c:266-304)
รายการเครือข่ายที่บันทึกไว้: probe ก่อน แล้วจึง read ทีละ slot ต่อหนึ่ง tick
รายการนี้ถูกเติมจาก slot ของ OPTIGA แบบไม่ประสานเวลา เพื่อไม่ให้หน้าจอบล็อก GFX task นานราว 1.5 s อย่างที่การโหลดแบบประสานเวลาต้องเสีย ลำดับต้องเคร่งครัด:
static void wifi_native_saved_probe_poll_cb(lv_timer_t *timer)
{
lv_timer_delete(timer);
s_saved_count = 0;
s_saved_load_idx = 0;
s_slot_state = SLOT_SEND;
lv_timer_create(wifi_native_saved_slot_cb, 50, NULL);
}
static void wifi_native_deferred_load_saved_cb(lv_timer_t *timer)
{
lv_timer_delete(timer);
if (s_ctx.ui == NULL) return;
s_saved_count = 0;
s_saved_load_idx = 0;
s_slot_state = SLOT_SEND;
lv_timer_create(wifi_native_saved_slot_cb, 50, NULL);
} else {
lv_timer_create(wifi_native_saved_probe_poll_cb, 100, NULL);
}
}
probe_start() → วนถาม probe_ready() บนตัวจับเวลาของ LVGL ทุก 100 ms → probe_finish() เพียงครั้งเดียว และต่อเมื่อ probe_ready() คืนค่า true แล้วเท่านั้น probe_ready() อาจเป็น true ทันที หลัง probe_start() (ผลที่แคชไว้) จึงต้องรองรับเส้นทางที่เร็วนี้ด้วย ห้ามคิดเอาเองว่าไม่เกิด ทุก callback ตรวจ s_ctx.ui == NULL ซ้ำ เพราะหน้านี้ถูกทำลายกลางคัน IPC ได้
จากนั้นอ่านทีละหนึ่งรายการเท่านั้นในเวลาหนึ่ง:
static void wifi_native_saved_slot_cb(lv_timer_t *timer)
{
if (s_ctx.ui == NULL) { lv_timer_delete(timer); return; }
if (s_slot_state == SLOT_SEND) {
lv_timer_delete(timer);
aic_wifi_set_saved_networks(s_ctx.ui, s_saved_buf, s_saved_count);
return;
}
s_slot_state = SLOT_POLL;
} else {
s_saved_load_idx++;
}
return;
}
memcpy(&s_saved_buf[s_saved_count], &tmp, sizeof(tmp));
s_saved_count++;
}
s_saved_load_idx++;
s_slot_state = SLOT_SEND;
}
read_start(slot) → ถ้ายังไม่พร้อมแปลว่า ให้ลองใหม่ใน tick ถัดไป → read_result() เติมค่าลง wifi_saved_entry_t บน stack ที่ผู้เรียกเป็นเจ้าของ ให้คัดลอกออกมาทันที ค่า false จาก read_start() คือเส้นทางความผิดพลาดจริง: ให้ข้าม slot นั้นแล้วเดินหน้าต่อ ห้ามลองใหม่ และห้ามยกเลิกทั้งรายการ
หลังการเปลี่ยนแปลงข้อมูล (เพิ่มหรือลบ) หน้านี้ใช้เส้นทางแบบ ประสานเวลา แทน ซึ่งยอมรับได้เพราะผู้ใช้เพิ่งแตะหน้าจอและคาดหวังให้รายการรีเฟรช:
static void wifi_native_refresh_saved(void)
{
if (!s_ctx.ui) return;
aic_wifi_set_saved_networks(s_ctx.ui, entries, count);
}
บัฟเฟอร์ ต้องเป็น wifi_saved_entry_t[WIFI_SAVED_MAX] เพราะไม่มีอาร์กิวเมนต์สำหรับระบุความจุ
Erase: คลี่หา slot ให้ได้ก่อน
static void wifi_native_on_saved_delete(int saved_index)
{
if (!s_ctx.ui) return;
if (saved_index < 0 || saved_index >= s_ctx.ui->saved_count) return;
const char *ssid = s_ctx.ui->saved_entries[saved_index].ssid;
if (slot >= 0) {
}
wifi_native_refresh_saved();
}
wifi_saved_find(ssid) คืนค่าเป็น slot ของที่เก็บ (คืน < 0 เมื่อไม่พบ) แล้วจึงค่อยเรียก wifi_saved_erase(slot) ค่า bool ที่ได้จาก erase ถูกละทิ้งโดยเจตนา เพราะการกู้คืนคือการรีเฟรชรายการทั้งหมด
ทีละขั้น
ขั้นที่ 1 — Scan จากหน้าจอ
Home → WiFi → Scan
สิ่งที่ควรสังเกต เครือข่ายสูงสุด 6 รายการ เรียงจากแรงที่สุดก่อน และไม่มีรายการซ้ำ เมื่อล้มเหลว หน้าจอแสดงข้อความ "Scan failed" (wifi_connect_native.c:397) ไม่มีอะไรบน UART เพราะทุกบรรทัด [WiFiIPC] ในไฟล์ของ worker ถูกปิดเสียง (sensor_auto_task.c:36) และ callback ฝั่ง ISR ไม่มีการพิมพ์โดยเจตนา ตามคอมเมนต์ของมันที่ระบุว่า No printf here — this is ISR context (sensor_auto_task.c:272)
ขั้นที่ 2 — Connect จากหน้าจอ
เลือกเครือข่าย พิมพ์รหัสผ่าน แล้วกด Connect หน้าจอจะแสดง "Connecting…" ก่อนเพราะการเลื่อน 50 ms จากนั้น worker บน CM33 จึงเข้าร่วมเครือข่ายแบบพยายาม 6 ครั้ง
สิ่งที่ควรสังเกต เมื่อสำเร็จ: glyph WiFi บน topbar เลิกซ่อน (IPC_CMD_WIFI_STATE_PUSH → ipc_sensorhub_wifi_connected()) นาฬิกาปรากฏหลัง NTP และเครือข่ายนั้นปรากฏในรายการที่บันทึกไว้ เมื่อล้มเหลว หน้าจอแสดง "Connect failed" (wifi_connect_native.c:437) จะเห็นบรรทัด [wifi-glue] จากบท C1 เพิ่มขึ้นมาก็ต่อเมื่อส่วนเชื่อม BLE เป็นตัวสั่งเชื่อมต่อเท่านั้น (build ที่ตั้ง ENABLE_PAGE_BENTO_BUDDY=1 พร้อมการสลับคลื่นวิทยุจากเดสก์ท็อป บท I1)
ขั้นที่ 3 — ดูรายการโหลดแบบไม่ประสานเวลา
ออกจากหน้านี้แล้วกลับเข้ามาใหม่ รายการที่บันทึกไว้จะทยอยเติมขึ้นในหลาย tick แทนที่จะปรากฏพร้อมกันทีเดียว นั่นคือกลไก probe แล้ว read ทีละ slot ต่อหนึ่ง tick ขนาด 50 ms กำลังทำงาน
สิ่งที่ควรสังเกต รายการทยอยปรากฏ ส่วนนาฬิกาบน topbar ยังเดินต่อเนื่อง (GFX task ไม่เคยถูกบล็อก) หากการอ่าน slot ใดล้มเหลว จะได้รายการที่สั้นลง ไม่ใช่หน้าจอที่ค้าง
ขั้นที่ 4 — ลบรายการที่สองในรายการ
ลบเครือข่ายที่บันทึกไว้เป็นลำดับที่สอง
สิ่งที่ควรสังเกต เครือข่ายนั้น และเฉพาะเครือข่ายนั้น หายไปหลังการรีเฟรช หากเห็นเครือข่ายอื่นหายไป แสดงว่ามีคนส่งดัชนีของ UI เข้า wifi_saved_erase() แทนที่จะคลี่หาผ่าน wifi_saved_find() ก่อน — กับดัก 1
ขั้นที่ 5 — รีบูตแล้วตรวจที่เก็บทั้ง 2 แห่ง
ตัดไฟแล้วจ่ายไฟใหม่ เครือข่ายที่เชื่อมต่อไว้ในขั้นที่ 2 จะเชื่อมต่ออัตโนมัติ (เพราะถูก worker บน CM33 เตรียมลงในที่เก็บ LFS ไว้แล้ว — บท C1) และยังคงแสดงอยู่ในรายการ (เพราะอยู่ใน OPTIGA)
สิ่งที่ควรสังเกต mtb-only: [HB] t=lus tasks=u แล้วตามด้วย glyph และนาฬิกา mtb-mpy: [MPY] GC heap u KB @ p in s แล้วตามด้วย glyph และนาฬิกา ยกเว้นกรณีที่ /main.py วนไม่รู้จบ ซึ่งรายการจะยังแสดงเครือข่ายนั้นแต่บอร์ดจะไม่เชื่อมต่ออัตโนมัติ (อันตรายเรื่องข้อมูลรับรองสูญหายในบท C1 ซึ่งตอนนี้มองเห็นได้ในรูปความไม่ตรงกันของที่เก็บทั้ง 2 แห่ง)
กับดัก
- กับดัก 1 — ดัชนีของรายการบน UI ไม่ใช่ดัชนี slot ของ OPTIGA
- รายการถูกสร้างจาก slot ที่ตอบกลับมาเท่านั้น เรียงตามลำดับที่ตอบ หลังจากที่การขับออกแบบ LRU อาจสลับตำแหน่งไปแล้ว wifi_saved_find() คือสะพานเดียวที่ถูกต้องจาก "แถวที่ผู้ใช้แตะ" ไปสู่ "slot ที่ต้องลบ" (ภาคผนวก X ข้อ 10)
- กับดัก 2 — เรียก probe_finish() 2 ครั้ง หรือเรียกก่อน probe_ready()
- ครั้งเดียว และต่อเมื่อพร้อมแล้วเท่านั้น หน้านี้ลบตัวจับเวลาของตนเองก่อนเดินหน้าเข้าสู่ช่วง read ให้ทำตามวินัยเดียวกัน
- กับดัก 3 — ออก read_start() ซ้ำก่อน read_ready()
- ให้มีการอ่านค้างอยู่ได้ครั้งเดียว กลไกที่ส่งมอบมาเป็นคู่ SEND → POLL ที่ชัดเจน ทีละ slot ต่อหนึ่ง tick การอ่านที่ซ้อนกันทำให้ slot ของคำตอบเสียหาย
- กับดัก 4 — หน้าจออาจถูกทำลายขณะที่คำตอบยังค้างอยู่
- lv_screen_load_anim(auto_del) คืนหน่วยความจำอ็อบเจ็กต์ของหน้านั้น callback ที่ยิงหลังจากนั้นจะอ้างถึงหน่วยความจำ LVGL ที่ถูกปลดไปแล้ว ทุก callback ในหน้าที่ส่งมอบมาตรวจ s_ctx.ui == NULL ซ้ำ ให้ทำแบบเดียวกันในหน้าของตน
- กับดัก 5 — ถอด barrier ทิ้ง
- ฝั่งผู้ผลิตต้องมี __DMB(); resp->ready = 1; และฝั่งผู้อ่านต้องมี __DMB() ก่อนอ่าน ready ต้องมีครบทั้งสองฝั่ง เสมอ
- กับดัก 6 — เฝ้าหา [WiFiIPC] SCAN cmd received
- ถูกปิดเสียง ดูบท C1 กับดัก 1
Variant
- variant ที่ใช้ได้
- mtb-mpy และ mtb-only
CM55 ไม่ขึ้นกับ variant ทั้งตัวหน้าจอ wifi_manager.c และกลไก wifi_saved_* เหมือนกันทั้งสอง variant ส่วน worker บน CM33_NS ก็เป็นไฟล์เดียวกันทั้งสอง variant มีเพียงการ บันทึก ข้อมูลรับรองที่ท้ายกรณี connect เท่านั้นที่แยกทาง (บท C1) ที่เก็บ OPTIGA ถูกเข้าถึงผ่านคำสั่ง IPC ฝั่งข้อมูลรับรองของ HSM ซึ่งหมายความว่าวินัยการเข้าถึงชิปในบท D1 มีผลอยู่ใต้การเรียก wifi_saved_* ทุกครั้ง — optiga_manager_acquire()/release() ทำงานบน CM33_NS ในทุกการอ่านและเขียน slot