From 0a75bf8b8c9e8e3f4627f230bb6261ec6393fc62 Mon Sep 17 00:00:00 2001 From: yuchou87 Date: Sat, 20 Jun 2026 11:37:02 +0800 Subject: [PATCH 1/2] feat(fw): refine 4.3C landscape layout - 5H% is now the hero (montserrat_48); 7-DAY demoted to montserrat_28 for clear visual hierarchy. - Thicker/wider 5H bar (360x30), thinner 7D bar (320x18). - Drop the redundant static '* RUNNING' (keep the single data-driven ONLINE/ STALE indicator top-right). - Replace ASCII '=' / '-' separators with thin drawn rules that span the width. Compile-verified (43C). --- firmware/main/ui_43c.c | 58 ++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/firmware/main/ui_43c.c b/firmware/main/ui_43c.c index b6a08fe..4f13735 100644 --- a/firmware/main/ui_43c.c +++ b/firmware/main/ui_43c.c @@ -22,6 +22,17 @@ static lv_obj_t *mklbl(lv_obj_t *p, const char *txt, int x, int y, return l; } +// Thin horizontal rule (cleaner + spans exactly, vs a row of '=' characters). +static void mksep(lv_obj_t *p, int x, int y, int w, uint32_t color) +{ + lv_obj_t *o = lv_obj_create(p); + lv_obj_remove_style_all(o); + lv_obj_set_size(o, w, 2); + lv_obj_set_pos(o, x, y); + lv_obj_set_style_bg_color(o, lv_color_hex(color), 0); + lv_obj_set_style_bg_opa(o, LV_OPA_COVER, 0); +} + static lv_obj_t *mkbar(lv_obj_t *p, int x, int y, int w, int h) { lv_obj_t *b = lv_bar_create(p); @@ -38,29 +49,26 @@ void ui_build(lv_display_t *disp) lv_obj_t *scr = lv_display_get_screen_active(disp); lv_obj_set_style_bg_color(scr, lv_color_hex(BG), 0); - // 顶栏 - mklbl(scr, "TOKENPULSE", 24, 18, &lv_font_montserrat_28, AMBER); - mklbl(scr, "CLAUDE CODE", 24, 56, &lv_font_montserrat_14, DIM); - lbl_state = mklbl(scr, "* ONLINE", 640, 24, &lv_font_montserrat_14, AMBER); - mklbl(scr, "================================================", 24, 78, - &lv_font_montserrat_14, DIM); - - // 左面板: 5H WINDOW - mklbl(scr, "5H WINDOW", 40, 110, &lv_font_montserrat_28, DIM); - lbl_5h_pct = mklbl(scr, "--%", 40, 150, &lv_font_montserrat_48, AMBER); - bar5 = mkbar(scr, 40, 240, 340, 22); - lbl_5h_reset = mklbl(scr, "RESET --:--:--", 40, 280, &lv_font_montserrat_28, DIM); - - // 右面板: 7-DAY - mklbl(scr, "7-DAY", 440, 110, &lv_font_montserrat_28, DIM); - lbl_7d_pct = mklbl(scr, "--%", 440, 150, &lv_font_montserrat_48, AMBER); - bar7 = mkbar(scr, 440, 240, 340, 22); - - // 底栏: SESSION 花费 + 分隔 - mklbl(scr, "------------------------------------------------", 24, 380, - &lv_font_montserrat_14, DIM); - lbl_session = mklbl(scr, "SESSION $--.--", 40, 410, &lv_font_montserrat_28, AMBER); - mklbl(scr, "* RUNNING", 640, 420, &lv_font_montserrat_14, AMBER); + // Header + mklbl(scr, "TOKENPULSE", 32, 18, &lv_font_montserrat_28, AMBER); + mklbl(scr, "CLAUDE CODE", 32, 56, &lv_font_montserrat_14, DIM); + lbl_state = mklbl(scr, "* ONLINE", 648, 26, &lv_font_montserrat_14, AMBER); + mksep(scr, 32, 88, 736, DIM); + + // Left column — 5H WINDOW (primary metric) + mklbl(scr, "5H WINDOW", 40, 120, &lv_font_montserrat_28, DIM); + lbl_5h_pct = mklbl(scr, "--%", 40, 158, &lv_font_montserrat_48, AMBER); + bar5 = mkbar(scr, 40, 252, 360, 30); + lbl_5h_reset = mklbl(scr, "RESET --:--:--", 40, 300, &lv_font_montserrat_28, DIM); + + // Right column — 7-DAY (secondary, smaller) + mklbl(scr, "7-DAY", 440, 120, &lv_font_montserrat_28, DIM); + lbl_7d_pct = mklbl(scr, "--%", 440, 160, &lv_font_montserrat_28, AMBER); + bar7 = mkbar(scr, 440, 210, 320, 18); + + // Footer — session cost + mksep(scr, 32, 372, 736, DIM); + lbl_session = mklbl(scr, "SESSION $--.--", 40, 398, &lv_font_montserrat_28, AMBER); } void ui_update(const ui_state_t *st) @@ -85,7 +93,7 @@ void ui_update(const ui_state_t *st) lv_obj_set_style_text_color(lbl_7d_pct, lv_color_hex(c), 0); lv_bar_set_value(bar7, st->stale ? 0 : st->week_pct, LV_ANIM_OFF); - // SESSION 花费 + // Session cost if (st->stale) { lv_label_set_text(lbl_session, "SESSION $--.--"); } else { @@ -93,7 +101,7 @@ void ui_update(const ui_state_t *st) lv_label_set_text(lbl_session, buf); } - // 状态行: stale 时灰显 + // Status (single indicator; greyed when stale) lv_obj_set_style_text_color(lbl_state, lv_color_hex(c), 0); lv_label_set_text(lbl_state, st->stale ? "* STALE" : "* ONLINE"); } From 89c2ce12bd9513babd86b91ed2d7597b2f27cec7 Mon Sep 17 00:00:00 2001 From: yuchou87 Date: Sun, 21 Jun 2026 08:24:00 +0800 Subject: [PATCH 2/2] docs(fw): expand re-flash guide with bring-up gotchas - Port name differs: app=cu.usbmodemTP_00011 (CDC) vs download=cu.usbmodem (ROM). - After flashing, esptool RTS reset may land back in download mode -> tap RST. - Don't open /dev/cu.* to check state (DTR resets the board); use ioreg instead. --- firmware/README.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/firmware/README.md b/firmware/README.md index 36a275f..4be601d 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -4,12 +4,27 @@ ESP-IDF v6.0.1 project for ESP32-S3-1.47B (172×320 ST7789 + USB CDC/HID). ## Re-flashing after TinyUSB starts -Once the app is running, TinyUSB takes over the USB port. A plain `idf.py flash` will fail to connect. To re-enter download mode: - -1. Hold the **BOOT** button. -2. Tap **RST**. -3. Release **BOOT**. -4. Run `idf.py flash` — esptool will find the device in download mode. +Once the app is running, TinyUSB takes over the native USB port (it enumerates as +`/dev/cu.usbmodemTP_00011`), so a plain `idf.py flash` can't auto-reset into the +ROM bootloader. Enter download mode manually: + +1. **Hold BOOT**, **tap RST**, **release BOOT**. +2. The board re-enumerates as the ROM USB-Serial/JTAG port (`/dev/cu.usbmodem`, + e.g. `usbmodem1101`) and waits for download. +3. Flash it, pointing at that port: + `idf.py -p /dev/cu.usbmodem flash` + (for 4.3C add `-B build_43c`). + +### Gotchas (learned during bring-up) + +- **After flashing, the board may stay in download mode.** esptool's "Hard + resetting via RTS" sometimes lands back in download (`boot:0x0 DOWNLOAD`) on + these native-USB boards. Just **tap RST once (no BOOT)** to boot the app. +- **Don't open the serial port to "check" it.** Opening `/dev/cu.*` (cat, + screen, `idf.py monitor`) asserts DTR and resets the board. Use + `ioreg -p IOUSB -l | grep "USB Product Name"` to see whether it enumerated as + `TokenPulse` (app running) vs `USB JTAG_serial debug unit` (ROM) without + resetting it. ## 目标板选择