家里总会有一些小米米家生态甚至不支持智能家居的电器或者设备。以前接触嵌入式的时候,我确实没有什么好的办法让这些设备智能起来。
直到最近,我确实需要远程控制一些设备。于是认真想了一下方案。
方案
有哪些方案可以让你的小爱同学可以控制不支持的智能家居设备呢?
- 世面上好像有专门的支持米家的模块。
- 通过米家的支持红外的设备。
- 米家的智能插座。
- 米家的第三方平台设备。
有没有可自定义编程的方案呢?
有的,兄弟,有的。 答案就是第三平台设备里的 巴法。
更具体地说,是因为巴法云是一个 MQTT 物联网平台,而巴法云接入了米家。因此控制路径变成了:米家(小爱同学) -> MQTT -> 巴法云 -> 终端设备。
这个终端设备在我这里就是一个 esp32s3 模块。
硬件
我这里通过 esp32s3 接入一个中继模块作为开关,中继模块再连接 led 氛围灯。esp32s3 模块通过 wifi 连接网络,并通过 MQTT 订阅巴法云的 topic 来接收指令。
软件
在 esp 平台上, wifi 连接是一个比较消耗资源和内存的操作。因此,也不能直接使用一个简单的循环来实现了。所以这里 esp-wifi 默认强制依赖了esp-rtos。
mqtt client 选型
crates.io 里有非常非常多的 mqtt client 选项。我尝试过的有mqttrust、rust-mqtt、minimq、mqtt-async-embedded、embedded-mqtt。
这些客户端并不是所有的都兼容。从巴法云的文档中,我们看到的条件如下:
# 协议简介
巴法云支持标准MQTT 3.1.1协议,提供以下功能:
- QoS支持:支持QoS 0和QoS 1消息质量等级
- 保留消息:支持Retain保留消息功能
- 标准兼容:完全兼容MQTT 3.1.1协议标准
它没说支持 MQTT 3.1,也没说支持 MQTT 5。事实上我试过了,都不支持,只支持 MQTT 3.1.1。因此我们选型 mqtt 库的时候,需要看是否说明兼容 MQTT 3.1.1。
而对于 esp 平台,我们需要这个客户端必须是支持异步、并且是 no-std 的。
我这里选择的是
mqttrust = { git = "https://github.com/BlackbirdHQ/mqttrust", branch = "master", default-features = false, features = ["mqttv3"] }
其他依赖
既然是上面的mqtt 的选择是异步的,那肯定得选择一个适合嵌入式的异步运行时。我的选择是 embassy。
embassy-executor = "0.9"
embassy-time = "0.5"
embassy-net = { version = "0.8", features = [
"dhcpv4",
"dns",
"medium-ethernet",
"proto-ipv4",
"tcp",
"udp",
] }
embassy-sync = "0.7"
embassy-futures = "0.1"
static_cell = "2"
heapless = "0.9"
还有 esp 官方的 wifi 库和 rtos 库。
esp-radio = { version = "0.17", features = ["esp32s3", "wifi", "log-04", "esp-alloc", "unstable"] }
esp-rtos = { version = "0.2", features = ["esp32s3", "esp-radio", "esp-alloc", "embassy"] }
添加mqtt设备
- 注册巴法云: https://cloud.bemfa.com。
- 打开米家app,进入
我的->连接三方设备->巴法->绑定帐户。 - 回到巴法后台,添加设备。巴法只支持以下类型的设备:插座、灯泡、风扇、传感器、空调、开关、窗帘、温控器、热水器、电视、净化器。这些设备都有编号的,添加设备时,需要填写主题编号,当主题名字后三位就是设备编号。
- 当主题名字后三位是001时为插座设备。
- 当主题名字后三位是002时为灯泡设备。
- 当主题名字后三位是003时为风扇设备。
- 当主题名字后三位是004时为传感器设备。
- 当主题名字后三位是005时为空调设备。
- 当主题名字后三位是006时为开关设备。
- 当主题名字后三位是009时为窗帘设备。
代码实现
代码实现最大的坑其实是 mqtt 客户端。像上面所述的,mqtt 客户端有很多选择,但是并不是所有的都兼容。因此我们需要选择支持 MQTT 3.1.1 的客户端。
另外一个就是,一般 mqtt 客户端不支持域名格式地址,而巴法云的 mqtt 地址是域名格式。因此,做域名解析,然后获取 IP 地址,再连接 MQTT 服务器。
println!("Resolving {} via DNS...", config::MQTT_SERVER);
let broker_ip = match stack.dns_query(config::MQTT_SERVER, embassy_net::dns::DnsQueryType::A).await {
Ok(addresses) if !addresses.is_empty() => {
println!("DNS resolved to: {:?}", addresses[0]);
// DNS query for A record returns IPv4 only
match addresses[0] {
embassy_net::IpAddress::Ipv4(ip) => ip,
}
}
_ => {
println!("DNS failed, using fallback IP");
Ipv4Addr::new(119, 91, 109, 180)
}
};
这时主循环我们留给灯控制逻辑。wifi的连接和mqtt的订阅都交给其他线程(协程?对embassy-executor不了解)处理。
spawner.spawn(connection(controller)).ok();
spawner.spawn(net_task(runner)).ok();
// Spawn MQTT task
spawner.spawn(mqtt_task(mqtt_stack, broker, stack)).ok();
// Spawn subscription task
spawner.spawn(mqtt_subscription(client, config::MQTT_SUBSCRIBE_TOPIC, &LED_ON)).ok();
保活与重连
默认 MQTT 的库保活时间是 59s。 如果你不设置这个值的话,在巴法后台会看到连上后一会就掉线了。
我没有找到相关的文档说明要多久的心跳。不过 30 秒的时间就不会掉了。
我这里先拿了一个 ws2812 led 灯来模拟控制设备模块。
见证奇迹的时刻
“小爱同学,开灯!”
