diff --git a/firmware/main/board_147b.h b/firmware/main/board_147b.h index b3503da..7882ab3 100644 --- a/firmware/main/board_147b.h +++ b/firmware/main/board_147b.h @@ -1,11 +1,11 @@ #pragma once -// ESP32-S3-LCD-1.47B — ST7789 SPI -#define LCD_CS 21 -#define LCD_SCL 38 // SPI CLK -#define LCD_SDA 39 // SPI MOSI -#define LCD_RST 40 -#define LCD_DC 45 -#define LCD_BL 46 // 背光,高=亮(8050) +// ESP32-S3-LCD-1.47B — ST7789 SPI (pins from board schematic) +#define LCD_CS 42 +#define LCD_SCL 40 // SPI CLK +#define LCD_SDA 45 // SPI MOSI (DIN) +#define LCD_RST 39 +#define LCD_DC 41 +#define LCD_BL 46 // backlight, high=on #define LCD_H_RES 172 #define LCD_V_RES 320 #define LCD_X_GAP 34 diff --git a/firmware/main/ui_147b.c b/firmware/main/ui_147b.c index 5ec2503..c3b1045 100644 --- a/firmware/main/ui_147b.c +++ b/firmware/main/ui_147b.c @@ -3,66 +3,108 @@ #define AMBER 0xFFB000 #define DIM 0xC8870F +#define EMPTY 0x5A3F08 +#define STALE 0x9C6A0C +#define BG 0x0B0A07 -static lv_obj_t *lbl_5h_pct, *lbl_reset, *lbl_7d, *bar5, *bar7, *lbl_bottom; +static lv_obj_t *lbl_5h_pct, *lbl_5h_reset, *bar5; +static lv_obj_t *lbl_7d_pct, *bar7; +static lv_obj_t *lbl_session, *lbl_state; -static lv_obj_t *mklbl(lv_obj_t *p, const char *txt, int y, int size, uint32_t color) +static lv_obj_t *mklbl(lv_obj_t *p, const char *txt, int x, int y, + const lv_font_t *font, uint32_t color) { - (void)size; /* size param reserved for future font switching */ lv_obj_t *l = lv_label_create(p); lv_label_set_text(l, txt); lv_obj_set_style_text_color(l, lv_color_hex(color), 0); - lv_obj_set_style_text_font(l, &lv_font_montserrat_14, 0); - lv_obj_set_pos(l, 8, y); + lv_obj_set_style_text_font(l, font, 0); + lv_obj_set_pos(l, x, y); return l; } -void ui_build(lv_display_t *disp) +// Thin horizontal rule spanning the column. +static void mksep(lv_obj_t *p, int x, int y, int w, uint32_t color) { - lv_obj_t *scr = lv_display_get_screen_active(disp); - lv_obj_set_style_bg_color(scr, lv_color_hex(0x0B0A07), 0); + 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); +} - mklbl(scr, "TOKENPULSE", 6, 14, AMBER); - mklbl(scr, "CLAUDE CODE", 40, 14, AMBER); - mklbl(scr, "5H WINDOW", 64, 14, DIM); +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); + lv_obj_set_size(b, w, h); + lv_obj_set_pos(b, x, y); + lv_obj_set_style_bg_color(b, lv_color_hex(EMPTY), 0); + lv_obj_set_style_bg_color(b, lv_color_hex(AMBER), LV_PART_INDICATOR); + lv_bar_set_range(b, 0, 100); + return b; +} - lbl_5h_pct = mklbl(scr, "--%", 82, 28, AMBER); +// Portrait 172x320 — mirrors the 4.3C dashboard: 5H hero, thicker bars, +// drawn separators, session line, single ONLINE/STALE status. +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); - bar5 = lv_bar_create(scr); - lv_obj_set_size(bar5, 156, 12); - lv_obj_set_pos(bar5, 8, 120); - lv_obj_set_style_bg_color(bar5, lv_color_hex(0x5A3F08), 0); - lv_obj_set_style_bg_color(bar5, lv_color_hex(AMBER), LV_PART_INDICATOR); + // Header + mklbl(scr, "TOKENPULSE", 8, 8, &lv_font_montserrat_14, AMBER); + mklbl(scr, "CLAUDE CODE", 8, 26, &lv_font_montserrat_14, DIM); + mksep(scr, 8, 48, 156, DIM); - lbl_reset = mklbl(scr, "RESET --:--:--", 138, 14, DIM); - lbl_7d = mklbl(scr, "7-DAY --%", 170, 14, DIM); + // 5H WINDOW (hero) + mklbl(scr, "5H WINDOW", 8, 58, &lv_font_montserrat_14, DIM); + lbl_5h_pct = mklbl(scr, "--%", 8, 78, &lv_font_montserrat_28, AMBER); + bar5 = mkbar(scr, 8, 124, 156, 18); + lbl_5h_reset = mklbl(scr, "RESET --:--:--", 8, 150, &lv_font_montserrat_14, DIM); + mksep(scr, 8, 176, 156, DIM); - bar7 = lv_bar_create(scr); - lv_obj_set_size(bar7, 156, 8); - lv_obj_set_pos(bar7, 8, 192); - lv_obj_set_style_bg_color(bar7, lv_color_hex(0x5A3F08), 0); - lv_obj_set_style_bg_color(bar7, lv_color_hex(AMBER), LV_PART_INDICATOR); + // 7-DAY (secondary, inline) + mklbl(scr, "7-DAY", 8, 188, &lv_font_montserrat_14, DIM); + lbl_7d_pct = mklbl(scr, "--%", 110, 188, &lv_font_montserrat_14, AMBER); + bar7 = mkbar(scr, 8, 214, 156, 14); + mksep(scr, 8, 238, 156, DIM); - lbl_bottom = mklbl(scr, "* RUNNING", 296, 14, AMBER); + // Footer + lbl_session = mklbl(scr, "SESSION $--.--", 8, 250, &lv_font_montserrat_14, AMBER); + lbl_state = mklbl(scr, "* ONLINE", 8, 290, &lv_font_montserrat_14, AMBER); } void ui_update(const ui_state_t *st) { - char buf[32]; - uint32_t c = st->stale ? 0x9C6A0C : AMBER; + char buf[40]; + uint32_t c = st->stale ? STALE : AMBER; + // 5H snprintf(buf, sizeof buf, "%d%%", st->five_pct); lv_label_set_text(lbl_5h_pct, st->stale ? "--%" : buf); lv_obj_set_style_text_color(lbl_5h_pct, lv_color_hex(c), 0); lv_bar_set_value(bar5, st->stale ? 0 : st->five_pct, LV_ANIM_OFF); long s = st->five_reset_s; - snprintf(buf, sizeof buf, "RESET %02ld:%02ld:%02ld", s / 3600, (s % 3600) / 60, s % 60); - lv_label_set_text(lbl_reset, st->stale ? "RESET --:--:--" : buf); + snprintf(buf, sizeof buf, "RESET %02ld:%02ld:%02ld", + s / 3600, (s % 3600) / 60, s % 60); + lv_label_set_text(lbl_5h_reset, st->stale ? "RESET --:--:--" : buf); - snprintf(buf, sizeof buf, "7-DAY %d%%", st->week_pct); - lv_label_set_text(lbl_7d, st->stale ? "7-DAY --%" : buf); + // 7D + snprintf(buf, sizeof buf, "%d%%", st->week_pct); + lv_label_set_text(lbl_7d_pct, st->stale ? "--%" : buf); + 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); - (void)lbl_bottom; /* bottom label stays static in ui_update */ + // Session cost + if (st->stale) { + lv_label_set_text(lbl_session, "SESSION $--.--"); + } else { + snprintf(buf, sizeof buf, "SESSION $%.2f", st->session_usd); + lv_label_set_text(lbl_session, buf); + } + + // 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"); } diff --git a/firmware/sdkconfig.defaults b/firmware/sdkconfig.defaults index 5a3fb85..c14c98f 100644 --- a/firmware/sdkconfig.defaults +++ b/firmware/sdkconfig.defaults @@ -7,6 +7,8 @@ CONFIG_SPIRAM_SPEED_80M=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_FREERTOS_HZ=1000 +# Bigger font for the hero metric (both boards) +CONFIG_LV_FONT_MONTSERRAT_28=y # TinyUSB CDC + HID composite CONFIG_TINYUSB_CDC_ENABLED=y CONFIG_TINYUSB_CDC_COUNT=1