修复ASI导致ConnectBle调用失效(setMac/pushMsg后补分号);新增MCM_INFO只读参数特征读取(BluetoothManager.readInfoChar+读结果按UUID分发,McmInfo.readDeviceInfo),主页参数轮询从INFO_QUERY切到read特征(...c72e0004);docs补修改记录

This commit is contained in:
evan.liu 2026-09-04 08:53:17 +08:00
parent 07509a2387
commit 30d072defc
6 changed files with 147 additions and 20 deletions

View 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` 搜编译产物即可定位。

View 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 流程不受影响。

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 字节切片)

View File

@ -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