修复ASI导致ConnectBle调用失效(setMac/pushMsg后补分号);新增MCM_INFO只读参数特征读取(BluetoothManager.readInfoChar+读结果按UUID分发,McmInfo.readDeviceInfo),主页参数轮询从INFO_QUERY切到read特征(...c72e0004);docs补修改记录
This commit is contained in:
parent
07509a2387
commit
30d072defc
18
docs/修改记录_2026-09-04_BLE连接ASI分号修复.md
Normal file
18
docs/修改记录_2026-09-04_BLE连接ASI分号修复.md
Normal file
@ -0,0 +1,18 @@
|
||||
# 修改记录 2026-09-04:修复 ASI 导致 BLE 连接调用失效
|
||||
|
||||
## 问题现象
|
||||
|
||||
点击设备列表连接、CLI 页点 connect 均无反应,控制台报
|
||||
`TypeError: setMac(...) is not a function` / `pushMsg(...) is not a function`。
|
||||
|
||||
## 根因
|
||||
|
||||
`pages/tabbar/home.vue:230` 与 `pages/bleTest/bluetoothOperation/bluetoothOperation.vue:147`
|
||||
两处,前一行语句末尾没有分号,下一行又以 `(` 开头(`(getApp().globalData as any).ConnectBle(...)`),
|
||||
JavaScript 自动分号插入(ASI)对此不生效,编译产物把两行合并成
|
||||
`setMac(...)(getApp()...)` —— 拿上一行的返回值(undefined)当函数调用。
|
||||
|
||||
## 修改
|
||||
|
||||
两处行末补分号。排查技巧:源码函数明明存在却报 "is not a function" 时,
|
||||
到 `unpackage/dist/dev/app-plus/app-service.js` 搜编译产物即可定位。
|
||||
39
docs/修改记录_2026-09-04_设备参数只读特征.md
Normal file
39
docs/修改记录_2026-09-04_设备参数只读特征.md
Normal file
@ -0,0 +1,39 @@
|
||||
# 修改记录 2026-09-04:设备参数改为只读特征 + App 定时读
|
||||
|
||||
设备参数查看(温度/风扇/电压/版本/uptime/heap)从「INFO_QUERY 帧 + notify 应答」
|
||||
改为「只读特征 + App 定时 read」,不再依赖 notify 上行通道。
|
||||
|
||||
## GATT 变更(固件 mcm-ddc-ble V1.00.04)
|
||||
|
||||
- 新增只读特征 `00002760-08c2-11e1-9073-0e8ac72e0004`(`MCM_INFO_UUID`):
|
||||
读出内容为全部信息项的 TLV 序列 `{id u8, len u8, value…}×n`(数值小端),
|
||||
与 INFO_RSP(all) 帧 payload 完全一致。信息项 id 不变:
|
||||
0x01 FW_VERSION(u32)、0x02 CHIP_TEMP(i16, 0.1℃, 0x7FFF=失败)、
|
||||
0x03 FAN_RPM(u16, 0xFFFF=无硬件)、0x04 VDD_MV(u16, 0=失败)、
|
||||
0x05 UPTIME_S(u32)、0x06 FREE_HEAP(u32)。
|
||||
- 既有特征不变:`...e0001`(帧协议下行,无响应写)、`...e0002`(notify 上行)、
|
||||
`...e0003`(CLI 读写特征:写原始命令行、读输出文本,V1.00.02 新增)。
|
||||
- 固件侧实现:`app_info.c` 抽出 `app_info_tlv_snapshot()`(INFO_QUERY 帧应答与其
|
||||
共用同一组包逻辑);`app_rdtss.c` 读请求按 `att_idx` 分发(CLI_VAL → 命令输出,
|
||||
INFO_VAL → TLV 快照,其余保持原 "NSING" 行为)。
|
||||
- 附带修复(V1.00.03):芯片温度采样前先给 TS 5ms 稳定时间并丢弃第一次转换,
|
||||
修复 `devinfo`/INFO_QUERY 温度显示 287.6°C 的问题(传感器未稳定即采样)。
|
||||
|
||||
## App 侧修改
|
||||
|
||||
- `src/BluetoothManager.ts`:新增 `MCM_INFO_UUID` 常量与 `readInfoChar(cb)`;
|
||||
`onBLECharacteristicValueChange` 监听重构为 `ensureValueChangeListener()`,
|
||||
notify 数据与 read 结果按特征 UUID 分发(uni 的读结果经此回调返回);
|
||||
读操作带 3s 超时。
|
||||
- `src/mcm/McmInfo.ts`:新增 `readDeviceInfo()`,读参数特征并复用 `parseTlv` 解析;
|
||||
原 `queryDeviceInfo()`(帧协议 INFO_QUERY)保留。
|
||||
- `pages/tabbar/home.vue`:参数轮询 `pollParams()` 从 `queryDeviceInfo` 切到
|
||||
`readDeviceInfo`,一次读出全部项取温度+风扇。
|
||||
|
||||
## 验证
|
||||
|
||||
- 固件 `UV4 -r` 全量重建 0 Error / 0 Warning;`tools/out/caiic_ble_full.hex` 出包,
|
||||
OTA 清单 version=0x00010004。
|
||||
- PC 侧验证(烧录后):`tools\ble_cli.bat param` 读参数特征并解析 TLV;
|
||||
`tools\ble_cli.bat -rw help` 验证 CLI 读写特征。
|
||||
- 真机回归:主页温度/风扇显示(轮询走 read),CLI 终端、OTA 流程不受影响。
|
||||
@ -144,7 +144,7 @@ function onConnect() {
|
||||
title: "Connecting...",
|
||||
mask: true
|
||||
})
|
||||
pushMsg("start", "connect: " + addr)
|
||||
pushMsg("start", "connect: " + addr);
|
||||
(getApp().globalData as any).ConnectBle(addr, false)
|
||||
}
|
||||
|
||||
|
||||
@ -103,8 +103,8 @@ let pageVisible = false
|
||||
|
||||
async function pollParams() {
|
||||
if (!connected.value || !pageVisible) return
|
||||
// 一次 INFO_QUERY 同时取温度+风扇(协议 V1.1 第 5 节)
|
||||
const info = await McmInfo.queryDeviceInfo([McmInfo.INFO_CHIP_TEMP, McmInfo.INFO_FAN_RPM])
|
||||
// 读只读参数特征(MCM_INFO_UUID,TLV 同 INFO_RSP 全部项),取温度+风扇
|
||||
const info = await McmInfo.readDeviceInfo()
|
||||
const t = info.chipTemp
|
||||
temperature.value = t != null ? t.toFixed(1) : "--"
|
||||
const f = info.fanRpm
|
||||
@ -227,7 +227,7 @@ function onLinkBluetooth(item: BleScanResult) {
|
||||
connecting.value = true
|
||||
deviceName.value = item.device.name ?? DEVICE_NAME_FILTER
|
||||
BleINfo.setName(deviceName.value)
|
||||
BleINfo.setMac(item.device.address)
|
||||
BleINfo.setMac(item.device.address);
|
||||
(getApp().globalData as any).ConnectBle(item.device.address, false)
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
export const MCM_SERVICE_UUID = "00002760-08c2-11e1-9073-0e8ac72e1001"
|
||||
export const MCM_WRITE_UUID = "00002760-08c2-11e1-9073-0e8ac72e0001" // Write Without Response
|
||||
export const MCM_NOTIFY_UUID = "00002760-08c2-11e1-9073-0e8ac72e0002" // Notify
|
||||
export const MCM_INFO_UUID = "00002760-08c2-11e1-9073-0e8ac72e0004" // 只读:设备参数 TLV(同 INFO_RSP 全部项)
|
||||
|
||||
export type MyApiResult = {
|
||||
type: number,
|
||||
@ -51,6 +52,40 @@ let connListenerRegistered = false
|
||||
let scanListenerRegistered = false
|
||||
let notifyListenerRegistered = false
|
||||
let notifyDataCb: ((res: MyApiResult) => void) | null = null
|
||||
let readPendingChar: string | null = null // 等待读结果的特征 UUID
|
||||
let readCb: ((res: MyApiResult) => void) | null = null
|
||||
let readTimer = 0
|
||||
|
||||
// 注册特征值变化监听(notify 数据与 read 结果都经此回调分发,幂等)
|
||||
function ensureValueChangeListener() {
|
||||
if (notifyListenerRegistered) return
|
||||
notifyListenerRegistered = true
|
||||
uni.onBLECharacteristicValueChange((res: any) => {
|
||||
const charId = res.characteristicId as string
|
||||
const bytes = new Uint8Array(res.value as ArrayBuffer)
|
||||
const arr = [] as number[]
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
arr.push(bytes[i])
|
||||
}
|
||||
if (uuidEq(charId, MCM_NOTIFY_UUID)) {
|
||||
const nd: NotityData = { data: arr, mac: res.deviceId } as NotityData
|
||||
const c = notifyDataCb
|
||||
if (c != null) {
|
||||
c({ type: 0, message: "notify", data: nd } as MyApiResult)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (readPendingChar != null && uuidEq(charId, readPendingChar)) {
|
||||
const c = readCb
|
||||
readCb = null
|
||||
readPendingChar = null
|
||||
clearTimeout(readTimer)
|
||||
if (c != null) {
|
||||
c({ type: 0, message: "read ok", data: arr } as MyApiResult)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function ok(message: string): MyApiResult {
|
||||
return { type: 0, message: message, data: null } as MyApiResult
|
||||
@ -318,22 +353,7 @@ export function onNotityReadBleData(readNotification: boolean, cb: (res: MyApiRe
|
||||
return
|
||||
}
|
||||
notifyDataCb = cb
|
||||
if (!notifyListenerRegistered) {
|
||||
notifyListenerRegistered = true
|
||||
uni.onBLECharacteristicValueChange((res: any) => {
|
||||
if (!uuidEq(res.characteristicId as string, MCM_NOTIFY_UUID)) return
|
||||
const bytes = new Uint8Array(res.value as ArrayBuffer)
|
||||
const arr = [] as number[]
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
arr.push(bytes[i])
|
||||
}
|
||||
const nd: NotityData = { data: arr, mac: res.deviceId } as NotityData
|
||||
const c = notifyDataCb
|
||||
if (c != null) {
|
||||
c({ type: 0, message: "notify", data: nd } as MyApiResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
ensureValueChangeListener()
|
||||
uni.notifyBLECharacteristicValueChange({
|
||||
deviceId: id,
|
||||
serviceId: MCM_SERVICE_UUID,
|
||||
@ -394,6 +414,40 @@ export function sendDataBytes(data: number[], writeType: number, cb: (res: MyApi
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 读取 =================
|
||||
|
||||
// 读取设备参数特征(只读 MCM_INFO_UUID,内容为全部信息项 TLV,同 INFO_RSP(all))。
|
||||
// uni 的读结果经 onBLECharacteristicValueChange 回传;type==0 时 data 为 number[]。
|
||||
export function readInfoChar(cb: (res: MyApiResult) => void) {
|
||||
const id = connectedDeviceId
|
||||
if (id == null) {
|
||||
cb({ type: 10001, message: "not connected", data: null } as MyApiResult)
|
||||
return
|
||||
}
|
||||
ensureValueChangeListener()
|
||||
readPendingChar = MCM_INFO_UUID
|
||||
readCb = cb
|
||||
readTimer = setTimeout(() => {
|
||||
if (readCb != null) {
|
||||
readCb = null
|
||||
readPendingChar = null
|
||||
cb({ type: 10008, message: "read timeout", data: null } as MyApiResult)
|
||||
}
|
||||
}, 3000)
|
||||
uni.readBLECharacteristicValue({
|
||||
deviceId: id,
|
||||
serviceId: MCM_SERVICE_UUID,
|
||||
characteristicId: MCM_INFO_UUID,
|
||||
success: () => {},
|
||||
fail: (err: any) => {
|
||||
readPendingChar = null
|
||||
readCb = null
|
||||
clearTimeout(readTimer)
|
||||
cb(failRes(10008, err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ================= 其他 =================
|
||||
|
||||
// 设置 mtu(仅 App 端有效;其他平台回调失败,调用方保持默认 20 字节切片)
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
*/
|
||||
import { queryInfo } from "./McmChannel"
|
||||
import { readU16LE, readI16LE, readU32LE } from "./FrameCodec"
|
||||
import * as Ble from "../BluetoothManager"
|
||||
import { MyApiResult } from "../BluetoothManager"
|
||||
|
||||
// 信息项 id
|
||||
export const INFO_FW_VERSION = 0x01 // u32 固件版本号,如 0x00010001 = V1.00.01
|
||||
@ -74,6 +76,20 @@ export async function queryDeviceInfo(items: number[]): Promise<DeviceInfo> {
|
||||
return parseTlv(payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* 读只读参数特征(MCM_INFO_UUID,内容为全部信息项 TLV,同 INFO_RSP(all))。
|
||||
* 不依赖 notify 通道;失败/超时返回各字段全 null 的 DeviceInfo。
|
||||
*/
|
||||
export async function readDeviceInfo(): Promise<DeviceInfo> {
|
||||
const payload = await new Promise<number[] | null>((resolve) => {
|
||||
Ble.readInfoChar((res: MyApiResult) => {
|
||||
resolve(res.type == 0 ? res.data as number[] : null)
|
||||
})
|
||||
})
|
||||
if (payload == null) return emptyInfo()
|
||||
return parseTlv(payload)
|
||||
}
|
||||
|
||||
/** 固件版本号 u32 格式化,如 0x00010001 → "V1.00.01" */
|
||||
export function formatVersion(v: number): string {
|
||||
const major = Math.floor(v / 65536) % 256
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user