Merge branch 'master' of https://github.com/tpunix/HMCLOCK.git into merge_test

This commit is contained in:
tpu
2025-08-18 17:58:49 +08:00
7 changed files with 1096 additions and 194 deletions

View File

@@ -16,8 +16,8 @@
<TargetCommonOption>
<Device>ARMCM0</Device>
<Vendor>ARM</Vendor>
<PackID>ARM.CMSIS.5.6.0</PackID>
<PackURL>http://www.keil.com/pack/</PackURL>
<PackID>ARM.Cortex_DFP.1.1.0</PackID>
<PackURL>https://www.keil.com/pack/</PackURL>
<Cpu>IROM(0x0,0x0) IRAM(0x0,0x0) CPUTYPE("Cortex-M0") CLOCK(12000000) ESEL ELITTLE</Cpu>
<FlashUtilSpec></FlashUtilSpec>
<StartupFile></StartupFile>
@@ -185,6 +185,8 @@
<uocXRam>0</uocXRam>
<RvdsVP>0</RvdsVP>
<RvdsMve>0</RvdsMve>
<RvdsCdeCp>0</RvdsCdeCp>
<nBranchProt>0</nBranchProt>
<hadIRAM2>0</hadIRAM2>
<hadIROM2>0</hadIROM2>
<StupSel>0</StupSel>
@@ -351,7 +353,7 @@
<NoWarn>0</NoWarn>
<uSurpInc>0</uSurpInc>
<useXO>0</useXO>
<uClangAs>0</uClangAs>
<ClangAsOpt>4</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -481,7 +483,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -580,7 +582,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -1319,7 +1321,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -1403,7 +1405,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -1548,8 +1550,8 @@
<TargetCommonOption>
<Device>ARMCM0</Device>
<Vendor>ARM</Vendor>
<PackID>ARM.CMSIS.5.6.0</PackID>
<PackURL>http://www.keil.com/pack/</PackURL>
<PackID>ARM.Cortex_DFP.1.1.0</PackID>
<PackURL>https://www.keil.com/pack/</PackURL>
<Cpu>IROM(0x0,0x0) IRAM(0x0,0x0) CPUTYPE("Cortex-M0") CLOCK(12000000) ESEL ELITTLE</Cpu>
<FlashUtilSpec></FlashUtilSpec>
<StartupFile></StartupFile>
@@ -1717,6 +1719,8 @@
<uocXRam>0</uocXRam>
<RvdsVP>0</RvdsVP>
<RvdsMve>0</RvdsMve>
<RvdsCdeCp>0</RvdsCdeCp>
<nBranchProt>0</nBranchProt>
<hadIRAM2>0</hadIRAM2>
<hadIROM2>0</hadIROM2>
<StupSel>0</StupSel>
@@ -1883,7 +1887,7 @@
<NoWarn>0</NoWarn>
<uSurpInc>0</uSurpInc>
<useXO>0</useXO>
<uClangAs>0</uClangAs>
<ClangAsOpt>4</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -2013,7 +2017,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -2112,7 +2116,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -2851,7 +2855,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -2935,7 +2939,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -3080,8 +3084,8 @@
<TargetCommonOption>
<Device>ARMCM0P</Device>
<Vendor>ARM</Vendor>
<PackID>ARM.CMSIS.5.6.0</PackID>
<PackURL>http://www.keil.com/pack/</PackURL>
<PackID>ARM.Cortex_DFP.1.1.0</PackID>
<PackURL>https://www.keil.com/pack/</PackURL>
<Cpu>IRAM(0x20000000,0x00020000) IROM(0x00000000,0x00040000) CPUTYPE("Cortex-M0+") CLOCK(12000000) ESEL ELITTLE</Cpu>
<FlashUtilSpec></FlashUtilSpec>
<StartupFile></StartupFile>
@@ -3249,6 +3253,8 @@
<uocXRam>0</uocXRam>
<RvdsVP>0</RvdsVP>
<RvdsMve>0</RvdsMve>
<RvdsCdeCp>0</RvdsCdeCp>
<nBranchProt>0</nBranchProt>
<hadIRAM2>0</hadIRAM2>
<hadIROM2>0</hadIROM2>
<StupSel>8</StupSel>
@@ -3415,7 +3421,7 @@
<NoWarn>0</NoWarn>
<uSurpInc>0</uSurpInc>
<useXO>0</useXO>
<uClangAs>0</uClangAs>
<ClangAsOpt>4</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -3535,7 +3541,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -3644,7 +3650,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -4317,7 +4323,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -4416,7 +4422,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -4561,8 +4567,8 @@
<TargetCommonOption>
<Device>ARMCM0P</Device>
<Vendor>ARM</Vendor>
<PackID>ARM.CMSIS.5.6.0</PackID>
<PackURL>http://www.keil.com/pack/</PackURL>
<PackID>ARM.Cortex_DFP.1.1.0</PackID>
<PackURL>https://www.keil.com/pack/</PackURL>
<Cpu>IRAM(0x20000000,0x00020000) IROM(0x00000000,0x00040000) CPUTYPE("Cortex-M0+") CLOCK(12000000) ESEL ELITTLE</Cpu>
<FlashUtilSpec></FlashUtilSpec>
<StartupFile></StartupFile>
@@ -4730,6 +4736,8 @@
<uocXRam>0</uocXRam>
<RvdsVP>0</RvdsVP>
<RvdsMve>0</RvdsMve>
<RvdsCdeCp>0</RvdsCdeCp>
<nBranchProt>0</nBranchProt>
<hadIRAM2>0</hadIRAM2>
<hadIROM2>0</hadIROM2>
<StupSel>8</StupSel>
@@ -4896,7 +4904,7 @@
<NoWarn>0</NoWarn>
<uSurpInc>0</uSurpInc>
<useXO>0</useXO>
<uClangAs>0</uClangAs>
<ClangAsOpt>4</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -5016,7 +5024,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -5125,7 +5133,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -5798,7 +5806,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -5897,7 +5905,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -6042,8 +6050,8 @@
<TargetCommonOption>
<Device>ARMCM0P</Device>
<Vendor>ARM</Vendor>
<PackID>ARM.CMSIS.5.6.0</PackID>
<PackURL>http://www.keil.com/pack/</PackURL>
<PackID>ARM.Cortex_DFP.1.1.0</PackID>
<PackURL>https://www.keil.com/pack/</PackURL>
<Cpu>IRAM(0x20000000,0x00020000) IROM(0x00000000,0x00040000) CPUTYPE("Cortex-M0+") CLOCK(12000000) ESEL ELITTLE</Cpu>
<FlashUtilSpec></FlashUtilSpec>
<StartupFile></StartupFile>
@@ -6211,6 +6219,8 @@
<uocXRam>0</uocXRam>
<RvdsVP>0</RvdsVP>
<RvdsMve>0</RvdsMve>
<RvdsCdeCp>0</RvdsCdeCp>
<nBranchProt>0</nBranchProt>
<hadIRAM2>0</hadIRAM2>
<hadIROM2>0</hadIROM2>
<StupSel>8</StupSel>
@@ -6377,7 +6387,7 @@
<NoWarn>0</NoWarn>
<uSurpInc>0</uSurpInc>
<useXO>0</useXO>
<uClangAs>0</uClangAs>
<ClangAsOpt>4</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -6497,7 +6507,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -6596,7 +6606,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -7279,7 +7289,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
@@ -7363,7 +7373,7 @@
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<uClangAs>2</uClangAs>
<ClangAsOpt>0</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>

