Files
hms/docs/discussions/2026-05-30-veepoo-sdk-integration-flow.md
iven 6d073840aa docs(wiki): 记录 Veepoo M2 BLE SDK 对接踩坑和正确流程
- wiki/index.md 症状导航新增 5 条:??语法错误、useRef白屏、扫描匹配、认证超时三层根因
- wiki/index.md 关键数字更新:Git 提交 1061 次,小程序描述补充原生分包页面
- docs/discussions/ 新增 SDK 对接流程文档:
  - 连接回调 4 次触发机制(只响应 connection:true)
  - veepooWeiXinSDKNotifyMonitorValueChange 必须在连接后注册
  - VPDeviceAck vs VPDevicepassword 字段区别
  - deviceChipStatus 布尔值兼容
  - 完整踩坑清单(9 项)
2026-05-30 23:07:03 +08:00

207 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Veepoo M2 BLE SDK 正确对接流程
> 日期: 2026-05-30 | 参与者: iven, Claude
> 状态: 已验证通过
## 背景
Veepoo M2 手环的 BLE SDK372KB Webpack 打包)在微信小程序中的对接遇到了多层问题。
本文档记录**经过实际验证的正确对接流程**和踩过的坑,避免后续开发者重复踩坑。
## SDK 架构
```
Taro 页面 (veepoo-measure/index.tsx)
↓ navigateTo
原生分包页面 (pkg-veepoo/index.js + index.wxml)
↓ require('./libs/veepoo-sdk')
Veepoo SDK (veepoo-sdk.js)
↓ wx.* BLE API
微信蓝牙底层
```
### 为什么用原生分包而非 Taro
1. SDK 是纯 JS372KB Webpack CommonJS2不兼容 Taro 编译流程
2. SDK 使用全局变量 `veepooBle`/`veepooFeature`/`veepooLogger`,需要 `require()` 直接加载
3. 微信小程序 JS 引擎不支持 ES2020 语法(`??``?.`),原生页面可精确控制语法
4. 分包独立,不影响主包体积
### 构建集成
```js
// config/index.ts — mini.copy.patterns
{ from: 'vendor/veepoo-sdk/libs/vp_sdk/index.js', to: 'dist/pkg-veepoo/libs/veepoo-sdk.js' },
{ from: 'native/pkg-veepoo/', to: 'dist/pkg-veepoo/', ignore: ['*.ts'] },
```
**注意**`dev:weapp` 的 watch 模式不监听 `native/` 目录变化,修改原生页面后需清除 dist 重建:
```bash
rm -rf apps/miniprogram/dist/pkg-veepoo
pnpm run build:weapp # 或 pnpm run dev:weapp
```
## 正确对接流程(已验证)
### 1. 页面加载onLoad
```js
onLoad: function () {
this._eventChannel = this.getOpenerEventChannel();
// ❌ 不要在这里注册 veepooWeiXinSDKNotifyMonitorValueChange
// 该函数内部会调用 wx.notifyBLECharacteristicValueChange
// 此时蓝牙适配器未初始化 → 返回 "not init" 错误
}
```
### 2. 扫描
```js
veepooBle.veepooWeiXinSDKStartScanDeviceAndReceiveScanningDevice(function (res) {
var name = (device.localName || device.name || '').toUpperCase();
// 匹配条件要放宽M2 / VPM / VEEPOO
if (name.indexOf('M2') !== -1 || name.indexOf('VPM') !== -1 || name.indexOf('VEEPOO') !== -1) {
// 找到设备
}
});
```
### 3. 停止扫描 → 连接
```js
veepooBle.veepooWeiXinSDKStopSearchBleManager(function () {
veepooBle.veepooWeiXinSDKBleConnectionServicesCharacteristicsNotifyManager(device, callback);
});
```
### 4. 连接回调(关键!)
**连接回调触发 4 次**,每个阶段一次:
| # | 回调内容 | 含义 |
|---|---------|------|
| 1 | `{errno:0, errMsg:"createBLEConnection:ok"}` | BLE TCP 连接建立 |
| 2 | `[{uuid:...}, ...]` | 服务发现完成 |
| 3 | `{characteristics:[...], errno:0}` | 特征值发现完成 |
| 4 | `{connection:true, deviceId:"..."}` | **特征值订阅完成,通道就绪** |
**只响应第 4 次 `connection:true`**
```js
veepooBle.veepooWeiXinSDKBleConnectionServicesCharacteristicsNotifyManager(device, function (result) {
// ❌ 不要用 errno===0 匹配!第 1 次回调就满足,但此时订阅未完成
// ✅ 只匹配 connection:true第 4 次回调)
if (result.connection === true) {
// 此时 BLE 通道完全就绪
}
});
```
### 5. 注册数据监听器(在 connection:true 回调内)
```js
if (result.connection === true) {
// ✅ 此时蓝牙适配器已初始化 + 连接已建立 + 特征值已订阅
veepooBle.veepooWeiXinSDKNotifyMonitorValueChange(function (data) {
// data.type 对应不同事件1=认证, 2=电量, 6=体温, 18=血压, 31=血氧, 51=心率, 58=压力
handleSdkEvent(data);
});
// 同时注册连接状态变化监听
veepooBle.veepooWeiXinSDKBLEConnectionStateChangeManager(function (res) {
if (!res.connected) { /* 断开处理 */ }
});
}
```
### 6. 认证(连接就绪后延迟 500ms
```js
setTimeout(function () {
veepooFeature.veepooBlePasswordCheckManager();
}, 500);
```
### 7. 认证结果判断(关键!)
```js
// SDK 认证回调结构:
// {
// type: 1,
// content: {
// VPDevicepassword: "0000", ← 设备密码原始值
// VPDeviceAck: "successfulVerification", ← ✅ 认证结果
// VPDeviceVersion: "01.63.01.00-7466",
// VPDeviceMAC: "BC:92:DC:9F:CA:6A",
// ...
// }
// }
// ❌ 错误:检查 VPDevicepassword值是 "0000",永远不匹配)
if (content.VPDevicepassword === 'successfulVerification') { ... }
// ✅ 正确:检查 VPDeviceAck
if (content.VPDeviceAck === 'successfulVerification' ||
content.VPDeviceAck === 'passTheVerification') {
// 认证成功
}
```
**VPDeviceAck 可能的值**
- `successfulVerification` — 密码和时间校验成功
- `passTheVerification` — 核验通过
- `verifyNotPass` — 核验不通过
- `setupFailed` — 设置不成功
### 8. Storage 轮询兜底
SDK 会写入 `deviceChipStatus` 到 Storage但**可能是布尔值 `true` 而非字符串**
```js
var status = wx.getStorageSync('deviceChipStatus');
// ✅ 同时匹配字符串和布尔值
if (status === 'successfulVerification' || status === 'passTheVerification' || status === true) {
// 认证成功
}
```
## 完整流程图
```
onLoad → 扫描 → 找到M2 → 停止扫描
连接(veepooWeiXinSDKBleConnectionServicesCharacteristicsNotifyManager)
→ 回调1(errno:0) → 忽略
→ 回调2(services) → 忽略
→ 回调3(characteristics) → 忽略
→ 回调4(connection:true) →
① 注册数据监听器(veepooWeiXinSDKNotifyMonitorValueChange)
② 注册连接状态监听器
③ 延迟500ms → 调用认证(veepooBlePasswordCheckManager)
④ 启动 Storage 轮询(deviceChipStatus) + 8s 超时
数据监听器收到 type=1 事件 → 检查 VPDeviceAck === "successfulVerification"
认证成功 → 设备就绪 → 可开始测量
```
## 踩坑清单
| # | 问题 | 根因 | 解决方案 |
|---|------|------|----------|
| 1 | 原生页面 `??` 报 SyntaxError | 微信小程序 JS 引擎不支持 ES2020 | 用 `!= null` 三元表达式替代 |
| 2 | veepoo-measure 白屏 `useRef is not defined` | TSX import 未解构 `useRef` | `import React, { useRef } from 'react'` |
| 3 | 扫描不到 M2 设备 | 过滤条件只匹配 `M2`,设备可能广播其他名 | 放宽匹配 M2/VPM/VEEPOO |
| 4 | 认证超时 — 回调匹配过早 | `errno:0` 在第 1 次回调就匹配 | 只匹配 `connection:true` |
| 5 | 认证超时 — 监听器注册过早 | `onLoad` 时适配器未初始化 → `not init` | 改到 `connection:true` 后注册 |
| 6 | 认证超时 — 字段检查错误 | 检查 `VPDevicepassword`(值="0000")而非 `VPDeviceAck` | 改为检查 `VPDeviceAck` |
| 7 | `deviceChipStatus` 轮询失败 | SDK 写入布尔值 `true` 而非字符串 | 同时匹配字符串和布尔值 |
| 8 | `vibrateShort` promise rejection | DevTools 不支持 `type` 参数try/catch 无法捕获异步 rejection | 改用 `.catch()` |
| 9 | dist 不更新 | `dev:weapp` watch 不监听 `native/` 目录 | 修改原生页面后需 `rm -rf dist/pkg-veepoo` 重建 |
## dev:weapp 注意事项
- 原生页面修改后**不会自动热更新**,需手动清除 dist 重建
- DevTools 日志需要选中正确的页面上下文(`pkg-veepoo`)才能看到原生页面日志
- 关闭 DevTools 后重新打开,确保加载最新的 dist 文件