diff --git a/firmware/cyd_35r/VERSION.txt b/firmware/cyd_35r/VERSION.txt index d17f10f..a828605 100644 --- a/firmware/cyd_35r/VERSION.txt +++ b/firmware/cyd_35r/VERSION.txt @@ -1,5 +1,5 @@ name: CYD wifitest (cyd_35r) -git: 9cbbb29 +git: d9773a8 date: 2026-10-25 board: ESP32-3248S035 (ST7796) flash_size: 4MB @@ -11,5 +11,5 @@ offsets: sha256: bootloader.bin 3D234A7471F67B013686DABD4DEE7C1FA915C9928463616A94BC9297ACF1ABF8 boot_app0.bin F94C5D786A7A8FAB06AC5D10E33BF37711A6697636DC037559EA19CC410A17F0 - firmware.bin DDB6B8EB5091F9C201A1696CEC05BA68E93A512EE7388C66EC7207850B74FC85 + firmware.bin 07AE55AB62FE6A53D343B0DDB1E2FE422581F6B02CD1E62E3161AE66C3056897 partitions.bin 148B959CBFF1C38AA8E1D5C0BA9D612C54997B945E56A63F41223EEF650653A1 diff --git a/firmware/cyd_35r/firmware.bin b/firmware/cyd_35r/firmware.bin index 94961db..9d72ed4 100644 Binary files a/firmware/cyd_35r/firmware.bin and b/firmware/cyd_35r/firmware.bin differ diff --git a/src/wifi_ui.cpp b/src/wifi_ui.cpp index 13becb1..de881b6 100644 --- a/src/wifi_ui.cpp +++ b/src/wifi_ui.cpp @@ -18,6 +18,9 @@ static constexpr uint32_t COL_BTN = 0x243140; static constexpr uint32_t COL_BTN_ACC = 0x1A4A6E; static constexpr uint32_t COL_BTN_WARN = 0x6B3A1E; +/* 3.5" LV_MEM=32K: dual screens + 16 heavy rows OOMs. Cap list hard. */ +static constexpr int WIFI_UI_LIST_MAX = 8; + static lv_obj_t *scr_status = nullptr; static lv_obj_t *scr_setup = nullptr; static lv_obj_t *lbl_title = nullptr; @@ -42,9 +45,24 @@ static char g_pick_ssid[WIFI_PROV_SSID_MAX + 1] = {}; static bool g_on_setup = false; static bool g_pass_visible = false; static bool g_scan_pending = false; +static bool g_auto_scan_armed = false; +static uint32_t g_auto_scan_at = 0; static WifiProvState g_last_shown = WIFI_PROV_IDLE; static uint32_t g_last_status_ms = 0; +static void on_scan(lv_event_t *e); +static void on_portal(lv_event_t *e); +static void on_join(lv_event_t *e); +static void on_dlg_back(lv_event_t *e); +static void on_toggle_pass(lv_event_t *e); +static void on_kb(lv_event_t *e); +static void on_change_wifi(lv_event_t *e); +static void on_forget(lv_event_t *e); +static void on_row_join(lv_event_t *e); +static void build_status(); +static void build_setup(); +static void ensure_pass_dialog(); + static void style_btn(lv_obj_t *btn, uint32_t bg) { lv_obj_set_style_radius(btn, 8, 0); lv_obj_set_style_bg_color(btn, lv_color_hex(bg), 0); @@ -59,15 +77,19 @@ static void style_btn(lv_obj_t *btn, uint32_t bg) { static lv_obj_t *make_btn(lv_obj_t *parent, const char *text, uint32_t bg, lv_event_cb_t cb, int w, int h) { lv_obj_t *btn = lv_button_create(parent); + if (!btn) { + return nullptr; + } lv_obj_set_size(btn, w, h); style_btn(btn, bg); lv_obj_t *lab = lv_label_create(btn); - lv_label_set_text(lab, text); - lv_obj_set_style_text_color(lab, lv_color_hex(COL_INK), 0); - lv_obj_set_style_text_font(lab, &lv_font_montserrat_14, 0); - lv_obj_center(lab); - /* Label must not steal clicks from the button. */ - lv_obj_clear_flag(lab, LV_OBJ_FLAG_CLICKABLE); + if (lab) { + lv_label_set_text(lab, text); + lv_obj_set_style_text_color(lab, lv_color_hex(COL_INK), 0); + lv_obj_set_style_text_font(lab, &lv_font_montserrat_14, 0); + lv_obj_center(lab); + lv_obj_clear_flag(lab, LV_OBJ_FLAG_CLICKABLE); + } if (cb) { lv_obj_add_event_cb(btn, cb, LV_EVENT_CLICKED, nullptr); } @@ -93,8 +115,6 @@ static void set_pass_visible(bool visible) { } } -static void on_kb(lv_event_t *e); /* defined below — used by ensure_keyboard */ - static void ensure_keyboard() { if (kb || !scr_setup || !ta_pass) { return; @@ -112,7 +132,101 @@ static void ensure_keyboard() { lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); } +static void ensure_pass_dialog() { + if (overlay || !scr_setup) { + return; + } + + overlay = lv_obj_create(scr_setup); + if (!overlay) { + Serial.println("[ui] OOM: pass overlay"); + return; + } + lv_obj_set_size(overlay, UI_HOR_RES, UI_VER_RES); + lv_obj_set_style_bg_color(overlay, lv_color_hex(0x000000), 0); + lv_obj_set_style_bg_opa(overlay, LV_OPA_70, 0); + lv_obj_set_style_border_width(overlay, 0, 0); + lv_obj_set_style_pad_all(overlay, 0, 0); + lv_obj_clear_flag(overlay, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(overlay, LV_OBJ_FLAG_HIDDEN); + + dlg = lv_obj_create(overlay); + if (!dlg) { + return; + } + lv_obj_set_size(dlg, UI_HOR_RES - UW(20), UH(100)); + lv_obj_align(dlg, LV_ALIGN_TOP_MID, UX(0), UY(8)); + lv_obj_set_style_bg_color(dlg, lv_color_hex(0x15202C), 0); + lv_obj_set_style_border_width(dlg, 0, 0); + lv_obj_set_style_radius(dlg, 10, 0); + lv_obj_set_style_pad_all(dlg, 8, 0); + lv_obj_clear_flag(dlg, LV_OBJ_FLAG_SCROLLABLE); + + lbl_dlg_ssid = lv_label_create(dlg); + if (lbl_dlg_ssid) { + lv_label_set_text(lbl_dlg_ssid, ""); + lv_obj_set_style_text_color(lbl_dlg_ssid, lv_color_hex(COL_ACCENT), 0); + lv_obj_set_style_text_font(lbl_dlg_ssid, &lv_font_montserrat_14, 0); + lv_obj_align(lbl_dlg_ssid, LV_ALIGN_TOP_LEFT, UX(0), UY(0)); + } + + lv_obj_t *pass_row = lv_obj_create(dlg); + if (pass_row) { + lv_obj_set_size(pass_row, UI_HOR_RES - UW(40), UH(32)); + lv_obj_align(pass_row, LV_ALIGN_TOP_LEFT, UX(0), UY(22)); + lv_obj_set_style_bg_opa(pass_row, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(pass_row, 0, 0); + lv_obj_set_style_pad_all(pass_row, 0, 0); + lv_obj_set_flex_flow(pass_row, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(pass_row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_column(pass_row, UW(6), 0); + lv_obj_clear_flag(pass_row, LV_OBJ_FLAG_SCROLLABLE); + + ta_pass = lv_textarea_create(pass_row); + if (ta_pass) { + lv_obj_set_size(ta_pass, UI_HOR_RES - UW(40) - UW(62), UH(32)); + lv_textarea_set_one_line(ta_pass, true); + lv_textarea_set_password_mode(ta_pass, true); + lv_textarea_set_placeholder_text(ta_pass, "Password"); + lv_obj_set_style_text_font(ta_pass, &lv_font_montserrat_14, 0); + } + + btn_show_pass = lv_button_create(pass_row); + if (btn_show_pass) { + lv_obj_set_size(btn_show_pass, UW(56), UH(28)); + style_btn(btn_show_pass, COL_BTN); + lbl_show_pass = lv_label_create(btn_show_pass); + if (lbl_show_pass) { + lv_label_set_text(lbl_show_pass, "Show"); + lv_obj_set_style_text_color(lbl_show_pass, lv_color_hex(COL_INK), 0); + lv_obj_set_style_text_font(lbl_show_pass, &lv_font_montserrat_14, 0); + lv_obj_center(lbl_show_pass); + lv_obj_clear_flag(lbl_show_pass, LV_OBJ_FLAG_CLICKABLE); + } + lv_obj_add_event_cb(btn_show_pass, on_toggle_pass, LV_EVENT_CLICKED, + nullptr); + } + } + + lv_obj_t *row = lv_obj_create(dlg); + if (row) { + lv_obj_set_size(row, UI_HOR_RES - UW(40), UH(28)); + lv_obj_align(row, LV_ALIGN_BOTTOM_MID, UX(0), UY(0)); + lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0); + lv_obj_set_style_border_width(row, 0, 0); + lv_obj_set_style_pad_all(row, 0, 0); + lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_CENTER); + lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE); + make_btn(row, "Back", COL_BTN, on_dlg_back, UW(70), UH(26)); + make_btn(row, "Join", COL_BTN_ACC, on_join, UW(70), UH(26)); + } +} + static void show_password_dialog(const char *ssid) { + ensure_pass_dialog(); strncpy(g_pick_ssid, ssid, WIFI_PROV_SSID_MAX); g_pick_ssid[WIFI_PROV_SSID_MAX] = '\0'; if (lbl_dlg_ssid) { @@ -152,6 +266,18 @@ static void on_row_join(lv_event_t *e) { show_password_dialog(g_items[idx].ssid); } +static void sort_items_by_rssi() { + for (int i = 0; i + 1 < g_item_n; ++i) { + for (int j = i + 1; j < g_item_n; ++j) { + if (g_items[j].rssi > g_items[i].rssi) { + WifiScanItem tmp = g_items[i]; + g_items[i] = g_items[j]; + g_items[j] = tmp; + } + } + } +} + static void rebuild_list() { if (!list_wifi) { return; @@ -160,67 +286,57 @@ static void rebuild_list() { if (g_item_n <= 0) { lv_obj_t *lab = lv_label_create(list_wifi); - if (!lab) { - return; + if (lab) { + lv_label_set_text(lab, "No networks. Tap Scan."); + lv_obj_set_style_text_color(lab, lv_color_hex(COL_MUTED), 0); } - lv_label_set_text(lab, "No networks. Tap Scan."); - lv_obj_set_style_text_color(lab, lv_color_hex(COL_MUTED), 0); return; } - /* Compact rows (label + Join) — 3.5 UI + keyboard needs LV_MEM headroom. */ - for (int i = 0; i < g_item_n; ++i) { - lv_obj_t *row = lv_obj_create(list_wifi); - if (!row) { - Serial.printf("[ui] OOM at wifi row %d/%d\n", i, g_item_n); + sort_items_by_rssi(); + const int n = + g_item_n > WIFI_UI_LIST_MAX ? WIFI_UI_LIST_MAX : g_item_n; + + /* One button per SSID — avoid nested row/label/button (OOM on 3.5). */ + for (int i = 0; i < n; ++i) { + char line[56]; + snprintf(line, sizeof(line), "%s %ddBm", g_items[i].ssid, + (int)g_items[i].rssi); + + lv_obj_t *btn = lv_button_create(list_wifi); + if (!btn) { + Serial.printf("[ui] OOM at wifi row %d/%d\n", i, n); break; } - lv_obj_set_width(row, lv_pct(100)); - lv_obj_set_height(row, UH(34)); - lv_obj_set_style_bg_color(row, lv_color_hex(0x121A24), 0); - lv_obj_set_style_bg_opa(row, LV_OPA_COVER, 0); - lv_obj_set_style_border_width(row, 0, 0); - lv_obj_set_style_radius(row, 6, 0); - lv_obj_set_style_pad_hor(row, 6, 0); - lv_obj_set_style_pad_ver(row, 2, 0); - lv_obj_set_style_margin_bottom(row, 4, 0); - lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_clear_flag(row, LV_OBJ_FLAG_CLICKABLE); - lv_obj_add_flag(row, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_set_width(btn, lv_pct(100)); + lv_obj_set_height(btn, UH(40)); + lv_obj_set_style_bg_color(btn, lv_color_hex(0x1A4A6E), 0); + lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0); + lv_obj_set_style_radius(btn, 6, 0); + lv_obj_set_style_border_width(btn, 0, 0); + lv_obj_set_style_shadow_width(btn, 0, 0); + lv_obj_set_style_margin_bottom(btn, 4, 0); + lv_obj_set_ext_click_area(btn, 8); - char line[64]; - snprintf(line, sizeof(line), LV_SYMBOL_WIFI " %s %ddBm", g_items[i].ssid, - (int)g_items[i].rssi); - lv_obj_t *lab = lv_label_create(row); + lv_obj_t *lab = lv_label_create(btn); if (!lab) { break; } lv_label_set_text(lab, line); lv_label_set_long_mode(lab, LV_LABEL_LONG_DOT); - lv_obj_set_flex_grow(lab, 1); + lv_obj_set_width(lab, lv_pct(92)); lv_obj_set_style_text_color(lab, lv_color_hex(COL_INK), 0); lv_obj_set_style_text_font(lab, &lv_font_montserrat_14, 0); + lv_obj_center(lab); + lv_obj_clear_flag(lab, LV_OBJ_FLAG_CLICKABLE); - lv_obj_t *join = lv_button_create(row); - if (!join) { - break; - } - lv_obj_set_size(join, UW(56), UH(28)); - style_btn(join, COL_BTN_ACC); - lv_obj_t *jl = lv_label_create(join); - if (jl) { - lv_label_set_text(jl, "Join"); - lv_obj_set_style_text_color(jl, lv_color_hex(COL_INK), 0); - lv_obj_set_style_text_font(jl, &lv_font_montserrat_14, 0); - lv_obj_center(jl); - lv_obj_clear_flag(jl, LV_OBJ_FLAG_CLICKABLE); - } - lv_obj_add_event_cb(join, on_row_join, LV_EVENT_CLICKED, + lv_obj_add_event_cb(btn, on_row_join, LV_EVENT_CLICKED, (void *)(intptr_t)i); } + + if (g_item_n > WIFI_UI_LIST_MAX) { + Serial.printf("[ui] showing top %d of %d by RSSI\n", n, g_item_n); + } } static void do_scan() { @@ -330,6 +446,9 @@ static void refresh_status_labels() { } static void build_status() { + if (scr_status) { + return; + } scr_status = lv_obj_create(NULL); lv_obj_set_style_bg_color(scr_status, lv_color_hex(COL_BG), 0); lv_obj_set_style_bg_opa(scr_status, LV_OPA_COVER, 0); @@ -376,6 +495,9 @@ static void build_status() { } static void build_setup() { + if (scr_setup) { + return; + } scr_setup = lv_obj_create(NULL); lv_obj_set_style_bg_color(scr_setup, lv_color_hex(COL_BG), 0); lv_obj_set_style_bg_opa(scr_setup, LV_OPA_COVER, 0); @@ -404,7 +526,6 @@ static void build_setup() { LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_column(bar, UW(8), 0); lv_obj_clear_flag(bar, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_add_flag(bar, LV_OBJ_FLAG_CLICKABLE); make_btn(bar, "Scan", COL_BTN_ACC, on_scan, UW(90), UH(42)); make_btn(bar, "Phone AP", COL_BTN, on_portal, UW(110), UH(42)); @@ -424,101 +545,40 @@ static void build_setup() { lv_obj_set_flex_align(list_wifi, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); lv_obj_set_scroll_dir(list_wifi, LV_DIR_VER); - // 必须 CLICKABLE,否则列表区域收不到拖动手势,只有点到子按钮才可能滚 lv_obj_add_flag(list_wifi, LV_OBJ_FLAG_SCROLLABLE); lv_obj_add_flag(list_wifi, LV_OBJ_FLAG_CLICKABLE); lv_obj_add_flag(list_wifi, LV_OBJ_FLAG_SCROLL_ELASTIC); lv_obj_add_flag(list_wifi, LV_OBJ_FLAG_SCROLL_MOMENTUM); - - // 密码层 - overlay = lv_obj_create(scr_setup); - lv_obj_set_size(overlay, UI_HOR_RES, UI_VER_RES); - lv_obj_set_style_bg_color(overlay, lv_color_hex(0x000000), 0); - lv_obj_set_style_bg_opa(overlay, LV_OPA_70, 0); - lv_obj_set_style_border_width(overlay, 0, 0); - lv_obj_set_style_pad_all(overlay, 0, 0); - lv_obj_clear_flag(overlay, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_add_flag(overlay, LV_OBJ_FLAG_HIDDEN); - - dlg = lv_obj_create(overlay); - lv_obj_set_size(dlg, UI_HOR_RES - UW(20), UH(100)); - lv_obj_align(dlg, LV_ALIGN_TOP_MID, UX(0), UY(8)); - lv_obj_set_style_bg_color(dlg, lv_color_hex(0x15202C), 0); - lv_obj_set_style_border_width(dlg, 0, 0); - lv_obj_set_style_radius(dlg, 10, 0); - lv_obj_set_style_pad_all(dlg, 8, 0); - lv_obj_clear_flag(dlg, LV_OBJ_FLAG_SCROLLABLE); - - lbl_dlg_ssid = lv_label_create(dlg); - lv_label_set_text(lbl_dlg_ssid, ""); - lv_obj_set_style_text_color(lbl_dlg_ssid, lv_color_hex(COL_ACCENT), 0); - lv_obj_set_style_text_font(lbl_dlg_ssid, &lv_font_montserrat_14, 0); - lv_obj_align(lbl_dlg_ssid, LV_ALIGN_TOP_LEFT, UX(0), UY(0)); - - lv_obj_t *pass_row = lv_obj_create(dlg); - lv_obj_set_size(pass_row, UI_HOR_RES - UW(40), UH(32)); - lv_obj_align(pass_row, LV_ALIGN_TOP_LEFT, UX(0), UY(22)); - lv_obj_set_style_bg_opa(pass_row, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(pass_row, 0, 0); - lv_obj_set_style_pad_all(pass_row, 0, 0); - lv_obj_set_flex_flow(pass_row, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(pass_row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - lv_obj_set_style_pad_column(pass_row, UW(6), 0); - lv_obj_clear_flag(pass_row, LV_OBJ_FLAG_SCROLLABLE); - - ta_pass = lv_textarea_create(pass_row); - lv_obj_set_size(ta_pass, UI_HOR_RES - UW(40) - UW(62), UH(32)); - lv_textarea_set_one_line(ta_pass, true); - lv_textarea_set_password_mode(ta_pass, true); - lv_textarea_set_placeholder_text(ta_pass, "Password"); - lv_obj_set_style_text_font(ta_pass, &lv_font_montserrat_14, 0); - - btn_show_pass = lv_button_create(pass_row); - lv_obj_set_size(btn_show_pass, UW(56), UH(28)); - style_btn(btn_show_pass, COL_BTN); - lbl_show_pass = lv_label_create(btn_show_pass); - lv_label_set_text(lbl_show_pass, "Show"); - lv_obj_set_style_text_color(lbl_show_pass, lv_color_hex(COL_INK), 0); - lv_obj_set_style_text_font(lbl_show_pass, &lv_font_montserrat_14, 0); - lv_obj_center(lbl_show_pass); - lv_obj_add_event_cb(btn_show_pass, on_toggle_pass, LV_EVENT_CLICKED, nullptr); - - lv_obj_t *row = lv_obj_create(dlg); - lv_obj_set_size(row, UI_HOR_RES - UW(40), UH(28)); - lv_obj_align(row, LV_ALIGN_BOTTOM_MID, UX(0), UY(0)); - lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0); - lv_obj_set_style_border_width(row, 0, 0); - lv_obj_set_style_pad_all(row, 0, 0); - lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, - LV_FLEX_ALIGN_CENTER); - lv_obj_clear_flag(row, LV_OBJ_FLAG_SCROLLABLE); - make_btn(row, "Back", COL_BTN, on_dlg_back, UW(70), UH(26)); - make_btn(row, "Join", COL_BTN_ACC, on_join, UW(70), UH(26)); - /* Keyboard is created lazily in show_password_dialog (saves LV_MEM on 3.5). */ + /* Password dialog + keyboard created lazily — keep LV_MEM free for scan list. */ } void wifi_ui_create() { - build_status(); - build_setup(); - if (wifi_prov_has_credentials()) { + build_status(); wifi_ui_show_status(); } else { + build_setup(); wifi_ui_show_setup(); - set_setup_status("Tap Scan"); + set_setup_status("Scanning..."); + /* Defer auto-scan so first frame paints; async so UI stays alive. */ + g_auto_scan_armed = true; + g_auto_scan_at = millis() + 800; } - refresh_status_labels(); } void wifi_ui_show_setup() { + if (!scr_setup) { + build_setup(); + } g_on_setup = true; hide_password_dialog(); lv_screen_load(scr_setup); } void wifi_ui_show_status() { + if (!scr_status) { + build_status(); + } g_on_setup = false; hide_password_dialog(); refresh_status_labels(); @@ -528,14 +588,21 @@ void wifi_ui_show_status() { bool wifi_ui_is_setup() { return g_on_setup; } void wifi_ui_tick() { + if (g_auto_scan_armed && (int32_t)(millis() - g_auto_scan_at) >= 0) { + g_auto_scan_armed = false; + do_scan(); + } + if (g_scan_pending) { const int n = wifi_prov_scan_poll(g_items, WIFI_PROV_MAX_SCAN); if (n >= 0) { g_scan_pending = false; g_item_n = n; rebuild_list(); - char buf[40]; - snprintf(buf, sizeof(buf), "Found %d", g_item_n); + char buf[48]; + const int shown = + g_item_n > WIFI_UI_LIST_MAX ? WIFI_UI_LIST_MAX : g_item_n; + snprintf(buf, sizeof(buf), "Found %d (top %d)", g_item_n, shown); set_setup_status(buf); } } @@ -555,7 +622,6 @@ void wifi_ui_tick() { } } - /* Throttle status refresh — every 5ms was wasteful and could feel sticky. */ if (!g_on_setup && (millis() - g_last_status_ms >= 500)) { g_last_status_ms = millis(); refresh_status_labels();