chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

RustでMQTT Publish

背景:ESP32C3にCO2センサを接続して、IoT PFにMQTTでPublishするシステムを試作する
課題:RustでMQTT Publishできるコードを作成する
取り組み:全部入りサンプルとか、事例集があるので、それらを参考にする
結果:全部入りサンプルを引用しただけだが、Publishまではできた
詳細:
MQTTサンプルがあるのでそれを動かしてみる
How to use MQTT in Rust | EMQ

cargo generate https://github.com/esp-rs/esp-idf-template cargo

全部入りのデモソースから、mqttの部分を切り取って、先のwifiサンプルに貼り付けた
細かい所はそのまま引用した状態。mqtt_clientの関数は不要なsubscribeも入っているのでもっと短くなるはず

#![allow(unused_imports)]
#![allow(clippy::single_component_path_imports)]


use core::ffi;

use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::env;
use std::time::*;
use std::{cell::RefCell, sync::atomic::*, sync::Arc, thread, time::*};

use anyhow::bail;
use log::*;

use embedded_svc::eth;
#[allow(deprecated)]
use embedded_svc::httpd::*;     // <<<<?????
use embedded_svc::io;
use embedded_svc::ipv4;
use embedded_svc::mqtt::client::{Client, Connection, MessageImpl, Publish, QoS};
use embedded_svc::ping::Ping;
use embedded_svc::wifi::*;

use esp_idf_svc::eventloop::*;
use esp_idf_svc::systime::EspSystemTime;
use esp_idf_svc::timer::*;

use esp_idf_svc::eventloop::*;
use esp_idf_svc::netif::*;
use esp_idf_svc::ping;
use embedded_svc::utils::mqtt::client::ConnState;
use esp_idf_svc::wifi::*;
use esp_idf_svc::mqtt::client::*;
use esp_idf_hal::peripheral;
use esp_idf_hal::prelude::*;
use esp_idf_hal::delay::FreeRtos;

use esp_idf_sys;
use esp_idf_sys::{esp, EspError};

const SSID: &str = env!("RUST_ESP32_STD_DEMO_WIFI_SSID");
const PASS: &str = env!("RUST_ESP32_STD_DEMO_WIFI_PASS");


fn main() -> Result<()> {

    esp_idf_sys::link_patches();

    #[allow(unused)]
    let peripherals = Peripherals::take().unwrap();
    let sysloop = EspSystemEventLoop::take()?;

    println!("**************** wifi start *****************");

    #[allow(clippy::redundant_clone)]
    #[allow(unused_mut)]
    let mut _wifi = wifi(peripherals.modem, sysloop.clone())?;

    println!("**************** mqtt test *****************");
    let mut mqtt_client = test_mqtt_client()?;

    for n in 1..5{
        FreeRtos::delay_ms(5000);
        println!("**************** mqtt publish({}) *****************", n);
        let msg = format!("Hello from rust-esp32-std-demo!({})",n);
        let payload = &msg.as_bytes();
        mqtt_client.publish(
            "rust-esp32-std-demo", QoS::AtMostOnce, false, payload)?;
    }
    println!("**************** fin *****************");

    Ok(())
}


