diff --git a/Cargo.lock b/Cargo.lock index 1e10fe9..8f73de9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,6 +701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f0f58453dd2ce08d99228fc8757fad39d05dfd26643665d1093b8844f42cc" dependencies = [ "critical-section", + "log", ] [[package]] @@ -792,6 +793,7 @@ dependencies = [ "esp-println", "esp-wifi", "heapless 0.8.0", + "log", "mqttrust", "rand", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 5d370ff..7fa71b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ authors = [ "Marvin Drescher " ] [dependencies] embassy-executor = { version = "0.5.0", features = ["nightly", "integrated-timers", "arch-riscv32", "executor-thread"] } -embassy-net = { version = "0.4.0", features = ["dhcpv4", "dhcpv4-hostname", "dns", "medium-ip", "proto-ipv4", "proto-ipv6", "tcp"] } +embassy-net = { version = "0.4.0", features = ["dhcpv4", "dhcpv4-hostname", "dns", "medium-ip", "proto-ipv4", "proto-ipv6", "tcp", "udp"] } embassy-sync = "0.5.0" embassy-time = { version = "0.3.0" } embedded-io-async = "0.6.1" @@ -17,9 +17,10 @@ esp-alloc = "0.3.0" esp-backtrace = { version = "0.11.0", features = ["esp32c3", "exception-handler", "panic-handler", "println"] } esp-hal = { version = "0.16.1", features = ["embassy", "embassy-time-timg0", "esp32c3"] } esp-hal-smartled = { version = "0.9.0", features = ["esp32c3"] } -esp-println = { version = "0.9.1", features = ["esp32c3", "uart"] } +esp-println = { version = "0.9.1", features = ["esp32c3", "log", "uart"] } esp-wifi = { version = "0.4.0", features = ["embassy-net", "esp32c3", "wifi"] } heapless = { version = "0.8.0", features = ["portable-atomic", "portable-atomic-unsafe-assume-single-core"] } +log = "0.4.21" mqttrust = "0.6.0" rand = { version = "0.8.5", default-features = false, features = ["std_rng"] } rand_core = "0.6.4" @@ -28,4 +29,10 @@ smart-leds = "0.4.0" static_cell = { version = "2.0.0", features = ["nightly"] } [profile.dev.package.esp-wifi] -opt-level = 2 +opt-level = 3 +[profile.dev.package.embedded-tls] +opt-level = 3 + +[profile.release] +opt-level = "s" +lto = true diff --git a/src/main.rs b/src/main.rs index 75b102a..dee6f8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,10 +33,12 @@ use esp_hal::{adc::AdcConfig, clock::ClockControl}; use esp_hal::{embassy, prelude::*, rmt, Rmt, Rng, Rtc, IO}; use esp_hal_smartled::SmartLedsAdapter; use esp_hal_smartled::*; +use esp_println::logger::init_logger; use esp_println::println; use esp_wifi::wifi::{get_random, ClientConfiguration, Configuration}; use esp_wifi::wifi::{WifiController, WifiDevice, WifiEvent, WifiStaDevice, WifiState}; use esp_wifi::{initialize as initialize_wifi, EspWifiInitFor}; +use log::{debug, error, info, trace}; use smart_leds::{SmartLedsWrite, RGB8}; use static_cell::{make_static, StaticCell}; @@ -69,7 +71,7 @@ impl MySmartLed { fn set_color(&mut self, color: RGB8) { if let Err(err) = self.write([color].into_iter()) { - esp_println::println!("{err:?}"); + error!("{err:?}"); } } } @@ -80,7 +82,6 @@ pub static DATA: Mutex, Stri #[embassy_executor::task] async fn blink(mut led: Box) { loop { - esp_println::println!("Bing!"); let scale = 4; for r in 0..(255 / scale) { Timer::after(Duration::from_millis(1)).await; @@ -100,17 +101,25 @@ async fn blink(mut led: Box) { } } +macro_rules! nb_await { + ($ex:expr, $interval:expr) => { + loop { + match $ex { + Err(nb::Error::WouldBlock) => Timer::after($interval).await, + Err(nb::Error::Other(e)) => break Err(e), + Ok(val) => break Ok(val), + } + } + }; + ($ex:expr) => { + $crate::nb_await!($ex, Duration::from_millis(10)) + }; +} async fn async_read( mut fun: impl FnMut() -> nb::Result, interval: Duration, ) -> Result { - loop { - match fun() { - Err(nb::Error::WouldBlock) => Timer::after(interval).await, - Err(nb::Error::Other(e)) => return Err(e), - Ok(val) => return Ok(val), - } - } + nb_await!(fun(), interval) } /// https://docs.espressif.com/projects/esp-idf/en/v4.4/esp32c3/api-reference/peripherals/adc.html @@ -148,7 +157,7 @@ async fn moisture( let moisture = ((milli_volt.checked_sub(submerged_in_water).unwrap_or(0)) << 8 / (dry - submerged_in_water)) >> 8; - esp_println::println!("moisture: {moisture}%, v_s: {milli_volt}"); + info!("moisture: {moisture}%, v_s: {milli_volt}"); { DATA.lock() .await @@ -174,7 +183,7 @@ async fn battery_monitor( ); // account for 50:50 voltage divider let v_bat = v_out * 2; - println!("V_bat: {}", v_bat); + info!("V_bat: {}", v_bat); { DATA.lock() .await @@ -187,7 +196,7 @@ static EXECUTOR: StaticCell = StaticCell::new(); #[entry] fn main() -> ! { - esp_println::println!("Init!"); + init_logger(log::LevelFilter::Info); init_heap(); let peripherals = Peripherals::take(); @@ -270,7 +279,7 @@ fn main() -> ! { spawner.spawn(net_task(&stack)).unwrap(); spawner.spawn(ip_task(&stack)).unwrap(); } - spawner.spawn(blink(led)).unwrap(); + // spawner.spawn(blink(led)).unwrap(); spawner .spawn(moisture(pin, adc1, moisture_sensor_suppy_pin.into())) .unwrap(); @@ -285,8 +294,8 @@ async fn wifi_connection( ssid: &'static str, psk: &'static str, ) { - println!("start connection task"); - println!("Device capabilities: {:?}", controller.get_capabilities()); + trace!("start wifi connection task"); + debug!("Device capabilities: {:?}", controller.get_capabilities()); loop { match esp_wifi::wifi::get_wifi_state() { WifiState::StaConnected => { @@ -303,16 +312,16 @@ async fn wifi_connection( ..Default::default() }); controller.set_configuration(&client_config).unwrap(); - println!("Starting wifi"); + trace!("starting wifi"); controller.start().await.unwrap(); - println!("Wifi started!"); + trace!("Wifi started!"); } - println!("About to connect..."); + trace!("About to connect..."); match controller.connect().await { - Ok(_) => println!("Wifi connected!"), + Ok(_) => info!("Wifi connected!"), Err(e) => { - println!("Failed to connect to wifi: {e:?}"); + error!("Failed to connect to wifi: {e:?}"); Timer::after(Duration::from_millis(5000)).await } } @@ -327,10 +336,10 @@ async fn ip_task(stack: NetworkStack) { } Timer::after(Duration::from_millis(500)).await; } - println!("Waiting to get IP address..."); + debug!("Waiting to get IP address..."); loop { if let Some(config) = stack.config_v4() { - println!("Got IP: {}", config.address); + info!("Got IP: {}", config.address); break; } Timer::after(Duration::from_millis(500)).await; diff --git a/src/mqtt.rs b/src/mqtt.rs index 8b6b018..9802695 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -1,8 +1,10 @@ use embassy_net::tcp::TcpSocket; use embassy_net::{dns::Error as DnsError, tcp::ConnectError}; -use embassy_time::{Duration, Timer}; +use embassy_time::{with_timeout, Duration, TimeoutError, Timer}; use embedded_tls::{Aes128GcmSha256, NoVerify, TlsConfig, TlsConnection, TlsContext, TlsError}; +use esp_backtrace as _; use esp_println::println; +use log::{debug, error, info, trace}; use rand::rngs::StdRng; use rand::{CryptoRng, RngCore, SeedableRng}; use rust_mqtt::client::client::MqttClient; @@ -19,6 +21,7 @@ pub enum SendError { Tls(TlsError), Connect(ConnectError), MqttReason(ReasonCode), + Timeout(TimeoutError), } macro_rules! from_impl { @@ -35,6 +38,7 @@ from_impl!(DnsError, Dns); from_impl!(TlsError, Tls); from_impl!(ConnectError, Connect); from_impl!(ReasonCode, MqttReason); +from_impl!(TimeoutError, Timeout); const MQTT_SERVER_HOSTNAME: &str = "mqtt.shimun.net"; const MQTT_SERVER_PORT: u16 = 8883; @@ -44,86 +48,104 @@ pub async fn send_message( mut messages: impl Iterator, mut rng: impl CryptoRng + RngCore, ) -> core::result::Result<(), SendError> { - let dns_resp = stack - .dns_query(MQTT_SERVER_HOSTNAME, embassy_net::dns::DnsQueryType::A) - .await - .map_err(SendError::Dns)?; - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; - let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(Duration::from_secs(10))); - let socket_addr = dns_resp - .into_iter() - .map(|addr| (addr, MQTT_SERVER_PORT)) - .next() - .ok_or(SendError::NXDomain(MQTT_SERVER_HOSTNAME))?; + async fn inner( + stack: NetworkStack, + mut messages: impl Iterator, + mut rng: impl CryptoRng + RngCore, + ) -> core::result::Result<(), SendError> { + let dns_resp = stack + .dns_query(MQTT_SERVER_HOSTNAME, embassy_net::dns::DnsQueryType::A) + .await + .map_err(SendError::Dns)?; + const TCP_BUFLEN: usize = 2048 << 2; + let mut rx_buffer = [0; TCP_BUFLEN]; + let mut tx_buffer = [0; TCP_BUFLEN]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(30))); + let socket_addr = dns_resp + .into_iter() + .map(|addr| (addr, MQTT_SERVER_PORT)) + .next() + .ok_or(SendError::NXDomain(MQTT_SERVER_HOSTNAME))?; - // establish TCP connection - socket.connect(socket_addr).await?; + debug!("got address {socket_addr:?}"); - // seed mqtt rng from rng - let mut mqtt_config = ClientConfig::<5, _>::new( - rust_mqtt::client::client_config::MqttVersion::MQTTv5, - CountingRng(rng.next_u64()), - ); + // establish TCP connection + socket.connect(socket_addr).await?; + trace!("connected"); - if let (Some(user), Some(pass)) = (option_env!("MQTT_USER"), option_env!("MQTT_PASSWORD")) { - mqtt_config.add_username(user); - mqtt_config.add_username(pass); - } - - // TLS layer - const TLS_BUF_LEN: usize = 1 << 12; - let mut tls_read_record_buffer = [0; TLS_BUF_LEN]; - let mut tls_write_record_buffer = [0; TLS_BUF_LEN]; - let mut tls = { - let config = TlsConfig::new() - .with_server_name(MQTT_SERVER_HOSTNAME) - .with_max_fragment_length(embedded_tls::MaxFragmentLength::Bits12); - - let mut tls = TlsConnection::new( - socket, - &mut tls_read_record_buffer, - &mut tls_write_record_buffer, + // seed mqtt rng from rng + let mut mqtt_config = ClientConfig::<5, _>::new( + rust_mqtt::client::client_config::MqttVersion::MQTTv5, + CountingRng(rng.next_u64()), ); - tls.open::<_, NoVerify>(TlsContext::::new(&config, &mut rng)) - .await?; - tls - }; + if let (Some(user), Some(pass)) = (option_env!("MQTT_USER"), option_env!("MQTT_PASSWORD")) { + mqtt_config.add_username(user); + mqtt_config.add_username(pass); + } - const BUF_LEN: usize = 1 << 13; - let mut recv_buffer = [0; BUF_LEN]; - let mut buffer = [0; BUF_LEN]; + // TLS layer + const TLS_BUF_LEN: usize = 4096; + let mut tls_read_record_buffer = [0; TLS_BUF_LEN]; + let mut tls_write_record_buffer = [0; TLS_BUF_LEN]; + let mut tls = { + let config = TlsConfig::new(); - // MQTT Layer - let mut mqtt_client = MqttClient::new( - tls, - &mut buffer, - BUF_LEN, - &mut recv_buffer, - BUF_LEN, - mqtt_config, - ); - mqtt_client.connect_to_broker().await?; - for (topic, message) in messages { - mqtt_client - .send_message( - topic, - message, - rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS1, - true, - ) - .await?; + let mut tls = TlsConnection::new( + socket, + &mut tls_read_record_buffer, + &mut tls_write_record_buffer, + ); + + tls.open::<_, NoVerify>(TlsContext::::new(&config, &mut rng)) + .await?; + tls + }; + debug!("tls handshake succeeded"); + + const BUF_LEN: usize = 1024; + let mut recv_buffer = [0; BUF_LEN]; + let mut buffer = [0; BUF_LEN]; + + mqtt_config.add_client_id("esp32c3"); + mqtt_config.max_packet_size = (BUF_LEN - 128) as _; + // MQTT Layer + let mut mqtt_client = MqttClient::new( + tls, + &mut buffer, + BUF_LEN, + &mut recv_buffer, + BUF_LEN, + mqtt_config, + ); + mqtt_client.connect_to_broker().await?; + info!("connected to broker"); + for (topic, message) in messages { + mqtt_client + .send_message( + topic, + message, + rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS1, + true, + ) + .await?; + } + Ok(()) } - Ok(()) + with_timeout(Duration::from_secs(60), inner(stack, messages, rng)).await? } #[embassy_executor::task] pub async fn publish_data(stack: NetworkStack, rng_seed: [u8; 32], interval: Duration) { let mut rng = StdRng::from_seed(rng_seed); loop { - Timer::after(interval).await; + Timer::after(interval / 10).await; + if stack.is_config_up() { + break; + } + } + loop { let mut data = DATA.lock().await; let res = send_message( stack, @@ -133,8 +155,9 @@ pub async fn publish_data(stack: NetworkStack, rng_seed: [u8; 32], interval: Dur ) .await; if let Err(e) = res { - println!("Oh no: {e:?}"); + error!("Oh no: {e:?}"); }; data.clear(); + Timer::after(interval).await; } }