97
src/epd/PC2U2_sfont.py Normal file
View File

@@ -0,0 +1,97 @@
import re
import struct
# 读取 font.txt 中的全部内容
with open("font.txt", "r", encoding="gbk", errors="ignore") as f:
lines = f.readlines()
# 用于存储字体数据
font_chars = []
# 临时缓存 bitmap 和字符信息
current_bitmap = []
current_char = ""
width = height = 0
# 正则匹配
bitmap_line_re = re.compile(r'\{([0-9xA-Fa-f, ]+)\}')
char_comment_re = re.compile(r'/\*\s*"(.+?)",\s*(\d+)\s*\*/')
size_comment_re = re.compile(r'/\*\s*\(\s*(\d+)\s*X\s*(\d+)\s*,.*\*/')
for line in lines:
# 如果是bitmap行
bitmap_match = bitmap_line_re.search(line)
if bitmap_match:
hex_str = bitmap_match.group(1)
bytes_list = [int(x, 16) for x in hex_str.split(',')]
current_bitmap.extend(bytes_list)
# 如果是字符注释行
char_match = char_comment_re.search(line)
if char_match:
current_char = char_match.group(1)
# 如果是尺寸注释行
size_match = size_comment_re.search(line)
if size_match:
width = int(size_match.group(1))
height = int(size_match.group(2))
# 生成 FontChar 结构
ucs = ord(current_char)
ft_adv = width
ft_bw = width
ft_bh = height
ft_bx = 0
ft_by = 0
header = [ft_adv, ft_bw, ft_bh, ft_bx, ft_by]
font_chars.append({
"char": current_char,
"ucs": ucs,
"header": header,
"bitmap": current_bitmap.copy()
})
# 清空临时变量
current_bitmap.clear()
current_char = ""
width = height = 0
# 构造 sfont[] 数据
font_count = len(font_chars)
offset = 2 + font_count * 4 # 2 字节总数 + 每个字符 4 字节索引
index_entries = []
data_blocks = []
for fc in font_chars:
# UCS + 偏移
index_entries.append(struct.pack('<HH', fc["ucs"], offset))
# 头 + bitmap
header = bytes(fc["header"])
bitmap = bytes(fc["bitmap"])
data_block = header + bitmap
data_blocks.append(data_block)
offset += len(data_block)
# 最终构建 sfont 数组
sfont_data = struct.pack('<H', font_count) + b''.join(index_entries) + b''.join(data_blocks)
# 输出为C数组
def format_c_array(data, name="sfont"):
lines = [f'static unsigned char {name}[] __attribute__((aligned(4))) = {{']
for i in range(0, len(data), 12):
chunk = ','.join(f'0x{b:02X}' for b in data[i:i+12])
lines.append(f' {chunk},')
lines.append('};')
return '\n'.join(lines)
# 保存为sfont.h
with open("sfont.h", "w", encoding="utf-8") as f:
f.write("#ifndef __sfont__\n#define __sfont__\n\n")
f.write(f"#define sfont_size {len(sfont_data)}\n\n")
f.write(format_c_array(sfont_data))
f.write("\n\n#endif // __sfont__\n")

BIN
src/epd/checkFont.exe Normal file

Binary file not shown.

View File

@@ -316,4 +316,4 @@ static const unsigned char sfont[] __attribute__((aligned(4))) = {
0x40, 0x11, 0x80, 0x23, 0x08, 0x45, 0x08, 0x88, 0xf8,
};
#endif
#endif // __sfont__

View File

