3、通信协议设计:物理层选型(Wi-Fi/BLE/Zigbee)、应用层协议设计(JSON/Protobuf)、心跳与重连机制

通信协议设计,说白了就是给窗帘电机定规矩——怎么说话、说什么话、说错了怎么办。我做了这么多年嵌入式,见过太多项目在通信上栽跟头。有的选错物理层,有的协议太啰嗦,有的断线了再也连不上。今天咱们就把这三个坑一个一个填平。

3.1 物理层选型:Wi-Fi / BLE / Zigbee

先聊物理层。你想想看,窗帘电机装在窗户边上,离路由器可能隔着两堵墙,旁边还有微波炉、蓝牙音箱在干扰。选什么无线技术,直接决定了产品能不能稳定工作。

核心原则:没有最好的协议,只有最合适的场景。Wi-Fi适合直连云端,BLE适合手机近场控制,Zigbee适合组网联动。

3.1.1 Wi-Fi:适合智能家居单品

Wi-Fi的好处是直接连路由器,用户不用买网关。我做过一个项目,客户要求「手机App直接控制窗帘,不要额外设备」。那没得选,只能用Wi-Fi。

但Wi-Fi有个大问题——功耗高。窗帘电机如果是电池供电的,Wi-Fi模块待机电流动辄几十毫安,几天就没电了。所以Wi-Fi方案一般只用于有220V供电的电动窗帘。

另外,Wi-Fi的穿墙能力其实一般。2.4GHz频段穿一堵墙还行,两堵墙就开始丢包。我建议如果家里面积大,最好用Mesh Wi-Fi或者加个中继。

选型建议:如果产品是插电的、需要远程控制、用户不想买网关,选Wi-Fi。推荐乐鑫ESP32或瑞昱RTL8710,生态成熟,SDK好使。

3.1.2 BLE:适合电池供电、手机直连

BLE(低功耗蓝牙)是另一个热门选择。它的功耗极低,一颗纽扣电池能撑一年。而且现在手机都自带蓝牙,用户不需要额外硬件。

我在一个智能窗帘项目中用过Nordic的nRF52832,待机电流只有几微安。用户靠近窗帘时,手机App自动弹出控制界面,体验很流畅。

但BLE的缺点是距离短(一般10米以内),而且不能直接连互联网。如果你想远程控制,还得加一个蓝牙网关,或者让手机做中转。嗯,这里要注意:BLE的广播包容易被干扰,尤其是在商场、写字楼这种蓝牙设备密集的地方。

避坑指南:我曾经在一个项目中用BLE做窗帘控制,结果用户反映「有时候拉一半就停了」。查了半天,发现是手机蓝牙和窗帘电机之间的连接被其他蓝牙设备干扰了。后来我们加了重传机制和信道跳频,问题才解决。

3.1.3 Zigbee:适合全屋智能组网

Zigbee是专门为物联网设计的协议,支持自组网、多跳路由。一个Zigbee网络可以挂几百个设备,而且每个设备都能当中继,信号覆盖范围很广。

如果你在做全屋智能方案,比如窗帘、灯光、传感器联动,Zigbee是首选。它的功耗比Wi-Fi低,但比BLE高一点。不过Zigbee需要网关,用户得多花一笔钱。

我个人的习惯是:如果客户要求「稳定、低功耗、多设备联动」,我会推荐Zigbee。比如涂鸦的Zigbee模组,价格已经降到十几块钱,性价比很高。

参数 Wi-Fi BLE Zigbee
功耗 高(~100mA) 极低(~10μA) 低(~30μA)
传输距离 30-100米 10米 10-100米(可中继)
是否需要网关 不需要 不需要(手机直连) 需要
组网能力 弱(星型) 弱(点对点) 强(Mesh)
典型场景 远程控制 近场控制 全屋联动

3.2 应用层协议设计:JSON vs Protobuf

物理层选好了,接下来要定「说话的内容和格式」。说白了,就是电机和服务器之间传什么数据、怎么编码。

3.2.1 JSON:简单直观,适合调试

JSON是文本协议,人类可读,调试起来非常方便。我刚开始做物联网时,所有协议都用JSON。比如控制窗帘打开50%:

{
  "cmd": "set_position",
  "params": {
    "target": 50,
    "speed": 30
  },
  "seq": 1234
}

JSON的好处是灵活,字段可以随意增减。但坏处也很明显——体积大。上面这条消息有80多个字节,而实际有效数据只有几个数字。对于Wi-Fi来说还好,但如果是BLE或者NB-IoT这种窄带宽场景,JSON就太浪费了。

