Files
HMCLOCK/weble/weble.html
2025-08-18 17:24:41 +08:00

368 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Bluetooth Example</title>
</head>
<body>
<button id="connect-button">连接</button>
<button id="setime-button" disabled>对时</button>
<button id="upfirm-button" disabled>升级</button>
<button id="calibration-button" disabled>校准</button>
<div id="device_name"></div>
<div id="current_voltage"></div>
<div id="current_time"></div>
<div id="system_time"></div>
<div id="update_progress"></div>
<script src="log.js"></script>
<script src="https://cdn.sheetjs.com/crc-32-latest/package/crc32.js"></script>
<script>
var connected = false;
var device = null;
var longValue = null;
function formatTime(year, month, mday, hour, min, sec) {
month += 1;
if(month<10) month = '0'+month;
if(mday<10) mday = '0'+mday;
if(hour<10) hour = '0'+hour;
if(min<10) min = '0'+min;
if(sec<10) sec = '0'+sec;
return year+"-"+month+"-"+mday+" "+hour+":"+min+":"+sec+" ";
}
function onClick() {
if(connected) {
disconnect();
}else{
connectToDevice();
}
}
async function connectToDevice() {
try {
console.log('请求设备...');
device = await navigator.bluetooth.requestDevice({
filters: [{ namePrefix: "DLG-CLOCK"}]
, optionalServices: [ 0xff00 ]
});
console.log('device: ', device.name);
document.getElementById('device_name').textContent = "设备名: "+device.name;
device.ongattserverdisconnected = onDisconnect;
var server = await device.gatt.connect();
console.log('设备已连接!');
var service = await server.getPrimaryService( 0xff00 );
console.log('获得service: ', service.uuid);
var ctrlPoint = await service.getCharacteristic( 0xff03 );
var adc1Value = await service.getCharacteristic( 0xff02 );
longValue = await service.getCharacteristic( 0xff01 );
cur_voltage = await adc1Value.readValue();
console.log('cur_voltage:', cur_voltage);
document.getElementById('current_voltage').textContent = "当前电压: "+cur_voltage.getUint16(0, true);
var year, month, mday, wday, hour, minute, second;
cur_time = await longValue.readValue();
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);
document.getElementById('current_time').textContent = "当前时间: "+formatTime(year, month, mday, hour, minute, second);
var buf = new Uint8Array(10);
var today = new Date();
year = today.getFullYear();
month = today.getMonth();
mday = today.getDate();
wday = today.getDay();
hour = today.getHours();
minute = today.getMinutes();
second = today.getSeconds();
document.getElementById('system_time').textContent = "系统时间: "+formatTime(year, month, mday, hour, minute, second);
connected = true;
document.getElementById('setime-button').disabled = false;
document.getElementById('upfirm-button').disabled = false;
document.getElementById('calibration-button').disabled = false;
document.getElementById('connect-button').textContent = "断开";
} catch (error) {
console.log('连接失败:', error);
disconnect();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
async function onSetTime() {
document.getElementById('setime-button').disabled = true;
// 等待整秒时间
while(true){
const tm_ms = Date.now();
if((tm_ms%1000)==0)
break;
}
var today = new Date();
year = today.getFullYear();
month = today.getMonth();
mday = today.getDate();
wday = today.getDay();
hour = today.getHours();
minute = today.getMinutes();
second = today.getSeconds();
locale_str = today.toLocaleDateString('zh-CN-u-ca-chinese',{month:'numeric',day:'numeric'});
l_month = 0;
if(locale_str.charAt(0)=='闰'){
l_month = 128;
locale_str = locale_str.substr(1,);
}
lstr_split = locale_str.split('-');
l_month += parseInt(lstr_split[0]);
l_mday = parseInt(lstr_split[1]);
locale_year = today.toLocaleDateString('zh-CN-u-ca-chinese',{year:'numeric'});
l_year = parseInt(locale_year.substr(0,4));
console.log('年:', l_year, '月:', l_month, '日:', l_mday);
var buf = new Uint8Array(12);
buf[0] = 0x91;
buf[1] = year%256;
buf[2] = year/256;
buf[3] = month;
buf[4] = mday;
buf[5] = hour;
buf[6] = minute;
buf[7] = second;
buf[8] = wday;
buf[9] = l_year-2020;
buf[10]= l_month-1;
buf[11]= l_mday;
await longValue.writeValue(buf);
document.getElementById('system_time').textContent = "系统时间: "+formatTime(year, month, mday, hour, minute, second);
console.log('同步时间成功!');
document.getElementById('setime-button').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) {
tlen = target.length;
plen = patten.length;
for(i=0; i<tlen-plen; i++){
for(j=0; j<plen; j++){
if(target[i+j] !== patten[j]){
break;
}
}
if(j==plen)
return i;
}
return -1;
}
async function onUpdate() {
document.getElementById('upfirm-button').disabled = true;
var firm_size;
var firm_buf;
try{
console.log('准备打开文件');
const file_handle = await window.showOpenFilePicker({
types: [{
description: 'Firm files',
accept: {
'text/plain': ['.bin'],
},
}]
});
console.log('选择文件: ', file_handle);
const file = await file_handle[0].getFile();
console.log('file: ', file);
abuf = await readFileAsArrayBuffer(file);
firm_buf = new Uint8Array(abuf);
firm_size = file.size;
}catch(err){
console.log('文件读取失败: ', err);
document.getElementById('upfirm-button').disabled = false;
return;
}
firm_magic = new Uint8Array([0x79, 0x13, 0xa5, 0xf9, 0x86, 0xec, 0x5a, 0x06]);
pos = find_patten(firm_buf, firm_magic);
if(pos == -1) {
console.log('无效固件: 未找到版本号!');
document.getElementById('upfirm-button').disabled = false;
return;
}
firm_ver = firm_buf[pos+9]*256+firm_buf[pos+8];
console.log('固件版本:', firm_ver);
console.log('固件大小:', firm_size);
firm_crc = CRC32.buf(firm_buf);
console.log('固件CRC:', (firm_crc >>> 0).toString(16));
var buf = new Uint8Array(136);
dataView = new DataView(buf.buffer);
console.log('开始升级');
buf[0] = 0xa0;
buf[1] = 0x00;
dataView.setUint16(2, firm_size, true);
await longValue.writeValue(buf);
var pos = 0;
for(i=0; i<firm_size+64; i+=256){
console.log('发送', i);
buf.fill(0xff);
if(i==0){
dataView.setUint32(8+0, 0x00aa5170, true);
dataView.setUint32(8+4, firm_size, true);
dataView.setUint32(8+8, firm_crc, true);
dataView.setUint32(8+28, (0xa50f0000+firm_ver), true);
buf[8+32] = 0;
buf[0] = 0xa2;
buf.set(firm_buf.subarray(pos, pos+64), 8+64);
await longValue.writeValue(buf);
pos += 64;
}else{
buf[0] = 0xa2;
buf.set(firm_buf.subarray(pos, pos+128), 8);
await longValue.writeValue(buf);
pos += 128;
}
buf[0] = 0xa3;
buf.set(firm_buf.subarray(pos, pos+128), 8);
await longValue.writeValue(buf);
pos += 128;
document.getElementById('update_progress').textContent =
'升级进度: '+((100*pos/(firm_size+64))>>0)+'%';
}
console.log('发送完毕');
buf[0] = 0xa4;
buf[1] = 0x00;
buf[2] = 0x00;
buf[3] = 0x00;
await longValue.writeValue(buf);
console.log('升级结束');
document.getElementById('upfirm-button').disabled = false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
async function onCalibration() {
document.getElementById('calibration-button').disabled = true;
var minute, second, last_minute, cal_minute;
cur_time = await longValue.readValue();
last_minute = cur_time.getUint8(5);
cal_minute = cur_time.getInt32(7, true);
console.log('对时后'+cal_minute+'分钟');
if(cal_minute==-1){
console.log('请先对时!');
}else if(cal_minute<2880){
console.log('对时与校准间隔太短(小于两天)!');
}else{
console.log('等待分钟跳变......');
while(true) {
cur_time = await longValue.readValue();
minute = cur_time.getUint8(5);
if(minute != last_minute)
break;
last_minute = minute;
}
second = cur_time.getUint8(6);
var today = new Date();
var sys_minute = today.getMinutes();
var sys_second = today.getSeconds();
console.log('设备时间: '+minute+'分'+second+'秒');
console.log('系统时间: '+sys_minute+'分'+sys_second+'秒');
if(minute>sys_minute){
if(minute-sys_minute>50)
sys_minute += 60;
}else{
if(sys_minute-minute>50)
minute += 60;
}
var diff = (minute*60+second)-(sys_minute*60+sys_second);
console.log('时间差: '+diff+'秒');
var buf = new Uint8Array(4);
buf[0] = 0x92;
buf[1] = diff%256;
buf[2] = diff/256;
buf[3] = 0x00;
await longValue.writeValue(buf);
console.log('校准完成');
}
document.getElementById('calibration-button').disabled = false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
function disconnect() {
document.getElementById('setime-button').disabled = true;
if(!device)
return;
if(device.gatt.connected){
device.gatt.disconnect();
}
onDisconnect();
}
function onDisconnect() {
device = null;
console.log('设备已断开连接');
connected = false;
document.getElementById('setime-button').disabled = true;
document.getElementById('calibration-button').disabled = true;
document.getElementById('connect-button').textContent = "连接";
}
document.getElementById('connect-button').addEventListener('click', onClick);
document.getElementById('setime-button').addEventListener('click', onSetTime);
document.getElementById('upfirm-button').addEventListener('click', onUpdate);
document.getElementById('calibration-button').addEventListener('click', onCalibration);
</script>
</body>
</html>