fn wifi(
    modem: impl peripheral::Peripheral<P = esp_idf_hal::modem::Modem> + 'static,
    sysloop: EspSystemEventLoop,) -> Result<Box<EspWifi<'static>>> {

    use std::net::Ipv4Addr;
    use esp_idf_svc::handle::RawHandle;
    let mut wifi = Box::new(EspWifi::new(modem, sysloop.clone(), None)?);
    info!("Wifi created, about to scan");
    let ap_infos = wifi.scan()?;
    let ours = ap_infos.into_iter().find(|a| a.ssid == SSID);
    let channel = if let Some(ours) = ours {
        info!(
            "Found configured access point {} on channel {}",
            SSID, ours.channel
        );
        Some(ours.channel)
    } else {
        info!(
            "Configured access point {} not found during scanning, will go with unknown channel",
            SSID
        );
        None
    };

    wifi.set_configuration(&Configuration::Mixed(
        ClientConfiguration {
            ssid: SSID.into(),
            password: PASS.into(),
            channel,
            ..Default::default()
        },
        AccessPointConfiguration {
            ssid: "aptest".into(),
            channel: channel.unwrap_or(1),
            ..Default::default()
        },
    ))?;

    wifi.start()?;
    info!("Starting wifi...");
    if !WifiWait::new(&sysloop)?
        .wait_with_timeout(Duration::from_secs(20), || wifi.is_started().unwrap())
    {
        bail!("Wifi did not start");
    }
    info!("Connecting wifi...");
    wifi.connect()?;
    if !EspNetifWait::new::<EspNetif>(wifi.sta_netif(), &sysloop)?.wait_with_timeout(
        Duration::from_secs(20),
        || {
            wifi.is_connected().unwrap()
                && wifi.sta_netif().get_ip_info().unwrap().ip != Ipv4Addr::new(0, 0, 0, 0)
        },
    ) {
        bail!("Wifi did not connect or did not receive a DHCP lease");
    }
    let ip_info = wifi.sta_netif().get_ip_info()?;
    info!("Wifi DHCP info: {:?}", ip_info);
    Ok(wifi)
}


fn test_mqtt_client() -> Result<EspMqttClient<ConnState<MessageImpl, EspError>>> {

    info!("About to start MQTT client");
    println!("About to start MQTT client");

    let conf = MqttClientConfiguration {
        client_id: Some("rust-esp32-std-demo"),
        crt_bundle_attach: Some(esp_idf_sys::esp_crt_bundle_attach),
        ..Default::default()
    };

    let (mut client, mut connection) = EspMqttClient::new_with_conn("mqtts://broker.emqx.io:8883", &conf)?;
    info!("MQTT client started");
    println!("MQTT client started");
    println!("broker: broker.emqx.io");
    thread::spawn(move || {

        info!("MQTT Listening for messages");
        while let Some(msg) = connection.next() {
            match msg {
                Err(e) => info!("MQTT Message ERROR: {}", e),
                Ok(msg) => info!("MQTT Message: {:?}", msg),
            }
        }

        info!("MQTT connection loop exit");
        println!("MQTT connection loop exit");
    });

    client.subscribe("rust-esp32-std-demo", QoS::AtMostOnce)?;
    info!("Subscribed to all topics (rust-esp32-std-demo)");
    client.publish(
        "rust-esp32-std-demo",
        QoS::AtMostOnce,
        false,
        "Hello from rust-esp32-std-demo!".as_bytes(),
    )?;

    info!("Published a hello message to topic \"rust-esp32-std-demo\"");
    println!("Published a hello message to topic \"rust-esp32-std-demo\"");

    Ok(client)
}

PC側でMQTT Clientを走らせてsubscribeしてみる
不要なメッセージを一回受信してしまうのは、retainが働いているからだろうか・・・

$ ./mqtt_sub.py
connect: broker.emqx.io
Connected with result code 0
--------------------------------
topic:rust-esp32-std-demo
payload:b'Hello from rust-esp32-std-demo!'
--------------------------------
topic:rust-esp32-std-demo
payload:b'Hello from rust-esp32-std-demo!'
--------------------------------
topic:rust-esp32-std-demo
payload:b'Hello from rust-esp32-std-demo!(1)'
--------------------------------
topic:rust-esp32-std-demo
payload:b'Hello from rust-esp32-std-demo!(2)'
--------------------------------
topic:rust-esp32-std-demo
payload:b'Hello from rust-esp32-std-demo!(3)'
--------------------------------
topic:rust-esp32-std-demo
payload:b'Hello from rust-esp32-std-demo!(4)'


■参考URL
embedded_svc::mqtt - Rust
GitHub - obabec/rust-mqtt: Rust native mqtt client for both std and no_std environmnents.
Let's take a look at MQTT and how you can use MQTT with Rust using the rumqttc crate in your next IoT project. · GitHub
client.rs - source
espressif-trainings/solution_publ.rs at main · esp-rs/espressif-trainings · GitHub
espressif-trainings/03_5_2_mqtt.md at main · esp-rs/espressif-trainings · GitHub
esp-idf-svc/client.rs at master · esp-rs/esp-idf-svc · GitHub