@@ -36,30 +36,36 @@
****************************************************************************************
*/
#include "gpio.h"
#include "app_api.h"
#include "app.h"
#include "prf_utils.h"
#include "custs1.h"
#include "custs1_task.h"
#include "user_custs1_def.h"
#include "user_custs1_impl.h"
#include "user_peripheral.h"
#include "user_periph_setup.h"
#include "adc.h"
#include "gpio.h" // GPIO控制相关头文件
#include "app_api.h" // 应用程序API
#include "app.h" // 应用程序核心功能
#include "prf_utils.h" // BLE配置文件工具
#include "custs1.h" // 自定义服务1
#include "custs1_task.h" // 自定义服务1任务
#include "user_custs1_def.h" // 自定义服务1定义
#include "user_custs1_impl.h" // 自定义服务1实现
#include "user_peripheral.h" // 用户外设相关
#include "user_periph_setup.h" // 用户外设设置
#include "adc.h" // ADC(模数转换)相关
#include "epd.h"
#include "epd.h" // 电子墨水屏驱动
/*
* GLOBAL VARIABLE DEFINITIONS
* 全局变量定义
* 这些变量使用__SECTION_ZERO("retention_mem_area0")属性放置在掉电保持内存区域
****************************************************************************************
*/
// 定时器ID用于系统定时任务
ke_msg_id_t timer_used __SECTION_ZERO("retention_mem_area0"); //@RETENTION MEMORY
// 指示计数器用于BLE通知计数
uint16_t indication_counter __SECTION_ZERO("retention_mem_area0"); //@RETENTION MEMORY
// 非数据库值计数器
uint16_t non_db_val_counter __SECTION_ZERO("retention_mem_area0"); //@RETENTION MEMORY
// ADC采样值用于电池电量检测
int adcval;
static uint8_t h24_format = 1; // 24小时制标志
static void get_holiday(void);
@@ -68,49 +74,116 @@ static void get_holiday(void);
****************************************************************************************
*/
/**
* @brief 更新ADC采样值并通过BLE发送电池电压数据
*
* 该函数执行以下操作:
* 1. 校准ADC偏移量
* 2. 获取电池电压采样值
* 3. 将采样值转换为实际电压值
* 4. 通过BLE发送电压值给连接的设备
*
* @return 返回计算后的电压值
*/
int adc1_update(void)
{
adc_offset_calibrate(ADC_INPUT_MODE_SINGLE_ENDED);
adcval = adc_get_vbat_sample(false);
int volt = (adcval*225)>>7;
// 校准ADC偏移使用单端输入模式
adc_offset_calibrate(ADC_INPUT_MODE_SINGLE_ENDED);
// 获取电池电压采样值
adcval = adc_get_vbat_sample(false);
// 将ADC值转换为实际电压值 (单位: mV)
int volt = (adcval*225)>>7;
struct custs1_val_set_req *req = KE_MSG_ALLOC_DYN(CUSTS1_VAL_SET_REQ, prf_get_task_from_id(TASK_ID_CUSTS1), TASK_APP, custs1_val_set_req, DEF_SVC1_ADC_VAL_1_CHAR_LEN);
req->conidx = app_env->conidx;
req->handle = SVC1_IDX_ADC_VAL_1_VAL;
req->length = DEF_SVC1_ADC_VAL_1_CHAR_LEN;
req->value[0] = volt&0xff;
req->value[1] = volt>>8;
KE_MSG_SEND(req);
return volt;
// 分配内存并构造BLE消息
struct custs1_val_set_req *req = KE_MSG_ALLOC_DYN(CUSTS1_VAL_SET_REQ,
prf_get_task_from_id(TASK_ID_CUSTS1),
TASK_APP,
custs1_val_set_req,
DEF_SVC1_ADC_VAL_1_CHAR_LEN);
// 设置连接索引
req->conidx = app_env->conidx;
// 设置特征值句柄
req->handle = SVC1_IDX_ADC_VAL_1_VAL;
// 设置数据长度
req->length = DEF_SVC1_ADC_VAL_1_CHAR_LEN;
// 设置电压值16位低字节在前
req->value[0] = volt&0xff;
req->value[1] = volt>>8;
// 发送BLE消息
KE_MSG_SEND(req);
return volt;
}
/****************************************************************************************/
/**
* 农历年份数据表2020-2051年
* 每个16位数据包含以下信息
* - 高4位(bit 15-12): 闰月月份0表示当年无闰月
* - 低12位(bit 11-0): 每个月的大小月标记1表示大月(30天)0表示小月(29天)
* 例如0x07954
* - 0: 无闰月
* - 7954: 从正月到十二月的大小月情况
*/
static const uint16_t lunar_year_info[32] =
{
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, //2020-2029
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x0d0b6, 0x0d250, 0x0d520, 0x0dd45, //2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x0b255, 0x06d20, 0x0ada0, //2040-2049
0x04b63, 0x09370, //2050-2051
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, //2020-2029
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x0d0b6, 0x0d250, 0x0d520, 0x0dd45, //2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x0b255, 0x06d20, 0x0ada0, //2040-2049
0x04b63, 0x09370, //2050-2051
};
// 额外的农历年份信息,用于标记特殊年份的闰月情况
static const uint32_t lunar_year_info2 = 0x48010000;
// 每个节气相对于"小寒"的秒数
/**
* 24节气时间数据表
* 存储了一年中24个节气相对于"小寒"的时间间隔(以秒为单位)
*
* 节气顺序:
* 1-6月小寒、大寒、立春、雨水、惊蛰、春分
* 7-12月清明、谷雨、立夏、小满、芒种、夏至
* 13-18月小暑、大暑、立秋、处暑、白露、秋分
* 19-24月寒露、霜降、立冬、小雪、大雪、冬至
*/
static int jieqi_info[] =
{
0, 1272283, 2547462, 3828568, 5117483, 6416376,
7726093, 9047327, 10379235, 11721860, 13072410, 14428379,
15787551, 17145937, 18501082, 19850188, 21190911, 22520708,
23839844, 25146961, 26443845, 27730671, 29010666, 30284551,
0, 1272283, 2547462, 3828568, 5117483, 6416376, // 小寒到春分
7726093, 9047327, 10379235, 11721860, 13072410, 14428379, // 清明到夏至
15787551, 17145937, 18501082, 19850188, 21190911, 22520708, // 小暑到秋分
23839844, 25146961, 26443845, 27730671, 29010666, 30284551, // 寒露到冬至
};
// 已知2020年"小寒"相对于1月1日的秒数
/**
* 2020年"小寒"节气的基准时间
* 相对于2020年1月1日的秒数
* 用于计算其他年份的节气时间
*/
#define xiaohan_2020 451804
/**
* 全局时间变量定义
* 公历日期相关变量:
* year: 年份如2025
* month: 月份0-11表示1-12月
* date: 日期0-30表示1-31日
* wday: 星期0-6表示星期日到星期六
*
* 农历日期相关变量:
* l_year: 农历年在lunar_year_info数组中的索引如4表示2024年
* l_month: 农历月份0-11表示正月到腊月最高位1表示闰月
* l_date: 农历日期0-29表示初一到三十
*
* 时间相关变量:
* hour: 小时0-23
* minute: 分钟0-59
* second: 秒0-59
*/
int year=2025, month=0, date=0, wday=3;
int l_year=4, l_month=11, l_date=1;
int hour=0, minute=0, second=0;
@@ -118,10 +191,19 @@ int hour=0, minute=0, second=0;
int cal_minute=-1;
/**
* 获取农历月份的天数
*
* @param mon 月份编号最高位为1表示闰月
* @param yinfo_out 输出参数,用于返回年份信息
* @return 返回该月的天数29或30
*/
static int get_lunar_mdays(int mon, int *yinfo_out)
{
int lflag = mon&0x80;
mon &= 0x7f;
// 获取闰月标志(最高位)
int lflag = mon&0x80;
// 获取实际月份(去除闰月标志)
mon &= 0x7f;
// 取得当年的信息
int yinfo = lunar_year_info[l_year];
@@ -338,44 +420,71 @@ static int epd_wait_state;
static timer_hnd epd_wait_hnd;
typedef struct {
char *name;
uint8_t mon;
uint8_t day;
char *name;
uint8_t mon;
uint8_t day;
}HOLIDAY_INFO;
HOLIDAY_INFO hday_info[] = {
{"除夕", 0xc0|12, 30},
{"春节", 0x80| 1, 1},
{"元宵节", 0x80| 1, 15},
{"龙抬头", 0x80| 2, 2},
{"端午", 0x80| 5, 5},
{"七夕", 0x80| 7, 7},
{"中秋节", 0x80| 8, 15},
{"重阳", 0x80| 9, 9},
{"腊八", 0x80|12, 8},
// 农历传统节日0x80 表示农历日期0xc0 表示月末)
{"腊八", 0x80|12, 8}, // 农历腊月初八
{"小年", 0x80|12, 23}, // 农历腊月廿三
{"除夕", 0xc0|12, 30}, // 农历腊月最后一天
{"", 0x80| 1, 1}, // 农历正月初一
{"元宵", 0x80| 1, 15}, // 农历正月十五
{"龙抬头", 0x80| 2, 2}, // 农历二月初二
{"寒食", 0x80| 3, 3}, // 农历三月初三
{"端午", 0x80| 5, 5}, // 农历五月初五
{"七夕节", 0x80| 7, 7}, // 农历七月初七
{"中元节", 0x80| 7, 15}, // 农历七月十五
{"中秋节", 0x80| 8, 15}, // 农历八月十五
{"重阳节", 0x80| 9, 9}, // 农历九月初九
{"下元节", 0x80|10, 15}, // 农历十月十五
{"元旦节", 1, 1},
{"情人节", 2, 14},
{"妇女节", 3, 8},
{"植树", 3, 12},
{"愚人", 4, 1},
{"劳动", 5, 1},
{"青年节", 5, 4},
{"母亲", 5, 0x97}, // 5月第二个周日
{"儿童节", 6, 1},
{"父亲节", 6, 0xa7}, // 6月第三个周日
{"教师节", 9, 10},
{"国庆", 10, 1},
{"程序员", 10, 24},
{"万圣", 11, 1},
{"光棍", 11, 11},
{"平安夜", 12, 24},
// 阳历法定节假日和纪念日
{"元旦", 1, 1},
{"湿地日", 2, 2},
{"情人", 2, 14},
{"妇女", 3, 8},
{"植树", 3, 12},
{"权益日", 3, 15},
{"愚人", 4, 1},
{"读书日", 4, 23},
{"阅读日", 4, 23},
{"航天日", 4, 24},
{"劳动", 5, 1},
{"青年", 5, 4},
{"护士", 5, 12},
{"儿童", 6, 1},
{"环境日", 6, 5},
{"遗产日", 6, 8},
{"建党节", 7, 1},
{"建军节", 8, 1},
{"抗战日", 9, 3},
{"教师节", 9, 10},
{"安全日", 9, 15},
{"烈士日", 9, 30},
{"国庆节", 10, 1},
{"万圣节", 10, 31},
{"消防日", 11, 9},
{"记者节", 11, 8},
{"宪法日", 12, 4},
{"志愿日", 12, 5},
{"公祭日", 12, 13},
{"圣诞节", 12, 25},
{"", 0, 0},
// 特殊周期性节日
{"母亲节", 5, 0x97}, // 5月第二个周日
{"父亲节", 6, 0xa7}, // 6月第三个周日
{"感恩节", 11, 0xa4}, // 11月第四个周四
{"", 0, 0} // 结束标记
};
static char *jieqi_str = "小寒";
static char *holiday_str = "元旦";
static char *holiday_str = "元旦";
static void ldate_str(char *buf)
{
@@ -484,15 +593,30 @@ static uint8_t batt_cal(uint16_t adc_sample)
return batt_lvl;
}
/**
* 绘制电池电量图标
*
* @param x 图标左上角的x坐标
* @param y 图标中心的y坐标
*
* 图标说明:
* - 外框大小16x8像素
* - 电量显示:根据实际电量百分比填充内部
* - 电池正极2x2像素
*/
static void draw_batt(int x, int y)
{
int p = batt_cal(adcval);
p /= 10;
// 获取电池电量百分比并转换为显示段数0-10
int p = batt_cal(adcval);
p /= 10;
draw_rect(x, y-4, x+14, y+4, BLACK);
draw_box(x-2, y-1, x-1, y+1, BLACK);
// 绘制电池外框
draw_rect(x, y-4, x+14, y+4, BLACK);
// 绘制电池正极
draw_box(x-2, y-1, x-1, y+1, BLACK);
draw_box(x+12-p, y-2, x+12, y+2, BLACK);
// 绘制电量填充部分
draw_box(x+12-p, y-2, x+12, y+2, BLACK);
}
@@ -502,6 +626,14 @@ const u8 font_bt[] = {
0x14, 0x32, 0x51, 0x92, 0x14, 0x18, 0x10,
};
/**
* 绘制蓝牙图标
*
* @param x 图标中心的x坐标
* @param y 图标中心的y坐标
*
* 图标说明: 一个8x15的字符
*/
static void draw_bt(int x, int y)
{
fb_draw_font_info(x, y, font_bt, BLACK);
@@ -557,20 +689,54 @@ void select_layout(int xres, int yres)
}
/**
* 电子墨水屏更新等待定时器
*
* 功能说明:
* - 检查电子墨水屏是否处于忙状态
* - 如果忙则40ms后再次检查
* - 如果空闲,则完成更新流程并进入省电模式
*
* 电子墨水屏更新完成后的处理:
* 1. 发送深度睡眠命令(0x10, 0x01)
* 2. 关闭电源
* 3. 关闭硬件接口
* 4. 设置系统进入扩展睡眠模式
*/
static void epd_wait_timer(void)
{
if(epd_busy()){
epd_wait_hnd = app_easy_timer(40, epd_wait_timer);
}else{
epd_wait_hnd = EASY_TIMER_INVALID_TIMER;
epd_cmd1(0x10, 0x01);
epd_power(0);
epd_hw_close();
arch_set_sleep_mode(ARCH_EXT_SLEEP_ON);
}
if(epd_busy()){
// 屏幕仍在忙40ms后再次检查
epd_wait_hnd = app_easy_timer(40, epd_wait_timer);
}else{
// 屏幕更新完成
epd_wait_hnd = EASY_TIMER_INVALID_TIMER;
// 发送深度睡眠命令
epd_cmd1(0x10, 0x01);
// 关闭电源
epd_power(0);
// 关闭硬件接口
epd_hw_close();
// 设置系统进入扩展睡眠模式
arch_set_sleep_mode(ARCH_EXT_SLEEP_ON);
}
}
/**
* 绘制时钟界面
*
* @param flags 显示控制标志
* bit0-1: 更新模式(快速/正常)
* bit2: 是否显示蓝牙图标
*
* 显示内容包括:
* - 电池电量图标
* - 蓝牙连接状态图标(可选)
* - 大字号时间显示
* - 公历日期和星期
* - 农历日期
* - 节气和节日信息
*/
void clock_draw(int flags)
{
char tbuf[64];
@@ -595,9 +761,35 @@ void clock_draw(int flags)
}
// 使用大字显示时间
select_font(lt->font_dseg);
sprintf(tbuf, "%02d:%02d", hour, minute);
draw_text(lt->x[3], lt->y[3], tbuf, BLACK);
if(h24_format){
// 24小时制
select_font(lt->font_dseg);
sprintf(tbuf, "%02d:%02d", hour, minute);
draw_text(lt->x[3], lt->y[3], tbuf, BLACK);
}else{
// 12小时制
int h = hour;
int ampm = 0;
if(h>=12){
if(h>12)
h -= 12;
ampm = 1;
}else if(h==0){
h = 12; // 0点显示为12点
}
select_font(lt->font_dseg);
sprintf(tbuf, "%2d:%02d", h, minute);
draw_text(lt->x[3], lt->y[3], tbuf, BLACK);
// 显示上午/下午
select_font(lt->font_char);
if(ampm){
strcpy(tbuf, "下午");
}else{
strcpy(tbuf, "上午");
}
draw_text(lt->x[7], lt->y[7], tbuf, BLACK);
}
// 显示公历日期
sprintf(tbuf, "%4d年%2d月%2d日 星期%s", year, month+1, date+1, wday_str[wday]);
@@ -630,19 +822,53 @@ void clock_draw(int flags)
/****************************************************************************************/
void user_svc1_ctrl_wr_ind_handler(ke_msg_id_t const msgid, struct custs1_val_write_ind const *param, ke_task_id_t const dest_id, ke_task_id_t const src_id)
/**
* 控制点写入指示处理函数
*
* @param msgid 消息ID
* @param param 写入参数
* @param dest_id 目标任务ID
* @param src_id 源任务ID
*
* 处理通过BLE接收到的控制命令
*/
void user_svc1_ctrl_wr_ind_handler(ke_msg_id_t const msgid,
struct custs1_val_write_ind const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
printk("Control Point: %02x\n", param->value[0]);
// 打印接收到的控制命令
printk("Control Point: %02x\n", param->value[0]);
}
void user_svc1_long_val_wr_ind_handler(ke_msg_id_t const msgid, struct custs1_val_write_ind const *param, ke_task_id_t const dest_id, ke_task_id_t const src_id)
/**
* 长值特征写入指示处理函数
*
* @param msgid 消息ID
* @param param 写入参数
* @param dest_id 目标任务ID
* @param src_id 源任务ID
*
* 处理命令:
* - 0x91: 时钟设置命令
* - 0xA0及以上: OTA升级相关命令
*/
void user_svc1_long_val_wr_ind_handler(ke_msg_id_t const msgid,
struct custs1_val_write_ind const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
//printk("Long value: %d\n", param->length);
if(param->value[0]==0x91){
// 设置时钟
clock_set((uint8_t*)param->value);
// 更新显示(带蓝牙图标,快速更新模式)
clock_draw(DRAW_BT|UPDATE_FAST);
// 打印当前时间信息
clock_print();
}else if(param->value[0]==0x90){
//修改24-12小时制
h24_format = !h24_format;
clock_draw(DRAW_BT|UPDATE_FAST);
}else if(param->value[0]==0x92){
int diff_sec;
diff_sec = param->value[1];
@@ -653,18 +879,42 @@ void user_svc1_long_val_wr_ind_handler(ke_msg_id_t const msgid, struct custs1_va
cal_minute = 0;
}else if(param->value[0]>=0xa0){
ota_handle((u8*)param->value);
}
}else if(param->value[0]>=0xa0){
// 处理OTA升级命令
ota_handle((u8*)param->value);
}
}
void user_svc1_long_val_att_info_req_handler(ke_msg_id_t const msgid, struct custs1_att_info_req const *param, ke_task_id_t const dest_id, ke_task_id_t const src_id)
/**
* 长值特征属性信息请求处理函数
*
* @param msgid 消息ID
* @param param 请求参数
* @param dest_id 目标任务ID
* @param src_id 源任务ID
*
* 响应BLE客户端的属性信息请求
*/
void user_svc1_long_val_att_info_req_handler(ke_msg_id_t const msgid,
struct custs1_att_info_req const *param,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
struct custs1_att_info_rsp *rsp = KE_MSG_ALLOC(CUSTS1_ATT_INFO_RSP, src_id, dest_id, custs1_att_info_rsp);
// 分配响应消息内存
struct custs1_att_info_rsp *rsp = KE_MSG_ALLOC(CUSTS1_ATT_INFO_RSP,
src_id,
dest_id,
custs1_att_info_rsp);
// 设置连接索引
rsp->conidx = app_env[param->conidx].conidx;
// 设置属性索引
rsp->att_idx = param->att_idx;
// 设置长度为0
rsp->length = 0;
// 设置状态为无错误
rsp->status = ATT_ERR_NO_ERROR;
// 发送响应消息
KE_MSG_SEND(rsp);
}

View File

@@ -1,64 +0,0 @@
// 创建日志容器与清除按钮
const logDiv = document.createElement("div");
logDiv.id = "fixed-log-window";
document.body.appendChild(logDiv);
// 添加清除按钮
const clearBtn = document.createElement("button");
clearBtn.textContent = "清空日志";
clearBtn.className = "clear-log-btn";
logDiv.appendChild(clearBtn);
// 按钮点击事件
clearBtn.onclick = () => {
logDiv.querySelectorAll(".log-entry").forEach(entry => entry.remove());
};
// 覆盖 console.log保持原有功能
const originalLog = console.log;
console.log = function(...args) {
originalLog(...args);
const logLine = document.createElement("div");
logLine.className = "log-entry";
logLine.textContent = args.join(" ");
logDiv.appendChild(logLine);
logDiv.scrollTop = logDiv.scrollHeight; // 自动滚动
};
// 样式增强
const style = document.createElement("style");
style.textContent = `
#fixed-log-window {
/* 原有样式保持不变 */
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 200px;
background: rgba(0, 0, 0, 0.85);
color: #fff;
overflow-y: auto;
padding: 40px 10px 10px; /* 顶部留出按钮空间 */
font-family: monospace;
}
.clear-log-btn {
position: fixed;
right: 20px;
bottom: 215px;
padding: 4px 12px;
background: #ff4444;
border: none;
color: white;
cursor: pointer;
border-radius: 3px;
}
.clear-log-btn:hover {
background: #cc0000;
}
.log-entry {
padding: 4px 0;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
`;
document.head.appendChild(style);

609
weble/webApp.html Normal file
View File

@@ -0,0 +1,609 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HM CLOCK</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- Tailwind 配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3B82F6',
secondary: '#10B981',
accent: '#8B5CF6',
warning: '#F59E0B',
danger: '#EF4444',
dark: '#1E293B',
light: '#F8FAFC'
},
fontFamily: {
inter: ['Inter', 'sans-serif'],
},
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.btn-hover {
@apply transition-all duration-300 hover:shadow-lg transform hover:-translate-y-0.5;
}
.card-effect {
@apply bg-white rounded-xl shadow-md hover:shadow-lg transition-all duration-300;
}
.text-gradient {
@apply bg-clip-text text-transparent bg-gradient-to-r from-primary to-accent;
}
}
</style>
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f0f9ff;
background-image:
radial-gradient(at 40% 20%, rgba(59, 130, 246, 0.05) 0px, transparent 50%),
radial-gradient(at 80% 0%, rgba(139, 92, 246, 0.05) 0px, transparent 50%),
radial-gradient(at 0% 50%, rgba(16, 185, 129, 0.05) 0px, transparent 50%);
}
.log-entry {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
</style>
</head>
<body class="min-h-screen p-4 md:p-6 flex justify-center items-start font-inter text-dark">
<div class="w-full max-w-4xl">
<!-- 头部标题 -->
<header class="mb-6 text-center">
<h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-gradient mb-2">HM CLOCK 控制中心</h1>
</header>
<!-- 主内容卡片 -->
<div class="card-effect p-5 md:p-6 space-y-6">
<!-- 控制按钮区域 -->
<div class="flex flex-wrap gap-3 md:gap-4">
<button id="connect-button" class="bg-primary text-white px-5 py-2.5 rounded-lg flex items-center gap-2 btn-hover font-medium">
<i class="fa fa-bluetooth"></i> 连接设备
</button>
<button id="setime-button" class="bg-warning text-white px-5 py-2.5 rounded-lg flex items-center gap-2 btn-hover font-medium opacity-50 cursor-not-allowed" disabled>
<i class="fa fa-clock-o"></i> 同步时间
</button>
<button id="upfirm-button" class="bg-accent text-white px-5 py-2.5 rounded-lg flex items-center gap-2 btn-hover font-medium opacity-50 cursor-not-allowed" disabled>
<i class="fa fa-refresh"></i> 固件升级
</button>
<button id="send90-button" class="bg-secondary text-white px-5 py-2.5 rounded-lg flex items-center gap-2 btn-hover font-medium opacity-50 cursor-not-allowed" disabled>
<i class="fa fa-send"></i> 切换时制
</button>
</div>
<!-- 设备信息区域 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 p-4 bg-gray-50 rounded-lg border border-gray-100">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary">
<i class="fa fa-microchip"></i>
</div>
<div>
<p class="text-gray-500 text-sm">设备名称</p>
<p id="device_name" class="font-semibold">未连接</p>
</div>
</div>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-warning/10 flex items-center justify-center text-warning">
<i class="fa fa-bolt"></i>
</div>
<div>
<p class="text-gray-500 text-sm">当前电压</p>
<p id="current_voltage" class="font-semibold">--.- V</p>
</div>
</div>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-secondary/10 flex items-center justify-center text-secondary">
<i class="fa fa-calendar"></i>
</div>
<div>
<p class="text-gray-500 text-sm">设备时间</p>
<p id="current_time" class="font-semibold">--:--:--</p>
</div>
</div>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-accent/10 flex items-center justify-center text-accent">
<i class="fa fa-laptop"></i>
</div>
<div>
<p class="text-gray-500 text-sm">系统时间</p>
<p id="system_time" class="font-semibold">加载中...</p>
</div>
</div>
</div>
<!-- 进度条区域 -->
<div id="progress-container" class="hidden">
<p class="text-sm font-medium text-gray-600 mb-1">固件升级进度</p>
<div class="h-2 bg-gray-100 rounded-full overflow-hidden">
<div id="update_progress_bar" class="h-full bg-gradient-to-r from-primary to-accent w-0 transition-all duration-300"></div>
</div>
<p id="update_progress_text" class="text-xs text-gray-500 mt-1">0%</p>
<div id="update_progress" class="hidden"></div>
</div>
<!-- 日志终端区域 -->
<div class="space-y-2">
<div class="flex justify-between items-center">
<h3 class="font-semibold text-gray-700 flex items-center gap-2">
<i class="fa fa-terminal text-primary"></i> 设备日志
</h3>
<button id="clear-log-btn" class="text-xs text-gray-500 hover:text-danger transition-colors">
<i class="fa fa-trash-o"></i> 清空日志
</button>
</div>
<div id="log-window" class="bg-gray-900 text-gray-100 text-xs md:text-sm rounded-lg h-48 md:h-64 overflow-y-auto p-3 font-mono">
<div class="text-gray-400 log-entry">终端已启动,等待设备连接...</div>
</div>
</div>
</div>
<!-- 页脚信息 -->
<footer class="mt-6 text-center text-xs text-gray-500">
<p>HM CLOCK蓝牙控制工具 &copy; https://github.com/tpunix/HMCLOCK</p>
</footer>
</div>
<script src="https://cdn.sheetjs.com/crc-32-latest/package/crc32.js"></script>
<script>
// 系统时间更新
function updateSystemTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const dateString = now.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
document.getElementById('system_time').textContent = `${dateString} ${timeString}`;
}
// 初始化更新系统时间并设置定时器
updateSystemTime();
setInterval(updateSystemTime, 1000);
// 日志系统
const logWindow = document.getElementById('log-window');
const clearLogBtn = document.getElementById('clear-log-btn');
// 自定义日志函数
function log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const logLine = document.createElement('div');
logLine.className = 'log-entry mb-1 border-b border-gray-800 pb-1';
let typeIcon = '';
let typeColor = '';
switch(type) {
case 'success':
typeIcon = '<span class="text-green-400 mr-1"><i class="fa fa-check-circle"></i></span>';
typeColor = 'text-green-300';
break;
case 'error':
typeIcon = '<span class="text-red-400 mr-1"><i class="fa fa-exclamation-circle"></i></span>';
typeColor = 'text-red-300';
break;
case 'warning':
typeIcon = '<span class="text-yellow-400 mr-1"><i class="fa fa-exclamation-triangle"></i></span>';
typeColor = 'text-yellow-300';
break;
case 'info':
default:
typeIcon = '<span class="text-blue-400 mr-1"><i class="fa fa-info-circle"></i></span>';
typeColor = 'text-gray-300';
}
logLine.innerHTML = `<span class="text-gray-500 mr-2">[${timestamp}]</span>${typeIcon}<span class="${typeColor}">${message}</span>`;
logWindow.appendChild(logLine);
logWindow.scrollTop = logWindow.scrollHeight;
}
// 替换console.log
const originalLog = console.log;
console.log = function (...args) {
originalLog(...args);
log(args.join(" "));
};
// 清空日志
clearLogBtn.addEventListener('click', () => {
logWindow.innerHTML = '<div class="text-gray-400 log-entry">日志已清空</div>';
});
// 核心功能代码
var connected = false;
var device = null;
var longValue = null;
var deviceTimeInterval = null; // 设备时间更新定时器
var deviceTimeBase = null; // 设备时间基准值
// 格式化时间显示
function formatTime(year, month, mday, hour, min, sec) {
month += 1;
return `${year}-${month.toString().padStart(2, '0')}-${mday.toString().padStart(2, '0')} ${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
}
// 更新设备时间显示
function updateDeviceTime() {
if (!deviceTimeBase) return;
// 获取基准时间
let { year, month, mday, hour, minute, second } = deviceTimeBase;
// 计算从基准时间开始经过的秒数
const now = new Date();
const elapsedSeconds = Math.floor((now - deviceTimeBase.timestamp) / 1000);
// 更新时间
let totalSeconds = second + elapsedSeconds;
second = totalSeconds % 60;
let totalMinutes = minute + Math.floor(totalSeconds / 60);
minute = totalMinutes % 60;
let totalHours = hour + Math.floor(totalMinutes / 60);
hour = totalHours % 24;
// 此处忽略天数变化,因为设备时间通常不会长时间运行
// 更新显示
document.getElementById('current_time').textContent = formatTime(year, month, mday, hour, minute, second);
}
function onClick() {
if(connected) disconnect();
else connectToDevice();
}
// 连接设备
async function connectToDevice() {
try {
// 更新按钮状态
const connectButton = document.getElementById('connect-button');
connectButton.disabled = true;
connectButton.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 连接中...';
console.log('请求设备...');
device = await navigator.bluetooth.requestDevice({
filters: [{ namePrefix: "DLG-CLOCK" }],
optionalServices: [ 0xff00 ]
});
console.log('设备名称:', device.name);
document.getElementById('device_name').textContent = device.name;
device.ongattserverdisconnected = onDisconnect;
let server = await device.gatt.connect();
console.log('设备已连接');
let service = await server.getPrimaryService(0xff00);
let ctrlPoint = await service.getCharacteristic(0xff03);
let adc1Value = await service.getCharacteristic(0xff02);
longValue = await service.getCharacteristic(0xff01);
let cur_voltage = await adc1Value.readValue();
console.log('当前电压:', cur_voltage.getUint16(0, true) / 1000 + 'V');
document.getElementById('current_voltage').textContent = (cur_voltage.getUint16(0, true) / 1000).toFixed(2) + ' V';
let cur_time = await longValue.readValue();
let year = cur_time.getUint16(0, true),
month = cur_time.getUint8(2),
mday = cur_time.getUint8(3),
hour = cur_time.getUint8(4),
minute = cur_time.getUint8(5),
second = cur_time.getUint8(6);
// 保存设备时间基准值和当前时间戳
deviceTimeBase = {
year, month, mday, hour, minute, second,
timestamp: new Date()
};
// 立即更新设备时间显示
updateDeviceTime();
// 设置定时器每秒更新一次设备时间
if (deviceTimeInterval) clearInterval(deviceTimeInterval);
deviceTimeInterval = setInterval(updateDeviceTime, 1000);
let now = new Date();
document.getElementById('system_time').textContent = formatTime(
now.getFullYear(), now.getMonth(), now.getDate(),
now.getHours(), now.getMinutes(), now.getSeconds()
);
connected = true;
document.getElementById('setime-button').disabled = false;
document.getElementById('upfirm-button').disabled = false;
document.getElementById('send90-button').disabled = false;
document.getElementById('setime-button').classList.remove('opacity-50', 'cursor-not-allowed');
document.getElementById('upfirm-button').classList.remove('opacity-50', 'cursor-not-allowed');
document.getElementById('send90-button').classList.remove('opacity-50', 'cursor-not-allowed');
connectButton.innerHTML = '<i class="fa fa-disconnect"></i> 断开连接';
connectButton.disabled = false;
} catch (error) {
console.log('连接失败:', error);
disconnect();
}
}
// 对时功能
async function onSetTime() {
const setimeButton = document.getElementById('setime-button');
setimeButton.disabled = true;
setimeButton.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 同步中...';
try {
let now = new Date();
let year = now.getFullYear(),
month = now.getMonth(),
mday = now.getDate(),
wday = now.getDay(),
hour = now.getHours(),
minute = now.getMinutes(),
second = now.getSeconds();
let locale_str = now.toLocaleDateString('zh-CN-u-ca-chinese',{month:'numeric',day:'numeric'});
let l_month = locale_str.startsWith('闰') ? 128 : 0;
if (l_month) locale_str = locale_str.slice(1);
let [l_month_num, l_day] = locale_str.split('-').map(Number);
l_month += l_month_num;
let l_year = parseInt(now.toLocaleDateString('zh-CN-u-ca-chinese',{year:'numeric'}));
console.log('农历年:', l_year, '月:', l_month, '日:', l_day);
let buf = new Uint8Array(12);
buf.set([0x91, year % 256, Math.floor(year / 256), month, mday, hour, minute, second, wday, l_year - 2020, l_month - 1, l_day]);
await longValue.writeValue(buf);
// 更新设备时间基准值
deviceTimeBase = {
year, month: month, mday, hour, minute, second,
timestamp: new Date()
};
// 立即更新设备时间显示
updateDeviceTime();
console.log('同步时间成功!');
} catch (error) {
console.log('同步时间失败:', error);
} finally {
setimeButton.innerHTML = '<i class="fa fa-clock-o"></i> 同步时间';
setimeButton.disabled = false;
}
}
// 文件读取辅助函数
function readFileAsArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
}
// 固件标识查找
function find_patten(target, patten) {
for (let i = 0; i <= target.length - patten.length; i++) {
let match = true;
for (let j = 0; j < patten.length; j++) {
if (target[i + j] !== patten[j]) {
match = false;
break;
}
}
if (match) return i;
}
return -1;
}
// 发送命令
async function onSend90() {
const send90Button = document.getElementById('send90-button');
send90Button.disabled = true;
send90Button.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 发送中...';
try {
let buf = new Uint8Array([0x90]); // 构造单字节命令
await longValue.writeValue(buf);
console.log('已发送切换命令');
} catch (err) {
console.log('发送失败:', err);
} finally {
send90Button.innerHTML = '<i class="fa fa-send"></i> 发送命令';
send90Button.disabled = false;
}
}
// 固件升级
async function onUpdate() {
const upfirmButton = document.getElementById('upfirm-button');
upfirmButton.disabled = true;
upfirmButton.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 升级中...';
document.getElementById('progress-container').classList.remove('hidden');
let firm_buf, firm_size;
try {
console.log('准备打开文件');
const [handle] = await window.showOpenFilePicker({
types: [{ description: 'Firm files', accept: { 'text/plain': ['.bin'] } }]
});
const file = await handle.getFile();
let abuf = await readFileAsArrayBuffer(file);
firm_buf = new Uint8Array(abuf);
firm_size = file.size;
} catch (err) {
console.log('文件读取失败:', err);
upfirmButton.innerHTML = '<i class="fa fa-refresh"></i> 固件升级';
upfirmButton.disabled = false;
document.getElementById('progress-container').classList.add('hidden');
return;
}
let firm_magic = new Uint8Array([0x79, 0x13, 0xa5, 0xf9, 0x86, 0xec, 0x5a, 0x06]);
let pos = find_patten(firm_buf, firm_magic);
if (pos == -1) {
console.log('无效固件: 未找到版本号!');
upfirmButton.innerHTML = '<i class="fa fa-refresh"></i> 固件升级';
upfirmButton.disabled = false;
document.getElementById('progress-container').classList.add('hidden');
return;
}
let firm_ver = firm_buf[pos + 9] * 256 + firm_buf[pos + 8];
let firm_crc = CRC32.buf(firm_buf);
console.log('固件版本:', firm_ver, '大小:', firm_size, 'CRC:', (firm_crc >>> 0).toString(16));
let buf = new Uint8Array(136);
let view = new DataView(buf.buffer);
buf[0] = 0xa0;
view.setUint16(2, firm_size, true);
await longValue.writeValue(buf);
let sent = 0;
const totalSize = firm_size + 64;
const progressBar = document.getElementById('update_progress_bar');
const progressText = document.getElementById('update_progress_text');
try {
for (let i = 0; i < totalSize; i += 256) {
buf.fill(0xff);
if (i === 0) {
view.setUint32(8, 0x00aa5170, true);
view.setUint32(12, firm_size, true);
view.setUint32(16, firm_crc, true);
view.setUint32(36, 0xa50f0000 + firm_ver, true);
buf[40] = 0;
buf[0] = 0xa2;
buf.set(firm_buf.slice(sent, sent + 64), 72);
await longValue.writeValue(buf);
sent += 64;
} else {
buf[0] = 0xa2;
buf.set(firm_buf.slice(sent, sent + 128), 8);
await longValue.writeValue(buf);
sent += 128;
}
buf[0] = 0xa3;
buf.set(firm_buf.slice(sent, sent + 128), 8);
await longValue.writeValue(buf);
sent += 128;
// 更新进度条
const progress = Math.min(Math.round(100 * sent / totalSize), 100);
progressBar.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
document.getElementById('update_progress').textContent = `升级进度: ${progress}%`;
}
buf[0] = 0xa4;
buf.fill(0, 1, 4);
await longValue.writeValue(buf);
console.log('升级完成');
} catch (error) {
console.log('升级结束,蓝牙已断开');
} finally {
upfirmButton.innerHTML = '<i class="fa fa-refresh"></i> 固件升级';
upfirmButton.disabled = false;
document.getElementById('progress-container').classList.add('hidden');
}
}
// 断开连接
function disconnect() {
const connectButton = document.getElementById('connect-button');
connectButton.disabled = true;
connectButton.innerHTML = '<i class="fa fa-spinner fa-spin"></i> 断开中...';
document.getElementById('setime-button').disabled = true;
document.getElementById('send90-button').disabled = true;
document.getElementById('upfirm-button').disabled = true;
document.getElementById('setime-button').classList.add('opacity-50', 'cursor-not-allowed');
document.getElementById('upfirm-button').classList.add('opacity-50', 'cursor-not-allowed');
document.getElementById('send90-button').classList.add('opacity-50', 'cursor-not-allowed');
// 清除设备时间更新定时器
if (deviceTimeInterval) {
clearInterval(deviceTimeInterval);
deviceTimeInterval = null;
}
deviceTimeBase = null;
if (device && device.gatt.connected) {
device.gatt.disconnect();
}
onDisconnect();
}
// 断开回调
function onDisconnect() {
device = null;
connected = false;
document.getElementById('device_name').textContent = "未连接";
document.getElementById('current_voltage').textContent = "--.- V";
document.getElementById('current_time').textContent = "--:--:--";
const connectButton = document.getElementById('connect-button');
connectButton.innerHTML = '<i class="fa fa-bluetooth"></i> 连接设备';
connectButton.disabled = false;
console.log('设备已断开连接');
}
// 事件绑定
document.getElementById('connect-button').addEventListener('click', onClick);
document.getElementById('setime-button').addEventListener('click', onSetTime);
document.getElementById('upfirm-button').addEventListener('click', onUpdate);
document.getElementById('send90-button').addEventListener('click', onSend90);
</script>
</body>
</html>