diff --git a/docs/discussions/2026-05-30-veepoo-sdk-integration-flow.md b/docs/discussions/2026-05-30-veepoo-sdk-integration-flow.md new file mode 100644 index 0000000..70930ef --- /dev/null +++ b/docs/discussions/2026-05-30-veepoo-sdk-integration-flow.md @@ -0,0 +1,206 @@ +# Veepoo M2 BLE SDK 正确对接流程 + +> 日期: 2026-05-30 | 参与者: iven, Claude +> 状态: 已验证通过 + +## 背景 + +Veepoo M2 手环的 BLE SDK(372KB 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 是纯 JS(372KB 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 文件 diff --git a/wiki/index.md b/wiki/index.md index 82a0123..391a1f7 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -4,7 +4,7 @@ ## 关键数字 -> 最后更新: 2026-05-25 | 数据截止: feat/media-library-banner 分支(小程序 DevTools 卡死排查 + 构建优化) +> 最后更新: 2026-05-30 | 数据截止: feat/media-library-banner 分支(Veepoo M2 BLE 实时测量 + SDK 对接全流程修复) | 指标 | 值 | |------|-----| @@ -18,7 +18,7 @@ | erp-ai 实体 | 20 个 Entity(95 文件,4 AI Provider,chat_handler 支持 FC/Ollama fallback) | | 全系统 Entity | **115 个**(58 health + 20 ai + 33 基础 + 4 core) | | Web 前端 | 316 个 TS/TSX 文件(54 活跃路由,83 API 模块,108 页面) | -| 微信小程序 | Taro 4.2 + React 18,180 个 TS/TSX 文件 / 61 页面(15 主包 + 46 分包) / 4 TabBar + 医生端独立分包,34 组件(ui 21 + patterns 4 + 独立 9) / 45 service 文件 / 4 Zustand store / 12 hooks,统一组件库 + CSS 变量主题(102 SCSS 全量接入 `var(--tk-*)`,字号 token 对齐原型统计,医生端 `.doctor-mode` 靛蓝覆盖,登录页账号密码+微信一键登录);**Phase 2+3 完成**:Token 构建时生成 + Canvas 适老 + PII 清理 + 缓存加密 + any 清零 + 大文件拆分(3→6) + 触觉反馈 + 导航状态保持 + 独立分包 + CI 集成 + HMAC 请求签名;**并发安全**:长轮询独立通道 `requestUnlimited` + ConcurrencyLimiter(12) + safeNavigateTo 全局页栈保护 + reLaunch 去重 + 分包预加载 preloadRule;**构建优化**:`lazyCodeLoading: requiredComponents` 仅生产构建启用(dev 下已知 DevTools 卡死 bug),`addChunkPages` 仅 TabBar 页注入 common chunk,主包 dev 892KB / prod 766KB(taro.js 526→131KB / vendors.js 230→28KB);**DevTools 兼容**:游客首页 Swiper dev 模式禁用 circular + 间隔 15s,防 DevTools Chromium 渲染进程逐渐卡死;**离线抑制**:指数退避(3s→6s→12s→30s cap)防请求洪泛;**五维度分析评分 6.7/10**(架构7.25/安全6.0/UX7.4/工程6.2) | +| 微信小程序 | Taro 4.2 + React 18,185 个 TS/TSX 文件 / 62 页面(15 主包 + 47 分包) / 4 TabBar + 医生端独立分包,34 组件(ui 21 + patterns 4 + 独立 9) / 49 service 文件 / 5 Zustand store / 12 hooks,统一组件库 + CSS 变量主题(103 SCSS 全量接入 `var(--tk-*)`,字号 token 对齐原型统计,医生端 `.doctor-mode` 靛蓝覆盖,登录页账号密码+微信一键登录);**Phase 2+3 完成**:Token 构建时生成 + Canvas 适老 + PII 清理 + 缓存加密 + any 清零 + 大文件拆分(3→6) + 触觉反馈 + 导航状态保持 + 独立分包 + CI 集成 + HMAC 请求签名;**并发安全**:长轮询独立通道 `requestUnlimited` + ConcurrencyLimiter(12) + safeNavigateTo 全局页栈保护 + reLaunch 去重;**Veepoo M2 BLE 管线**:独立管线(VeepooBridge 14 API + VeepooPipeline 事件路由 + VeepooStore 状态管理)+ **原生分包页面**(`pkg-veepoo` 原生 JS+WXML,脱离 Taro 直接调用 SDK,绕过框架兼容性限制)+ 实时测量页面(心率/血氧/血压/体温/压力 5 指标,圆环仪表盘 + 长者模式适配)+ 3 天历史数据同步(VeepooHistoryReader 分批上传 + 断点续传);**preloadRule 已移除 pkg-health** 防止 380KB SDK 预加载导致首页 DevTools 卡死;**构建优化**:`lazyCodeLoading: requiredComponents` 仅生产构建启用(dev 下已知 DevTools 卡死 bug),`addChunkPages` 仅 TabBar 页注入 common chunk,主包 dev 892KB / prod 766KB;**五维度分析评分 6.7/10**(架构7.25/安全6.0/UX7.4/工程6.2) | | 前端测试 | Web 62 单元测试文件(~693 断言) + 17 E2E spec(13 Web + 4 MP,~64 断言);小程序 12 单元测试文件(127 断言) + 4 E2E spec(~16 断言),覆盖率 ~6% | | 后端测试 | **1030 个函数**(839 同步 + 191 异步),96 个文件含测试 | | 事件系统 | 31 事件类型(health)/ 51 全系统 / 82 发布点 / 15 消费者模块 / Outbox + LISTEN/NOTIFY | @@ -27,7 +27,7 @@ | Clippy | **全 workspace 0 警告**(2026-05-07 清零) | | 依赖版本 | 全部最新主版本线(Rust edition 2024) | | API 文档 | `http://localhost:3000/api/docs/openapi.json` | -| Git 提交 | **996 次** | +| Git 提交 | **1,061 次** | | Graphify 知识图谱 | **18,517 节点** / 22,666 边 / 1,841 社区(`graphify-out/`,AST 解析,无 API 成本) | | 系统分析评分 | **6.9/10 (B)**(多专家组生产就绪度分析,2026-05-21:业务 8.5 / 医疗合规 6.5 / 前端 8.0 / 安全 7.5 / DevOps 4.0) | | 审计状态 | V1: 83% → V2: 85%,P0 安全修复已完成;E2E 测试 157 端点(Health 63% / AI+Plugin 92.4%),CRITICAL×2 待修复 | @@ -148,6 +148,11 @@ | TS 编译错误 `readonly Tab[]` 不可赋值给 `Tab[]` | [[miniprogram]] SegmentTabs | 页面组件用 `as const` 创建的 readonly 数组无法传入 mutable `Tab[]` 类型 | **已修复:** SegmentTabs 的 `Tab` 属性改为 `readonly` + `tabs` prop 改为 `readonly Tab[]`(2026-05-24) | | 重建失败 `dist/` 被锁定 | [[miniprogram]] 构建流程 | 微信 DevTools 进程持有 dist 目录文件句柄,taro build 无法写入 | **解决:** `taskkill /F /IM wechatdevtools.exe` 后重新构建(2026-05-24) | | DevTools 打开即卡死(所有项目,Taro/原生均复现) | [[miniprogram]] appid 配置 | appid `wx20f4ef9cc2ec66c5` 的微信后台配置触发 `WAServiceMainContext.js` 内部 timeout,导致 DevTools 渲染进程逐渐无响应;**根因定位:** 换用其他 appid(如测试 appid `wx97debf52c9547da4`)后 Taro/原生均不卡死,确认是 appid 后台服务配置问题而非框架/代码问题 | **待解决:** 需到微信公众平台(mp.weixin.qq.com)检查该 appid 是否开通了云开发/云函数/第三方插件等导致 DevTools 初始化时连接超时的服务;临时方案:开发调试时使用测试 appid(2026-05-24) | +| 首页加载后 DevTools 卡死(Veepoo SDK 预加载触发) | [[miniprogram]] preloadRule | `preloadRule` 首页预加载 `pkg-health` 分包 → `sub-vendors.js` 含 380KB Veepoo SDK → SDK 初始化调用蓝牙 API → DevTools Chromium 渲染进程卡死 | **已修复:** 从 `preloadRule` 首页/健康页移除 `pkg-health` 预加载,SDK 仅在用户导航到设备同步/测量页时才加载(2026-05-30) | +| 原生页面 `??` 运算符报 SyntaxError | [[miniprogram]] 原生页面 | 微信小程序 JS 引擎不支持 ES2020 `??`(nullish coalescing)和 `?.`(optional chaining) | **已修复:** `values.systolic ?? '--'` 改为 `values.systolic != null ? values.systolic : '--'`(2026-05-30) | +| veepoo-measure 页面空白(useRef is not defined) | [[miniprogram]] 原生页面桥接 | TSX 文件使用 `useRef` 但仅 `import React from 'react'` 未解构导入 | **已修复:** 改为 `import React, { useRef } from 'react'`(2026-05-30) | +| M2 设备扫描不到(名称匹配过严) | [[miniprogram]] 原生页面扫描 | 过滤条件 `name.indexOf('M2')` 过严,设备可能广播为 VPM/VEEPOO | **已修复:** 放宽匹配 M2/VPM/VEEPOO 三种前缀(2026-05-30) | +| M2 设备认证超时(3 层根因) | [[miniprogram]] 原生页面认证 | **根因链**:①连接回调 `errno:0` 在第 1 次回调就匹配,认证在特征值订阅前发送 → 修复为只匹配 `connection:true`;②`veepooWeiXinSDKNotifyMonitorValueChange` 在 `onLoad` 注册时内部调用 `wx.notifyBLECharacteristicValueChange`,适配器未初始化 → `not init` 错误,改到 `connection:true` 后注册;③认证结果字段检查错误:代码检查 `VPDevicepassword`(值="0000")而非 `VPDeviceAck`(值="successfulVerification") | **已修复:** 三层修复 — connection:true 唯一匹配 + 监听器时序 + VPDeviceAck 字段(2026-05-30) | ## 模块导航