MQTT 练习:接收 LED 命令

✅ 订阅 color_topic(uuid) 主题

✅ 在单独的终端里运行 host_clienthost_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() 来转换 EspMqttMessagedata() 字段。首先需要用 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 ColorDataenum 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(你可以在日志输出中看见它)