diff --git a/Document/W13无卡取电设备 - 蓝牙通讯协议.md b/Document/W13无卡取电设备 - 蓝牙通讯协议.md new file mode 100644 index 0000000..05473c3 --- /dev/null +++ b/Document/W13无卡取电设备 - 蓝牙通讯协议.md @@ -0,0 +1,154 @@ +# W13无卡取电设备 - 蓝牙通讯协议 + +> 版本同步记录: +> - 同步来源:Document/W13无卡取电设备 - 蓝牙通讯协议(1).pdf +> - 同步日期:2026-01-12 +> - 说明:依据PDF抽取与差异比对,更新命令与参数细节,使Markdown与PDF一致。 + +## 1. 通讯方式 +使用蓝牙BLE通讯 + +## 2. 帧结构定义 + +### 2.1 帧字段说明 +| 字节范围 | 功能 | 长度(Bytes) | 取值范围(&H) | 备注 | +|---------|------|-------------|---------|------| +| B0~B1 | Head | 2 | 0xCC 0xC0 | 固定包头 | +| B2~B3 | Len | 2 | 00~548 | 数据的总长度,包括包头和CRC校验,低地址在前 | +| B4~B5 | CRC | 2 | 00~FF | 整包CRC16校验 | +| B6~B7 | Frame | 2 | 00~FF | 帧号 | +| B8~B9 | FramNum | 2 | 00~FF | 帧总数 | +| B10 | Frame_Type | 1 | 00~FF | 帧类型,命令字 | +| B11~B1023 | PARA_0~1012 | Max 1013 | 00~FF | 参数,不同类型有不同的参数字(不定长) | + +### 2.2 帧结构示意图 + +```mermaid +sequenceDiagram + participant PC as 上位机 + participant Device as 无卡取电设备 + + PC->>Device: 发送命令帧 + Note over PC,Device: 帧结构:Head + Len + CRC + Frame + FramNum + Frame_Type + Parameters + Device->>PC: 返回响应帧 + Note over Device,PC: 帧结构:Head + Len + CRC + Frame + FramNum + Frame_Type + Response +``` + +## 3. 详细命令列表 + +命令总览表(快速索引): + +| 序号 | 功能 | 方向 | 命令字 | 备注 | +|------|------|------|--------|------| +| 1 | 读版本号 | PC→MCU / MCU→PC | 0x01 | P0=0x00,请求;返回软件/硬件版本 | +| 2 | 设置无卡取电条件信息 | PC→MCU / MCU→PC | 0x08 | 条件参数设置;返回P0=0x01/0x02 | +| 3 | 设置无卡取电端口信息 | PC→MCU / MCU→PC | 0x09 | 端口配置;返回P0=0x01/0x02 | +| 4 | OTA升级开始 | PC→MCU | 0x0B | P0=0x01,进入bootloader等待OTA | +| 5 | 开启蓝牙打印 | PC→MCU / MCU→PC | 0x0C | 打印开关bit0..bit4;返回P0=0x01/0x02 | +| 6 | 雷达状态获得 | PC→MCU / MCU→PC | 0x11 | 开/关读取;返回端口状态位与有人/无人 | +| 7 | 测试按键功能 | PC→MCU / MCU→PC | 0x13 | 点按控制与状态返回 | +| 8 | 事件设置(门磁/卫浴灯) | PC→MCU / MCU→PC | 0x16 | 控制位bit0/bit1,事件时序参数与单位 | + +### 3.1 读版本号 +| 方向 | 命令字 | 参数 | 备注 | +|------|-------|------|------| +| PC→MCU | 0x01 | P0: 0x00 | 读取版本号命令 | +| MCU→PC | 0x01 | P0: 软件版本号
P1: 硬件版本号 | 返回版本信息 | + +### 3.2 设置无卡取电条件信息(命令1) +| 方向 | 命令字 | 参数 | 备注 | +|------|-------|------|------| +| PC→MCU | 0x08 | P0: 有无逻辑标记
P1: 条件组
P2: 条件序号
P3~P4: 条件判定时间
P5: 条件判定时间单位
P6~P9: 端口1~10状态
P10: 触发阈值
P11~P12: 条件超时时间
P13: 条件超时时间单位 | 设置无卡取电条件 | +| MCU→PC | 0x08 | P0: 0x01(参数正确) / 0x02(参数错误) | 返回设置结果 | + +### 3.3 设置无卡取电条件信息(命令2) +| 方向 | 命令字 | 参数 | 备注 | +|------|-------|------|------| +| PC→MCU | 0x09 | P0: 端口设备类型
P1: 端口设备地址
P2~P3: 端口设备回路
P4: 有人->无人阈值
P5: 虚拟端口号
P6: 回路是否启用检测统计
P7~P8: 回路检测统计时间
P9: 回路检测统计时间单位
P10: 无人->有人阈值 | 设置无卡取电条件 | +| MCU→PC | 0x09 | P0: 0x01(参数正确) / 0x02(参数错误) | 返回设置结果 | + +### 3.4 OTA升级开始 +| 方向 | 命令字 | 参数 | 备注 | +|------|-------|------|------| +| PC→MCU | 0x0B | P0: 0x01(开始升级) | 启动OTA升级,设备进入 bootloader,等待 OTA 升级 APP 连接 | + +### 3.5 开启蓝牙打印 +| 方向 | 命令字 | 参数 | 备注 | +|------|-------|------|------| +| PC→MCU | 0x0C | P0: bit0(系统调试信息打印开关)
bit1(设备驱动层打印调试信息打印开关)
bit2(蓝牙信息打印开关)
bit3(PC通讯打印开关)
bit4(临时调试信息打印开关) | 设置打印开关 | +| MCU→PC | 0x0C | P0: 0x01(参数正确) / 0x02(参数错误) | 返回设置结果 | + +### 3.6 雷达状态获得 +| 方向 | 命令字 | 参数 | 备注 | +|------|-------|------|------| +| PC→MCU | 0x11 | P0: 0x01(开启读取端口状态) / 0x02(关闭读取) | 请求雷达状态;端口状态值:0=释放,1=触发 | +| MCU→PC | 0x11 | P0: 有效端口数量
P1: 有无人状态(0x01=有人,0x02=无人)
P2: bit0(端口1状态), bit1(端口2状态), bit2(端口3状态), bit3(端口4状态)...(0=释放,1=触发) | 返回雷达状态 | + +补充说明: +- 端口状态值:0=释放,1=触发。 +- 典型端口位含义:bit0=端口1(门磁),bit1=端口2(洗手间),bit2=端口3(卧室),bit3=端口4(门口),后续端口依次类推。 + +### 3.7 测试按键功能 +| 方向 | 命令字 | 参数 | 备注 | +|------|-------|------|------| +| PC→MCU | 0x13 | P0: 0x01(按键点按控制)
P1: bit0(按键1触发), bit1(按键2触发), bit2(按键3触发), bit3(按键4触发), bit4(按键5触发), bit5(按键6触发) | 测试按键功能(对应按键仅支持点按,不具备开关状态) | +| MCU→PC | 0x13 | P0: 0x01(参数正确) / 0x02(参数错误) | 返回设置结果 | + +### 3.8 设置门磁开关走廊灯、卫浴雷达开关卫浴灯事件 +| 方向 | 命令字 | 参数 | 备注 | +|------|-------|------|------| +| 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=时)
P9: 事件持续时间数值
P10: 时间单位(1=秒,2=分,3=时)
P11: 事件释放延迟时间数值
P12: 时间单位(1=秒,2=分,3=时) | 设置事件参数(用于控制门磁亮走廊灯、卫浴雷达亮卫浴灯等) | +| MCU→PC | 0x16 | P0: 0x01(参数正确) / 0x02(参数错误) | 返回设置结果 | + +## 4. 命令交互流程图 + +### 4.1 读版本号流程 +```mermaid +flowchart TD + A[上位机发送读版本号命令
Frame_Type=0x01, P0=0x00] --> B[设备接收命令] + B --> C{命令解析正确?} + C -->|是| D[设备准备版本信息] + C -->|否| E[设备返回错误响应] + D --> F[设备发送响应帧
Frame_Type=0x01, 包含软硬件版本号] + E --> G[设备发送错误响应帧
Frame_Type=0x01, P0=0x02] + F --> H[上位机接收版本信息] + G --> I[上位机处理错误] +``` + +### 4.2 设置无卡取电条件流程 +```mermaid +flowchart TD + A[上位机发送设置条件命令
Frame_Type=0x08/0x09] --> B[设备接收命令] + B --> C{参数验证通过?} + C -->|是| D[设备保存条件设置] + C -->|否| E[设备标记参数错误] + D --> F[设备发送成功响应
P0=0x01] + E --> G[设备发送失败响应
P0=0x02] + F --> H[上位机确认设置成功] + G --> I[上位机重新发送或处理错误] +``` + +## 5. 数据传输流程 + +```mermaid +sequenceDiagram + participant PC as 上位机 + participant BLE as 蓝牙模块 + participant Device as 设备主控 + + PC->>BLE: 发送BLE数据 + BLE->>Device: 转发数据帧 + Device->>BLE: 处理并返回响应 + BLE->>PC: 转发响应帧 +``` + +## 6. 异常处理 + +| 错误类型 | 错误码 | 处理方式 | +|---------|-------|---------| +| 参数错误 | 0x02 | 重新发送正确参数 | +| 命令不支持 | - | 检查命令字是否正确 | +| 通讯超时 | - | 重新发送命令 | + +--- + diff --git a/app.json b/app.json index 461b1a2..6f1c6b8 100644 --- a/app.json +++ b/app.json @@ -14,7 +14,9 @@ "pages/test/test", "pages/basics/FacialDeviceBinding/FacialDeviceBinding", "pages/basics/progress/progress", - "pages/basics/progress/RoomTypeControlLog/RoomTypeControlLog" + "pages/basics/progress/RoomTypeControlLog/RoomTypeControlLog", + "pages/basics/BluetoothDebugging/BluetoothDebugging", + "pages/basics/BluetoothDebugging/B13page/B13page" ], "window": { "backgroundTextStyle": "light", diff --git a/img/lanya.png b/img/lanya.png new file mode 100644 index 0000000..9832846 Binary files /dev/null and b/img/lanya.png differ diff --git a/img/xinhaodi.png b/img/xinhaodi.png new file mode 100644 index 0000000..d7664d7 Binary files /dev/null and b/img/xinhaodi.png differ diff --git a/img/xinhaogao.png b/img/xinhaogao.png new file mode 100644 index 0000000..85f4c6e Binary files /dev/null and b/img/xinhaogao.png differ diff --git a/img/xinhaozhong.png b/img/xinhaozhong.png new file mode 100644 index 0000000..0a02c71 Binary files /dev/null and b/img/xinhaozhong.png differ diff --git a/pages/NewHome/NewHome.js b/pages/NewHome/NewHome.js index 232aad4..75786a1 100644 --- a/pages/NewHome/NewHome.js +++ b/pages/NewHome/NewHome.js @@ -32,6 +32,12 @@ Page({ color: 'pink', icon: 'btn' }, + { + title: '蓝牙调试', + name: 'BluetoothDebugging', + color: 'cyan', + icon: 'tagfill' + }, { title: '红外转发码库下载', name: 'InfraredLibraryDownload', diff --git a/pages/basics/BluetoothDebugging/B13page/B13page.js b/pages/basics/BluetoothDebugging/B13page/B13page.js new file mode 100644 index 0000000..ae85dce --- /dev/null +++ b/pages/basics/BluetoothDebugging/B13page/B13page.js @@ -0,0 +1,945 @@ + +const { buildCommand, buildReadVersion, COMMANDS } = require('../../../../utils/w13Packet.js') +const FIXED_CONNECT_CMD = new Uint8Array([0xCC, 0xC0, 0x0C, 0x00, 0x4F, 0xC0, 0x01, 0x00, 0x02, 0x00, 0x0C, 0x08]) +Page({ + data: { + TabCur: 1, // 1: 蓝牙调试 2: 蓝牙升级 + DevName: '', + bleName: '', + // 预设占位数据,未获取真实设备信息时用于展示 + bleMac: '00:00:00:00:00:00', + bleAMC: '-', + bleVersion: '-', + openDelay: 20, + bathDelay: 20, + logs: [], + hexSend: false, + hexShow: false, + withTimestamp: false, + wrapCRLF: false, + sendText: '', + importFileName: '', + // BLE上下文(从上一页传入) + deviceId: '', + serviceId: '', + txCharId: '', + rxCharId: '', + logList: [], + timeUnits: ['时', '分', '秒'], + hourValues: Array.from({ length: 24 }, (_, i) => i + 1), + msValues: Array.from({ length: 60 }, (_, i) => i + 1), + ports: [ + { name: '无卡取电 CH1', portLabel: '开门磁', alias: '开门磁', deviceType: 0, deviceAddr: 0, loop: 1, thresholdUp: 0, thresholdDown: 0, enabled: false, detectTime: 0, detectUnit: 0 }, + { name: '无卡取电 CH2', portLabel: '门口红外', alias: '门口红外', deviceType: 0, deviceAddr: 0, loop: 2, thresholdUp: 0, thresholdDown: 0, enabled: false, detectTime: 0, detectUnit: 0 }, + { name: '无卡取电 CH3', portLabel: '床头红外', alias: '床头红外', deviceType: 0, deviceAddr: 0, loop: 3, thresholdUp: 0, thresholdDown: 0, enabled: false, detectTime: 0, detectUnit: 0 }, + { name: '无卡取电 CH4', portLabel: '卫浴红外', alias: '卫浴红外', deviceType: 0, deviceAddr: 0, loop: 4, thresholdUp: 0, thresholdDown: 0, enabled: false, detectTime: 0, detectUnit: 0 } + ], + // 条件“有无人标记”选项,参考截图:无人至有人/短暂离开/长时间离开/有人至无人 + tagOptions: ['无人至有人', '短暂离开', '长时间离开', '有人至无人'], + stateOptions: ['不判断', '触发', '释放', '开启', '关闭'], + // 默认条件:与截图一致(组1..6,各1条) + conditions: [ + { group: 1, seq: 1, tag: 0, cardPower: 0, doorMag: 1, irHall: 0, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 0, timeoutUnit: 2 }, + { group: 2, seq: 1, tag: 0, cardPower: 0, doorMag: 0, irHall: 1, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 20, timeoutUnit: 2 }, + { group: 2, seq: 2, tag: 0, cardPower: 0, doorMag: 0, irHall: 1, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 20, timeoutUnit: 2 }, + { group: 2, seq: 3, tag: 0, cardPower: 0, doorMag: 0, irHall: 1, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 20, timeoutUnit: 2 }, + { group: 3, seq: 1, tag: 0, cardPower: 0, doorMag: 0, irHall: 1, bathRadar: 0, bathroomRadar: 0, judgeTime: 0, judgeUnit: 2, timeout: 0, timeoutUnit: 2 }, + { group: 4, seq: 1, tag: 1, cardPower: 0, doorMag: 4, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 0, judgeUnit: 2, timeout: 2, timeoutUnit: 2 }, + { group: 5, seq: 1, tag: 2, cardPower: 0, doorMag: 4, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 }, + { group: 6, seq: 1, tag: 3, cardPower: 0, doorMag: 4, irHall: 2, bathRadar: 2, bathroomRadar: 2, judgeTime: 2, judgeUnit: 1, timeout: 10, timeoutUnit: 1 } + ], + // 二级菜单:按组折叠 + condGroups: [], + // 雷达指示灯(bit0=门磁,bit1=卫浴,bit2=卧室,bit3=走廊) + radarLights: [ + { key: 'door', label: '门磁', colorClass: 'gray', triggered: false }, + { key: 'bath', label: '卫浴', colorClass: 'gray', triggered: false }, + { key: 'bed', label: '卧室', colorClass: 'gray', triggered: false }, + { key: 'hall', label: '走廊', colorClass: 'gray', triggered: false } + ] + }, + + onLoad(options) { + const raw = options && (options.DevName || options.name) || '' + // 处理通过 URL 传递的编码,避免中文显示为乱码 + let devName = '' + try { + devName = decodeURIComponent(raw) + } catch (e) { + devName = raw + } + if (!devName) devName = 'B13设备' + const rawMac = options && options.mac ? options.mac : '' + const bleMac = rawMac ? decodeURIComponent(rawMac) : '00:00:00:00:00:00' + const deviceId = rawMac ? decodeURIComponent(rawMac) : '' + const serviceId = options && options.serviceId ? decodeURIComponent(options.serviceId) : '' + const txCharId = options && options.txCharId ? decodeURIComponent(options.txCharId) : '' + const rxCharId = options && options.rxCharId ? decodeURIComponent(options.rxCharId) : '' + this.setData({ DevName: devName, bleName: devName, bleMac, deviceId, serviceId, txCharId, rxCharId }) + // 页面进入时打印当前蓝牙连接状态 + this.logBleStatus() + // 构建条件组 + this.buildCondGroups() + // 自动发现特征并启动雷达订阅/读取 + this.ensureBleChannels(() => { + this.startRadarStatusWatch() + }) + wx.setNavigationBarTitle({ title: devName }) + this.logBleStatus() + + }, + + onShow() { + // 页面显示时也打印一次,方便返回/二次进入场景 + this.logBleStatus() + }, + + onUnload() { + this.teardownBleListener() + }, + + // 顶部标签切换 + tabSelect(e) { + const id = Number(e.currentTarget.dataset.id || 1) + this.setData({ TabCur: id }) + }, + + onOpenDelayChange(e) { + this.setData({ openDelay: e.detail.value }) + }, + onBathDelayChange(e) { + this.setData({ bathDelay: e.detail.value }) + }, + + // 便捷示例:读版本号 + onSendReadVersion() { + try { + const pkt = buildReadVersion() + const text = this.data.hexShow ? this.toHex(pkt) : `[${Array.from(pkt).join(', ')}]` + this.appendLog('TX', `读版本号: ${text}`) + } catch (err) { + wx.showToast({ title: '构包失败', icon: 'none' }) + } + }, + + // 开始OTA升级(命令0x0B, P0=0x01) + onStartOta() { + try { + const pkt = buildCommand(COMMANDS.OTA_START, [0x01]) + const text = this.toHex(pkt) + this.appendLog('TX', `OTA开始: ${text}`) + wx.showToast({ title: '已发送OTA开始', icon: 'success' }) + } catch (err) { + wx.showToast({ title: '构包失败', icon: 'none' }) + } + }, + + // 开启蓝牙打印(命令0x0C,示例掩码0x1F) + onEnableBleLog() { + try { + const mask = 0x1F + const pkt = buildCommand(COMMANDS.ENABLE_BLE_LOG, [mask]) + const text = this.toHex(pkt) + this.appendLog('TX', `开启打印(0x${mask.toString(16).toUpperCase()}): ${text}`) + wx.showToast({ title: '已发送打印开关', icon: 'success' }) + } catch (err) { + wx.showToast({ title: '构包失败', icon: 'none' }) + } + }, + + toHex(u8) { + return Array.from(u8).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ') + }, + + // 控制台输出当前蓝牙连接状态 + logBleStatus() { + const { deviceId, serviceId, txCharId, rxCharId } = this.data || {} + console.info(`[BLE] B13page enter: device=${deviceId || '-'} svc=${serviceId || '-'} tx=${txCharId || '-'} rx=${rxCharId || '-'}`) + const onUnknown = () => console.info('[BLE] connection state: unknown (no deviceId or API missing)') + if (!deviceId) { + onUnknown() + return + } + const logConnected = (connected) => console.info(`[BLE] connection state: ${connected ? 'connected' : 'disconnected'}`) + // 先尝试新版接口 + if (typeof wx.getBLEConnectionState === 'function') { + try { + wx.getBLEConnectionState({ + deviceId, + success: (res) => logConnected(!!(res && res.connected)), + fail: () => console.warn('[BLE] get connection state failed') + }) + } catch (e) { + console.warn('[BLE] get connection state exception') + } + return + } + // 兼容旧端:检查已连接设备列表 + if (typeof wx.getConnectedBluetoothDevices === 'function') { + try { + wx.getConnectedBluetoothDevices({ + success: (res) => { + const list = (res && res.devices) || [] + const hit = list.some(d => (d.deviceId || '').toUpperCase() === deviceId.toUpperCase()) + logConnected(hit) + }, + fail: () => console.warn('[BLE] getConnectedBluetoothDevices failed') + }) + } catch (e) { + console.warn('[BLE] getConnectedBluetoothDevices exception') + } + return + } + onUnknown() + }, + + // 发送前确认当前蓝牙连接状态(兼容无 getBLEConnectionState 场景) + ensureBleConnected(next, attempt = 0) { + const { deviceId } = this.data || {} + if (!deviceId) { + wx.showToast({ title: '未连接BLE', icon: 'none' }) + return + } + + const proceed = () => { if (typeof next === 'function') next() } + + // 优先使用 getBLEConnectionState + if (typeof wx.getBLEConnectionState === 'function') { + try { + wx.getBLEConnectionState({ + deviceId, + success: (res) => { + const connected = !!(res && res.connected) + if (!connected) { + // 某些机型首次查询可能短暂返回断开,允许一次快速重试 + if (attempt < 1) { + this.appendLog('WARN', 'BLE未连接,重试查询...') + setTimeout(() => this.ensureBleConnected(next, attempt + 1), 250) + return + } + this.appendLog('WARN', 'BLE未连接,取消发送') + wx.showToast({ title: '蓝牙未连接', icon: 'none' }) + return + } + proceed() + }, + fail: () => { + this.appendLog('WARN', '查询BLE状态失败,尝试兜底') + this._fallbackCheckConnected(deviceId, proceed) + } + }) + } catch (e) { + this.appendLog('WARN', '查询BLE状态异常,尝试兜底') + this._fallbackCheckConnected(deviceId, proceed) + } + return + } + + // 兼容旧端:直接兜底检查 + this._fallbackCheckConnected(deviceId, proceed) + }, + + // 兜底检查:使用已连接设备列表;若接口不可用则直接继续 + _fallbackCheckConnected(deviceId, proceed) { + if (typeof wx.getConnectedBluetoothDevices !== 'function') { + proceed() + return + } + try { + wx.getConnectedBluetoothDevices({ + success: (res) => { + const list = (res && res.devices) || [] + const norm = (s) => (s || '').replace(/-/g, '').toUpperCase() + const target = norm(deviceId) + const hit = list.some(d => norm(d.deviceId) === target) + if (hit) { + proceed() + return + } + // 若列表为空或匹配不到,但已有 service/char,放行并记录警告(部分机型/安卓可能返回空列表) + if (list.length === 0 || (this.data.serviceId && this.data.txCharId)) { + this.appendLog('WARN', '未在已连接列表中找到,假定已连接尝试发送') + proceed() + return + } + this.appendLog('WARN', '未在已连接设备列表中找到,取消发送') + wx.showToast({ title: '蓝牙未连接', icon: 'none' }) + }, + fail: () => { + this.appendLog('WARN', '查询已连接设备失败,尝试继续发送') + proceed() + } + }) + } catch (e) { + this.appendLog('WARN', '查询已连接设备异常,尝试继续发送') + proceed() + } + }, + + ab2hex(buffer) { + if (!buffer) return '' + return Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ') + }, + + _matchUuid(uuid, needle) { + if (!uuid || !needle) return false + const u = String(uuid).replace(/-/g, '').toUpperCase() + const n = String(needle).toUpperCase().replace(/^0X/, '') + return u.includes(n) + }, + + hexToBytes(hex) { + const clean = (hex || '').replace(/\s+/g, '').toUpperCase() + if (clean.length === 0) return new Uint8Array(0) + if (clean.length % 2 !== 0 || /[^0-9A-F]/.test(clean)) return null + const out = new Uint8Array(clean.length / 2) + for (let i = 0; i < clean.length; i += 2) out[i/2] = parseInt(clean.substr(i,2), 16) + return out + }, + + strToBytes(str) { + const s = String(str || '') + const out = new Uint8Array(s.length) + for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 0xFF + return out + }, + + writeBleBytes(u8, label) { + const { deviceId, serviceId, txCharId } = this.data || {} + if (!deviceId) { + wx.showToast({ title: '未连接BLE', icon: 'none' }) + return + } + const doWrite = () => { + // 若缺少特征/服务,先自动发现再发送 + if (!serviceId || !txCharId) { + this.ensureBleChannels(() => this.writeBleBytes(u8, label)) + return + } + + const bytes = (u8 instanceof Uint8Array) ? u8 : new Uint8Array(u8 || []) + this.appendLog('CFG', `device=${deviceId || '-'} svc=${serviceId || '-'} tx=${txCharId || '-'}`) + // 发送完成时打印当前蓝牙连接状态 + this.logBleStatus() + wx.writeBLECharacteristicValue({ + deviceId, + serviceId, + characteristicId: txCharId, + value: bytes.buffer, + success: () => { + // 发送完成时打印当前蓝牙连接状态 + this.logBleStatus() + }, + fail: (err) => { + const msg = (err && err.errMsg) ? err.errMsg : '发送失败' + this.appendLog('WARN', `${label || '发送'} ${msg}`) + wx.showToast({ title: '发送失败', icon: 'none' }) + } + }) + } + + // 写入前检查当前蓝牙连接状态(兼容旧接口) + this.ensureBleConnected(doWrite) + }, + + ensureBleChannels(done) { + const { deviceId, serviceId, txCharId, rxCharId } = this.data || {} + // 若未携带 deviceId,尝试从系统已连接设备列表中兜底获取 + let devId = deviceId + if (!devId && typeof wx.getConnectedBluetoothDevices === 'function') { + try { + wx.getConnectedBluetoothDevices({ + success: (res) => { + const list = (res && res.devices) || [] + if (list.length > 0) { + const first = list[0] + devId = first && (first.deviceId || first.deviceId) + if (devId) this.setData({ deviceId: devId }) + // 继续后续流程 + this.ensureBleChannels(done) + } else { + wx.showToast({ title: '未发现已连接设备', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '获取连接设备失败', icon: 'none' }) + }) + } catch (e) { + wx.showToast({ title: '蓝牙接口异常', icon: 'none' }) + } + return + } + if (!devId) { + wx.showToast({ title: '未连接BLE', icon: 'none' }) + return + } + if (serviceId && txCharId && rxCharId) { + if (typeof done === 'function') done() + return + } + if (typeof wx.getBLEDeviceServices !== 'function') { + wx.showToast({ title: 'BLE接口不可用', icon: 'none' }) + return + } + wx.showLoading({ title: '发现服务...', mask: true }) + wx.getBLEDeviceServices({ + deviceId: devId, + success: (srvRes) => { + const services = srvRes.services || [] + let found = false + let pending = services.length + const score = (s) => { + const u = (s.uuid || '').toUpperCase() + return u.includes('FFE') ? 2 : (s.isPrimary === true ? 1 : 0) + } + const sorted = services.slice().sort((a, b) => score(b) - score(a)) + sorted.forEach(s => { + wx.getBLEDeviceCharacteristics({ + deviceId: devId, + serviceId: s.uuid, + success: (chRes) => { + const chars = chRes.characteristics || [] + const ffe1 = chars.find(c => this._matchUuid(c.uuid, 'FFE1')) + const ffe2 = chars.find(c => this._matchUuid(c.uuid, 'FFE2')) + if (!found && ffe1 && ffe2) { + found = true + this.setData({ serviceId: s.uuid, txCharId: ffe1.uuid, rxCharId: ffe2.uuid }) + // 立即开启通知 + this.enableNotify() + wx.hideLoading() + if (typeof done === 'function') done() + } + }, + complete: () => { + pending -= 1 + if (!found && pending === 0) { + wx.hideLoading() + wx.showToast({ title: '未找到FFE1/FFE2', icon: 'none' }) + } + } + }) + }) + }, + fail: () => { + wx.hideLoading() + wx.showToast({ title: '获取服务失败', icon: 'none' }) + } + }) + }, + + startRadarStatusWatch() { + // 开启订阅(若已传入rx特征),保证能接收数据 + this.enableNotify() + this.setupBleListener() + this.sendRadarStatusCommand(true) + }, + + enableNotify() { + const { deviceId, serviceId, rxCharId } = this.data || {} + if (!deviceId || !serviceId || !rxCharId || typeof wx.notifyBLECharacteristicValueChange !== 'function') { + this.appendLog('WARN', '通知前置条件不足') + return + } + this.appendLog('CFG', `enableNotify device=${deviceId} svc=${serviceId} rx=${rxCharId}`) + const tryEnable = (attempt) => { + try { + wx.notifyBLECharacteristicValueChange({ + state: true, + deviceId, + serviceId, + characteristicId: rxCharId, + success: () => this.appendLog('UI', `已开启通知 device=${deviceId} svc=${serviceId} rx=${rxCharId}`), + fail: (err) => { + const code = (err && (err.errCode ?? err.code)) + const msg = (err && (err.errMsg || err.message)) || '未知原因' + const detail = code !== undefined ? `code=${code} ${msg}` : msg + this.appendLog('WARN', `开启通知失败(重试${attempt}) ${detail}`) + wx.showToast({ title: '通知失败', icon: 'none' }) + if (attempt < 2) { + // 触发一次服务特征刷新后再重试 + this.ensureBleChannels(() => setTimeout(() => tryEnable(attempt + 1), 120)) + } + } + }) + } catch (e) { + const msg = (e && (e.errMsg || e.message)) || '异常' + this.appendLog('WARN', `开启通知异常 ${msg}`) + wx.showToast({ title: '通知异常', icon: 'none' }) + } + } + tryEnable(0) + }, + + sendRadarStatusCommand(enable) { + try { + const payload = [enable ? 0x01 : 0x02] + const pkt = buildCommand(COMMANDS.RADAR_STATUS, payload) + this.transmitPacket(pkt, `雷达状态${enable ? '开启读取' : '关闭读取'}`) + } catch (err) { + wx.showToast({ title: '雷达命令构建失败', icon: 'none' }) + } + }, + + transmitPacket(pkt, label) { + + const hex = this.toHex(pkt) + this.appendLog('TX', `${label}: ${hex}`) + // 如果页面接入了BLE连接参数,则尝试写入;未配置则仅记录日志 + const { deviceId, serviceId, txCharId } = this.data || {} + if (!deviceId || !serviceId || !txCharId || typeof wx.writeBLECharacteristicValue !== 'function') return + try { + wx.writeBLECharacteristicValue({ + deviceId, + serviceId, + characteristicId: txCharId, + value: (pkt && pkt.buffer) ? pkt.buffer : new Uint8Array(pkt || []).buffer, + fail: (err) => { + const code = err && (err.errCode ?? err.code) + const msg = (err && (err.errMsg || err.message)) || '未知原因' + const detail = code !== undefined ? `code=${code} ${msg}` : msg + this.appendLog('WARN', `写入BLE失败 device=${deviceId} svc=${serviceId} tx=${txCharId} ${detail}`) + wx.showToast({ title: '发送失败', icon: 'none' }) + } + }) + } catch (err) { + const msg = (err && (err.errMsg || err.message)) || '异常' + this.appendLog('WARN', `写入BLE异常 ${msg}`) + } + }, + + /** + * 注册 BLE 通知监听 + * - 先取消旧监听,避免重复回调造成日志噪音或重复解析 + * - 仅处理目标 rxCharId 的通知(按 UUID 片段过滤,兼容 16/128 位 UUID) + * - 兼容两类上报载荷:ArrayBuffer 与十六进制字符串 + * - 将原始数据规范化为 Uint8Array,记录日志后交由协议解析器处理 + */ + setupBleListener() { + // 移除旧监听,防止重复触发(页面重复进入或多次初始化的场景) + this.teardownBleListener() + // 运行环境不支持通知回调则直接返回(避免报错) + if (typeof wx.onBLECharacteristicValueChange !== 'function') return + // 定义并缓存通知回调,便于后续 off 解绑 + this._onBleChange = (res) => { + const { rxCharId } = this.data || {} + // 过滤:如果能拿到通知的特征 ID,仅处理与当前订阅 rxCharId 匹配的通知 + if (rxCharId && res && res.characteristicId) { + const cid = String(res.characteristicId).replace(/-/g, '').toUpperCase() + const rx = String(rxCharId).replace(/-/g, '').toUpperCase() + if (!cid.includes(rx)) return + } + // 数据抽取:res 可能包含 value(ArrayBuffer) 或自定义的十六进制字符串字段 + const buffer = res && res.value + const hexStr = res && (res.hexStr || res.hex || res.data) + let hex = '' + let u8 = null + // ArrayBuffer → Uint8Array,并生成十六进制视图文本 + if (buffer) { + u8 = new Uint8Array(buffer) + hex = this.ab2hex(buffer) + } else if (typeof hexStr === 'string') { + // 规范化十六进制字符串:去空格、转大写,并按字节切分 + hex = hexStr.replace(/\s+/g, '').toUpperCase().replace(/(..)/g, '$1 ').trim() + const clean = hex.replace(/\s+/g, '') + const arr = [] + for (let i = 0; i < clean.length; i += 2) arr.push(parseInt(clean.substr(i, 2), 16) || 0) + u8 = Uint8Array.from(arr) + } + // 若解析失败(没有有效数据),直接忽略此次通知 + if (!u8) return + // 按显示偏好记录日志(HEX 或原始文本) + const viewText = this.data.hexShow ? hex : `[${hex}]` + this.appendLog('RX', viewText) + // 将规范化数据交给协议解析器,进行业务处理与 UI 更新 + this.handleIncomingPacket(u8) + } + // 注册系统 BLE 通知回调 + wx.onBLECharacteristicValueChange(this._onBleChange) + }, + + teardownBleListener() { + if (this._onBleChange && typeof wx.offBLECharacteristicValueChange === 'function') { + wx.offBLECharacteristicValueChange(this._onBleChange) + } + this._onBleChange = null + }, + + handleIncomingPacket(u8) { + if (!u8 || u8.length < 11) return + const headOk = u8[0] === 0xCC && u8[1] === 0xC0 + const frameType = u8[10] + if (!headOk) return + if (frameType === (COMMANDS.RADAR_STATUS & 0xFF)) { + const parsed = this.parseRadarStatus(u8) + if (parsed) { + this.updateRadarLights(parsed.bits) + this.appendLog('PARSE', `雷达状态: 有效端口${parsed.portCount} 有人标记=${parsed.human === 0x01 ? '有人' : '无人'} 位=0b${parsed.bits.toString(2).padStart(8, '0')}`) + } + } + }, + + parseRadarStatus(u8) { + // u8: Head Len CRC Frame FramNum Type Params... + if (!u8 || u8.length < 13) return null + const params = u8.slice(11) + const portCount = params[0] || 0 + const human = params[1] + const bits = params[2] || 0 + return { portCount, human, bits } + }, + + updateRadarLights(bits) { + const next = (this.data.radarLights || []).map((it, idx) => { + const triggered = ((bits >> idx) & 0x01) === 1 + return { + ...it, + triggered, + colorClass: triggered ? 'red' : 'green' + } + }) + this.setData({ radarLights: next }) + }, + + // 数值约束 + clamp(v, min, max) { + v = Number(v || 0) + if (isNaN(v)) return min + if (v < min) return min + if (v > max) return max + return v + }, + clampDetectByUnit(unit, v) { + // unit: 0=时(1..24) 1=分(1..60) 2=秒(1..60) + const max = unit === 0 ? 24 : 60 + return this.clamp(v, 1, max) + }, + + // === 条件组(二级菜单)逻辑 === + buildCondGroups() { + const map = new Map() + const list = (this.data.conditions || []).slice().sort((a, b) => (a.group - b.group) || (a.seq - b.seq)) + list.forEach(it => { + if (!map.has(it.group)) { + map.set(it.group, { group: it.group, timeout: it.timeout, timeoutUnit: it.timeoutUnit, expanded: false, items: [] }) + } + map.get(it.group).items.push({ ...it, expanded: true }) + }) + this.setData({ condGroups: Array.from(map.values()) }) + }, + onToggleGroup(e) { + const idx = Number(e.currentTarget.dataset.idx) + const groups = this.data.condGroups.slice() + if (groups[idx]) groups[idx].expanded = !groups[idx].expanded + this.setData({ condGroups: groups }) + }, + onGroupNumberInput(e) { + const idx = Number(e.currentTarget.dataset.idx) + const field = e.currentTarget.dataset.field + let val = Number(e.detail.value || 0) + const groups = this.data.condGroups.slice() + if (groups[idx]) { + if (field === 'timeout') { + const unit = Number(groups[idx].timeoutUnit || 0) + val = this.clampDetectByUnit(unit, val || 1) + } + groups[idx][field] = val + } + this.setData({ condGroups: groups }) + }, + onGroupPickerChange(e) { + const idx = Number(e.currentTarget.dataset.idx) + const field = e.currentTarget.dataset.field + const val = Number(e.detail.value || 0) + const groups = this.data.condGroups.slice() + if (groups[idx]) { + if (field === 'timeout') { + // 选择的是索引,真实值=索引+1 + groups[idx].timeout = (val + 1) + } else { + groups[idx][field] = val + } + if (field === 'timeoutUnit') { + const t = Number(groups[idx].timeout || 0) + groups[idx].timeout = this.clampDetectByUnit(val, t || 1) + } + } + this.setData({ condGroups: groups }) + }, + onItemNumberInput(e) { + const gidx = Number(e.currentTarget.dataset.gidx) + const iidx = Number(e.currentTarget.dataset.iidx) + const field = e.currentTarget.dataset.field + // 序号为只读,不允许通过输入更新 + if (field === 'seq') return + let val = Number(e.detail.value || 0) + const groups = this.data.condGroups.slice() + const grp = groups[gidx] + if (grp && grp.items[iidx]) { + if (field === 'judgeTime') { + const unit = Number(grp.items[iidx].judgeUnit || 0) + val = this.clampDetectByUnit(unit, val || 1) + } + grp.items[iidx][field] = val + } + this.setData({ condGroups: groups }) + }, + onItemPickerChange(e) { + const gidx = Number(e.currentTarget.dataset.gidx) + const iidx = Number(e.currentTarget.dataset.iidx) + const field = e.currentTarget.dataset.field + const val = Number(e.detail.value || 0) + const groups = this.data.condGroups.slice() + const grp = groups[gidx] + if (grp && grp.items[iidx]) { + if (field === 'judgeTime') { + // 选择的是索引,真实值=索引+1 + grp.items[iidx].judgeTime = (val + 1) + } else { + grp.items[iidx][field] = val + } + if (field === 'judgeUnit') { + const t = Number(grp.items[iidx].judgeTime || 0) + grp.items[iidx].judgeTime = this.clampDetectByUnit(val, t || 1) + } + } + this.setData({ condGroups: groups }) + }, + onToggleItem(e) { + const gidx = Number(e.currentTarget.dataset.gidx) + const iidx = Number(e.currentTarget.dataset.iidx) + const groups = this.data.condGroups.slice() + const grp = groups[gidx] + if (grp && grp.items[iidx]) { + grp.items[iidx].expanded = !grp.items[iidx].expanded + } + this.setData({ condGroups: groups }) + }, + + // 端口配置交互 + onPortAliasInput(e) { + const idx = Number(e.currentTarget.dataset.idx) + const val = String(e.detail.value || '') + const list = this.data.ports.slice() + if (list[idx]) list[idx].alias = val + this.setData({ ports: list }) + }, + onPortNumberInput(e) { + const idx = Number(e.currentTarget.dataset.idx) + const field = e.currentTarget.dataset.field + let val = Number(e.detail.value || 0) + const list = this.data.ports.slice() + if (list[idx]) { + if (field === 'loop') { + val = this.clamp(val, 1, 12) + } else if (field === 'thresholdUp' || field === 'thresholdDown') { + val = this.clamp(val, 0, 100) + } else if (field === 'detectTime') { + const unit = Number(list[idx].detectUnit || 0) + val = this.clampDetectByUnit(unit, val) + } + list[idx][field] = val + } + this.setData({ ports: list }) + }, + onPortSwitch(e) { + const idx = Number(e.currentTarget.dataset.idx) + const field = e.currentTarget.dataset.field + const checked = !!e.detail.value + const list = this.data.ports.slice() + if (list[idx]) list[idx][field] = checked + this.setData({ ports: list }) + }, + onPortUnitChange(e) { + const idx = Number(e.currentTarget.dataset.idx) + const field = e.currentTarget.dataset.field + const val = Number(e.detail.value || 0) + const list = this.data.ports.slice() + if (list[idx]) { + list[idx][field] = val + // 切换单位时同时校正检测时间范围 + const dt = Number(list[idx].detectTime || 0) + list[idx].detectTime = this.clampDetectByUnit(val, dt || 1) + } + this.setData({ ports: list }) + }, + onSavePorts() { + this.data.ports.forEach((p, i) => { + const P0 = p.deviceType & 0xFF + const P1 = p.deviceAddr & 0xFF + const loopLE = [p.loop & 0xFF, (p.loop >>> 8) & 0xFF] + const P4 = p.thresholdDown & 0xFF + const P5 = (i + 1) & 0xFF + const P6 = p.enabled ? 0x01 : 0x00 + const dtLE = [p.detectTime & 0xFF, (p.detectTime >>> 8) & 0xFF] + const P9 = p.detectUnit & 0xFF + const P10 = p.thresholdUp & 0xFF + const payload = [P0, P1, ...loopLE, P4, P5, P6, ...dtLE, P9, P10] + const pkt = buildCommand(COMMANDS.SET_CONDITION_2, payload, { frame: i + 1, framNum: this.data.ports.length }) + this.appendLog('TX', `端口配置[${p.name}]: ${this.toHex(pkt)}`) + }) + wx.showToast({ title: '端口配置已发送', icon: 'success' }) + }, + + // 读取端口配置(占位示例,后续可接入真实读取命令) + onReadPorts() { + this.appendLog('UI', '请求读取端口配置') + wx.showToast({ title: '已请求读取端口配置', icon: 'none' }) + }, + + // 条件配置交互 + onCondNumberInput(e) { + const idx = Number(e.currentTarget.dataset.idx) + const field = e.currentTarget.dataset.field + const val = Number(e.detail.value || 0) + const list = this.data.conditions.slice() + if (list[idx]) list[idx][field] = val + this.setData({ conditions: list }) + }, + onCondPickerChange(e) { + const idx = Number(e.currentTarget.dataset.idx) + const field = e.currentTarget.dataset.field + const val = Number(e.detail.value || 0) + const list = this.data.conditions.slice() + if (list[idx]) list[idx][field] = val + this.setData({ conditions: list }) + }, + onSaveConditions() { + // 将二级菜单分组扁平化回 conditions + const flat = [] + (this.data.condGroups || []).forEach(grp => { + (grp.items || []).forEach(it => { + flat.push({ ...it, group: grp.group, timeout: grp.timeout, timeoutUnit: grp.timeoutUnit }) + }) + }) + this.setData({ conditions: flat }) + flat.forEach((c, i) => { + const P0 = c.tag & 0xFF + const P1 = c.group & 0xFF + const P2 = c.seq & 0xFF + const jtLE = [c.judgeTime & 0xFF, (c.judgeTime >>> 8) & 0xFF] + const P5 = c.judgeUnit & 0xFF + const bit = (v) => (v === 1 ? 1 : 0) + const b0 = bit(c.doorMag) + const b1 = bit(c.irHall) + const b2 = bit(c.bathRadar) + const b3 = bit(c.bathroomRadar) + const P6 = (b0 | (b1 << 1) | (b2 << 2) | (b3 << 3)) & 0xFF + const P7 = 0x00 + const P8 = 0x00 + const P9 = 0x00 + const P10 = 0x00 + const toLE = [c.timeout & 0xFF, (c.timeout >>> 8) & 0xFF] + const P13 = c.timeoutUnit & 0xFF + const payload = [P0, P1, P2, ...jtLE, P5, P6, P7, P8, P9, P10, ...toLE, P13] + const pkt = buildCommand(COMMANDS.SET_CONDITION_1, payload, { frame: i + 1, framNum: flat.length }) + this.appendLog('TX', `条件配置[组${c.group}/序${c.seq}]: ${this.toHex(pkt)}`) + }) + wx.showToast({ title: '条件配置已发送', icon: 'success' }) + }, + + // 功能栏示例事件 + onDeleteCondGroup() { this.appendLog('UI', '操作: 删除条件组'); wx.showToast({ title: '删除条件组', icon: 'none' }) }, + onDeleteCondition() { this.appendLog('UI', '操作: 删除条件'); wx.showToast({ title: '删除条件', icon: 'none' }) }, + onAddCondGroup() { this.appendLog('UI', '操作: 添加条件组'); wx.showToast({ title: '添加条件组', icon: 'none' }) }, + onAddCondition() { this.appendLog('UI', '操作: 添加条件'); wx.showToast({ title: '添加条件', icon: 'none' }) }, + onExport() { this.appendLog('UI', '操作: 导出'); wx.showToast({ title: '导出', icon: 'none' }) }, + + onImport() { + const now = new Date() + const stamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}` + this.setData({ importFileName: `已导入 ${stamp}` }) + this.appendLog('UI', '操作: 导入') + wx.showToast({ title: '导入', icon: 'none' }) + }, + + // 一键下发:同时下发端口配置与条件配置 + onOneKeySend() { + try { + // 先发送端口配置 + this.onSavePorts() + // 再发送条件配置 + this.onSaveConditions() + wx.showToast({ title: '已一键下发', icon: 'success' }) + this.appendLog('UI', '操作: 一键下发') + } catch (err) { + wx.showToast({ title: '下发失败', icon: 'none' }) + } + }, + + onCheckboxChange(e) { + const key = e.currentTarget.dataset.key + if (!key) return + const checked = (e.detail.value || []).includes(key) + this.setData({ [key]: checked }) + }, + + onInputChange(e) { + this.setData({ sendText: e.detail.value }) + }, + + /** + * 发送蓝牙数据 + * + * 处理用户输入并发送到蓝牙设备: + * 1. 检查输入内容是否为空 + * 2. 根据HEX发送模式转换数据格式 + * 3. 记录发送日志 + * 4. 通过蓝牙发送数据 + * + * @function onSend + * @memberof B13page + * @description 发送数据到已连接的蓝牙设备 + */ + onSend() { + + + + const content = this.data.sendText.trim() + if (!content) { + wx.showToast({ title: '请输入内容', icon: 'none' }) + return + } + + let viewText = content + let bytes = null + if (this.data.hexSend) { + const u8 = this.hexToBytes(content) + if (!u8) { + wx.showToast({ title: 'HEX格式错误', icon: 'none' }) + return + } + bytes = u8 + viewText = this.toHex(u8) + } else { + const text = this.data.wrapCRLF ? (content + '\r\n') : content + bytes = this.strToBytes(text) + viewText = text + } + + // 记录日志(按显示偏好) + const show = this.data.hexShow && bytes ? this.toHex(bytes) : viewText + this.appendLog('TX', show) + this.writeBleBytes(bytes, '发送') + + this.setData({ sendText: '' }) + }, + + + + onClearLogs() { + this.setData({ logList: [] }) + }, + + /** + * 追加一条日志到页面列表 + * - 可选带时间戳前缀(withTimestamp) + * - 头插方式存储,最新在前 + */ + appendLog(direction, text) { + const now = new Date() + const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}` + const finalText = this.data.withTimestamp ? `[${timeStr}] ${direction}: ${text}` : `${direction}: ${text}` + const id = `${Date.now()}-${Math.random().toString(16).slice(2)}` + const next = [{ id, time: timeStr, text: finalText }, ...this.data.logList] + this.setData({ logList: next }) + } +}) diff --git a/pages/basics/BluetoothDebugging/B13page/B13page.json b/pages/basics/BluetoothDebugging/B13page/B13page.json new file mode 100644 index 0000000..518243f --- /dev/null +++ b/pages/basics/BluetoothDebugging/B13page/B13page.json @@ -0,0 +1,8 @@ +{ + "navigationBarBackgroundColor": "#fff", + "navigationBarTextStyle": "black", + "navigationStyle": "custom", + "usingComponents": { + "cu-custom": "/colorui/components/cu-custom" + } +} diff --git a/pages/basics/BluetoothDebugging/B13page/B13page.wxml b/pages/basics/BluetoothDebugging/B13page/B13page.wxml new file mode 100644 index 0000000..6d70f3a --- /dev/null +++ b/pages/basics/BluetoothDebugging/B13page/B13page.wxml @@ -0,0 +1,296 @@ + + + + {{DevName || 'B13设备'}} + + + + + + 蓝牙调试 + 蓝牙升级 + + + + + + + + + + {{item.label}} + + + + + + + + 房间有人 + + + + 房间无人 + + + + 门开 + + + + + 门关 + + + + 卫浴有人 + + + + 卫浴无人 + + + + + + + 开门延时 + {{openDelay}}s + + + + + + + 卫浴延时 + {{bathDelay}}s + + + + + + + + 通讯日志 + 清空 + + + + + + + + + + + + + + + + + + +