use alloc::string::ToString; use embassy_sync::channel::DynamicSender; use embassy_sync::pubsub::DynSubscriber; use embassy_time::{Duration, Instant, WithTimeout}; use esp_hal::rng::Rng; use esp_radio::wifi::{ClientConfig, ScanConfig, WifiController, WifiDevice, WifiEvent}; use log::*; use alloc::format; use embassy_net::dns::DnsSocket; use embassy_net::tcp::client::{TcpClient, TcpClientState}; use embassy_net::Stack; use nalgebra::Vector2; use reqwless::client::{HttpClient, TlsConfig}; use crate::ego::engine::gps_to_local_meters_haversine; use crate::events::{Measurement, SensorSource, SensorState}; use crate::{backoff::Backoff, events::{Prediction}}; #[embassy_executor::task] pub async fn net_task(mut runner: embassy_net::Runner<'static, WifiDevice<'static>>) { info!("Starting network stack"); runner.run().await } #[embassy_executor::task] pub async fn wifi_connect_task(mut wifi: WifiController<'static>, stack: Stack<'static>, motion: DynamicSender<'static, Measurement>) { wifi.set_config(&esp_radio::wifi::ModeConfig::Client( ClientConfig::default() .with_ssid("The Frequency".to_string()) .with_auth_method(esp_radio::wifi::AuthMethod::Wpa2Personal) .with_password("thepasswordkenneth".to_string()) )).unwrap(); wifi.set_mode(esp_radio::wifi::WifiMode::Sta).unwrap(); wifi.set_power_saving(esp_radio::wifi::PowerSaveMode::Maximum).unwrap(); loop { Backoff::from_secs(3).forever().attempt(async || { info!("Connecting to wifi..."); wifi.start_async().await.unwrap(); motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::AcquiringFix)).await; let networks = wifi.scan_with_config_async(ScanConfig::default().with_show_hidden(true)).await.unwrap(); for network in networks { info!("wifi: {} @ {}db", network.ssid, network.signal_strength); } match wifi.connect_async().await { Ok(_) => Ok(()), Err(e) => { error!("Unable to connect to wifi: {e:?}"); wifi.stop_async().await.unwrap(); Err(()) } } }).await.unwrap(); info!("Waiting for DHCP..."); motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Degraded)).await; if stack.wait_config_up().with_timeout(Duration::from_secs(5)).await.is_ok() { info!("Online!"); motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Online)).await; let ip_cfg = stack.config_v4().unwrap(); info!("ip={ip_cfg:?}"); wifi.wait_for_event(WifiEvent::ApStaDisconnected).await; info!("Wifi disconnected!"); } else { warn!("DHCP timed out after 5 seconds. Disconnecting wifi"); wifi.disconnect_async().await.unwrap(); } warn!("Stopping wifi device"); wifi.stop_async().await.unwrap(); motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Offline)).await; } } // TODO: Wifi task needs to know when there is data to upload, so it only connects when needed. #[embassy_executor::task] pub async fn http_telemetry_task(mut predictions: DynSubscriber<'static, Prediction>, stack: Stack<'static>) { // TODO: should wait for wifi disconnect event somehow and use that to restart sending the wifi around let seed = Rng::new().random() as i32; let mut rx_buf = [0; 4096]; let mut tx_buf = [0; 4096]; let dns = DnsSocket::new(stack); let tcp_state = TcpClientState::<1, 4096, 4096>::new(); let tcp = TcpClient::new(stack, &tcp_state); let tls = TlsConfig::new( seed as u64, &mut rx_buf, &mut tx_buf, reqwless::client::TlsVerify::None, ); let mut client = HttpClient::new_with_tls(&tcp, &dns, tls); // TODO: Only should upload a data point after some distance has occurred let mut last_location = Vector2::default(); let mut last_push = Instant::from_ticks(0); loop { if let Prediction::Location(coords) = predictions.next_message_pure().await { if stack.is_config_up() { // Only push to HTTP if we have an ip config etc if last_push.elapsed().as_secs() >= 5 || gps_to_local_meters_haversine(&last_location, &coords).norm() >= 10.0 { last_location = coords; last_push = Instant::now(); if let Err(e) = Backoff::from_secs(3).attempt(async || { push_location(&mut client, coords, Instant::now().as_millis()).await }).await { warn!("Could not submit location! {e:?}"); } } } } } } async fn push_location(client: &mut HttpClient<'_, TcpClient<'_, 1, 4096, 4096>, DnsSocket<'_>>, location: Vector2, timestamp: u64) -> Result<(), reqwless::Error> { let mut buffer = [0u8; 4096]; let base = "https://nextcloud.malloc.hackerbots.net/nextcloud/index.php/apps/phonetrack/logGet/a062000067304e9dee6590f1b8f9e0db/renderbug"; let url = format!("{base}?lon={}&lat={}×tamp={}", location.y, location.x, timestamp); info!("Pushing to {url}"); let mut http_req = client .request( reqwless::request::Method::GET, &url, ) .await?; let response = http_req.send(&mut buffer).await?; let res = response.body().read_to_end().await?; let content = core::str::from_utf8(res).unwrap(); debug!("HTTP response: {content}"); Ok(()) }