MQTT 练习:接收 LED 命令
✅ 订阅 color_topic(uuid) 主题
✅ 在单独的终端里运行 host_client。host_client 大约每秒会发布一个开发板 LED 的颜色 color。
✅ 通过记录从这个主题收到的信息,来验证订阅是否有效。
✅ 对 LED 命令作出响应:用 led.set_pixel(/* 收到的颜色 */) 函数把新收到的颜色设置到板子上。
intro/mqtt/exercise/solution/solution_publ_rcv.rs 包含解答。你可以用下面的命令运行它:
cargo run --example solution_publ_rcv
编码和解码消息 payload
开发板 LED 命令包含三个字节,分别表示红、绿、蓝。
enum ColorData包含一个主题color_topic(uuid)和BoardLed- 可以使用
try_from()来转换EspMqttMessage的data()字段。首先需要用let message_data: &[u8] = &message.data();将消息强制转换为 slice
#![allow(unused)] fn main() { // RGB LED 命令 if let Ok(ColorData::BoardLed(color)) = ColorData::try_from(message_data) { /* 在这里设置新的颜色 */ } }
发布 & 订阅
EspMqttClient 不止负责发布消息,也用于订阅主题。
#![allow(unused)] fn main() { let subscribe_topic = /* ... */; client.subscribe(subscribe_topic, QoS::AtLeastOnce) }
处理收到的消息
处理函数闭包里的 message_event 参数的类型是 EspMqttEvent,它有一个 payload() 方法,用于访问 EventPayload。
由于我们只对接收成功的消息感兴趣:
#![allow(unused)] fn main() { let mut client = EspMqttClient::new_cb( &broker_url, &mqtt_config, move |message_event| match message_event.payload() { Received { data, details, .. } => process_message(data, details, &mut led), Error(e) => warn!("Received error from MQTT: {:?}", e), _ => info!("Received from MQTT: {:?}", message_event.payload()), }, )?; }
在处理函数中,我们将会处理 Complete 消息。
💡 使用 Rust Analyzer 来生成缺失的 match 分支,或者匹配所有其他类型,输出一个 info!()。
#![allow(unused)] fn main() { fn process_message(data: &[u8], details: Details, led: &mut WS2812RMT) { match details { Complete => { info!("{:?}", data); let message_data: &[u8] = data; if let Ok(ColorData::BoardLed(color)) = ColorData::try_from(message_data) { info!("{}", color); if let Err(e) = led.set_pixel(color) { error!("Could not set board LED: {:?}", e) }; } } _ => {} } } }
💡 用 logger 来查看接收到的东西,例如:info!("{}", color); 或 dbg!(color)。
额外的任务
实现具有分层主题的 MQTT
✅ 如果你已经完成了所有其他工作,可以考虑实现这个任务。我们不提供完整的解答,因为这是用于测试你自己能走多远。
检查 common/lib/mqtt-messages:
✅ 使用分层主题的 MQTT 实现相同的功能。订阅所有的“命令”消息,在 cmd_topic_fragment(uuid) 后面加一个 # 通配符。
✅ 用 enum Command 代替 enum ColorData。enum Command 表示所有可能的命令(这里仅有 BoardLed)。
✅ RawCommandData 存储了消息主题的最后一部分(例如 a-uuid/command/board_led 中的 board_led)。可以用 try_from 将其转换为 Command。
#![allow(unused)] fn main() { // RGB LED 命令 let raw = RawCommandData { path: command, data: message.data(), }; }
检查 host-client:
✅ 你需要将 color 替换成 command。例如:
#![allow(unused)] fn main() { let command = Command::BoardLed(color) }
其他任务
✅ 利用 serde_json 将消息数据编码/解码为 JSON。
✅ 从主机客户端上发送一些带有大量 payload 的消息,并在微控制器上处理它们。大体积的消息将会分部分传递,而不是使用 Details::Complete:
#![allow(unused)] fn main() { InitialChunk(chunk_info) => { /* 第一块 */}, SubsequentChunk(chunk_data) => { /* 所有后续块 */ } }
💡 不需要根据消息 ID 来区分收到的块,因为在任意时刻,最多只有一条消息正在传输。
Troubleshooting
- 构建示例客户端时出现 error: expected expression, found .:将你的 stable Rust 更新到 1.58 或更新的版本
- 没有显示 MQTT 消息?确保所有客户端(板子和电脑)使用的是相同的 UUID(你可以在日志输出中看见它)