我的经验:在产品原型阶段,我建议先用JSON。调试方便,改字段不用重新编译。等产品稳定了,再考虑换成更高效的协议。

3.2.2 Protobuf:高效紧凑,适合量产

Protobuf是Google出的二进制序列化协议。同样的控制命令,用Protobuf编码后只有十几个字节:

message MotorCommand {
  int32 cmd = 1;        // 1: set_position
  int32 target = 2;     // 0-100
  int32 speed = 3;      // 0-100
  int32 seq = 4;        // 序列号
}

编码后的二进制数据:08 01 10 32 18 1E 20 D2 09(仅9个字节)。

Protobuf的优点是体积小、解析快。但缺点是需要定义.proto文件,两端都要生成代码,改字段要重新编译。而且调试时看不到明文,得用工具转码。

我个人习惯是:如果产品已经量产,或者设备数量超过1000台,我会用Protobuf。省流量、省带宽、省电。

注意:Protobuf的字段编号不能随意改。一旦发布,旧字段编号就废弃了,否则会导致解析错误。我曾经在升级协议时忘了这茬,结果旧设备全部离线,被运维骂了一下午。

3.3 心跳与重连机制

通信协议设计好了,但网络是不稳定的。Wi-Fi会断、蓝牙会掉、服务器会重启。如果没有心跳和重连机制,窗帘电机可能「失联」好几天。

3.3.1 心跳:确认对方还活着

心跳就是设备定期给服务器发一个「我还活着」的消息。服务器收到后回复一个ACK。如果连续几次没收到回复,就认为连接断了。

心跳间隔怎么定?太频繁浪费流量,太稀疏反应迟钝。我一般这样设置:

  • Wi-Fi设备:30秒一次心跳。Wi-Fi功耗高,但带宽大,30秒一次可以接受。
  • BLE设备:5秒一次心跳。BLE连接容易断开,需要快速检测。
  • Zigbee设备:60秒一次心跳。Zigbee网络稳定,心跳可以慢一点。

心跳消息的内容很简单:

{
  "type": "heartbeat",
  "device_id": "curtain_001",
  "timestamp": 1700000000
}

服务器收到后,回复一个空ACK即可。

优化技巧:心跳消息可以携带一些状态信息,比如电池电量、信号强度。这样既保活,又顺便上报了状态,一举两得。

3.3.2 重连:断线后自动恢复

心跳检测到断线后,设备要自动重连。重连不是简单的「再连一次」,而是一套策略:

  1. 立即重连:第一次断线后,马上尝试重连。因为可能是临时抖动。
  2. 指数退避:如果连续重连失败,间隔时间逐渐增加。比如第一次等1秒,第二次等2秒,第三次等4秒...最多等60秒。
  3. 最大重试次数:如果重试了10次还是连不上,就进入深度休眠,等用户手动唤醒。

我曾经在一个项目中,设备断线后疯狂重连,把服务器打挂了。后来加了指数退避,服务器压力瞬间降下来。

// 伪代码:重连策略
int retry_count = 0;
int max_retry = 10;
int base_delay = 1000; // 1秒

while (retry_count < max_retry) {
    if (connect() == SUCCESS) {
        break;
    }
    retry_count++;
    int delay = base_delay * (1 << retry_count); // 指数退避
    if (delay > 60000) delay = 60000; // 最多60秒
    sleep(delay);
}
// 如果还是连不上,进入休眠
if (retry_count >= max_retry) {
    enter_deep_sleep();
}

避坑指南:重连时一定要清理旧的连接资源。我见过一个案例,设备每次重连都新建一个socket,旧的socket没关闭,最后内存泄漏导致死机。重连前先close(),再connect(),这个顺序不能乱。

3.3.3 状态同步:重连后恢复现场

重连成功后,设备要主动和服务器同步状态。比如窗帘当前的位置、电机的工作模式。服务器也要把离线期间下发的命令补发给设备。

我建议在重连后做一次「全量同步」:设备上报所有状态,服务器下发所有待执行的命令。这样双方状态就一致了。

嗯,这里有个细节:如果设备在离线期间被人手动拉过窗帘,位置变了,但服务器不知道。重连后设备上报的位置才是真实的。所以一定要以设备上报为准,服务器不能覆盖。


好了,通信协议设计这块就聊到这儿。物理层选型决定了产品的「骨架」,应用层协议决定了「血肉」,心跳重连则是「免疫系统」。三者缺一不可。下一章咱们聊聊电机控制的底层驱动,那才是真正让窗帘动起来的关键。