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

12 KiB
Raw Blame History

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 → 有 → 直接进入设备列表(无需登录)

退出登录

如需切换账号,在设置页或设备列表页提供"退出登录"按钮,调用:

const storage = require('../../utils/storage.js');

// 退出登录
function logout() {
  storage.clearAll();  // 清除本地所有数据
  wx.reLaunch({ url: '/pages/login/login' });  // 跳转登录页
}

关键技术实现

1. 蓝牙 BLE 采集(services/ble.js)

// 核心流程
wx.openBluetoothAdapter()           // 初始化
  → wx.startBluetoothDevicesDiscovery()  // 扫描
  → wx.createBLEConnection()              // 连接
  → wx.getBLEDeviceServices()             // 获取服务
  → wx.getBLEDeviceCharacteristics()      // 获取特征值
  → wx.notifyBLECharacteristicValueChange() // 启用通知
  → wx.onBLECharacteristicValueChange()   // 监听数据

使用示例:

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 安装)

连接配置:

{
  clientId: 'S&{设备编号}&{产品ID}&{用户ID}',  // 简单认证
  username: '{设备编号}',
  password: '{产品密钥}',
  clean: true,
  keepalive: 30
}

协议:wxs://domain.com:8084/mqtt(微信小程序专用)

使用示例:

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 字符串(原始数据) 同上(平台脚本解析)

示例:

// 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. 注释规范

    /**
     * 扫描蓝牙设备
     * @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() 发送

相关文档