# 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} serviceUUIDs - 服务 UUID 列表 * @returns {Promise} 设备列表 */ 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`)