Files
stc32g128k/App/ui.c

438 lines
20 KiB
C
Raw Normal View History

#include "ui.h"
2026-07-31 13:57:06 +08:00
#include "database.h"
#include "language.h"
#include "../Drivers/lcd.h"
#include "../Drivers/adc.h"
2026-07-31 13:57:06 +08:00
// 引入外部主菜单对码项名称的字符串 ID 表 (例如门磁/PIR/烟感等防区类型)
extern const StringID device_menu_ids[5];
/**
* @brief
* @param state (STATE_ARMED: , )
* @param hour (0~23)
* @param min (0~59)
* @details
* 1.
* 2. 16x32 "HH:MM" AM/PM
* 3. 使 "VEN 10-07" ( 710)
*/
void UI_ShowClockPage(SystemState state, u8 hour, u8 min)
{
char time_str[6];
char batt_str[7];
u8 batt;
// 清屏,底色填充为纯黑
LCD_Clear(COLOR_BLACK);
// ====== 绘制左上角布撤防图标 ======
if (state == STATE_ARMED)
{
// 状态: ARMED (已布防) -> 绘制红色闭合锁头
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_RED); // 锁身矩形
LCD_DrawRectBorder(14, 15, 12, 9, COLOR_RED); // 闭合状态的拱形锁环
LCD_FillRect(18, 28, 4, 5, COLOR_RED); // 锁芯暗扣
}
else
{
// 状态: NORMAL / DISARMED (非布防) -> 绘制绿色开启锁头
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN); // 锁身矩形
LCD_FillRect(15, 15, 2, 8, COLOR_GREEN); // 左锁环
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN); // 锁环顶
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN); // 开启状态的偏置锁环
}
// ====== 绘制右上角真实电池电量 ======
batt = ADC_GetBatteryPercent();
if (batt >= 100) {
batt_str[0] = '1'; batt_str[1] = '0'; batt_str[2] = '0'; batt_str[3] = '%'; batt_str[4] = '\0';
} else {
batt_str[0] = '0' + (batt / 10);
batt_str[1] = '0' + (batt % 10);
batt_str[2] = '%';
batt_str[3] = '\0';
}
// 充电中则在百分比后追加 '+' 后缀
if (Is_Charging()) {
u8 len = (batt >= 100) ? 4 : 3;
batt_str[len] = '+';
batt_str[len + 1] = '\0';
}
LCD_ShowString(70, 24, batt_str, COLOR_GRAY, COLOR_BLACK); // 渲染真实动态百分比
LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 渲染 20x10 电池图标
// ====== 绘制中部时间 "HH:MM" ======
time_str[0] = '0' + (hour / 10); // 小时十位
time_str[1] = '0' + (hour % 10); // 小时个位
time_str[2] = ':'; // 冒号分隔符
time_str[3] = '0' + (min / 10); // 分钟十位
time_str[4] = '0' + (min % 10); // 分钟个位
time_str[5] = '\0'; // 字符串终止字符
LCD_ShowString16x32Centered(100, time_str, COLOR_WHITE, COLOR_BLACK); // 使用 16x32 特大字体居中绘制时间
// ====== 绘制 AM/PM 状态 ======
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(150, Lang_Get(STR_CLOCK_AMPM), COLOR_WHITE, COLOR_BLACK);
// ====== 绘制日期星期栏 ======
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(195, Lang_Get(STR_CLOCK_DATE), COLOR_GRAY, COLOR_BLACK);
}
/**
* @brief
* @param selected_index (0~4 5 )
* @details
* - 110 使
* -
*/
void UI_ShowPairMenuPage(u8 selected_index)
{
LCD_Clear(COLOR_BLACK);
2026-07-31 13:57:06 +08:00
// 绘制标题 "MENU APPAI." (配对/对码设置菜单)
LCD_ShowStringCentered(30, Lang_Get(STR_PAIR_MENU_TITLE), COLOR_AMBER, COLOR_BLACK);
// 渲染上方相邻项 (如果当前选择的不是第一行)
if (selected_index > 0) {
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(78, Lang_Get(device_menu_ids[selected_index - 1]), COLOR_GRAY, COLOR_BLACK);
}
2026-07-31 13:57:06 +08:00
// 渲染当前选中的高亮中间项
LCD_DrawRectBorder(15, 110, 104, 28, COLOR_CYAN); // 青色对齐外框 (在 135px 宽度下居中)
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(118, Lang_Get(device_menu_ids[selected_index]), COLOR_CYAN, COLOR_BLACK);
// 渲染下方相邻项 (如果当前选择的不是最后一行)
if (selected_index < 4) {
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(158, Lang_Get(device_menu_ids[selected_index + 1]), COLOR_GRAY, COLOR_BLACK);
}
}
/**
* @brief
* @param frame_index (0~2 )
* @details "DECLENCH. CAPT" ()
*/
void UI_ShowPairWaitPage(u8 frame_index)
{
LCD_Clear(COLOR_BLACK);
// 绘制头部 "APPAIRAGE" (对码中)
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(35, Lang_Get(STR_PAIRING_TITLE), COLOR_GRAY, COLOR_BLACK);
// 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉 (在 135px 下居中 X = 51)
if (frame_index == 0)
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f1, COLOR_CYAN, COLOR_BLACK);
else if (frame_index == 1)
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f2, COLOR_CYAN, COLOR_BLACK);
else
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
// 绘制状态描述
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(165, Lang_Get(STR_PAIR_SEARCHING), COLOR_WHITE, COLOR_BLACK); // 搜寻中
LCD_ShowStringCentered(200, Lang_Get(STR_PAIR_TRIGGER), COLOR_CYAN, COLOR_BLACK); // 请触发传感器以发射射频信号
}
/**
* @brief
* @param type (0:, 1:PIR, 2:, 3:, 4:)
* @param zone_index (1~99)
*/
void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
{
char zone_str[3];
LCD_Clear(COLOR_BLACK);
2026-07-31 13:57:06 +08:00
// 顶部显示捕获回馈标志 "v RECU" (信号已接收)
LCD_ShowStringCentered(32, Lang_Get(STR_PAIR_RECEIVED), COLOR_GREEN, COLOR_BLACK);
// 在上部居中绘制设备类别所对应的图形 (135px 下居中 X = 51)
if (type == 0) {
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_door_32x32, COLOR_WHITE, COLOR_BLACK); // 门磁图标
} else if (type == 2 || type == 3) {
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_siren_32x32, COLOR_WHITE, COLOR_BLACK); // 警号喇叭图标
} else {
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_confirm_lock_32x32, COLOR_WHITE, COLOR_BLACK); // 锁型常规图标
}
// 渲染传感器类型的文字
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(105, Lang_Get(sensor_type_name_ids[type]), COLOR_WHITE, COLOR_BLACK);
// 绘制一个青色边框,在中间以 16x32 大号字体展示当前防区号 (在 135px 下居中 X=22, 宽度 90)
LCD_DrawRectBorder(22, 140, 90, 44, COLOR_CYAN);
zone_str[0] = '0' + (zone_index / 10);
zone_str[1] = '0' + (zone_index % 10);
zone_str[2] = '\0';
LCD_ShowString16x32(51, 146, zone_str, COLOR_CYAN, COLOR_BLACK); // 渲染两位防区编号 (135px 下居中 X = 51)
// 底部提示按键动作
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(215, Lang_Get(STR_PAIR_CONFIRM), COLOR_GRAY, COLOR_BLACK); // 短按以确认保存
}
/**
* @brief
*/
void UI_ShowPairSuccessPage(void)
{
LCD_Clear(COLOR_BLACK);
// 渲染 32x32 对勾图标 (135px 下居中 X = 51)
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK);
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(145, Lang_Get(STR_PAIR_SUCCESS), COLOR_GREEN, COLOR_BLACK); // 成功
LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_SAVED), COLOR_GRAY, COLOR_BLACK); // 配置已保存
}
/**
* @brief
* @param is_timeout 1退0
*/
void UI_ShowPairFailPage(u8 is_timeout)
{
LCD_Clear(COLOR_BLACK);
// 渲染 32x32 红色叉号图标 (135px 下居中 X = 51)
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK);
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(145, Lang_Get(STR_PAIR_FAILED), COLOR_RED, COLOR_BLACK); // 失败
if (is_timeout) {
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_TIMEOUT), COLOR_GRAY, COLOR_BLACK); // 对码超时
} else {
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_WRONG_TYPE), COLOR_GRAY, COLOR_BLACK); // 传感器类型不符
}
}
/**
* @brief
* @param type (0~4)
* @param zone_index (1~99, 0)
* @details (COLOR_RED)
* "PORTE 02"
* "INTRUSION!" ()
*/
void UI_ShowAlarmPage(u8 type, u8 zone_index)
{
char zone_info[20];
LCD_Clear(COLOR_BLACK);
// 绘制双重红色爆闪粗边界框 (在新宽 135px 下对称扩展)
LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED);
LCD_DrawRectBorder(6, 6, 123, 228, COLOR_RED);
// 绘制报警设备的图形 (135px 下居中 X = 51)
if (type == 0) {
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_door_32x32, COLOR_RED, COLOR_BLACK); // 门磁报警
} else if (type == 2 || type == 3) {
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_siren_32x32, COLOR_RED, COLOR_BLACK); // 烟感/燃气报警
} else {
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_confirm_lock_32x32, COLOR_RED, COLOR_BLACK); // 其他防区报警
}
// 拼接字符串: "设备类型名 两位防区号"
{
u8 len = 0;
2026-07-31 13:57:06 +08:00
char *p = Lang_Get(sensor_type_name_ids[type]);
while(*p) {
zone_info[len++] = *p++;
}
zone_info[len++] = ' ';
zone_info[len++] = '0' + (zone_index / 10);
zone_info[len++] = '0' + (zone_index % 10);
zone_info[len] = '\0';
}
LCD_ShowStringCentered(135, zone_info, COLOR_RED, COLOR_BLACK); // 红字绘制设备防区
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(175, Lang_Get(STR_ALARM_INTRUSION), COLOR_WHITE, COLOR_BLACK); // 白字入侵提示
}
/**
* @brief SOS
* @details
* "SOS ENVOI" (...) "APPEL..." ()
*/
void UI_ShowSosPage(void)
{
LCD_Clear(COLOR_BLACK);
// 绘制红白双层紧急警示框 (在新宽 135px 下对称扩展)
LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED);
LCD_DrawRectBorder(5, 5, 125, 230, COLOR_RED);
LCD_DrawRectBorder(8, 8, 119, 224, COLOR_WHITE);
// ====== 用画点和填充矩形函数像素级绘制精细的警徽灯架 (在新宽 135px 下整体右移 7 像素) ======
LCD_FillRect(51, 45, 32, 2, COLOR_RED); // 警灯顶部
LCD_FillRect(55, 47, 24, 12, COLOR_RED); // 警灯红色灯罩
LCD_FillRect(47, 59, 40, 4, COLOR_GRAY); // 警灯灰色底座
LCD_FillRect(64, 51, 6, 6, COLOR_WHITE); // 中间高亮光影
LCD_FillRect(65, 36, 4, 6, COLOR_RED); // 发射的垂直红色闪光
LCD_FillRect(53, 38, 4, 4, COLOR_RED); // 左侧散射闪光
LCD_FillRect(77, 38, 4, 4, COLOR_RED); // 右侧散射闪光
2026-07-31 13:57:06 +08:00
// 绘制求救中文字样
LCD_ShowStringCentered(120, Lang_Get(STR_SOS_TITLE), COLOR_RED, COLOR_BLACK);
LCD_ShowStringCentered(150, Lang_Get(STR_SOS_SENDING), COLOR_RED, COLOR_BLACK); // 求救中
LCD_ShowStringCentered(195, Lang_Get(STR_SOS_CALLING), COLOR_WHITE, COLOR_BLACK); // 紧急呼叫中
}
/**
* @brief (IPC)
* @param addr 24
*/
void UI_ShowBindingPage(u32 addr)
{
char id_str[20];
LCD_Clear(COLOR_BLACK);
// ====== 像素级拼装绘制一个青色网络摄像头简图 ======
LCD_DrawRectBorder(43, 46, 32, 26, COLOR_CYAN); // 相机主体框 (135px 下居中等比右移 X = 43)
LCD_FillRect(51, 56, 6, 6, COLOR_CYAN); // 镜头光圈点 (135px 下居中等比右移 X = 51)
LCD_FillRect(75, 48, 2, 22, COLOR_CYAN); // 右侧红外指示圈 (135px 下居中等比右移 X = 75)
LCD_FillRect(77, 50, 2, 18, COLOR_CYAN); // 镜头固定翼 1 (135px 下居中等比右移 X = 77)
LCD_FillRect(79, 52, 2, 14, COLOR_CYAN); // 镜头固定翼 2 (135px 下居中等比右移 X = 79)
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(120, Lang_Get(STR_LINK_TITLE), COLOR_WHITE, COLOR_BLACK); // 连接/绑定
LCD_ShowStringCentered(155, Lang_Get(STR_BIND_SENDING), COLOR_CYAN, COLOR_BLACK); // 发送对码帧中
// 拼接要对外广播的手环 24 位克隆物理 ID 十六进制字符串并显示在最下方
id_str[0] = 'I'; id_str[1] = 'D'; id_str[2] = ':'; id_str[3] = ' ';
id_str[4] = '0'; id_str[5] = 'x';
{
u8 val1 = (u8)(addr >> 16);
u8 val2 = (u8)(addr >> 8);
u8 val3 = (u8)addr;
u8 nibble = val1 >> 4;
id_str[6] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10));
nibble = val1 & 0x0F;
id_str[7] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10));
nibble = val2 >> 4;
id_str[8] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10));
nibble = val2 & 0x0F;
id_str[9] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10));
nibble = val3 >> 4;
id_str[10] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10));
nibble = val3 & 0x0F;
id_str[11] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10));
id_str[12] = '\0';
}
LCD_ShowStringCentered(205, id_str, COLOR_GRAY, COLOR_BLACK);
}
/**
* @brief
2026-07-31 13:57:06 +08:00
* @param selected_index (0:, 1:, 2:, 3:)
* @details /
2026-07-31 13:57:06 +08:00
*
*/
void UI_ShowMainMenu(u8 selected_index)
{
LCD_Clear(COLOR_BLACK);
2026-07-31 13:57:06 +08:00
// ====== 绘制顶部的向上箭头三角标记 (在 135px 宽度下居中) ======
LCD_FillRect(65, 20, 4, 2, COLOR_GRAY);
LCD_FillRect(63, 22, 8, 2, COLOR_GRAY);
LCD_FillRect(61, 24, 12, 2, COLOR_GRAY);
if (selected_index == 0)
{
2026-07-31 13:57:06 +08:00
// 菜单 0语言切换。用当前语言的两字母缩写代替图标直接展示切换后的效果
LCD_ShowString16x32Centered(75, (Lang_Current() == LANG_FR) ? "FR" : "EN", COLOR_CYAN, COLOR_BLACK);
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_LANGUAGE), COLOR_WHITE, COLOR_BLACK);
}
else if (selected_index == 1)
{
// 菜单 1时间修改设置。中部绘制一个青色闹钟样式 (135px 下居中 X = 51)
LCD_DrawRectBorder(51, 70, 32, 32, COLOR_CYAN); // 闹钟外圈
LCD_FillRect(66, 76, 2, 10, COLOR_CYAN); // 闹钟时针
LCD_FillRect(66, 85, 8, 2, COLOR_CYAN); // 闹钟分针
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_HORLOGE), COLOR_WHITE, COLOR_BLACK); // 时钟/时间修改
}
2026-07-31 13:57:06 +08:00
else if (selected_index == 2)
{
2026-07-31 13:57:06 +08:00
// 菜单 2无线对码匹配。中部绘制一个青色同心圆雷达波纹图形 (135px 下居中 X = 51)
LCD_DrawMonoBitmap(51, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_APPAIRAGE), COLOR_WHITE, COLOR_BLACK); // 配对
}
2026-07-31 13:57:06 +08:00
else if (selected_index == 3)
{
2026-07-31 13:57:06 +08:00
// 菜单 3网络摄像机绑定。中部绘制一个相机/联通关系图 (135px 下居中等比右移 X = 57)
LCD_DrawRectBorder(57, 74, 20, 16, COLOR_CYAN);
LCD_FillRect(65, 80, 4, 4, COLOR_CYAN);
LCD_FillRect(77, 76, 2, 12, COLOR_CYAN);
LCD_FillRect(79, 78, 2, 8, COLOR_CYAN);
LCD_FillRect(81, 80, 2, 4, COLOR_CYAN);
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_LIAISON), COLOR_WHITE, COLOR_BLACK); // 摄像机绑定
}
// ====== 绘制底部的向下箭头三角标记 (在 135px 宽度下居中) ======
LCD_FillRect(61, 210, 12, 2, COLOR_GRAY);
LCD_FillRect(63, 212, 8, 2, COLOR_GRAY);
LCD_FillRect(65, 214, 4, 2, COLOR_GRAY);
}
/**
* @brief ()
* @param hour
* @param min
* @param selection (1: , 2: )
* @param blink_on 500ms (1-0-)
*/
// 小时/分钟数字在屏幕上的固定坐标 (16x32 字体,两位数字宽度为 32px)
#define SET_TIME_DIGIT_Y 100
#define SET_TIME_DIGIT_W 32
#define SET_TIME_DIGIT_H 32
#define SET_TIME_HOUR_X 23
#define SET_TIME_MIN_X 75
void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
{
char time_str[6];
LCD_Clear(COLOR_BLACK);
// 绘制绿锁头等背景静态信息
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN);
LCD_FillRect(15, 15, 2, 8, COLOR_GREEN);
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN);
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN);
// 移动电池百分比和图标到 135px 右上角
LCD_ShowString(77, 24, "85%", COLOR_GRAY, COLOR_BLACK);
LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
// 整页绘制时,小时/分钟数字一律完整显示 (消隐闪烁效果交给 UI_UpdateSetTimeDigit 局部刷新处理)
time_str[0] = '0' + (hour / 10);
time_str[1] = '0' + (hour % 10);
time_str[2] = '\0';
LCD_ShowString16x32(SET_TIME_HOUR_X, SET_TIME_DIGIT_Y, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘小时 (在 135px 下整体居中 X = 23)
// 绘制中间冒号 (在 135px 下整体居中 X = 59)
LCD_ShowString16x32(59, 98, ":", COLOR_WHITE, COLOR_BLACK);
time_str[0] = '0' + (min / 10);
time_str[1] = '0' + (min % 10);
time_str[2] = '\0';
LCD_ShowString16x32(SET_TIME_MIN_X, SET_TIME_DIGIT_Y, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘分钟 (在 135px 下整体居中 X = 75)
// 补齐底部的固定辅助元素
2026-07-31 13:57:06 +08:00
LCD_ShowStringCentered(150, Lang_Get(STR_CLOCK_AMPM), COLOR_WHITE, COLOR_BLACK);
LCD_ShowStringCentered(195, Lang_Get(STR_CLOCK_DATE), COLOR_GRAY, COLOR_BLACK);
}
/**
* @brief "正在调节的那一个" ()
* @param selection 1:, 2:
* @param blink_on 1:, 0:() 500ms
* @details 32x32px ///AM-PM/
* LCD_Clear()
*/
void UI_UpdateSetTimeDigit(u8 hour, u8 min, 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;
if (!blink_on) {
// 灭周期:直接把这一小块涂黑,形成消隐效果
LCD_FillRect(x, SET_TIME_DIGIT_Y, SET_TIME_DIGIT_W, SET_TIME_DIGIT_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);
}