fastbee/.claude/skills/yack-mini-dev.md

364 lines
12 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.

# yack-mini 开发指南
微信小程序蓝牙网关项目,采集 BLE 设备数据透传上报至 yack 平台。
## 项目定位
- **名称**:yack-mini
- **技术栈**:原生微信小程序
- **目标**:轻量化蓝牙网关,透传数据至 yack
- **与 yack-app 区别**:yack-app 是全功能移动端(UniApp),yack-mini 是轻量蓝牙网关(原生小程序)
## 架构概览
```
┌─────────────┐ BLE ┌──────────────────┐ wxs ┌─────────────────┐
│ BLE 设备 │ ←──────→ │ yack-mini │ ←──────→ │ yack 平台 │
│ (传感器) │ │ (微信小程序) │ MQTT │ (数据存储) │
└─────────────┘ └──────────────────┘ └─────────────────┘
|
|
——————————————————
| 本地缓存/队列 |
——————————————————
```
## 项目结构
```
miniprogram/
├── app.js # 应用入口
├── app.json # 全局配置
├── app.wxss # 全局样式
├── project.config.json # 项目配置(需修改 appid)
├── sitemap.json # 站点地图
├── env.config.js # 环境配置(API/MQTT 地址)
│
├── pages/
│ ├── login/ # 登录页(首次使用时获取 userId)
│ │ ├── login.js
│ │ ├── login.wxml
│ │ ├── login.wxss
│ │ └── login.json
│ │
│ ├── index/ # 首页(设备列表)
│ │ ├── index.js
│ │ ├── index.wxml
│ │ ├── index.wxss
│ │ └── index.json
│ │
│ └── device/
│ ├── scan/ # 扫描设备页
│ │ ├── scan.js
│ │ ├── scan.wxml
│ │ ├── scan.wxss
│ │ └── scan.json
│ │
│ └── detail/ # 设备详情/实时数据页
│ ├── detail.js
│ ├── detail.wxml
│ ├── detail.wxss
│ └── detail.json
│
├── services/ # 核心服务模块
│ ├── ble.js # 蓝牙采集模块
│ │ • initBluetooth() # 初始化蓝牙适配器
│ │ • scanDevices(uuids, timeout) # 扫描设备
│ │ • stopScan() # 停止扫描
│ │ • connectDevice(id, serviceId) # 连接设备
│ │ • onData(callback) # 订阅数据通知(非 subscribeData)
│ │ • onStateChange(cb) # 状态变化回调
│ │ • writeCommand(hex) # 写入指令
│ │ • disconnect() # 断开连接
│ │ • closeBluetooth() # 关闭蓝牙适配器
│ │ • getState() # 获取当前状态
│ │ • isConnected() # 是否已连接
│ │
│ └── mqtt-client.js # MQTT 连接模块
│ • connect(options) # 连接 Broker
│ • publish(topic, msg, opts) # 发布消息
│ • subscribe(topic, cb, opts) # 订阅主题
│ • unsubscribe(topic) # 取消订阅
│ • onMessage(cb) # 消息回调
│ • onStateChange(cb) # 状态变化回调
│ • disconnect() # 断开连接
│ • getState() # 获取当前状态
│ • isConnected() # 是否已连接
│
├── utils/
│ ├── hex.js # 十六进制工具
│ │ • arrayBufferToHex(buf) # ArrayBuffer → Hex
│ │ • hexToArrayBuffer(hex) # Hex → ArrayBuffer
│ │
│ ├── storage.js # 本地存储
│ │ • getToken/setToken/removeToken # Token 管理
│ │ • getUserInfo/setUserInfo/removeUserInfo # 用户信息
│ │ • getDeviceList/saveDeviceList # 设备列表
│ │ • addDevice(device) # 添加设备(非 saveDevice)
│ │ • removeDevice/updateDevice/getDevice
│ │ • cachePendingData/getPendingData/clearPendingData # 离线缓存
│ │ • clearAll() # 清除所有数据
│ │
│ ├── request.js # HTTP 请求模块(仅登录使用)
│ │ • login(username, password, code, uuid) # 登录
│ │ • getInfo() # 获取用户信息
│ │ • captchaImage() # 获取验证码
│ │
│ └── auth.js # MQTT 凭证生成
│ • generateClientId(serialNumber, productId, userId) # 生成 clientId
│ • getMqttCredentials(serialNumber, productId, productSecret) # 获取凭证
│ • getMqttCredentialsFromDevice(device) # 从设备配置获取凭证
│ • buildPropertyPostTopic(productId, serialNumber) # 构建上报 Topic
│ • buildFunctionGetTopic(productId, serialNumber) # 构建指令 Topic
│ • buildPropertyGetTopic(productId, serialNumber) # 构建读取 Topic
│ • checkLoginStatus() # 检查登录状态
│ • getCurrentUserId() # 获取当前用户 ID
│
├── components/ # 公共组件
│ └── device-card/ # 设备卡片组件
│
├── libs/ # 第三方库
│ └── mqtt.min.js # MQTT 客户端(预置,非 npm 安装)
│
└── assets/ # 静态资源
└── images/
```
## 登录机制说明
### 设计原则
**登录仅用于首次获取 userId,后续无需重复登录。**
- userId 用于生成 MQTT clientId,是设备上报数据的必要参数
- 登录成功后,userId 永久保存在本地
- 后续启动直接读取本地 userId,无需网络请求
- Token 不参与后续业务逻辑(仅登录时使用)
### 登录流程
```
首次启动:
检查本地 userId → 无 → 显示登录页 → 输入账号密码 → 获取 userId → 保存本地 → 进入设备列表
后续启动:
检查本地 userId → 有 → 直接进入设备列表(无需登录)
```
### 退出登录
如需切换账号,在设置页或设备列表页提供"退出登录"按钮,调用:
```javascript
const storage = require('../../utils/storage.js');
// 退出登录
function logout() {
storage.clearAll(); // 清除本地所有数据
wx.reLaunch({ url: '/pages/login/login' }); // 跳转登录页
}
```
## 关键技术实现
### 1. 蓝牙 BLE 采集(services/ble.js)
```javascript
// 核心流程
wx.openBluetoothAdapter() // 初始化
→ wx.startBluetoothDevicesDiscovery() // 扫描
→ wx.createBLEConnection() // 连接
→ wx.getBLEDeviceServices() // 获取服务
→ wx.getBLEDeviceCharacteristics() // 获取特征值
→ wx.notifyBLECharacteristicValueChange() // 启用通知
→ wx.onBLECharacteristicValueChange() // 监听数据
```
**使用示例:**
```javascript
const { bleService } = require('../../services/ble.js');
// 初始化并连接
await bleService.initBluetooth();
bleService.onData((data) => {
console.log('收到数据:', data.hex); // hex 字符串
});
const devices = await bleService.scanDevices([], 5000);
await bleService.connectDevice(devices[0].deviceId);
```
### 2. MQTT 连接(services/mqtt-client.js)
**库文件**:使用预置的 `libs/mqtt.min.js`(非 npm 安装)
**连接配置:**
```javascript
{
clientId: 'S&{设备编号}&{产品ID}&{用户ID}', // 简单认证
username: '{设备编号}',
password: '{产品密钥}',
clean: true,
keepalive: 30
}
```
**协议**:`wxs://domain.com:8084/mqtt`(微信小程序专用)
**使用示例:**
```javascript
const { mqttClient } = require('../../services/mqtt-client.js');
const auth = require('../../utils/auth.js');
// 获取凭证
const credentials = auth.getMqttCredentialsFromDevice(device);
// 连接
await mqttClient.connect({
clientId: credentials.clientId,
username: credentials.username,
password: credentials.password,
});
// 发布数据
const topic = auth.buildPropertyPostTopic(device.productId, device.serialNumber);
await mqttClient.publish(topic, hexData, { qos: 1 });
// 订阅指令
const cmdTopic = auth.buildFunctionGetTopic(device.productId, device.serialNumber);
await mqttClient.subscribe(cmdTopic, (topic, message) => {
console.log('收到指令:', message);
});
```
### 3. 数据透传格式
| 场景 | 上报格式 | Topic |
|:-----|:--------|:------|
| Modbus-RTU | Hex 字符串(原始帧) | `/{productId}/{sn}/property/post` |
| 其他协议 | Hex 字符串(原始数据) | 同上(平台脚本解析) |
**示例:**
```javascript
// Modbus 帧上报
mqttClient.publish('/1/BLE001/property/post', '010304009A005B8B7C');
// 私有协议数据上报
mqttClient.publish('/1/BLE002/property/post', '009A005B');
```
## yack 平台配置
### 产品配置
1. **创建产品**
- 产品名称:蓝牙传感器
- 协议:`MODBUS-RTU` 或 `JSON`(取决于设备协议)
2. **定义物模型**
- 属性:temperature、humidity 等
- 标识符:与 Modbus 配置/脚本解析对应
3. **Modbus 配置**(`iot_modbus_config`,仅 Modbus 设备)
- 从站地址、功能码、寄存器地址、数据类型
4. **规则脚本**(`iot_script`,仅非 Modbus 设备)
- 脚本用途:数据流处理(1)
- 触发事件:设备上报(1)
- 脚本内容:解析 hex 数据,输出物模型 JSON
### 设备配置
- 设备编号:小程序 clientId 中的 `{设备编号}`
- 产品密钥:作为 MQTT password
## 开发规范
### 代码规范
1. **命名规范**
- 文件名:小写 + 连字符(`device-card.js`)
- 变量名:小驼峰(`deviceId`)
- 常量:大写 + 下划线(`SERVICE_UUID`)
- 私有方法:下划线前缀(`_handleData()`)
2. **注释规范**
```javascript
/**
* 扫描蓝牙设备
* @param {Array<string>} serviceUUIDs - 服务 UUID 列表
* @returns {Promise<Array>} 设备列表
*/
async scanDevices(serviceUUIDs) { ... }
```
3. **错误处理**
- 所有异步操作使用 try-catch
- 用户友好错误提示(`wx.showToast`)
### 包体积控制
**目标**:主包 < 1.5MB
**策略**:
1. 不使用 UI 框架(原生组件)
2. mqtt.js 已压缩并预置在 libs 目录
3. 图片使用 CDN 或压缩
4. 分包加载(如有需要)
## 调试与测试
### 微信开发者工具
1. **真机调试**:蓝牙功能必须在真机上测试
2. **MQTT 调试**:使用 `wxs://` 协议,确保域名配置正确
### 平台验证
1. yack 控制台查看设备数据
2. 检查 `iot_device_log` 表
3. 查看规则脚本日志
## 部署流程
1. **微信公众平台配置**
- 服务器域名:添加 `socket 合法域名`(wxs://...)
- 业务域名:如需 H5 页面
2. **项目配置**
- 修改 `project.config.json` 中的 `appid`
- 修改 `env.config.js` 中的 API 和 MQTT 地址
- 将 `ENV` 改为 `'production'`
3. **代码上传**
- 微信开发者工具 → 上传代码
- 设置版本号、备注
4. **提交审核**
- 填写功能说明
- 蓝牙权限说明
## 扩展路径
1. **新增 Modbus 设备**
- 平台配置 Modbus 参数
- 小程序扫描新设备(可能需更新 Service UUID)
2. **新增私有协议设备**
- 平台编写规则脚本
- 小程序适配 BLE 特征值
3. **设备控制(下发指令)**
- 订阅 `/function/get` 或 `/property/get`
- 通过 `bleService.writeCommand()` 发送
## 相关文档
- [微信小程序蓝牙 API](https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.openBluetoothAdapter.html)
- [mqtt.js 文档](https://github.com/mqttjs/MQTT.js)
- [yack 协议解析](CLAUDE.md#架构说明)
- [Modbus 配置表](sql/yack-init.sql 中 `iot_modbus_config`)