技术实现
小程序 IoT 功能的关键技术实现方案,涵盖 WebSocket 客户端、BLE 配网、Pinia 状态管理和物模型组件映射。作为 02.界面与交互设计 的技术落地文档。
1. WebSocket 客户端
1.1 连接管理
- 使用 UniApp
uni.connectSocketAPI - 连接时机: App
onLaunch或首页onShow(Token 已存在时自动连接) - 连接参数:
wss://host/ws?token=xxx - 断线重连: 在
onSocketClose回调中实现指数退避重连(1s → 2s → 4s → 最大 30s) - 前后台切换: App 切后台时断开连接节省资源,切前台时自动重连
1.2 消息处理
onSocketMessage 收到推送后解析 type 字段:
| type | 处理 |
|---|---|
device.status | 更新 Pinia deviceStore 中对应设备的 onlineMap |
device.shadow | 更新 Pinia deviceStore 中对应设备的 shadowMap |
错误处理:收到格式不符的消息时记录日志并忽略,不做 Toast 提示(避免骚扰用户)。
1.3 订阅管理
| 时机 | 操作 |
|---|---|
设备详情页 onShow | 发送 {"type":"subscribe","deviceIds":["d1..."]} |
设备详情页 onHide | 发送 {"type":"unsubscribe","deviceIds":["d1..."]} |
| 首页 | 只订阅设备在线状态(列表不需实时属性值,点击进入详情页才订阅影子) |
2. BLE 配网
2.1 蓝牙 API 调用链
wx.openBluetoothAdapter 初始化
→ wx.startBluetoothDevicesDiscovery 扫描
→ 过滤广播名称(YunStack- 前缀)
→ 用户点击目标设备
→ wx.createBLEConnection 建立连接
→ wx.getBLEDeviceServices 获取服务
→ wx.getBLEDeviceCharacteristics 获取特征值2.2 GATT 服务定义
| 参数 | 值 |
|---|---|
| Service UUID | 0xFF00(YunStack 配网服务) |
| Characteristic UUID | 0xFF01(WiFi 凭据写入,Write + Notify) |
| 数据格式 | JSON 字符串 {"ssid":"xxx","password":"xxx"} |
| 写入方式 | wx.writeBLECharacteristicValue,分片发送(MTU 限制,每包最大 20 字节) |
2.3 状态反馈
设备通过 Notify 回复配网结果:
json
{"status":"connecting"} // 正在连接 WiFi
{"status":"connected"} // 连接成功
{"status":"failed","reason":"wrong_password"} // 连接失败小程序通过 onBLECharacteristicValueChange 接收通知。连接成功后设备关闭 BLE 广播,小程序断开 BLE 连接,等待设备通过 MQTT 上线(WebSocket 通知)。
2.4 错误处理
| 错误场景 | 处理 |
|---|---|
| 蓝牙未开启 | 引导用户打开手机蓝牙 |
| 未扫描到设备 | 提示"未发现设备,请确认设备已通电并靠近手机" |
| 连接超时(10 秒) | 提示重试 |
| WiFi 密码错误 | 提示重新输入 |
3. Pinia 状态管理
3.1 Store 设计
新增两个 IoT 相关 store:
deviceStore
| state | 类型 | 说明 |
|---|---|---|
devices | Device[] | 设备列表(首页) |
currentDevice | Device | 当前查看的设备详情 |
loading | boolean | 列表加载状态 |
onlineMap | Map<string, boolean> | deviceId → 在线状态(WebSocket 更新) |
shadowMap | Map<string, object> | deviceId → 最新属性值(WebSocket 更新) |
provisionStore
| state | 类型 | 说明 |
|---|---|---|
step | number | 当前配网步骤(1-5) |
bleDevices | BLEDevice[] | 扫描到的 BLE 设备列表 |
connectedDevice | BLEDevice | 已连接的设备 |
wifiSSID | string | 用户输入的 WiFi SSID |
error | string | 配网错误信息 |
3.2 数据流
首页 onShow → deviceStore.fetchDevices() → GET /api/v1/devices → 填充 devices[]
WebSocket 推送 → deviceStore.updateOnline(deviceId, status) → 更新 onlineMap
设备详情 → deviceStore.fetchDeviceDetail(id) → GET /api/v1/devices/:id → 填充 currentDevice
控制操作 → deviceStore.controlDevice(id, params) → POST /api/v1/devices/:id/control → 等待 set_reply → 乐观更新 shadowMap4. 物模型 → UI 组件映射
4.1 组件注册表
根据物模型中属性的 ui.component 字段渲染对应组件:
ui.component | 渲染组件 | 说明 |
|---|---|---|
switch | <switch-card> | 大圆形开关按钮,点击切换 |
multi-switch | <switch-list> | 多路开关列表 |
slider | <slider-card> | 滑块控件,显示当前值 + 单位 |
display | <display-card> | 只读数显,大字体数值 + 单位 + 名称 |
| 未指定 | <display-card> | 默认渲染为只读显示 |
4.2 组件数据绑定
| 属性字段 | 绑定 |
|---|---|
属性 id | 组件标题 |
属性 type | 值格式(bool 渲染开关,float/int 渲染数值) |
属性 access: "r" | 组件不可操作(只读) |
属性 unit | 组件单位显示 |
5. API 客户端
5.1 请求封装
- 使用 UniApp
uni.request封装,统一baseURL和请求头(Authorization: Bearer <token>) - 响应拦截:
code !== "00000"时统一 Toast 错误信息 - Token 过期(
code=A0004)→ 自动调用refresh-token→ 重试原请求 → 刷新失败则跳转登录页
5.2 请求重试
- 网络超时(
errMsg: "request:fail timeout")→ Toast 提示"网络异常,请检查网络" + 手动重试按钮 - 不自动重试(避免重复提交控制指令)
