diff options
feat: ok more minimal
Diffstat (limited to 'src/bin/server.rs')
-rw-r--r-- | src/bin/server.rs | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/src/bin/server.rs b/src/bin/server.rs new file mode 100644 index 0000000..106295f --- /dev/null +++ b/src/bin/server.rs @@ -0,0 +1,400 @@ +//! Using a static IP example +//! +//! - set SSID and PASSWORD env variable +//! - set STATIC_IP and GATEWAY_IP env variable (e.g. "192.168.2.191" / "192.168.2.1") +//! - might be necessary to configure your WiFi access point accordingly +//! - uses the given static IP +//! - responds with some HTML content when connecting to port 8080 +//! + +//% 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; +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::{ + 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<T> { + /// No value. + None, + /// Some value of type `T`. + Some(T), +} + +static BUTTON: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None)); +static mut IS_OPEN: bool = false; +static mut LED: OurOption<RefCell<Output<'_>>> = 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 GPIO1 as an output, and set its state low initially. + let led = RefCell::new(Output::new(peripherals.GPIO1, Level::Low)); + let led = unsafe { + LED = OurOption::Some(led); + #[allow(static_mut_refs)] + match &LED { + OurOption::None => panic!(), + OurOption::Some(v) => v, + } + }; + + // Set GPIO4 as an input + let mut button = Input::new(peripherals.GPIO3, 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 { + ssid: network_data::SSID.try_into().unwrap(), + password: network_data::PASSWORD.try_into().unwrap(), + ..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 res: Result<(heapless::Vec<AccessPointInfo, 30>, usize), WifiError> = controller.scan_n(); + if let Ok((res, _count)) = res { + for ap in res { + debug!("AP Info: {:?}", ap); + } + } + + debug!("{:?}", controller.capabilities()); + debug!("wifi_connect {:?}", controller.connect()); + + // wait to get connected + info!("Wait to get connected"); + loop { + match controller.is_connected() { + Ok(true) => break, + Ok(false) => {} + 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 {}", network_data::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(network_data::STATIC_IP)), + subnet: blocking_network_stack::ipv4::Subnet { + gateway: blocking_network_stack::ipv4::Ipv4Addr::from(parse_ip( + network_data::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/", + network_data::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() { + // debug!("Established Connection"); + + // let mut time_out = false; + // let deadline = time::now() + Duration::millis(500); + // let mut buffer = [0u8; 8192]; + // let mut pos = 0; + // while let Ok(len) = socket.read(&mut buffer[pos..]) { + // if pos + len > buffer.len() { + // error!( + // "We got {} bytes. Buffer overflowed, treating as timeout.", + // pos + len + // ); + // time_out = true; + // break; + // } + + // let to_print = unsafe { core::str::from_utf8_unchecked(&buffer[..(pos + len)]) }; + + // if to_print.contains("\r\n\r\n") { + // break; + // } + + // pos += len; + + // if time::now() > deadline { + // println!("Timeout"); + // time_out = true; + // break; + // } + // } + let is_open = button.is_high(); + unsafe { + set_open_led_state(is_open); + } + let parts = [ + // (r##"{ + // "api": "0.13", + // "api_compatibility": [ + // "14", + // "15" + // ], + // "space": "Chaostreff Bern", + // "logo": "https://www.chaostreffbern.ch/images/logo_v1.1.png", + // "url": "https://www.chaostreffbern.ch","##) + // .as_bytes(), + // TODO: Possibly add location.hint that we are in the basement? + // TODO: Possibly add location.areas for each area in the space? + // (r##" + // "location": { + // "address": "Zwyssigstrasse 45, 3007 Bern, Switzerland", + // "lon": 7.421927, + // "lat": 46.944178, + // "timezone": "Europe/Zurich", + // "country_code": "CH" + // }, + // "spacefed": { + // "spacenet": false, + // "spacesaml": false, + // "spacephone": false + // }, + // "state": { + // "open": "##) + // .as_bytes(), + (if is_open { "true" } else { "false" }).as_bytes(), +// r##", +// "message": "Open every Tuesday from 19h" +// }, +// "contact": { +// "email": "info@chaostreffbern.ch", +// "ml": "bern@chaostreff.ch", +// "matrix": "#chaostreffbern:chaostreffbern.ch", +// "jabber": "xmpp://chaostreffbern@conference.chaostreffbern.ch", +// "mastodon": "@chaostreffbern@chaos.social" +// }, +// "issue_report_channels": [ +// "email" +// ], +// "feeds": { +// "blog": { +// "type": "rss", +// "url": "https://www.chaosbern.ch/feeds/chaosbern_rss.xml" +// }, +// "calendar": { +// "type": "caldav", +// "url": "https://nextcloud.jenix.ch/remote.php/dav/public-calendars/xFMZfKSBNfp3mRNR/" +// } +// }, +// "ext_ccc": "chaostreff" +// } +// "## +// .as_bytes(), + ]; + + // if !time_out { + let r =socket + .write_all( + b"HTTP/1.0 200 OK\r\n\ +Content-Type: application/json\r\n\ +UwU: if u read this u have been catgirled :3\r\n\ +\r\n\ +", + ); + if !r.is_ok() { + error!("{:#?}",r.unwrap_err()); + continue; + }; + for part in parts { + let r = socket.write_all(part); + + 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 +}
\ No newline at end of file |