Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion docs/current-progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。
Expand Down Expand Up @@ -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`。右侧月历的最终
物理画面仍由现场目视确认。
2 changes: 1 addition & 1 deletion main/app_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <driver/gpio.h>
#include <driver/spi_common.h>

#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"

Expand Down
134 changes: 81 additions & 53 deletions main/ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t*>(heap_caps_malloc(qrcodegen_BUFFER_LEN_MAX, MALLOC_CAP_8BIT));
uint8_t* tmp = static_cast<uint8_t*>(heap_caps_malloc(qrcodegen_BUFFER_LEN_MAX, MALLOC_CAP_8BIT));
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions main/ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
18 changes: 18 additions & 0 deletions scripts/check_firmware_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading