蓝牙网关与桥接:手机作为网关、MQTT桥接、数据转发、云端同步
说实话,做 BLE 开发做到一定阶段,你一定会遇到这个问题:手机怎么把蓝牙设备的数据送到云端?
我最早做智能家居项目时,客户要求手环数据必须实时同步到服务器。当时我想,这不简单吗?手机连上手环,拿到数据,再通过 Wi-Fi 发出去就行了。结果一上手才发现,这里面坑不少——手机屏幕一关,蓝牙断了;App 退到后台,数据丢了;网络不稳定,消息重复了……
嗯,今天我们就来聊聊这个场景:手机作为蓝牙网关,把 BLE 设备的数据桥接到云端。
手机当网关?听起来简单,做起来要小心
说白了,手机就是一个“中间人”。它一边通过 BLE 跟传感器、手环、温控器通信,另一边通过 MQTT 或者 HTTP 把数据推到服务器。
我个人的习惯是,不要指望手机 7×24 小时稳定工作。手机不是专用网关硬件,它会被用户杀掉进程、会没电、会切换网络。所以架构上一定要考虑断连重连、数据缓存、消息去重这些事。
整体架构长什么样?
我画了一张图,帮你理清数据流向。你看一眼就明白了:
你看,数据从 BLE 设备出发,经过手机这个“二传手”,通过 MQTT 到达 Broker,最后落地到云端。每一步都有讲究。
手机端:BLE 数据采集与缓存
先说说手机这边怎么做。我一般会开一个 Foreground Service,保证 App 被推到后台时 BLE 扫描和连接不会断。你想想看,用户把 App 切出去回个微信,蓝牙就断了,这体验多糟糕。
数据拿到之后,不要立刻发。为什么?因为网络可能不稳定。我习惯先写进本地 SQLite,打上一个时间戳和发送状态标记。然后由一个后台 Worker 定期检查,把未发送的数据批量推出去。
MQTT 桥接:选对 QoS 级别
MQTT 这块,我踩过最大的坑就是 QoS 选择。
一开始我图省事,全用 QoS 0。结果网络抖动时丢了一堆数据,用户投诉说体脂秤数据少了几天。后来改成 QoS 1,配合本地去重,才算稳下来。
我的建议是:
- 普通传感器数据(温湿度、电量):QoS 0 就行,丢了也不心疼,反正下次还会上报。
- 关键事件(门锁开关、报警):必须 QoS 1,配合本地 ACK 确认。
- 慎用 QoS 2:手机端资源有限,QoS 2 的四次握手太耗电,而且容易造成消息堆积。
代码示例,发布消息的核心逻辑:
// MQTT 发布,带本地缓存兜底
fun publishWithCache(topic: String, payload: ByteArray, qos: Int = 1) {
// 1. 先存本地
val record = CacheRecord(
deviceMac = extractMac(payload),
payload = payload,
timestamp = System.currentTimeMillis(),
sentFlag = false
)
database.cacheDao().insert(record)
// 2. 尝试发布
mqttClient.publish(topic, payload, qos, false).whenComplete { _, throwable ->
if (throwable == null) {
// 发布成功,标记已发送
database.cacheDao().markSent(record.id)
} else {
// 发布失败,等下次 Worker 重试
Log.w("Gateway", "MQTT publish failed: ${throwable.message}")
}
}
}
数据转发:格式统一与字段映射
不同 BLE 设备的数据格式千奇百怪。有的用 JSON,有的用二进制,有的甚至直接裸传字符串。我个人的做法是:在手机端做一次统一格式化,转成标准 JSON 再发出去。
举个例子,温湿度传感器原始数据可能是这样的字节数组:
// 原始 BLE 数据:0x02 0x1A 0x25 0x0E
// 解析后:
{
"type": "temperature_humidity",
"device_id": "BLE:AC:23:3F:12:88",
"timestamp": 1698123456789,
"data": {
"temperature": 26.5,
"humidity": 62.3
}
}
云端那边只要认这一种格式就行,不用为每种设备写一套解析逻辑。说白了,手机就是那个“翻译官”。
云端同步:幂等性与时序保证
数据到了云端,最怕什么?重复和乱序。
手机端可能因为网络问题重发消息,云端就会收到两条一模一样的数据。我的解决方案是:每条消息带一个全局唯一的 messageId,云端根据这个 ID 做去重。
另外,BLE 设备可能离线一段时间,然后一次性上报一堆历史数据。这时候时间戳就很重要了。云端不能简单按“到达时间”存,必须按“设备上报时间”排序。
| 字段 | 说明 | 是否必填 |
|---|---|---|
| messageId | 全局唯一,UUID 或 自增ID | 是 |
| deviceId | 设备唯一标识,MAC 或自定义 | 是 |
| deviceTimestamp | 设备本地时间戳(毫秒) | 是 |
| gatewayTimestamp | 手机收到数据的时间戳 | 推荐 |
| payload | 业务数据 JSON | 是 |
避坑指南:我踩过的几个雷
- 不要用 BLE 的 MTU 大小来限制 MQTT 消息。MQTT 是走 Wi-Fi/蜂窝的,跟 BLE MTU 没关系。我见过有人把 MQTT 消息切成 20 字节发,纯属多此一举。
- 手机息屏后 BLE 扫描间隔会变长。Android 系统为了省电,会在息屏后自动拉长扫描周期。如果你需要实时性,记得申请
PARTIAL_WAKE_LOCK。 - MQTT 连接断开后要指数退避重连。不要每 1 秒重试一次,否则服务器会把你当攻击者。我一般用 1s、2s、4s、8s……最大 60 秒。
好了,这一章的内容就到这里。如果你正在做类似的网关项目,希望这些经验能帮你少走弯路。有什么问题,欢迎交流。
公众号:蓝海资料掘金营,微信deep3321