Mar 22, 2026
3 min read
ESP32,
Rust,
C,
Embedded Systems,

Rust 嵌入式开发:使用 Rust 实现通过小爱同学和 esp32 控制家庭氛围灯

家里总会有一些小米米家生态甚至不支持智能家居的电器或者设备。以前接触嵌入式的时候,我确实没有什么好的办法让这些设备智能起来。

直到最近,我确实需要远程控制一些设备。于是认真想了一下方案。

方案

有哪些方案可以让你的小爱同学可以控制不支持的智能家居设备呢?

  1. 世面上好像有专门的支持米家的模块。
  2. 通过米家的支持红外的设备。
  3. 米家的智能插座。
  4. 米家的第三方平台设备。

有没有可自定义编程的方案呢?

有的,兄弟,有的。 答案就是第三平台设备里的 巴法

更具体地说,是因为巴法云是一个 MQTT 物联网平台,而巴法云接入了米家。因此控制路径变成了:米家(小爱同学) -> MQTT -> 巴法云 -> 终端设备。

这个终端设备在我这里就是一个 esp32s3 模块。

硬件

我这里通过 esp32s3 接入一个中继模块作为开关,中继模块再连接 led 氛围灯。esp32s3 模块通过 wifi 连接网络,并通过 MQTT 订阅巴法云的 topic 来接收指令。

软件

在 esp 平台上, wifi 连接是一个比较消耗资源和内存的操作。因此,也不能直接使用一个简单的循环来实现了。所以这里 esp-wifi 默认强制依赖了esp-rtos

mqtt client 选型

crates.io 里有非常非常多的 mqtt client 选项。我尝试过的有mqttrustrust-mqttminimqmqtt-async-embeddedembedded-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设备

  1. 注册巴法云: https://cloud.bemfa.com。
  2. 打开米家app,进入 我的->连接三方设备->巴法->绑定帐户
  3. 回到巴法后台,添加设备。巴法只支持以下类型的设备:插座、灯泡、风扇、传感器、空调、开关、窗帘、温控器、热水器、电视、净化器。这些设备都有编号的,添加设备时,需要填写主题编号,当主题名字后三位就是设备编号。
  • 当主题名字后三位是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 灯来模拟控制设备模块。

见证奇迹的时刻

“小爱同学,开灯!”

ws

完整代码见https://github.com/k9ame/esp-rs-examples