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(你可以在日志输出中看见它)