364 lines
12 KiB
Markdown
364 lines
12 KiB
Markdown
# 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`) |