From 6baf93c5501c26d80293af789cb6e331fe54c309 Mon Sep 17 00:00:00 2001 From: edisondeng Date: Fri, 31 Jul 2026 19:43:43 +0800 Subject: [PATCH] feat(ui): show real weekday/day/month on HOME screen instead of placeholder UI_ShowClockPage()/UI_ShowSetTimePage() now take weekday/day/month(/year) and build the date bar dynamically via Build_DateString() instead of the fixed placeholder string. app_home.c reads the full date from RTC via PCF8563_ReadDateTime() and recalculates weekday since the RTC's stored date may differ from the software clock's. config.h exposes current_day/month/year/weekday alongside the existing time fields so main.c's debug commands don't need a separate include. --- App/app_home.c | 11 +++-- App/config.h | 4 ++ App/main.c | 6 +-- App/ui.c | 125 ++++++++++++++++++++++++++++++++++++++++--------- App/ui.h | 21 ++++++--- 5 files changed, 131 insertions(+), 36 deletions(-) diff --git a/App/app_home.c b/App/app_home.c index 66b3478..c20da95 100644 --- a/App/app_home.c +++ b/App/app_home.c @@ -44,16 +44,17 @@ static void HomeApp_OnStart(void) current_state = STATE_NORMAL; // 系统切入正常待机状态 RF_SetMode(1); // 开启天线并置为接收模式,监听无线探测器 - // 尝试从 PCF8563 RTC 芯片中读取硬件时间 (具备非阻塞防卡死保护) + // 尝试从 PCF8563 RTC 芯片中读取硬件时间与日期 (具备非阻塞防卡死保护) if (PCF8563_Init()) { - PCF8563_ReadTime(¤t_hour, ¤t_min, ¤t_sec); + PCF8563_ReadDateTime(¤t_hour, ¤t_min, ¤t_sec, ¤t_day, ¤t_month, ¤t_year); + Clock_RecalcWeekday(); // 读回的日期可能与软时钟不同,重新算一次星期 XTELL_LOG("[HomeApp] RTC Hardware Synced OK!\r\n"); } else { XTELL_LOG("[HomeApp] RTC Hardware Offline, using system clock.\r\n"); } // 绘制并渲染待机时钟主页面画面 - UI_ShowClockPage(current_state, current_hour, current_min); + UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month); // 关断/熄灭 FCOB 双灯条 RGB_Send(0, 0, 0, 0, 0, 0); @@ -69,7 +70,7 @@ static void HomeApp_onRun(void) // 软时钟发生分钟改变,需要通知屏幕重画时钟主页 if (clock_updated) { clock_updated = 0; - UI_ShowClockPage(current_state, current_hour, current_min); + UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month); } } @@ -117,7 +118,7 @@ static EventResult HomeApp_onEvent(SystemEvent *evt) } // 布撤防状态切换成功后进行 100ms 短震确认,并重新刷写时钟待机页 MOTOR = 1; Delay_ms(100); MOTOR = 0; - UI_ShowClockPage(current_state, current_hour, current_min); + UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month); return EVENT_HANDLED; } diff --git a/App/config.h b/App/config.h index d95bab3..cc37069 100644 --- a/App/config.h +++ b/App/config.h @@ -141,6 +141,10 @@ extern SystemState current_state; // 系统当前整体运行状态 extern u8 current_hour; // 当前系统时间:小时 extern u8 current_min; // 当前系统时间:分钟 extern u8 current_sec; // 当前系统时间:秒 +extern u8 current_day; // 当前系统日期:日 +extern u8 current_month; // 当前系统日期:月 +extern u8 current_year; // 当前系统日期:年 (0~99,代表 2000~2099) +extern u8 current_weekday; // 当前系统日期:星期 (0=周日, ..., 6=周六) extern volatile u8 clock_updated; // 时钟分钟更新标志 (1表示需要刷新待机页时间) extern volatile bit second_tick_flag; // 整秒滴答标志位,每秒置 1,由主循环转换为 KEY_EVENT_SECOND_TICK 事件 diff --git a/App/main.c b/App/main.c index 885756a..648a650 100644 --- a/App/main.c +++ b/App/main.c @@ -186,21 +186,21 @@ void Debug_ProcessCommand(char cmd) // 模拟切换至 正常撤防 (NORMAL) 状态并刷新时钟 if (AppManager_GetActiveAppID() == APP_ID_HOME) { current_state = STATE_NORMAL; - UI_ShowClockPage(current_state, current_hour, current_min); + UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month); } Uart_SendString("[UART] State: NORMAL\r\n"); } else if (cmd == '2') { // 模拟切换至 已布防 (ARMED) 状态,普通门磁可触发警报 if (AppManager_GetActiveAppID() == APP_ID_HOME) { current_state = STATE_ARMED; - UI_ShowClockPage(current_state, current_hour, current_min); + UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month); } Uart_SendString("[UART] State: ARMED\r\n"); } else if (cmd == '3') { // 模拟切换至 已撤防 (DISARMED) 状态,忽略普通报警 if (AppManager_GetActiveAppID() == APP_ID_HOME) { current_state = STATE_DISARMED; - UI_ShowClockPage(current_state, current_hour, current_min); + UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month); } Uart_SendString("[UART] State: DISARMED\r\n"); } else if (cmd == 'z' || cmd == 'Z') { diff --git a/App/ui.c b/App/ui.c index 265b7f3..7da2408 100644 --- a/App/ui.c +++ b/App/ui.c @@ -7,19 +7,77 @@ // 引入外部主菜单对码项名称的字符串 ID 表 (例如门磁/PIR/烟感等防区类型) extern const StringID device_menu_ids[5]; +// 底部日期栏固定用 8x16 小字体、LCD_ShowStringCentered 居中绘制在 y=195; +// 编辑日期时对 日/月/年 做局部刷新需要精确知道每个字段的像素 X 坐标,这里按 +// LCD_ShowStringCentered 的居中公式 (LCD_WIDTH - 字符数*8) / 2 手工算好, +// 前提是字符串格式固定为 "WWW DD-MM-YY" (12 个字符),不能随意改动格式。 +#define DATE_BAR_Y 195 +#define DATE_BAR_START_X 19 // (135 - 12*8) / 2 = 19 +#define DATE_BAR_DAY_X (DATE_BAR_START_X + 4 * 8) // 第 4 个字符起 = 51 +#define DATE_BAR_MONTH_X (DATE_BAR_START_X + 7 * 8) // 第 7 个字符起 = 75 +#define DATE_BAR_YEAR_X (DATE_BAR_START_X + 10 * 8) // 第 10 个字符起 = 99 +#define DATE_BAR_FIELD_W 16 // 2 位数字,8px/字符 + +/** + * @brief 拼出"星期缩写 日-月"格式的日期字符串 (如 "VEN 10-07"),供 HOME 页时钟显示 + * @param out 输出缓冲区,调用方需保证至少 10 字节 (3字母+空格+2位+'-'+2位+'\0') + */ +static void Build_DateString(char *out, u8 weekday, u8 day, u8 month) +{ + char *wd = Lang_Get((StringID)(STR_WEEKDAY_SUN + weekday)); + u8 i = 0; + while (*wd) { + out[i++] = *wd++; + } + out[i++] = ' '; + out[i++] = '0' + (day / 10); + out[i++] = '0' + (day % 10); + out[i++] = '-'; + out[i++] = '0' + (month / 10); + out[i++] = '0' + (month % 10); + out[i] = '\0'; +} + +/** + * @brief 拼出"星期缩写 日-月-年"格式的日期字符串 (如 "VEN 10-07-25"),供时间设置页编辑日期时显示 + * @param out 输出缓冲区,调用方需保证至少 13 字节 (3字母+空格+2位+'-'+2位+'-'+2位+'\0') + */ +static void Build_DateStringWithYear(char *out, u8 weekday, u8 day, u8 month, u8 year) +{ + char *wd = Lang_Get((StringID)(STR_WEEKDAY_SUN + weekday)); + u8 i = 0; + while (*wd) { + out[i++] = *wd++; + } + out[i++] = ' '; + out[i++] = '0' + (day / 10); + out[i++] = '0' + (day % 10); + out[i++] = '-'; + out[i++] = '0' + (month / 10); + out[i++] = '0' + (month % 10); + out[i++] = '-'; + out[i++] = '0' + (year / 10); + out[i++] = '0' + (year % 10); + out[i] = '\0'; +} + /** * @brief 渲染并显示系统待机时钟主页面 * @param state 当前的系统布防状态 (STATE_ARMED: 已布防, 否则为正常或撤防) * @param hour 当前待显示的小时数 (0~23) * @param min 当前待显示的分钟数 (0~59) + * @param weekday 当前星期 (0=周日, ..., 6=周六) + * @param day 当前日 (1~31) + * @param month 当前月 (1~12) * @details 该页面被划分为三大功能视觉区: * 1. 顶部状态栏:左侧绘制布撤防指示锁,右侧显示真实剩余电量百分比以及电池单色位图。 * 2. 中部核心栏:垂直居中以 16x32 大号粗字体绘制格式化时间 "HH:MM",在其下方标注 AM/PM 标识。 - * 3. 底部信息栏:使用灰色小字模绘制法语星期日期 "VEN 10-07" (星期五 7月10日)。 + * 3. 底部信息栏:使用灰色小字模绘制真实星期日期 "VEN 10-07" (星期五 7月10日)。 */ -void UI_ShowClockPage(SystemState state, u8 hour, u8 min) +void UI_ShowClockPage(SystemState state, u8 hour, u8 min, u8 weekday, u8 day, u8 month) { char time_str[6]; + char date_str[10]; char batt_str[7]; u8 batt; @@ -74,8 +132,9 @@ void UI_ShowClockPage(SystemState state, u8 hour, u8 min) // ====== 绘制 AM/PM 状态 ====== LCD_ShowStringCentered(150, Lang_Get(STR_CLOCK_AMPM), COLOR_WHITE, COLOR_BLACK); - // ====== 绘制日期星期栏 ====== - LCD_ShowStringCentered(195, Lang_Get(STR_CLOCK_DATE), COLOR_GRAY, COLOR_BLACK); + // ====== 绘制日期星期栏 (真实日期,不再是写死的占位文案) ====== + Build_DateString(date_str, weekday, day, month); + LCD_ShowStringCentered(DATE_BAR_Y, date_str, COLOR_GRAY, COLOR_BLACK); } /** @@ -364,10 +423,14 @@ void UI_ShowMainMenu(u8 selected_index) } /** - * @brief 渲染修改时间闪烁调节页面 (调节小时或分钟) + * @brief 渲染修改时间/日期闪烁调节页面 * @param hour 修改状态中的小时 * @param min 修改状态中的分钟 - * @param selection 当前正在编辑聚焦的属性位 (1: 修改小时, 2: 修改分钟) + * @param day 修改状态中的日 + * @param month 修改状态中的月 + * @param year 修改状态中的年 (0~99,代表 2000~2099) + * @param weekday 根据当前日期算出的星期 (0=周日, ..., 6=周六) + * @param selection 当前正在编辑聚焦的属性位 (1:小时, 2:分钟, 3:日, 4:月, 5:年) * @param blink_on 当前 500ms 控制隐现的闪烁周期标志位 (1-绘制正常字,0-以空格消隐该数值字符) */ // 小时/分钟数字在屏幕上的固定坐标 (16x32 字体,两位数字宽度为 32px) @@ -377,9 +440,10 @@ void UI_ShowMainMenu(u8 selected_index) #define SET_TIME_HOUR_X 23 #define SET_TIME_MIN_X 75 -void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on) +void UI_ShowSetTimePage(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 weekday, u8 selection, u8 blink_on) { char time_str[6]; + char date_str[13]; LCD_Clear(COLOR_BLACK); // 绘制绿锁头等背景静态信息 @@ -392,7 +456,7 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on) LCD_ShowString(77, 24, "85%", COLOR_GRAY, COLOR_BLACK); LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); - // 整页绘制时,小时/分钟数字一律完整显示 (消隐闪烁效果交给 UI_UpdateSetTimeDigit 局部刷新处理) + // 整页绘制时,各数字一律完整显示 (消隐闪烁效果交给 UI_UpdateSetTimeDigit 局部刷新处理) time_str[0] = '0' + (hour / 10); time_str[1] = '0' + (hour % 10); time_str[2] = '\0'; @@ -408,30 +472,49 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on) // 补齐底部的固定辅助元素 LCD_ShowStringCentered(150, Lang_Get(STR_CLOCK_AMPM), COLOR_WHITE, COLOR_BLACK); - LCD_ShowStringCentered(195, Lang_Get(STR_CLOCK_DATE), COLOR_GRAY, COLOR_BLACK); + + // 编辑日期时,日期栏额外带上年份 ("WWW DD-MM-YY"),方便看清正在改的年份数值 + Build_DateStringWithYear(date_str, weekday, day, month, year); + LCD_ShowStringCentered(DATE_BAR_Y, date_str, COLOR_GRAY, COLOR_BLACK); } /** - * @brief 局部刷新时间设置界面里"正在调节的那一个"数字 (小时或分钟) - * @param selection 1:刷新小时区域, 2:刷新分钟区域 + * @brief 局部刷新时间设置界面里"正在调节的那一个"字段 (小时/分钟/日/月/年) + * @param selection 1:小时, 2:分钟, 3:日, 4:月, 5:年 * @param blink_on 1:显示数值, 0:消隐(涂黑),用于 500ms 周期闪烁效果 - * @details 只重画 32x32px 的数字区域,不触碰锁头/电池/冒号/AM-PM/日期等其它内容, + * @details 小时/分钟用 32x32px 大字体区域;日/月/年用日期栏里对应的 16x16px 小字体区域, + * 都只重画自己那一小块,不触碰锁头/电池/冒号/AM-PM/星期文字等其它内容, * 避免每次闪烁或调值都整屏 LCD_Clear() 造成全屏闪烁。 */ -void UI_UpdateSetTimeDigit(u8 hour, u8 min, u8 selection, u8 blink_on) +void UI_UpdateSetTimeDigit(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 selection, u8 blink_on) { - char time_str[3]; - u16 x = (selection == 1) ? SET_TIME_HOUR_X : SET_TIME_MIN_X; - u8 value = (selection == 1) ? hour : min; + char buf[3]; + u16 x; + u16 y; + u16 w; + u16 h; + u8 value; + + switch (selection) { + case 1: x = SET_TIME_HOUR_X; y = SET_TIME_DIGIT_Y; w = SET_TIME_DIGIT_W; h = SET_TIME_DIGIT_H; value = hour; break; + case 2: x = SET_TIME_MIN_X; y = SET_TIME_DIGIT_Y; w = SET_TIME_DIGIT_W; h = SET_TIME_DIGIT_H; value = min; break; + case 3: x = DATE_BAR_DAY_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = day; break; + case 4: x = DATE_BAR_MONTH_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = month; break; + default: x = DATE_BAR_YEAR_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = year; break; + } if (!blink_on) { // 灭周期:直接把这一小块涂黑,形成消隐效果 - LCD_FillRect(x, SET_TIME_DIGIT_Y, SET_TIME_DIGIT_W, SET_TIME_DIGIT_H, COLOR_BLACK); + LCD_FillRect(x, y, w, h, COLOR_BLACK); return; } - time_str[0] = '0' + (value / 10); - time_str[1] = '0' + (value % 10); - time_str[2] = '\0'; - LCD_ShowString16x32(x, SET_TIME_DIGIT_Y, time_str, COLOR_CYAN, COLOR_BLACK); + buf[0] = '0' + (value / 10); + buf[1] = '0' + (value % 10); + buf[2] = '\0'; + if (selection <= 2) { + LCD_ShowString16x32(x, y, buf, COLOR_CYAN, COLOR_BLACK); + } else { + LCD_ShowString(x, y, buf, COLOR_GRAY, COLOR_BLACK); + } } diff --git a/App/ui.h b/App/ui.h index 96f8aeb..f4cad1d 100644 --- a/App/ui.h +++ b/App/ui.h @@ -10,10 +10,13 @@ * @param state 当前的系统运行状态 (STATE_NORMAL/STATE_ARMED/STATE_DISARMED) * @param hour 当前待显示的小时 * @param min 当前待显示的分钟 + * @param weekday 当前星期 (0=周日, ..., 6=周六) + * @param day 当前日 (1~31) + * @param month 当前月 (1~12) * @details 负责在 120x240 大小的 AMOLED 屏幕上居中绘制巨大的时钟字样,并在顶部 * 绘制对应的状态图标 (如一把打开的锁代表已撤防、闭合的锁代表已布防等)。 */ -void UI_ShowClockPage(SystemState state, u8 hour, u8 min); +void UI_ShowClockPage(SystemState state, u8 hour, u8 min, u8 weekday, u8 day, u8 month); /** * @brief 渲染显示传感器对码的二级设备选择菜单页面 @@ -74,19 +77,23 @@ void UI_ShowBindingPage(u32 addr); void UI_ShowMainMenu(u8 selected_index); /** - * @brief 渲染显示修改时间页面 (时钟设置编辑状态) + * @brief 渲染显示修改时间/日期页面 (时钟设置编辑状态) * @param hour 待修改的小时 * @param min 待修改的分钟 - * @param selection 当前选中的修改项 (1:修改小时, 2:修改分钟) + * @param day 待修改的日 + * @param month 待修改的月 + * @param year 待修改的年 (0~99,代表 2000~2099) + * @param weekday 根据当前日期算出的星期 (0=周日, ..., 6=周六) + * @param selection 当前选中的修改项 (1:小时, 2:分钟, 3:日, 4:月, 5:年) * @param blink_on 控制被选中数值以 500ms 周期进行隐现闪烁的周期标志位 (1-显示, 0-消隐) */ -void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on); +void UI_ShowSetTimePage(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 weekday, u8 selection, u8 blink_on); /** - * @brief 局部刷新时间设置界面里正在调节的那一个数字 (小时或分钟),用于闪烁/调值时避免整屏重绘 - * @param selection 1:刷新小时区域, 2:刷新分钟区域 + * @brief 局部刷新时间设置界面里正在调节的那一个字段 (小时/分钟/日/月/年),用于闪烁/调值时避免整屏重绘 + * @param selection 1:小时, 2:分钟, 3:日, 4:月, 5:年 * @param blink_on 1:显示数值, 0:消隐(涂黑) */ -void UI_UpdateSetTimeDigit(u8 hour, u8 min, u8 selection, u8 blink_on); +void UI_UpdateSetTimeDigit(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 selection, u8 blink_on); #endif // __UI_H__