// // Space API HTTP Button ("dumbswitch") Main Entrypoint // Copyright (C) 2025 memdmp // // This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License along with this program. If not, see . // //! Production ESP is built with `cargo r --bin dumbswitch-as-server --features led-as-busy-led -r` //! Debug ESP is built with `ESP_LOG=debug cargo r --bin dumbswitch-as-server --features led-as-busy-led` //! To get logs, use while true; do if ls /dev/ | grep ttyACM >/dev/null 2>/dev/null; then echo 'Found Device'; cat /dev/ttyACM*; else sleep 0.001; fi; done //! To change networks, copy ../network_data.example.rs to ../network_data.rs and change it's contents //% FEATURES: esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable //% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 #![no_std] #![no_main] use core::cell::{OnceCell, RefCell}; use core::ops::Deref; use core::str::FromStr; use blocking_network_stack::Stack; use critical_section::Mutex; use dumbswitch::{network_data, network_type::Network}; use embedded_io::*; use esp_alloc as _; use esp_backtrace as _; use esp_hal::delay::Delay; use esp_hal::gpio::{Event, Input, Io, Level, Output, Pull}; use esp_hal::handler; use esp_hal::interrupt::InterruptConfigurable; use esp_hal::{ clock::CpuClock, main, rng::Rng, time::{self, Duration}, timer::timg::TimerGroup, }; use esp_println::println; use esp_wifi::wifi::ScanConfig; use esp_wifi::{ init, wifi::{ utils::create_network_interface, AccessPointInfo, ClientConfiguration, Configuration, WifiError, WifiStaDevice, }, }; use log::{debug, error, info, trace, warn}; use smoltcp::iface::{SocketSet, SocketStorage}; pub enum OurOption { /// No value. None, /// Some value of type `T`. Some(T), } static BUTTON: Mutex>> = Mutex::new(RefCell::new(None)); static mut LED: OurOption>> = OurOption::None; fn set_open_led_state(val: bool) { #[cfg(not(feature = "led-as-busy-led"))] { let v = unsafe { #[allow(static_mut_refs)] &LED }; let v = match v { OurOption::None => { return; } OurOption::Some(v) => v, }; let mut b = v.borrow_mut(); b.set_level(if val { Level::High } else { Level::Low }); } } // TODO: in future, add another LED for busy fn set_busy_led_state(val: bool) { #[cfg(feature = "led-as-busy-led")] { let v = unsafe { #[allow(static_mut_refs)] &LED }; let v = match v { OurOption::None => { return; } OurOption::Some(v) => v, }; let mut b = v.borrow_mut(); b.set_level(if val { Level::High } else { Level::Low }); } } #[main] fn main() -> ! { esp_println::logger::init_logger_from_env(); let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); let delay = Delay::new(); esp_alloc::heap_allocator!(72 * 1024); info!("Preparing GPIO"); let mut io = Io::new(peripherals.IO_MUX); // Set GPIO0 as an output, and set its state low initially. let led = RefCell::new(Output::new(peripherals.GPIO0, Level::Low)); let led = unsafe { LED = OurOption::Some(led); #[allow(static_mut_refs)] match &LED { OurOption::None => panic!(), OurOption::Some(v) => v, } }; // Set GPIO6 as an input let mut button = Input::new(peripherals.GPIO6, Pull::Down); info!("Waiting 500ms pre-init"); delay.delay_millis(500); led.borrow_mut().set_low(); let timg0 = TimerGroup::new(peripherals.TIMG0); let mut rng = Rng::new(peripherals.RNG); let init = init(timg0.timer0, rng.clone(), peripherals.RADIO_CLK).unwrap(); let mut wifi = peripherals.WIFI; let (iface, device, mut controller) = create_network_interface(&init, &mut wifi, WifiStaDevice).unwrap(); let mut socket_set_entries: [SocketStorage; 3] = Default::default(); let socket_set = SocketSet::new(&mut socket_set_entries[..]); let now = || time::now().duration_since_epoch().to_millis(); let mut stack = Stack::new(iface, device, socket_set, now, rng.random()); let client_config = Configuration::Client(ClientConfiguration { ..Default::default() }); let res = controller.set_configuration(&client_config); debug!("wifi_set_configuration returned {:?}", res); controller.start().unwrap(); debug!("is wifi started: {:?}", controller.is_started()); info!("Start Wifi Scan"); let mut net: Option = None; let mut attempts = 0; while (net.is_none() && attempts < 16) { attempts += 1; let res: Result<(heapless::Vec, usize), WifiError> = controller .scan_with_config_sync(ScanConfig { show_hidden: true, ..Default::default() }); if let Ok((res, _count)) = res { for ap in res { info!("Saw AP: {:?}", ap); for ap_comp in network_data::NETWORKS { if ap_comp.ssid == ap.ssid.clone() { let client_config = Configuration::Client(ClientConfiguration { ssid: ap_comp.ssid.try_into().unwrap(), password: ap_comp.password.try_into().unwrap(), ..Default::default() }); controller.set_configuration(&client_config); net = Some(ap_comp); break; }; } } } } if net.is_none() { loop { error!("Failed to connect to wifi: No saved network found."); let mut high = false; loop { delay.delay_millis(1000); high = !high; if high { led.borrow_mut().set_high(); } else { led.borrow_mut().set_low(); } } } } debug!("{:?}", controller.capabilities()); let connres = controller.connect(); debug!("wifi_connect {:?}", connres); if !connres.is_ok() { error!("Failed to connect to wifi! {:#?}", connres.unwrap_err()); let mut high = false; loop { delay.delay_millis(2000); high = !high; if high { led.borrow_mut().set_high(); } else { led.borrow_mut().set_low(); } } } let net = net.unwrap(); // wait to get connected info!("Wait to get connected"); loop { match controller.is_connected() { Ok(true) => break, Ok(false) => { set_busy_led_state(true); delay.delay_millis(10); set_busy_led_state(false); delay.delay_millis(500); } Err(err) => { error!("Failed to connect to wifi: {:?}", err); let mut high = false; loop { delay.delay_millis(1000); high = !high; if high { led.borrow_mut().set_high(); } else { led.borrow_mut().set_low(); } } } } } debug!("{:?}", controller.is_connected()); info!("Setting static IP {}", net.static_ip); stack .set_iface_configuration(&blocking_network_stack::ipv4::Configuration::Client( blocking_network_stack::ipv4::ClientConfiguration::Fixed( blocking_network_stack::ipv4::ClientSettings { ip: blocking_network_stack::ipv4::Ipv4Addr::from(parse_ip(net.static_ip)), subnet: blocking_network_stack::ipv4::Subnet { gateway: blocking_network_stack::ipv4::Ipv4Addr::from(parse_ip(net.gateway_ip)), mask: blocking_network_stack::ipv4::Mask(24), }, dns: None, secondary_dns: None, }, ), )) .unwrap(); for i in 0..4 { delay.delay_millis(100); if i % 2 == 0 { led.borrow_mut().set_high(); } else { led.borrow_mut().set_low(); } } info!( "Start busy loop on main. Point your browser to http://{}:8080/", net.static_ip ); let mut rx_buffer = [0u8; 1536]; let mut tx_buffer = [0u8; 1536]; let mut socket = stack.get_socket(&mut rx_buffer, &mut tx_buffer); socket.listen(8080).unwrap(); // fn update_open(is_open: bool, button: &Input<'_>, led: &mut Output<'_>, delay: &Delay) -> bool { // let mut is_open = if is_open { true } else { false }; // if button.is_low() { // is_open = !is_open; // if is_open { // led.set_high(); // } else { // led.set_low(); // } // while button.is_low() { // trace!("waiting for unpress btn"); // delay.delay_millis(10); // } // } // is_open // } loop { socket.work(); if !socket.is_open() { socket.listen(8080).unwrap(); } set_busy_led_state(true); if socket.is_connected() { let is_open = button.is_high(); unsafe { set_open_led_state(is_open); } let r = socket.write_all( // 0b01100001 - true (open) (if is_open { b"HTTP/1.0 200 OK\r\n\ \r\n\ a" } else { // 0b01111010 - false (closed) b"HTTP/1.0 200 OK\r\n\ \r\n\ z" }), ); if !r.is_ok() { error!("{:#?}", r.unwrap_err()); continue; }; let r = socket.flush(); if !r.is_ok() { error!("{:#?}", r.unwrap_err()); continue; }; socket.work(); // } socket.close(); } set_busy_led_state(false); // TODO: what let deadline = time::now() + Duration::millis(100); while time::now() < deadline && !socket.is_connected() { socket.work(); } let is_open = button.is_high(); set_open_led_state(is_open); } } fn parse_ip(ip: &str) -> [u8; 4] { let mut result = [0u8; 4]; for (idx, octet) in ip.split(".").into_iter().enumerate() { result[idx] = u8::from_str_radix(octet, 10).unwrap(); } result }