From c0d6dbcf7d61cee399ed378ba963794f6b222eb5 Mon Sep 17 00:00:00 2001 From: csc Date: Sun, 26 Jul 2026 18:57:51 +0800 Subject: [PATCH] fix: redraw QR calendar after time sync --- CMakeLists.txt | 2 +- README.md | 2 +- docs/current-progress.md | 18 +++- main/app_config.h | 2 +- main/ui.cc | 134 ++++++++++++++++---------- main/ui.h | 2 + scripts/check_firmware_regressions.py | 18 ++++ 7 files changed, 121 insertions(+), 57 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c444bc..ff6e066 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -set(PROJECT_VER "0.1.90") +set(PROJECT_VER "0.1.91") include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(weclawbot) diff --git a/README.md b/README.md index ec92e38..5d8bf73 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ BUILD_DIR="$PWD/build-note4" OUT_DIR="$PWD/web/firmware-note4" ./scripts/prepare ``` Both hardware targets and both build channels render the same versioned -upper-left title, for example `微笺 v0.1.90`. The title is derived from +upper-left title, for example `微笺 v0.1.91`. The title is derived from `WEC_FIRMWARE_VERSION`; there is no separate development-build label. ## Repository Map diff --git a/docs/current-progress.md b/docs/current-progress.md index a705c43..0ee9c05 100644 --- a/docs/current-progress.md +++ b/docs/current-progress.md @@ -68,7 +68,7 @@ WeClawBot / 微笺屏是基于 Waveshare ESP32-S3-RLCD-4.2 的开源固件项目 已知版本问题: -- 公开 `main` 的当前固件源码版本为 `0.1.90`,正式 Release/安装资产仍为 +- 公开 `main` 的当前固件源码版本为 `0.1.91`,正式 Release/安装资产仍为 `v0.1.78`;Web 安装页连接真机后显示设备上报版本。 - `scripts/prepare_web_firmware.sh` 已重新生成 `web/firmware/`。 - 下一次正式发布前仍必须核对 release 产物和安装页 manifest。 @@ -1293,3 +1293,19 @@ weclawbotctl clear (应用分区余 23%),Note 4 `0x633bb0`(余 22%);两份二进制均确认包含 `微笺 v0.1.90`,生成配置中不存在旧开发版标记。正式 Release/安装页资产和 真机目视验证仍需单独记录。 + +## 2026-07-26 二维码日历联网校时后重绘 + +- 用户报告微雪 RLCD 已显示联网信号和正确时分,但右侧月历仍停在 + `---- 年 / --月 / 正在校时`。串口 `WEC:GET` 同时确认设备时间为 + `2026-07-26`、Wi-Fi 已连接,排除网络和 SNTP 本身故障。 +- 根因是 `Ui::Tick()` 原先显式排除了二维码日历页;系统时间从无效变为有效时只更新 + 左侧时钟标签,没有重建右侧按日期生成的月历对象。`0.1.91` 将二维码日历的构建收敛 + 为可复用路径,并在校时完成或跨日时保留二维码资源、接管状态和底部提示后重建页面。 + 普通日历原有校时和跨日逻辑不变。 +- 回归契约新增二维码页校时重建及状态保留检查。固件回归通过;ESP-IDF 5.5.2 两款公开 + 配置隔离构建通过:微雪 `0x623410`(应用分区余 23%),Note 4 `0x633c30` + (余 22%)。微雪 RLCD 已保留 NVS 自动刷入 `0.1.91`,全部镜像哈希校验通过;无按键 + 清除强制下载位并以看门狗复位回应用态后,`WEC:GET` 确认板型、版本、日期时间和 + Wi-Fi 正确,官方二维码日历页处于活动状态,MQTT 已恢复 `online`。右侧月历的最终 + 物理画面仍由现场目视确认。 diff --git a/main/app_config.h b/main/app_config.h index bce9e3c..d76f3f7 100644 --- a/main/app_config.h +++ b/main/app_config.h @@ -3,7 +3,7 @@ #include #include -#define WEC_FIRMWARE_VERSION "0.1.90" +#define WEC_FIRMWARE_VERSION "0.1.91" #define WEC_PRODUCT_URL "https://weclawbot.link" #define WEC_PRODUCT_HOST "weclawbot.link" diff --git a/main/ui.cc b/main/ui.cc index 224d5c1..4a80092 100644 --- a/main/ui.cc +++ b/main/ui.cc @@ -431,6 +431,66 @@ void Ui::ShowSetup(const char* status) { display_.Unlock(); } +void Ui::ShowQrCalendarLocked() { + calendar_home_active_ = true; + note_view_active_ = false; + qr_calendar_active_ = true; + + HeaderLocked(WEC_SCREEN_TITLE, ""); + + home_time_shadow_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_30_4, + Black(), 27, 42, 160); + home_time_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_30_4, + Black(), 26, 42, 160); + lv_obj_set_style_text_align(home_time_shadow_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_align(home_time_label_, LV_TEXT_ALIGN_CENTER, 0); + + home_date_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_16_4, + Black(), 16, 86, 176); + lv_obj_add_flag(home_date_label_, LV_OBJ_FLAG_HIDDEN); + + home_sensor_shadow_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_20_4, + Black(), 21, 83, 176); + home_sensor_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_20_4, + Black(), 20, 83, 176); + lv_label_set_long_mode(home_sensor_shadow_label_, LV_LABEL_LONG_CLIP); + lv_label_set_long_mode(home_sensor_label_, LV_LABEL_LONG_CLIP); + lv_obj_set_style_text_align(home_sensor_shadow_label_, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_style_text_align(home_sensor_label_, LV_TEXT_ALIGN_CENTER, 0); + + std::time_t now = 0; + std::time(&now); + std::tm tm = {}; + localtime_r(&now, &tm); + calendar_time_ready_ = tm.tm_year >= 120; + calendar_date_key_ = CalendarDateKey(tm); + + DrawCalendarLocked(tm); + DrawQrPanelLocked(qr_status_.c_str(), 0); + FooterLocked(calendar_footer_.c_str()); + UpdateStatusLabelsLocked(); +} + +void Ui::RebuildQrCalendarLocked() { + lv_image_dsc_t* qr_dsc = qr_dsc_; + uint8_t* qr_buf = qr_buf_; + const TakeoverView takeover_view = takeover_view_; + const std::string qr_status = qr_status_; + const std::string footer = calendar_footer_; + + // Clear the LVGL objects without releasing the active QR bitmap. The same + // descriptor is attached to the rebuilt page after the date becomes valid. + qr_dsc_ = nullptr; + qr_buf_ = nullptr; + ClearLocked(); + qr_dsc_ = qr_dsc; + qr_buf_ = qr_buf; + takeover_view_ = takeover_view; + qr_status_ = qr_status; + calendar_footer_ = footer; + ShowQrCalendarLocked(); +} + void Ui::ShowQr(const char* qr_url, int seconds_left) { uint8_t* qr = static_cast(heap_caps_malloc(qrcodegen_BUFFER_LEN_MAX, MALLOC_CAP_8BIT)); uint8_t* tmp = static_cast(heap_caps_malloc(qrcodegen_BUFFER_LEN_MAX, MALLOC_CAP_8BIT)); @@ -533,48 +593,12 @@ void Ui::ShowQr(const char* qr_url, int seconds_left) { return; } ClearLocked(); - ReleaseQrAssets(); qr_buf_ = pixel_buf; qr_dsc_ = dsc; takeover_view_ = TakeoverView::kOfficial; - calendar_home_active_ = true; - note_view_active_ = false; - qr_calendar_active_ = true; qr_status_ = "等待扫码"; calendar_footer_ = QrFooterText("微信扫码连接", seconds_left); - - HeaderLocked(WEC_SCREEN_TITLE, ""); - - home_time_shadow_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_30_4, - Black(), 27, 42, 160); - home_time_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_30_4, - Black(), 26, 42, 160); - lv_obj_set_style_text_align(home_time_shadow_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_align(home_time_label_, LV_TEXT_ALIGN_CENTER, 0); - - home_date_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_16_4, - Black(), 16, 86, 176); - lv_obj_add_flag(home_date_label_, LV_OBJ_FLAG_HIDDEN); - - home_sensor_shadow_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_20_4, - Black(), 21, 83, 176); - home_sensor_label_ = LabelLocked(lv_screen_active(), "", &font_puhui_20_4, - Black(), 20, 83, 176); - lv_label_set_long_mode(home_sensor_shadow_label_, LV_LABEL_LONG_CLIP); - lv_label_set_long_mode(home_sensor_label_, LV_LABEL_LONG_CLIP); - lv_obj_set_style_text_align(home_sensor_shadow_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_align(home_sensor_label_, LV_TEXT_ALIGN_CENTER, 0); - - std::time_t now = 0; - std::time(&now); - std::tm tm = {}; - localtime_r(&now, &tm); - calendar_time_ready_ = tm.tm_year >= 120; - - DrawCalendarLocked(tm); - DrawQrPanelLocked("微信扫码连接", seconds_left); - FooterLocked(calendar_footer_.c_str()); - UpdateStatusLabelsLocked(); + ShowQrCalendarLocked(); display_.Unlock(); } @@ -972,23 +996,27 @@ void Ui::Tick() { const int date_key = CalendarDateKey(tm); const bool date_changed = time_ready && calendar_date_key_ != 0 && date_key != calendar_date_key_; - if (calendar_home_active_ && !qr_calendar_active_ && + if (calendar_home_active_ && (time_ready != calendar_time_ready_ || date_changed)) { - const std::string header_detail = header_detail_; - const std::string footer = calendar_footer_; - const bool thinking = calendar_thinking_; - const bool show_pet = calendar_pet_visible_; - const TakeoverView takeover_view = takeover_view_; - const CalendarOverlay overlay = calendar_overlay_; - const std::string overlay_text = calendar_overlay_text_; - const std::string overlay_detail = calendar_overlay_detail_; - ClearLocked(); - takeover_view_ = takeover_view; - calendar_overlay_ = overlay; - calendar_overlay_text_ = overlay_text; - calendar_overlay_detail_ = overlay_detail; - ShowCalendarHomeLocked(header_detail.c_str(), footer.c_str(), thinking, show_pet); - RenderCalendarOverlayLocked(); + if (qr_calendar_active_) { + RebuildQrCalendarLocked(); + } else { + const std::string header_detail = header_detail_; + const std::string footer = calendar_footer_; + const bool thinking = calendar_thinking_; + const bool show_pet = calendar_pet_visible_; + const TakeoverView takeover_view = takeover_view_; + const CalendarOverlay overlay = calendar_overlay_; + const std::string overlay_text = calendar_overlay_text_; + const std::string overlay_detail = calendar_overlay_detail_; + ClearLocked(); + takeover_view_ = takeover_view; + calendar_overlay_ = overlay; + calendar_overlay_text_ = overlay_text; + calendar_overlay_detail_ = overlay_detail; + ShowCalendarHomeLocked(header_detail.c_str(), footer.c_str(), thinking, show_pet); + RenderCalendarOverlayLocked(); + } } else { calendar_time_ready_ = time_ready; calendar_date_key_ = date_key; diff --git a/main/ui.h b/main/ui.h index 8637043..48628d0 100644 --- a/main/ui.h +++ b/main/ui.h @@ -99,6 +99,8 @@ class Ui { const char* footer = "等待内容上屏", bool thinking = false, bool show_pet = true); + void ShowQrCalendarLocked(); + void RebuildQrCalendarLocked(); void RenderCalendarOverlayLocked(); void UpdateStatusLabelsLocked(); void UpdateNetworkIndicatorLocked(); diff --git a/scripts/check_firmware_regressions.py b/scripts/check_firmware_regressions.py index 450aebf..e187dc8 100755 --- a/scripts/check_firmware_regressions.py +++ b/scripts/check_firmware_regressions.py @@ -298,6 +298,24 @@ def check_network_time_sync() -> None: ]: if required not in body: fail(f"network time sync contract is missing {required}") + ui_source = read("main/ui.cc") + tick_body = find_function_body(ui_source, "void Ui::Tick()") + if ( + "if (qr_calendar_active_)" not in tick_body + or "RebuildQrCalendarLocked();" not in tick_body + ): + fail("QR calendar must rebuild after network time becomes valid") + rebuild_body = find_function_body(ui_source, "void Ui::RebuildQrCalendarLocked()") + for required in [ + "qr_dsc_ = nullptr;", + "qr_buf_ = nullptr;", + "takeover_view_ = takeover_view;", + "qr_status_ = qr_status;", + "calendar_footer_ = footer;", + "ShowQrCalendarLocked();", + ]: + if required not in rebuild_body: + fail(f"QR calendar time-sync rebuild must preserve {required}") def check_epd_carousel_contract() -> None: