From 1043e83bd37b0c8135195f91e84f5f229f241f21 Mon Sep 17 00:00:00 2001
From: chenzhihao <1798906853@qq.com>
Date: Fri, 23 Jan 2026 10:14:55 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AF=BB=E5=8F=96=E9=97=A8?=
=?UTF-8?q?=E7=A3=81=E3=80=81=E5=8D=AB=E6=B5=B4=E6=8C=89=E9=92=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Document/W13无卡取电设备 - 蓝牙通讯协议.md | 13 +-
app.json | 4 +-
.../BluetoothDebugging/B13page/B13page.js | 365 +++++++++++++++++-
.../BluetoothDebugging/B13page/B13page.wxml | 178 +++++----
.../BluetoothDebugging/B13page/B13page.wxss | 63 ++-
.../B13page/B13page_描述.md | 148 +++++++
.../BluetoothDebugging/BluetoothDebugging.js | 1 +
.../EquipmentCaontrol/EquipmentCaontrol.js | 2 +-
utils/w13Packet.js | 13 +
9 files changed, 671 insertions(+), 116 deletions(-)
create mode 100644 pages/basics/BluetoothDebugging/B13page/B13page_描述.md
diff --git a/Document/W13无卡取电设备 - 蓝牙通讯协议.md b/Document/W13无卡取电设备 - 蓝牙通讯协议.md
index fa7fefe..b1fe2e0 100644
--- a/Document/W13无卡取电设备 - 蓝牙通讯协议.md
+++ b/Document/W13无卡取电设备 - 蓝牙通讯协议.md
@@ -190,8 +190,19 @@ MCU -> PC
| PC→MCU | 0x16 | P0: 控制位
bit0: 设置门磁开关廊灯事件
bit1: 设置卫浴灯开关事件
门磁开关廊灯事件:
P1: 事件触发延迟时间数值
P2: 时间单位,1=秒 2=分 3=时
P3: 事件释放延迟时间
P4: 时间单位,1=秒 2=分 3=时
卫浴灯开关事件:
P5: 事件触发延迟时间数值
P6: 时间单位,1=秒 2=分 3=时
P7: 事件释放延迟时间
P8: 时间单位,1=秒 2=分 3=时 | 设置门磁与卫浴雷达的触发/释放时序参数 |
| MCU→PC | 0x16 | P0:
0x01: 参数正确
0x02: 参数错误 | 返回设置结果 |
-## 4. 命令交互流程图
+
+### 3.9 读取门磁/卫浴事件触发/释放参数
+| 方向 | 命令字 | 参数 | 备注 |
+|------|-------|------|------|
+| PC→MCU | 0x17 | P0: 0x01 (读取参数) | 请求设备返回门磁与卫浴雷达的触发/释放延时参数(低地址在前) |
+| MCU→PC | 0x17 | P0~P1: 门磁事件触发延迟时间(16-bit,单位:秒,低地址在前)
P2~P3: 门磁事件释放延迟时间(16-bit,低地址在前)
P4~P5: 卫浴雷达事件触发延迟时间(16-bit,低地址在前)
P6~P7: 卫浴雷达事件释放延迟时间(16-bit,低地址在前) | 设备返回各项时序参数,均为 16-bit 小端格式,单位为秒。 |
+
+说明:
+- 请求示例(PC→MCU):Frame_Type=0x17,P0=0x01。
+- 响应示例(MCU→PC):Frame_Type=0x17,参数区域例如 P0~P7 = [0x0A,0x00, 0x14,0x00, 0x05,0x00, 0x08,0x00] 表示:门磁触发延时 10s,门磁释放延时 20s,卫浴触发 5s,卫浴释放 8s(均为小端)。
+
+## 4. 命令交互流程图
### 4.1 读版本号流程
```mermaid
flowchart TD
diff --git a/app.json b/app.json
index 8c287fb..2458a29 100644
--- a/app.json
+++ b/app.json
@@ -1,8 +1,8 @@
{
"pages": [
- "pages/autho/index",
- "pages/basics/BluetoothDebugging/B13page/B13page",
+ "pages/autho/index",
+ "pages/basics/BluetoothDebugging/B13page/B13page",
"pages/login/login",
"pages/index/index",
"pages/mycenter/index",
diff --git a/pages/basics/BluetoothDebugging/B13page/B13page.js b/pages/basics/BluetoothDebugging/B13page/B13page.js
index 9169852..d9b1aed 100644
--- a/pages/basics/BluetoothDebugging/B13page/B13page.js
+++ b/pages/basics/BluetoothDebugging/B13page/B13page.js
@@ -1,5 +1,5 @@
-const { buildCommand, buildReadVersion, buildSetDoorBathEvent, COMMANDS, verifyHexPacket } = require('../../../../utils/w13Packet.js')
+const { buildCommand, buildReadVersion, buildSetDoorBathEvent, buildReadDoorBathEvent, COMMANDS, verifyHexPacket } = require('../../../../utils/w13Packet.js')
const FIXED_CONNECT_CMD = new Uint8Array([0xCC, 0xC0, 0x0C, 0x00, 0x4F, 0xC0, 0x01, 0x00, 0x02, 0x00, 0x0C, 0x08])
// Optional encoding library for robust GBK/GB18030 decoding
let EncodingLib = null
@@ -38,6 +38,12 @@ Page({
txCharId: '',
rxCharId: '',
logList: [],
+ logScrollTo: '',
+ // 日志滚动区高度(px),由页面显示时计算并设置
+ logListHeight: 200,
+ debugDeviceInfoLogs: false,
+ isReconnecting: false,
+ showLastKnown: true,
timeUnits: ['秒', '分', '时'],
hourValues: Array.from({ length: 24 }, (_, i) => i + 1),
msValues: Array.from({ length: 60 }, (_, i) => i + 1),
@@ -128,6 +134,25 @@ Page({
addDialogSelectedGroupIndex: 0,
},
+ // 动态计算日志区域高度(像素)
+ // 在页面显示或切换到日志 Tab 时调用,计算公式:高度 = 窗口高度 - 日志卡片顶部
+ // 若计算结果过小则使用最小值保障可见性
+
+
+ // 中间层:在执行测试按键动作前检查连接状态
+ maybeOnTestKeyTap(e) {
+ try {
+ if (!this.data.isConnected) {
+ wx.showToast({ title: '未连接设备', icon: 'none' })
+ this.appendLog('UI', '尝试操作测试按键但设备未连接')
+ return
+ }
+ if (typeof this.onTestKeyTap === 'function') return this.onTestKeyTap(e)
+ } catch (err) {
+ // swallow
+ }
+ },
+
onLoad(options) {
const raw = options && (options.DevName || options.name) || ''
// 处理通过 URL 传递的编码,避免中文显示为乱码
@@ -167,50 +192,94 @@ Page({
// 更新当前设备的 RSSI / signal / 连接状态(基于 deviceId)
updateDeviceInfo() {
+ //console.log('开始获取信号值')
const deviceId = this.data.deviceId
if (!deviceId) {
this.setData({ bleSignal: '-', bleRSSI: '-', isConnected: false })
return
}
- const setUnknown = () => this.setData({ bleSignal: '-', bleRSSI: '-', isConnected: false })
+ const setUnknown = () => {
+ this.setData({ bleSignal: '-', bleRSSI: '-', isConnected: false })
+ }
try {
- // 优先使用 getConnectedBluetoothDevices 查询当前已连接设备
+
+ // 优先使用 getBLEDeviceRSSI 查询当前已连接设备
const svc = this.data.serviceId
- if (typeof wx.getConnectedBluetoothDevices === 'function' && svc) {
- wx.getConnectedBluetoothDevices({ services: [svc], success: (res) => {
- const devices = (res && res.devices) || []
- const found = devices.find(d => d.deviceId === deviceId)
- if (found) {
- const rssi = (typeof found.RSSI === 'number') ? found.RSSI : (found.RSSI ? Number(found.RSSI) : 0)
+ //console.log('开始获取信号值1')
+ if (typeof wx.getBLEDeviceRSSI === 'function' && svc) {
+ //console.log('开始获取信号值2')
+ wx.getBLEDeviceRSSI({ deviceId: deviceId, success: (res) => {
+ //console.log('开始获取信号值3')
+ ////console.log(res)
+
+
+ if (res) {
+ //console.log('开始获取信号值4')
+ //console.log(res.RSSI)
+
+ const rssi = (typeof res.RSSI === 'number') ? res.RSSI : (found.RSSI ? Number(res.RSSI) : 0)
+
const sigText = isNaN(rssi) ? '-' : `${rssi} dBm`
+ // 记录最后已知 RSSI
+ try { this._lastKnownRSSI = isNaN(rssi) ? null : rssi; this._lastKnownAt = Date.now() } catch (e) { /* ignore */ }
this.setData({ bleRSSI: isNaN(rssi) ? '-' : rssi, bleSignal: sigText, isConnected: true })
return
}
// 未连接
+
+ // 若存在最后已知RSSI则展示最后已知值,否则展示未知
+ if (this._lastKnownRSSI != null) {
+ //console.log('开始获取信号值5')
+ const age = Math.floor((Date.now() - (this._lastKnownAt || 0)) / 1000)
+ const sigText = `${this._lastKnownRSSI} dBm (最后已知 ${age}s)`
+ this.setData({ bleRSSI: this._lastKnownRSSI, bleSignal: sigText, isConnected: false })
+ } else {
+ //console.log('开始获取信号值6')
+ setUnknown()
+ }
+ }, fail: () => {
+ //console.log('开始获取信号值7')
setUnknown()
- }, fail: () => setUnknown() })
+ } })
return
}
-
+ //console.log('开始获取信号值8')
// 兜底使用 getBluetoothDevices 查询缓存设备
if (typeof wx.getBluetoothDevices === 'function') {
+ //console.log('开始获取信号值9')
wx.getBluetoothDevices({ success: (res) => {
+ //console.log('开始获取信号值10')
const devices = (res && res.devices) || []
const found = devices.find(d => d.deviceId === deviceId)
if (found) {
+ //console.log('开始获取信号值11')
const rssi = (typeof found.RSSI === 'number') ? found.RSSI : (found.RSSI ? Number(found.RSSI) : 0)
const sigText = isNaN(rssi) ? '-' : `${rssi} dBm`
// connected 字段在缓存中可能为 true/false
- this.setData({ bleRSSI: isNaN(rssi) ? '-' : rssi, bleSignal: sigText, isConnected: !!found.connected })
+ try { this._lastKnownRSSI = isNaN(rssi) ? null : rssi; this._lastKnownAt = Date.now() } catch (e) { /* ignore */ }
+
+ this.setData({ bleRSSI: isNaN(rssi) ? '-' : rssi, bleSignal: sigText, isConnected: !!found.connected })
return
}
- setUnknown()
- }, fail: () => setUnknown() })
+ //console.log('开始获取信号值12')
+ if (this._lastKnownRSSI != null) {
+ //console.log('开始获取信号值13')
+ const age = Math.floor((Date.now() - (this._lastKnownAt || 0)) / 1000)
+ const sigText = `${this._lastKnownRSSI} dBm (最后已知 ${age}s)`
+ this.setData({ bleRSSI: this._lastKnownRSSI, bleSignal: sigText, isConnected: false })
+ } else {
+ //console.log('开始获取信号值14')
+ setUnknown()
+ }
+ }, fail: () =>{
+ //console.log('开始获取信号值15')
+ setUnknown()
+ } })
return
}
-
+ //console.log('开始获取信号值16')
setUnknown()
} catch (e) {
setUnknown()
@@ -322,6 +391,41 @@ Page({
}, 250)
} catch (e) { /* ignore */ }
try { this.updateDeviceInfo && this.updateDeviceInfo() } catch (e) {}
+
+ // 启动周期性刷新设备信息(信号/连接状态)
+ try { this.startDeviceInfoTimer && this.startDeviceInfoTimer() } catch (e) { /* ignore */ }
+ // 延迟计算日志区域高度,确保布局完成后获取正确位置
+ try { setTimeout(() => { try { this.updateLogListHeight && this.updateLogListHeight() } catch (e) {} }, 250) } catch (e) {}
+ },
+
+ onHide() {
+ // 页面隐藏时不停止设备信息轮询,保留以便断开后继续刷新(如需可在此处暂停)
+ },
+
+ updateLogListHeight() {
+ // 1. 拿到屏幕可用高度(px)
+ const sys = wx.getWindowInfo();
+ const screenHeight = sys.windowHeight; // px
+
+ // 2. 拿到 scroll-view 的 top(px)
+
+ wx.createSelectorQuery()
+ .in(this)
+ .select('#cnmdgdx')
+ .boundingClientRect(rect => {
+ if (rect) {
+ const topPx = rect.top; // px
+ const bottomPx = 10 / 2; // 10rpx → 5px(2倍屏)
+ const heightPx = screenHeight - topPx - bottomPx;
+
+ // 3. 转 rpx 并写入
+ this.setData({
+ logListHeight: heightPx * 2 // px→rpx
+ });
+ }
+ })
+ .exec();
+
},
onUnload() {
@@ -334,6 +438,7 @@ Page({
} catch (e) { /* ignore */ }
this._onBleConnChange = null
try { this.stopReconnectTimer && this.stopReconnectTimer() } catch (e) {}
+ try { this.stopDeviceInfoTimer && this.stopDeviceInfoTimer() } catch (e) { /* ignore */ }
},
// 顶部标签切换
@@ -351,6 +456,10 @@ Page({
// this.onStartOta()
// }, 200)
}
+ if (id === 4) {
+ // 切换到通信日志页时计算日志区域高度
+ try { setTimeout(() => { try { this.updateLogListHeight && this.updateLogListHeight() } catch (e) {} }, 120) } catch (e) {}
+ }
},
onOpenDelayChange(e) {
@@ -479,6 +588,77 @@ Page({
}
},
+ // 发送读取门磁/卫浴事件参数请求
+ sendReadDoorBathEvent() {
+ try {
+ const pkt = buildReadDoorBathEvent()
+ // 设置一个短期的 pendingResponse,若收到匹配类型则由 handleIncomingPacket 解析并回调
+ const self = this
+ // 清理旧的 pending
+ try { if (this._pendingResponse && this._pendingResponse._timeout) clearTimeout(this._pendingResponse._timeout) } catch (e) {}
+ this._pendingResponse = {
+ expectedType: COMMANDS.READ_DOOR_BATH_EVENT & 0xFF,
+ resolve(params) {
+ try {
+ // params 应为至少 8 字节:P0..P7 四个 16-bit 小端数(单位:秒)
+ if (!params || params.length < 8) {
+ self.appendLog('PARSE', '读取门卫事件返回长度不足')
+ wx.showToast({ title: '读取返回长度异常', icon: 'none' })
+ return
+ }
+ const toU16 = (off) => (params[off] & 0xFF) | ((params[off + 1] & 0xFF) << 8)
+ const doorTriggerSec = toU16(0)
+ const doorReleaseSec = toU16(2)
+ const bathTriggerSec = toU16(4)
+ const bathReleaseSec = toU16(6)
+
+ const conv = (sec) => {
+ const n = Number(sec || 0)
+ if (n <= 60) return { val: n, unitIdx: 0 }
+ const mins = Math.max(1, Math.round(n / 60))
+ return { val: Math.min(mins, 30), unitIdx: 1 }
+ }
+
+ const dTrig = conv(doorTriggerSec)
+ const dRel = conv(doorReleaseSec)
+ const bTrig = conv(bathTriggerSec)
+ const bRel = conv(bathReleaseSec)
+
+ self.setData({
+ doorTriggerDelay: dTrig.val,
+ doorTriggerUnitIndex: dTrig.unitIdx,
+ doorReleaseDelay: dRel.val,
+ doorReleaseUnitIndex: dRel.unitIdx,
+ bathTriggerDelay: bTrig.val,
+ bathTriggerUnitIndex: bTrig.unitIdx,
+ bathReleaseDelay: bRel.val,
+ bathReleaseUnitIndex: bRel.unitIdx
+ })
+ self.appendLog('PARSE', `读取门卫事件: 门触发=${doorTriggerSec}s, 门释放=${doorReleaseSec}s, 卫触发=${bathTriggerSec}s, 卫释放=${bathReleaseSec}s`)
+ wx.showToast({ title: '已更新事件参数', icon: 'success' })
+ } catch (ex) {
+ self.appendLog('WARN', `解析门卫事件返回异常 ${ex && (ex.message||ex)}`)
+ }
+ }
+ }
+ // 设置超时,3s后清理 pending
+ this._pendingResponse._timeout = setTimeout(() => { try { this._pendingResponse = null } catch (e) {} }, 3000)
+ this.transmitPacket(pkt, '读取门卫事件')
+ } catch (err) {
+ wx.showToast({ title: '构包失败', icon: 'none' })
+ }
+ },
+
+ onReadDoorEvent() {
+ if (!this.data.isConnected) { wx.showToast({ title: '未连接设备', icon: 'none' }); return }
+ this.sendReadDoorBathEvent()
+ },
+
+ onReadBathEvent() {
+ if (!this.data.isConnected) { wx.showToast({ title: '未连接设备', icon: 'none' }); return }
+ this.sendReadDoorBathEvent()
+ },
+
// 切换雷达读取状态:开始/停止
toggleRadarRead() {
const reading = !!this.data.radarReading
@@ -1250,6 +1430,7 @@ Page({
// 移除旧监听,防止重复触发(页面重复进入或多次初始化的场景)
this.teardownBleListener()
+ try { if (this.data.debugDeviceInfoLogs) this.appendLog('DBG', 'setupBleListener: re-registering listeners') } catch (e) {}
// 运行环境不支持通知回调则直接返回(避免报错)
if (typeof wx.onBLECharacteristicValueChange !== 'function') return
// 定义并缓存通知回调,便于后续 off 解绑
@@ -1312,12 +1493,20 @@ Page({
if (connected) {
// 连接成功,停止重连计时并启用发送按钮
this.setData({ isConnected: true })
+ try { if (this.data.debugDeviceInfoLogs) this.appendLog('DBG', 'onBLEConnectionStateChange: connected') } catch (e) {}
+ this.setData({ isReconnecting: false })
try { this.stopReconnectTimer && this.stopReconnectTimer() } catch (e) {}
try { this.ensureBleChannels(() => {}) } catch (e) {}
+ try { this.updateDeviceInfo && this.updateDeviceInfo() } catch (e) { /* ignore */ }
+ try { this.startDeviceInfoTimer && this.startDeviceInfoTimer() } catch (e) { /* ignore */ }
} else {
// 断开,禁用发送并启动重连计时
this.setData({ isConnected: false })
+ try { if (this.data.debugDeviceInfoLogs) this.appendLog('DBG', 'onBLEConnectionStateChange: disconnected') } catch (e) {}
+ this.setData({ isReconnecting: true })
try { this.startReconnectTimer && this.startReconnectTimer() } catch (e) {}
+ try { this.updateDeviceInfo && this.updateDeviceInfo() } catch (e) { /* ignore */ }
+ try { this.startDeviceInfoTimer && this.startDeviceInfoTimer() } catch (e) { /* ignore */ }
}
} catch (e) { /* ignore */ }
}
@@ -1330,6 +1519,7 @@ Page({
if (this._reconnectTimer) return
const deviceId = this.data.deviceId
if (!deviceId) return
+ try { this.setData({ isReconnecting: true }) } catch (e) { /* ignore */ }
this._reconnectTimer = setInterval(() => {
try {
if (typeof wx.createBLEConnection === 'function') {
@@ -1337,7 +1527,14 @@ Page({
// createBLEConnection 成功即认为已连接
this.setData({ isConnected: true })
try { this.stopReconnectTimer() } catch (e) {}
- try { this.ensureBleChannels(() => {}) } catch (e) {}
+ try { this.setData({ isReconnecting: false }) } catch (e) { /* ignore */ }
+ try {
+ this.ensureBleChannels(() => {
+ try { this.setupBleListener() } catch (e) { /* ignore */ }
+ })
+ } catch (e) { /* ignore */ }
+ try { this.updateDeviceInfo && this.updateDeviceInfo() } catch (e) { /* ignore */ }
+ try { this.startDeviceInfoTimer && this.startDeviceInfoTimer() } catch (e) { /* ignore */ }
}, fail: () => {
// ignore failure, will retry
} })
@@ -1351,6 +1548,29 @@ Page({
clearInterval(this._reconnectTimer)
this._reconnectTimer = null
}
+ try { this.setData({ isReconnecting: false }) } catch (e) { /* ignore */ }
+ },
+ // 设备信息轮询管理:每 intervalMs 刷新一次设备信息(幂等)
+ startDeviceInfoTimer(intervalMs = 1000) {
+ try {
+ if (this._deviceInfoTimer) return
+ const ms = Number(intervalMs) || 1000
+ this._deviceInfoTimer = setInterval(() => {
+ try {
+ if (this.data.debugDeviceInfoLogs) this.appendLog('DBG', 'deviceInfoTimer tick')
+ this.updateDeviceInfo && this.updateDeviceInfo()
+ } catch (e) { /* ignore */ }
+ }, ms)
+ } catch (e) { /* ignore */ }
+ },
+
+ stopDeviceInfoTimer() {
+ try {
+ if (this._deviceInfoTimer) {
+ clearInterval(this._deviceInfoTimer)
+ this._deviceInfoTimer = null
+ }
+ } catch (e) { /* ignore */ }
},
onDownloadOtaTool() {
const url = 'https://www.baidu.com/';
@@ -1410,10 +1630,20 @@ Page({
} catch (e) { return '' }
},
teardownBleListener() {
- if (this._onBleChange && typeof wx.offBLECharacteristicValueChange === 'function') {
- wx.offBLECharacteristicValueChange(this._onBleChange)
- }
+ try {
+ if (this._onBleChange && typeof wx.offBLECharacteristicValueChange === 'function') {
+ try { if (this.data.debugDeviceInfoLogs) this.appendLog('DBG', 'teardownBleListener: offBLECharacteristicValueChange') } catch (e) {}
+ wx.offBLECharacteristicValueChange(this._onBleChange)
+ }
+ } catch (e) { /* ignore */ }
this._onBleChange = null
+ try {
+ if (this._onBleConnChange && typeof wx.offBLEConnectionStateChange === 'function') {
+ try { if (this.data.debugDeviceInfoLogs) this.appendLog('DBG', 'teardownBleListener: offBLEConnectionStateChange') } catch (e) {}
+ try { wx.offBLEConnectionStateChange(this._onBleConnChange) } catch (e) { /* ignore */ }
+ }
+ } catch (e) { /* ignore */ }
+ this._onBleConnChange = null
},
handleIncomingPacket(u8) {
@@ -1458,6 +1688,53 @@ Page({
this.appendLog('WARN', '解析读版本响应失败')
}
}
+ // 读取门磁/卫浴事件参数响应 (Frame_Type = 0x17)
+ if (frameType === (COMMANDS.READ_DOOR_BATH_EVENT & 0xFF)) {
+ try {
+ const params = u8.slice(11) || []
+ if (params.length >= 8) {
+ const toU16 = (off) => (params[off] & 0xFF) | ((params[off + 1] & 0xFF) << 8)
+ const doorTriggerSec = toU16(0)
+ const doorReleaseSec = toU16(2)
+ const bathTriggerSec = toU16(4)
+ const bathReleaseSec = toU16(6)
+ const conv = (sec) => {
+ const n = Number(sec || 0)
+ if (n <= 60) return { val: n, unitIdx: 0 }
+ const mins = Math.max(1, Math.round(n / 60))
+ return { val: Math.min(mins, 30), unitIdx: 1 }
+ }
+ const dTrig = conv(doorTriggerSec)
+ const dRel = conv(doorReleaseSec)
+ const bTrig = conv(bathTriggerSec)
+ const bRel = conv(bathReleaseSec)
+ this.setData({
+ doorTriggerDelay: dTrig.val,
+ doorTriggerUnitIndex: dTrig.unitIdx,
+ doorReleaseDelay: dRel.val,
+ doorReleaseUnitIndex: dRel.unitIdx,
+ bathTriggerDelay: bTrig.val,
+ bathTriggerUnitIndex: bTrig.unitIdx,
+ bathReleaseDelay: bRel.val,
+ bathReleaseUnitIndex: bRel.unitIdx
+ })
+ this.appendLog('PARSE', `读取门卫事件: 门触发=${doorTriggerSec}s, 门释放=${doorReleaseSec}s, 卫触发=${bathTriggerSec}s, 卫释放=${bathReleaseSec}s`)
+ } else {
+ this.appendLog('PARSE', '读取门卫事件:返回数据长度不足')
+ }
+ // 若存在 pendingResponse,交由其 resolve(avoid duplicate parsing)
+ try {
+ if (this._pendingResponse && this._pendingResponse.expectedType === (COMMANDS.READ_DOOR_BATH_EVENT & 0xFF)) {
+ const params = u8.slice(11) || []
+ try { this._pendingResponse.resolve(params) } catch (e) { /* ignore */ }
+ try { if (this._pendingResponse._timeout) clearTimeout(this._pendingResponse._timeout) } catch (e) {}
+ this._pendingResponse = null
+ }
+ } catch (e) { /* ignore */ }
+ } catch (e) {
+ this.appendLog('WARN', '解析读取门卫事件响应异常')
+ }
+ }
// 如果存在挂起的等待响应(onOneKeySend 使用),并且类型匹配,则回调并清理
try {
if (this._pendingResponse && this._pendingResponse.expectedType != null) {
@@ -2733,9 +3010,56 @@ Page({
const key = e.currentTarget.dataset.key
if (!key) return
const checked = (e.detail.value || []).includes(key)
+ // 合并逻辑:当是 hexShow 时,同时维护 showChineseLogs 并发送协议命令
+ if (key === 'hexShow') {
+ // hexShow 为 true 时表示以 HEX 形式展示,需关闭中文日志;反之打开中文日志
+ const showChinese = !checked
+ this.setData({ hexShow: checked, showChineseLogs: showChinese })
+ try { this.sendChineseLogCommand(showChinese) } catch (err) { this.appendLog('WARN', `发送中文日志命令失败: ${err && (err.message || err)}`) }
+ return
+ }
this.setData({ [key]: checked })
},
+ // 发送“中文日志 开/关”命令:enable=true 表示开启中文日志(设备返回中文),false 表示关闭中文日志
+ sendChineseLogCommand(enable) {
+ try {
+ const flag = enable ? 1 : 0
+ const pkt = buildCommand(COMMANDS.ENABLE_BLE_LOG, [flag])
+ // 记录发送日志
+ try { this.appendLog('TX', `中文日志 ${enable ? '开' : '关'} -> ${this.toHex ? this.toHex(pkt) : ''}`) } catch (e) { /* ignore */ }
+
+ // 优先使用已存在的写入接口
+ if (typeof this.writeRawBytes === 'function') {
+ try { this.writeRawBytes(pkt, '中文日志开关') } catch (e) { /* ignore */ }
+ return
+ }
+ if (typeof this.writeBleBytes === 'function') {
+ try { this.writeBleBytes(pkt, '中文日志开关') } catch (e) { /* ignore */ }
+ return
+ }
+
+ // 回退到原生 API(若页面保存了 deviceId/serviceId/txCharId)
+ const deviceId = this.data.deviceId || ''
+ const serviceId = this.data.serviceId || ''
+ const charId = this.data.txCharId || this.data.txChar || ''
+ if (deviceId && serviceId && charId && typeof wx.writeBLECharacteristicValue === 'function') {
+ const ab = new Uint8Array(pkt).buffer
+ try {
+ wx.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId: charId, value: ab, success: () => { this.appendLog('UI', '中文日志命令写入成功') }, fail: (e) => { this.appendLog('WARN', `写入中文日志命令失败 ${e && e.errMsg}`) } })
+ } catch (e) {
+ this.appendLog('WARN', `写入中文日志命令异常 ${e && (e.message || e)}`)
+ }
+ return
+ }
+
+ // 无可用写入路径
+ this.appendLog('WARN', '未找到可用的写入接口以发送中文日志命令')
+ } catch (err) {
+ this.appendLog('WARN', `构造中文日志命令失败: ${err && (err.message || err)}`)
+ }
+ },
+
onInputChange(e) {
this.setData({ sendText: e.detail.value })
},
@@ -2818,7 +3142,8 @@ Page({
const cur = Array.isArray(this.data.logList) ? this.data.logList : []
const next = [{ id, time: timeStr, text: finalText }, ...cur]
if (next.length > maxLogs) next.length = maxLogs
- this.setData({ logList: next })
+ // 更新日志并触发滚动到最新项
+ this.setData({ logList: next, logScrollTo: id })
} catch (e) {
// 若 setData 出现异常,仍保证不抛到外层
try { console.warn('appendLog setData failed', e) } catch (xx) { /* ignore */ }
diff --git a/pages/basics/BluetoothDebugging/B13page/B13page.wxml b/pages/basics/BluetoothDebugging/B13page/B13page.wxml
index 32d86c2..7e866b9 100644
--- a/pages/basics/BluetoothDebugging/B13page/B13page.wxml
+++ b/pages/basics/BluetoothDebugging/B13page/B13page.wxml
@@ -19,7 +19,8 @@
-
+
+
蓝牙名称:
{{bleName || '-'}}
@@ -27,19 +28,30 @@
MAC:
{{bleMac || '-'}}
- 版本:
- {{bleVersion || '-'}}
-
-
- 信号:
- {{bleSignal}}
- RSSI:
- {{bleRSSI}}
- 状态:
- {{isConnected ? '已连接' : '未连接'}}
-
+
+
- 读取蓝牙信息
+
+
+
+
+
+
+ 信号:
+ {{bleSignal}}
+
+
+ 版本:
+ {{bleVersion || '-'}}
+
+
+ 状态:
+ {{isConnected ? '已连接' : '未连接'}}
+
+
+
+
+
@@ -52,19 +64,17 @@
-->
-
+
-
- {{
+ {{
item.key === 'door' ? (item.triggered ? '门磁(开门)' : '门磁(关门)') :
item.key === 'bath' ? (item.triggered ? '卫浴(触发)' : '卫浴(释放)') :
item.key === 'bed' ? (item.triggered ? '卧室(触发)' : '卧室(释放)') :
item.key === 'hall' ? (item.triggered ? '走廊(触发)' : '走廊(释放)') : item.label
- }}
-
+ }}
@@ -73,38 +83,38 @@
-
+
房间有人
-
+
房间无人
-
+
门开
-
+
门关
-
+
卫浴有人
-
+
@@ -116,8 +126,9 @@
门磁开廊灯事件设置
-
-
+
+
+
@@ -142,7 +153,8 @@
卫浴雷达开卫浴灯事件设置
-
+
+
@@ -163,22 +175,32 @@
-
-
-
- 通讯日志
-
-
-
+
+
+
+
+
+
+ 通信日志
+
+
+
+
+
+
+
+
+
+
-
+
@@ -192,41 +214,18 @@
-
-
-
-
-
-
-
- 通信日志
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
- {{item.time}}
- {{item.text}}
-
-
- 暂无日志记录
-
+
+
+ {{item.time}} - {{item.text}}
+
+
@@ -242,7 +241,7 @@
-
+
@@ -251,8 +250,8 @@
端口配置
-
-
+
+
@@ -300,7 +299,7 @@
条件配置
-
+
@@ -316,7 +315,8 @@
{{grp.group}}
超时:
-
+
+
单位:
{{timeUnits[grp.timeoutUnit]}}
@@ -454,35 +454,43 @@
设备升级说明
-
+
前提条件
-
- 1)手机已安装 “OTA升级工具”。
+
+ 1)手机已安装“OTA升级工具”
- 操作步骤
-
- 1. 在本页面点击“发送 OTA 升级命令”。
- 2. 打开已下载并安装的 OTA 升级工具。
- 3. 手机蓝牙扫描并连接名称为:OTAOTA_OTAOTA_OTA 的设备。
- 4. 连接后依次点击 GETINFO → IMAGEA 。
- 5. 选择升级固件文件后,点击 START ,并选择芯片类型 “CH573” 开始升级。
+ 操作步骤
+
+ 1. 在本页面点击“发送 OTA 升级命令”。
+
+ 2. 打开已下载并安装的 OTA 升级工具。
+
+
+ 3. 手机蓝牙扫描并连接名称为:“OTAOTA_OTAOTA_OTA”的设备。
+
+
+ 4. 连接后依次点击GETINFO→IMAGEA。
+
+
+ 5. 选择升级固件文件后,点击START,并选择芯片类型“CH573”开始升级。
+
+
注意事项
• 升级过程中请保持设备与手机的蓝牙连接稳定,勿中断电源。
- • 升级失败后请重试一次,仍失败请联系技术支持并提供日志。
- • 点击“下载工具”将复制下载链接到剪贴板,需在浏览器中打开并下载。
+ • 升级失败后请重试一次,仍失败请联系技术支持并提供日志。
diff --git a/pages/basics/BluetoothDebugging/B13page/B13page.wxss b/pages/basics/BluetoothDebugging/B13page/B13page.wxss
index 2229f23..628599d 100644
--- a/pages/basics/BluetoothDebugging/B13page/B13page.wxss
+++ b/pages/basics/BluetoothDebugging/B13page/B13page.wxss
@@ -9,15 +9,27 @@
.container { background: #f5f6f8; min-height: 100vh; display: flex; flex-direction: column; }
.nav { padding: 4rpx 6rpx; }
.nav .flex { gap: 6rpx; }
+.tucontainer {
+ font-size: 16px;
+ line-height: 1.5;
+}
+.bold-text {
+ font-weight: bold;
+ color: #0291f7e5; /* 红色,可以根据需要修改 */
+ /* 或者使用其他颜色 */
+ /* color: #3498db; 蓝色 */
+ /* color: #2ecc71; 绿色 */
+ /* color: #f39c12; 橙色 */
+}
.nav .cu-item { padding: 6rpx 8rpx; margin-right: 4rpx; font-size: 32rpx; }
.content { padding: 8rpx 10rpx 0rpx; box-sizing: border-box; display: flex; flex-direction: column; gap: 8rpx; flex: 1; }
.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10rpx; margin-bottom: 12rpx; }
-.grid-item { background: #fff; border-radius: 14rpx; padding: 10rpx 0; display: flex; flex-direction: column; align-items: center; }
-.circle { width: 40rpx; height: 40rpx; border-radius: 50%; background: #e9ecef; margin-bottom: 6rpx; }
+.grid-item { background: #fff; border-radius: 14rpx; padding: 20rpx 22rpx; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; box-sizing: border-box; }
+.circle { width: 40rpx; height: 40rpx; border-radius: 50%; background: #e9ecef; margin-top: 8rpx; margin-bottom: 6rpx; }
.circle.green { background: #21c161; }
.circle.red { background: #ff3b30; }
.circle.gray { background: #e9ecef; }
-.label { font-size: 24rpx; color: #606266; }
+.label { font-size: 24rpx; color: #606266; text-align:center; }
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8rpx; margin-bottom: 10rpx; }
.card { background: #fff; border-radius: 14rpx; padding: 24rpx 8rpx; display: flex; flex-direction: column; align-items: center; min-height: 140rpx; box-sizing: border-box; transition: transform 120ms ease, filter 120ms ease; }
@@ -34,6 +46,9 @@
/* 按下效果 */
.card.pressed { transform: translateY(4rpx) scale(0.985); filter: brightness(0.94); box-shadow: none; }
+/* 禁用样式:视为不可点击并降低视觉优先级 */
+.card.disabled { opacity: 0.5; }
+
.slider-card { background: #fff; border-radius: 14rpx; padding: 12rpx; box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04); margin-bottom: 10rpx; }
.slider-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6rpx; }
.slider-head .title { font-size: 26rpx; color: #333; }
@@ -65,6 +80,7 @@
border: 1rpx solid rgba(43,171,153,0.18) !important;
}
.log-head .log-actions .clear-btn:active { opacity: 0.9 }
+
.log-options { display: flex; flex-wrap: nowrap; gap: 14rpx; }
.log-options .option { display: flex; align-items: center; font-size: 24rpx; color: #555; white-space: nowrap; }
@@ -78,7 +94,8 @@
/* 设备信息行 */
.device-row { display:flex; align-items:flex-start; justify-content: space-between; gap: 12rpx; background:#fff; border-radius: 14rpx; padding: 10rpx 12rpx; box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.04); flex-wrap: wrap; row-gap: 6rpx; }
-.device-left { display:flex; flex-direction: column; gap: 6rpx; flex: 1; min-width: 60%; }
+.device-left { display:flex; flex-direction: column; gap: 6rpx; min-width: 60%; }
+.device-leftNt { display:flex; flex-direction: column; gap: 4rpx; min-width: 40%; }
.device-line { display:flex; align-items:center; gap: 8rpx; flex-wrap: wrap; }
.device-row .dr-label { color:#606266; font-size: 24rpx; }
.device-row .dr-value { color:#333; font-size: 26rpx; }
@@ -232,6 +249,13 @@
.full-log-view .cfg-card { flex: none; }
.full-log-view .log-card { flex: 1; min-height: 0; }
.full-log-view .log-scroll { flex: 1; min-height: 0; max-height: none; }
+.full-log-view .log-list { flex: 1; min-height: 0; max-height: none; /* ensure flex sizing */
+ /* height:0 helps flex children not expand the parent when content grows */
+ height: 0; overflow: hidden;
+}
+
+/* Ensure individual log items don't force parent to grow */
+.full-log-view .log-list .log-item { display: block; box-sizing: border-box; }
.log-item { margin-bottom: 10rpx; }
.log-item:last-child { margin-bottom: 0; }
.log-time { display: block; font-size: 22rpx; color: #9aa0a6; margin-bottom: 4rpx; }
@@ -264,9 +288,9 @@
/* If screen too narrow, allow wrapping but keep label + control groups together */
@media (max-width: 360px) {
- .form-inline { flex-wrap: wrap; }
- .inline-input { width: 90rpx; }
- .inline-picker .picker-text { width: 80rpx; }
+ .form-inline { flex-wrap: wrap; }
+ .inline-input { width: 90rpx; }
+ .inline-picker .picker-text { width: 72rpx; }
}
/* 增大门磁/卫浴卡片中的控件高度,便于触控 */
@@ -282,6 +306,21 @@
.cfg-card.tall-controls .form-inline { gap: 14rpx; }
.cfg-card.tall-controls .label { font-size: 24rpx; }
+/* 调整门磁/卫浴事件设置卡片的内间距与控件布局 */
+.cfg-card.tall-controls {
+ padding: 18rpx !important;
+}
+.cfg-card.tall-controls .cfg-head {
+ padding-bottom: 10rpx;
+}
+.cfg-card.tall-controls .form-row { padding: 6rpx 0 0 0; }
+.cfg-card.tall-controls .form-inline { align-items: center; gap: 16rpx; }
+.cfg-card.tall-controls .picker-text { padding: 0 4rpx;min-width: 60rpx; }
+.cfg-card.tall-controls .inline-picker .picker-text { padding: 0 4rpx; width: 80rpx; text-align: center; }
+.cfg-card.tall-controls .label { margin-right: 8rpx; color: #525252; }
+.cfg-card.tall-controls .picker-text, .cfg-card.tall-controls input.picker-text { border-radius: 10rpx; }
+
+
/* 全屏日志视图:增大工具栏中“清空”按钮宽度,提升点击目标 */
.full-log-view .toolbar .toolbar-actions button:last-child {
min-width: 150rpx;
@@ -292,3 +331,13 @@
.chinese-log-btn { padding: 0 12rpx; height: 54rpx; line-height: 54rpx; border-radius: 10rpx; font-size: 24rpx; color: #ffffff; border: none; }
.chinese-log-btn.on { background: #ff3b30 !important; }
.chinese-log-btn.off { background: #21c161 !important; }
+
+/* 使门磁/卫浴卡片头部的操作按钮更紧凑(减少内间距与高度) */
+.cfg-card.tall-controls .head-actions { gap: 6rpx; }
+.cfg-card.tall-controls .head-actions button {
+ padding: 0 10rpx !important;
+ height: 52rpx !important;
+ line-height: 52rpx !important;
+ font-size: 24rpx !important;
+ border-radius: 9rpx !important;
+}
diff --git a/pages/basics/BluetoothDebugging/B13page/B13page_描述.md b/pages/basics/BluetoothDebugging/B13page/B13page_描述.md
new file mode 100644
index 0000000..c52039d
--- /dev/null
+++ b/pages/basics/BluetoothDebugging/B13page/B13page_描述.md
@@ -0,0 +1,148 @@
+# B13page 页面描述文档
+
+> 文件位置: pages/basics/BluetoothDebugging/B13page/B13page_描述.md
+
+## 一、页面目标
+B13page 为 B13 设备提供蓝牙调试与设备交互入口,目标包括:
+- 显示设备基本信息(名称、MAC、版本)。
+- 实时展示连接状态与信号(RSSI/Signal)。
+- 提供设备测试操作(模拟按键、门磁/卫浴事件下发)。
+- 支持设备配置(端口、条件等)与通信协议日志查看/发送。
+- 支持 OTA 升级入口及调试日志导出。
+
+## 二、页面显示结构(按视觉区域划分)
+1. 顶部导航(横向 scroll-tabs)
+ - Tab 1: 设备测试(默认)
+ - Tab 2: 设备配置
+ - Tab 3: 设备升级
+ - Tab 4: 设备通信日志(独立全屏日志视图)
+
+2. 公共设备信息栏(位于标签下方、所有 Tab 共用)
+ - 显示字段:蓝牙名称(`bleName`)、MAC(`bleMac`)、版本(`bleVersion`)、信号(`bleSignal`)、RSSI(`bleRSSI`)、状态(`isConnected`)。
+ - 操作按钮:快速读取蓝牙信息(`onSendReadVersion`)。
+
+3. Tab 1 - 设备测试
+ - 雷达指示灯网格(四项:门磁/卫浴/卧室/走廊,显示触发状态)。
+ - 测试按键卡片组(房间有人/无人、门开/关、卫浴有人/无人),支持长按/短按行为并发送测试指令。
+ - 门磁/卫浴事件设置面板(延时与单位选择,并有“设置”按钮)。
+ - (原)通讯日志卡片已移除,放置于 Tab 4(避免重复)。
+
+4. Tab 2 - 设备配置
+ - 端口配置表(表格形式,支持编辑端口号、阈值、启用开关、检测时间与单位)。
+ - 条件配置(折叠的条件组卡片,支持添加/删除、下发/读取/保存操作)。
+
+5. Tab 3 - 设备升级
+ - 文件导入导出、OTA 一键下发等入口。
+
+6. Tab 4 - 设备通信日志(全屏)
+ - 工具栏:导出日志、中文日志开关、清空。
+ - 日志选项:`HEX发送`、`HEX显示`、`时间戳`、`回车换行`。
+ - 发送面板:多行输入与 `发送` 按钮(受 `isConnected` 控制)。
+ - 日志滚动区:按时间序列展示 `logList`(每条包含时间与文本)。
+
+## 三、数据模型(重要字段与说明)
+- `bleName` (string):显示的设备名称。
+- `bleMac` (string):设备 MAC / deviceId。
+- `bleVersion` (string):蓝牙固件/协议版本。
+- `bleSignal` (string):可读文本形式的信号描述(例如 `-42 dBm` 或 `-`)。
+- `bleRSSI` (number|string):RSSI 原始值或 `-`。
+- `isConnected` (boolean):当前连接状态(UI 显示“已连接/未连接”)。
+- `deviceId`, `serviceId`, `txCharId`, `rxCharId`:BLE 通信上下文。
+- `logList` (array):日志项数组,每项结构至少包含 `{ time, text }`。
+- `sendText` (string):发送框内容。
+- `hexSend`, `hexShow`, `withTimestamp`, `wrapCRLF` (boolean):日志/发送选项。
+
+## 四、主要交互与事件(函数与触发条件)
+- 页面生命周期
+ - `onLoad(options)`:解析入参,初始化字段、构建条件组、发现特征并可能启动雷达读取。
+ - `onShow()`:尝试恢复或重建 BLE 通道、重新订阅通知并触发一次设备信息更新。
+ - `onUnload()`:解绑通知、移除连接监听、清理定时器。
+
+- 用户交互事件
+ - `tabSelect(e)`:切换 Tab。
+ - `onTestKeyTap/onTestKeyTouch*`:测试按键事件,发送协议命令并记录日志。
+ - `sendDoorEvent/sendBathEvent`:下发门磁/卫浴延时配置到设备。
+ - `onInputChange`、`onSend`:日志发送输入与发送执行。
+ - `onExportLogs`、`onClearLogs`、`onToggleChineseLogs`:日志导出/清空/中文切换。
+
+- BLE/通信事件
+ - `setupBleListener()`:注册 `wx.onBLECharacteristicValueChange`(处理 ArrayBuffer/hex),并注册 `wx.onBLEConnectionStateChange` 回调。
+ - `teardownBleListener()`:解绑上述回调。
+ - `updateDeviceInfo()`:查询并更新 `bleSignal`/`bleRSSI`/`isConnected`(优先 `getConnectedBluetoothDevices`,兜底 `getBluetoothDevices`)。
+ - `startReconnectTimer()` / `stopReconnectTimer()`:断开时每 N 秒尝试 `createBLEConnection`。
+
+## 五、功能需求(详细)
+1. 信号与连接实时性
+ - 页面应定期(配置间隔)调用 `updateDeviceInfo()` 刷新 `bleSignal`/`bleRSSI`/`isConnected`。
+ - 当发生连接/断开事件时,应立即触发一次 `updateDeviceInfo()` 并在 UI 上反映变化。
+
+2. 日志管理
+ - `logList` 应支持追加 RX/TX/调试条目;日志视图支持滚动与清空/导出。
+ - 支持 HEX 显示与 GBK 解码(可选中文日志)。
+
+3. 监听与清理
+ - 每次进入页面或成功建立连接后,应确保只有一次 `onBLECharacteristicValueChange` 与 `onBLEConnectionStateChange` 注册。
+ - 页面卸载时必须移除所有监听与定时器,避免内存泄漏。
+
+4. 重连与恢复
+ - 在断连时启动重连机制(间隔可配置),重连成功后恢复 notify 与读取流程。
+ - 在重连期间保留“最后已知 RSSI/Signal”以便用户参考(而非立即显示 `-`)。
+
+5. 可配置性与调试
+ - 支持 debug 模式(短轮询间隔、更多日志),生产模式下使用更长间隔以节省电量。
+
+## 六、非功能需求
+- 兼容性:兼容不同微信/小程序基础库版本的 BLE API(优先使用 `getConnectedBluetoothDevices`,回落 `getBluetoothDevices`)。
+- 性能:避免过短轮询导致 UI 卡顿或耗电;默认发布轮询建议 2-5 秒。
+- 可维护性:关键逻辑(轮询/回调/重连)封装为方法,利于单元测试与复用。
+
+## 七、验收与测试要点
+- 断连/重连场景验证:断开后仍能按间隔调用 `updateDeviceInfo()`,连接恢复时立即更新并恢复 notify。
+- 日志功能验收:发送/接收日志正确记录、导出与中文解析功能可切换。
+- 资源管理:多次进入/离开页面不重复注册回调,无残留定时器。
+
+---
+
+文档生成:开发助手
+日期:2026-01-22
+
+## 分角色讨论(补充)
+以下为与主要角色(开发、测试、产品、架构/安全、运维)深入讨论后的结论与可执行项,已合并到页面描述内,便于对齐实施优先级与责任人。
+
+### 开发(实现者)
+- 关注点:稳定的轮询逻辑、避免重复注册回调、兼容不同基础库 BLE API、异常安全处理。
+- 可执行项:
+ - A1: 封装 `startDeviceInfoTimer(intervalMs)` / `stopDeviceInfoTimer()`,默认调试 1s,发布 2-5s。
+ - A2: 在 `setupBleListener()` 确保 `onBLECharacteristicValueChange` 与 `onBLEConnectionStateChange` 只注册一次,并在 `teardownBleListener()` 中完全解绑。
+ - A3: 在 `onBLEConnectionStateChange` 的连接与断开分支都立即调用 `updateDeviceInfo()` 并确保轮询器处于运行状态;在连接成功后立即恢复 notify 流程(`ensureBleChannels()` / `enableNotify()`)。
+
+### 测试(QA)
+- 关注点:真机复现性、基础库差异与边界场景。
+- 可执行项:
+ - Q1: 制定一套标准化用例(见文档第七节)并使用 debug 日志验证 `updateDeviceInfo()` 调用频率与返回。
+ - Q2: 在不同小程序基础库版本与 Android/iOS 设备上验证 `getConnectedBluetoothDevices` 与 `getBluetoothDevices` 的行为差异。
+
+### 产品(UX)
+- 关注点:状态的可理解性与节电策略。
+- 可执行项:
+ - P1: 在 UI 上显示“最后已知信号(xx 秒前)”或“正在重连”提示,避免断连时直接显示 `-` 导致误解。
+ - P2: 将轮询间隔作为设置或根据“调试模式”自动切换(调试=1s,普通=2-5s)。
+
+### 架构 / 安全
+- 关注点:资源泄漏、异常路径、统一日志。
+- 可执行项:
+ - S1: 所有外部 API 调用加 try/catch,失败写入调试日志并不抛出致命错误。
+ - S2: 页面卸载时必须清理所有定时器与回调(`onUnload` 明确调用 `stopDeviceInfoTimer()` 与 `teardownBleListener()`)。
+
+### 运维 / 支持
+- 关注点:定位线上问题、快速回滚。
+- 可执行项:
+ - O1: 日志导出保持易读(时间戳、方向 RX/TX、HEX/文本),并在 Bug 报告中附带。
+ - O2: 发布前将轮询间隔恢复为节电安全值(2-5s),并在内测版本保留 1s 调试开关。
+
+### 责任与优先级建议
+- 优先级(高): A1, A3, S2 — 先保证轮询与连接恢复逻辑正确且无泄漏。
+- 优先级(中): Q1, P1, S1 — 改善可测试性与 UX,增加异常保护。
+- 优先级(低): O1, P2 — 运营与配置相关,发布前调整。
+
+本节已写入页面描述文档,便于团队按角色分配实现与验证任务。
diff --git a/pages/basics/BluetoothDebugging/BluetoothDebugging.js b/pages/basics/BluetoothDebugging/BluetoothDebugging.js
index 6738152..cd14426 100644
--- a/pages/basics/BluetoothDebugging/BluetoothDebugging.js
+++ b/pages/basics/BluetoothDebugging/BluetoothDebugging.js
@@ -654,6 +654,7 @@ Page({
// 将已连接设备合并到列表并按过滤规则筛选
const list = [...this.data.deviceList]
devices.forEach(d => {
+
const name = d.name || d.localName || ''
if (!matchByTab(name)) return
const idx = list.findIndex(x => x.id === d.deviceId)
diff --git a/pages/basics/HostUpgrade/EquipmentCaontrol/EquipmentCaontrol.js b/pages/basics/HostUpgrade/EquipmentCaontrol/EquipmentCaontrol.js
index e4f3b88..e19e134 100644
--- a/pages/basics/HostUpgrade/EquipmentCaontrol/EquipmentCaontrol.js
+++ b/pages/basics/HostUpgrade/EquipmentCaontrol/EquipmentCaontrol.js
@@ -213,7 +213,7 @@ if (Nres.Status==200) {
},
calcScrollHeight() {
// 1. 拿到屏幕可用高度(px)
- const sys = wx.getSystemInfoSync();
+ const sys = wx.getWindowInfo();
const screenHeight = sys.windowHeight; // px
// 2. 拿到 scroll-view 的 top(px)
diff --git a/utils/w13Packet.js b/utils/w13Packet.js
index 74f7cd9..3a5d8b7 100644
--- a/utils/w13Packet.js
+++ b/utils/w13Packet.js
@@ -12,6 +12,8 @@ export const COMMANDS = {
ENABLE_BLE_LOG: 0x0C,
// 设置门磁/卫浴事件触发/释放参数
SET_DOOR_BATH_EVENT: 0x16,
+ // 读取门磁/卫浴事件触发/释放参数
+ READ_DOOR_BATH_EVENT: 0x17,
RADAR_STATUS: 0x11,
TEST_KEYS: 0x13,
};
@@ -225,6 +227,17 @@ export function buildSetDoorBathEvent(opts = {}, options = {}) {
return buildCommand(COMMANDS.SET_DOOR_BATH_EVENT, payload, options);
}
+/**
+ * 构造:读取门磁/卫浴事件触发/释放参数 (Frame_Type=0x17)
+ * PC -> MCU: P0 = 0x01 (读请求)
+ * 返回:MCU -> PC 帧中 P0..P7 为四个 16-bit 小端延时值(单位:秒)
+ * @param {{frame?:number, framNum?:number, crcType?:('CCITT'|'MODBUS'), head?:number[]}} [options]
+ */
+export function buildReadDoorBathEvent(options = {}) {
+ // 按文档,发送 P0 = 0x01 表示读请求
+ return buildCommand(COMMANDS.READ_DOOR_BATH_EVENT, [0x01], options);
+}
+
/**
* 验证十六进制字符串包并计算/写入 CRC(默认 MODBUS),返回完整包与CRC值
* @param {string} hexStr 如 'CC C0 0C 00 00 00 01 00 02 00 0C 08' 或连续hex