#![no_std] #![no_main] #![feature(type_alias_impl_trait)] use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Config, Ipv4Address, Stack, StackResources}; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; use esp_hal::{self, IO}; use embassy_time::{Duration, Timer}; use embedded_hal_async::digital::Wait; use esp_backtrace as _; use esp_hal::clock::ClockControl; use esp_hal::gpio::{AnyPin, Input, Output, PullUp, PushPull}; use esp_hal::Rng; use esp_hal::{embassy, peripherals::Peripherals, prelude::*, timer::TimerGroup}; use esp_println::println; use esp_wifi::wifi::{ClientConfiguration, Configuration}; use esp_wifi::wifi::{WifiController, WifiDevice, WifiEvent, WifiStaDevice, WifiState}; use esp_wifi::{initialize, EspWifiInitFor}; use rust_mqtt::client::client::MqttClient; use rust_mqtt::client::client_config::ClientConfig; use rust_mqtt::packet::v5::publish_packet::QualityOfService; use rust_mqtt::utils::rng_generator::CountingRng; use static_cell::make_static; const SSID: &str = env!("SSID"); const PASSWORD: &str = env!("PASSWORD"); const MQTT_USERNAME: &str = env!("MQTT_USERNAME"); const MQTT_PASSWORD: &str = env!("MQTT_PASSWORD"); const MQTT_TOPIC_NAME: &str = "home/doorbell/state"; const MQTT_AVAILABILITY_TOPIC_NAME: &str = "home/doorbell/available"; #[main] async fn main(spawner: Spawner) { #[cfg(feature = "log")] esp_println::logger::init_logger(log::LevelFilter::Info); let peripherals = Peripherals::take(); let system = peripherals.SYSTEM.split(); let clocks = ClockControl::max(system.clock_control).freeze(); #[cfg(target_arch = "xtensa")] let timer = esp_hal::timer::TimerGroup::new(peripherals.TIMG1, &clocks).timer0; #[cfg(target_arch = "riscv32")] let timer = esp_hal::systimer::SystemTimer::new(peripherals.SYSTIMER).alarm0; let init = initialize( EspWifiInitFor::Wifi, timer, Rng::new(peripherals.RNG), system.radio_clock_control, &clocks, ) .unwrap(); let wifi = peripherals.WIFI; let (wifi_interface, controller) = esp_wifi::wifi::new_with_mode(&init, wifi, WifiStaDevice).unwrap(); let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); embassy::init(&clocks, timer_group0); let config = Config::dhcpv4(Default::default()); // TODO: generate random let seed = 0x50EA111DA6CC1D83; // Init network stack let stack = &*make_static!(Stack::new( wifi_interface, config, make_static!(StackResources::<3>::new()), seed )); spawner.spawn(connection(controller)).ok(); spawner.spawn(net_task(stack)).ok(); loop { if stack.is_link_up() { break; } Timer::after(Duration::from_millis(500)).await; } println!("Waiting to get IP address..."); loop { if let Some(config) = stack.config_v4() { println!("Got IP: {}", config.address); break; } Timer::after(Duration::from_millis(500)).await; } let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); let ring_button = io.pins.gpio12.into_pull_up_input(); let open_button = io.pins.gpio14.into_pull_up_input(); let status_led = io.pins.gpio27.into_push_pull_output(); let opener = make_static!(Mutex::new(io.pins.gpio26.into_push_pull_output().into())); spawner.must_spawn(ring_button_task(ring_button.into(), stack)); spawner.must_spawn(open_button_task(open_button.into(), opener)); spawner.must_spawn(status_led_task(status_led.into())); } async fn ring(stack: &'static Stack>) -> Result<(), &str> { let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; Timer::after(Duration::from_millis(1_000)).await; let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); let remote_endpoint = (Ipv4Address::new(192, 168, 1, 3), 1883); println!("connecting..."); let r = socket.connect(remote_endpoint).await; if let Err(e) = r { println!("connect error: {:?}", e); return Err("connect error"); } // https://github.com/JurajSadel/esp32c3-no-std-async-mqtt-demo/blob/main/src/main.rs let mut config = ClientConfig::new( rust_mqtt::client::client_config::MqttVersion::MQTTv5, CountingRng(20000), ); config.add_username(MQTT_USERNAME); config.add_password(MQTT_PASSWORD); // Do we need those? // config.add_max_subscribe_qos(rust_mqtt::packet::v5::publish_packet::QualityOfService::QoS1); // config.add_client_id("clientId-8rhWgBODCl"); // config.max_packet_size = 100; let mut recv_buffer = [0; 80]; let mut write_buffer = [0; 80]; let mut client = MqttClient::<_, 5, _>::new(socket, &mut write_buffer, 80, &mut recv_buffer, 80, config); if let Err(e) = client.connect_to_broker().await { println!("connect error: {:?}", e); return Err("connect error"); } // TODO: This should be done at boot client .send_message( MQTT_AVAILABILITY_TOPIC_NAME, "online".as_bytes(), QualityOfService::QoS0, true, ) .await .unwrap(); client .send_message( MQTT_TOPIC_NAME, "{ \"event_type\": \"press\" }".as_bytes(), QualityOfService::QoS0, true, ) .await .unwrap(); Timer::after(Duration::from_millis(5000)).await; Ok(()) } #[embassy_executor::task] async fn ring_button_task( mut button: AnyPin>, stack: &'static Stack>, ) { loop { button.wait_for_falling_edge().await.unwrap(); Timer::after_millis(10).await; if button.is_low().unwrap() { println!("Bell button pressed"); ring(stack) .await .unwrap_or_else(|e| println!("ring error: {}", e)); } } } #[embassy_executor::task] async fn status_led_task(mut status_led: AnyPin>) { loop { status_led.set_high().unwrap(); Timer::after_millis(100).await; status_led.set_low().unwrap(); Timer::after_secs(1).await; } } #[embassy_executor::task] async fn open_button_task( mut button: AnyPin>, opener: &'static Mutex>>, ) { loop { button.wait_for_falling_edge().await.unwrap(); Timer::after_millis(10).await; if button.is_low().unwrap() { println!("Open button pressed"); let spawner = Spawner::for_current_executor().await; spawner .spawn(open_task(opener)) .unwrap_or_else(|_| println!("Already opening")); } } } #[embassy_executor::task] async fn open_task(opener: &'static Mutex>>) { let mut opener = opener.lock().await; opener.set_high().unwrap(); Timer::after_secs(1).await; opener.set_low().unwrap(); } #[embassy_executor::task] async fn connection(mut controller: WifiController<'static>) { println!("start connection task"); println!("Device capabilities: {:?}", controller.get_capabilities()); loop { if esp_wifi::wifi::get_wifi_state() == WifiState::StaConnected { controller.wait_for_event(WifiEvent::StaDisconnected).await; Timer::after(Duration::from_millis(5000)).await } if !matches!(controller.is_started(), Ok(true)) { let client_config = Configuration::Client(ClientConfiguration { ssid: SSID.try_into().unwrap(), password: PASSWORD.try_into().unwrap(), ..Default::default() }); controller.set_configuration(&client_config).unwrap(); println!("Starting wifi"); controller.start().await.unwrap(); println!("Wifi started!"); } println!("About to connect..."); match controller.connect().await { Ok(_) => println!("Wifi connected!"), Err(e) => { println!("Failed to connect to wifi: {e:?}"); Timer::after(Duration::from_millis(5000)).await } } } } #[embassy_executor::task] async fn net_task(stack: &'static Stack>) { stack.run().await }