From 27ed97ea6c36df2199e67b8c4f3063e5c6423cb7 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 31 May 2024 14:45:50 +0200 Subject: [PATCH] Refactor async completely for a more intuitive API. URCs over PPP UDP socket is still not working properly --- .vscode/settings.json | 4 + Cargo.toml | 11 +- examples/rpi-pico/Cargo.toml | 26 +- .../rpi-pico/src/bin/embassy-smoltcp-ppp.rs | 84 ++- src/asynch/at_udp_socket.rs | 60 ++ src/asynch/control.rs | 174 ++++-- src/asynch/internal_stack.rs | 117 ---- src/asynch/mod.rs | 54 +- src/asynch/network.rs | 292 +++++++++ src/asynch/ppp.rs | 338 ----------- src/asynch/resources.rs | 47 +- src/asynch/runner.rs | 562 ++++++++---------- src/asynch/state.rs | 157 ++--- src/asynch/ublox_stack/tcp.rs | 2 +- src/asynch/ublox_stack/tls.rs | 2 +- src/command/custom_digest.rs | 14 - src/command/network/mod.rs | 2 +- src/command/system/mod.rs | 2 +- src/config.rs | 17 + src/connection.rs | 24 +- src/lib.rs | 24 +- 21 files changed, 955 insertions(+), 1058 deletions(-) create mode 100644 src/asynch/at_udp_socket.rs delete mode 100644 src/asynch/internal_stack.rs create mode 100644 src/asynch/network.rs delete mode 100644 src/asynch/ppp.rs create mode 100644 src/config.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index bd3b577..b297366 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,10 @@ "rust-analyzer.cargo.target": "thumbv6m-none-eabi", "rust-analyzer.check.allTargets": false, "rust-analyzer.linkedProjects": [], + "rust-analyzer.cargo.features": [ + "odin_w2xx", + "ppp" + ], "rust-analyzer.server.extraEnv": { "WIFI_NETWORK": "foo", "WIFI_PASSWORD": "foo", diff --git a/Cargo.toml b/Cargo.toml index d4825d0..c8b21f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,16 +15,13 @@ name = "ublox_short_range" doctest = false [dependencies] -# atat = { version = "0.21", features = ["derive", "bytes"] } -atat = { path = "../atat/atat", features = ["derive", "bytes"] } +atat = { version = "0.22", features = ["derive", "bytes"] } -# atat = { git = "https://github.com/BlackbirdHQ/atat", rev = "70283be", features = ["derive", "defmt", "bytes"] } heapless = { version = "^0.8", features = ["serde"] } no-std-net = { version = "0.6", features = ["serde"] } serde = { version = "^1", default-features = false, features = ["derive"] } # ublox-sockets = { version = "0.5", features = ["edm"], optional = true } ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", rev = "9f7fe54", optional = true } -postcard = "1.0.4" portable-atomic = "1.6" log = { version = "^0.4", default-features = false, optional = true } @@ -38,7 +35,6 @@ embassy-futures = "0.1" embedded-nal-async = { version = "0.7" } futures-util = { version = "0.3.29", default-features = false } -embedded-io = "0.6" embedded-io-async = "0.6" embassy-net-ppp = { version = "0.1", optional = true } @@ -49,7 +45,7 @@ embassy-net = { version = "0.4", features = [ [features] -default = ["socket-tcp", "socket-udp", "odin_w2xx"] +default = ["socket-tcp", "socket-udp"] internal-network-stack = ["dep:ublox-sockets", "edm"] edm = ["ublox-sockets?/edm"] @@ -62,7 +58,6 @@ socket-udp = ["ublox-sockets?/socket-udp", "embassy-net?/udp"] defmt = [ "dep:defmt", - "postcard/use-defmt", "heapless/defmt-03", "atat/defmt", "ublox-sockets?/defmt", @@ -86,4 +81,4 @@ exclude = ["examples"] [patch.crates-io] no-std-net = { git = "https://github.com/rushmorem/no-std-net", branch = "issue-15" } -atat = { path = "../atat/atat" } +atat = { path = "../atat/atat" } \ No newline at end of file diff --git a/examples/rpi-pico/Cargo.toml b/examples/rpi-pico/Cargo.toml index 196424c..a07f340 100644 --- a/examples/rpi-pico/Cargo.toml +++ b/examples/rpi-pico/Cargo.toml @@ -49,11 +49,14 @@ embassy-net = { version = "0.4", optional = true, features = [ "medium-ip", "tcp", "udp", + "dns" ] } embassy-net-ppp = { version = "0.1", optional = true, features = ["defmt"] } -embedded-tls = { path = "../../../embedded-tls", default-features = false, features = [ - "defmt", -] } +reqwless = { git = "https://github.com/drogue-iot/reqwless", features = ["defmt"] } +smoltcp = { version = "*", default-features = false, features = ["dns-max-server-count-4"]} +rand_chacha = { version = "0.3", default-features = false } +embedded-tls = { path = "../../../embedded-tls", default-features = false, features = ["defmt"] } + [features] internal-network-stack = ["ublox-short-range-rs/internal-network-stack"] @@ -68,16 +71,13 @@ ppp = ["dep:embassy-net", "dep:embassy-net-ppp", "ublox-short-range-rs/ppp"] # embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -# embassy-executor = { path = "../../../embassy/embassy-executor" } -# embassy-hal-internal = { path = "../../../embassy/embassy-hal-internal" } -# embassy-time = { path = "../../../embassy/embassy-time" } -# embassy-futures = { path = "../../../embassy/embassy-futures" } -# embassy-sync = { path = "../../../embassy/embassy-sync" } -# embassy-rp = { path = "../../../embassy/embassy-rp" } -# embassy-net-driver = { path = "../../../embassy/embassy-net-driver" } -# embassy-net = { path = "../../../embassy/embassy-net" } -# embassy-net-ppp = { path = "../../../embassy/embassy-net-ppp" } -atat = { path = "../../../atat/atat" } +embassy-rp = { path = "../../../embassy/embassy-rp" } +embassy-time = { path = "../../../embassy/embassy-time" } +embassy-sync = { path = "../../../embassy/embassy-sync" } +embassy-net = { path = "../../../embassy/embassy-net" } +embassy-net-ppp = { path = "../../../embassy/embassy-net-ppp" } +embassy-futures = { path = "../../../embassy/embassy-futures" } +embassy-executor = { path = "../../../embassy/embassy-executor" } ublox-sockets = { path = "../../../ublox-sockets" } no-std-net = { path = "../../../no-std-net" } diff --git a/examples/rpi-pico/src/bin/embassy-smoltcp-ppp.rs b/examples/rpi-pico/src/bin/embassy-smoltcp-ppp.rs index f89cdb9..2991638 100644 --- a/examples/rpi-pico/src/bin/embassy-smoltcp-ppp.rs +++ b/examples/rpi-pico/src/bin/embassy-smoltcp-ppp.rs @@ -1,16 +1,30 @@ -#![cfg(feature = "ppp")] #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[cfg(not(feature = "ppp"))] +compile_error!("You must enable the `ppp` feature flag to build this example"); + use defmt::*; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Stack, StackResources}; use embassy_rp::gpio::{AnyPin, Level, Output, Pin}; use embassy_rp::peripherals::UART1; use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart}; use embassy_rp::{bind_interrupts, uart}; use embassy_time::{Duration, Timer}; +use embedded_tls::Aes128GcmSha256; +use embedded_tls::TlsConfig; +use embedded_tls::TlsConnection; +use embedded_tls::TlsContext; +use embedded_tls::UnsecureProvider; +use rand_chacha::rand_core::SeedableRng; +use rand_chacha::ChaCha8Rng; +use reqwless::headers::ContentType; +use reqwless::request::Request; +use reqwless::request::RequestBuilder as _; +use reqwless::response::Response; use static_cell::StaticCell; use ublox_short_range::asynch::{PPPRunner, Resources}; use {defmt_rtt as _, panic_probe as _}; @@ -26,7 +40,7 @@ async fn net_task(stack: &'static Stack>) -> ! #[embassy_executor::task] async fn ppp_task( - mut runner: PPPRunner<'static, Output<'static, AnyPin>, INGRESS_BUF_SIZE, URC_CAPACITY>, + mut runner: PPPRunner<'static, Output<'static>, INGRESS_BUF_SIZE, URC_CAPACITY>, interface: BufferedUart<'static, UART1>, stack: &'static embassy_net::Stack>, ) -> ! { @@ -68,7 +82,7 @@ async fn main(spawner: Spawner) { // Init network stack static STACK: StaticCell>> = StaticCell::new(); - static STACK_RESOURCES: StaticCell> = StaticCell::new(); + static STACK_RESOURCES: StaticCell> = StaticCell::new(); let stack = &*STACK.init(Stack::new( net_device, @@ -91,19 +105,51 @@ async fn main(spawner: Spawner) { control.join_open("Test").await; - // // Then we can use it! - // 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 remote_endpoint = (Ipv4Address::new(93, 184, 216, 34), 12345); - // info!("connecting to {:?}...", remote_endpoint); - // let r = socket.connect(remote_endpoint).await; - // if let Err(e) = r { - // warn!("connect error: {:?}", e); - // return; - // } - // info!("TCP connected!"); + info!("We have network!"); + + 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 hostname = "ecdsa-test.germancoding.com"; + + let mut remote = stack + .dns_query(hostname, smoltcp::wire::DnsQueryType::A) + .await + .unwrap(); + let remote_endpoint = (remote.pop().unwrap(), 443); + info!("connecting to {:?}...", remote_endpoint); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + warn!("connect error: {:?}", e); + return; + } + info!("TCP connected!"); + + let mut read_record_buffer = [0; 16384]; + let mut write_record_buffer = [0; 16384]; + let config = TlsConfig::new().with_server_name(hostname); + let mut tls = TlsConnection::new(socket, &mut read_record_buffer, &mut write_record_buffer); + + tls.open(TlsContext::new( + &config, + UnsecureProvider::new::(ChaCha8Rng::seed_from_u64(seed)), + )) + .await + .expect("error establishing TLS connection"); + + info!("TLS Established!"); + + let request = Request::get("/") + .host(hostname) + .content_type(ContentType::TextPlain) + .build(); + request.write(&mut tls).await.unwrap(); + + let mut rx_buf = [0; 4096]; + let response = Response::read(&mut tls, reqwless::request::Method::GET, &mut rx_buf) + .await + .unwrap(); + info!("{=[u8]:a}", rx_buf); } diff --git a/src/asynch/at_udp_socket.rs b/src/asynch/at_udp_socket.rs new file mode 100644 index 0000000..6dd777d --- /dev/null +++ b/src/asynch/at_udp_socket.rs @@ -0,0 +1,60 @@ +use embassy_net::{udp::UdpSocket, Ipv4Address}; +use embedded_io_async::{Read, Write}; + +pub struct AtUdpSocket<'a>(pub(crate) UdpSocket<'a>); + +impl<'a> AtUdpSocket<'a> { + pub(crate) const PPP_AT_PORT: u16 = 23; +} + +impl<'a> embedded_io_async::ErrorType for &AtUdpSocket<'a> { + type Error = core::convert::Infallible; +} + +impl<'a> Read for &AtUdpSocket<'a> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let (len, _) = self.0.recv_from(buf).await.unwrap(); + debug!("READ {} bytes: {=[u8]:a}", len, &buf[..len]); + Ok(len) + } +} + +impl<'a> Write for &AtUdpSocket<'a> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.0 + .send_to( + buf, + (Ipv4Address::new(172, 30, 0, 251), AtUdpSocket::PPP_AT_PORT), + ) + .await + .unwrap(); + + Ok(buf.len()) + } +} + +impl<'a> embedded_io_async::ErrorType for AtUdpSocket<'a> { + type Error = core::convert::Infallible; +} + +impl<'a> Read for AtUdpSocket<'a> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let (len, _) = self.0.recv_from(buf).await.unwrap(); + debug!("READ {} bytes: {=[u8]:a}", len, &buf[..len]); + Ok(len) + } +} + +impl<'a> Write for AtUdpSocket<'a> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.0 + .send_to( + buf, + (Ipv4Address::new(172, 30, 0, 251), AtUdpSocket::PPP_AT_PORT), + ) + .await + .unwrap(); + + Ok(buf.len()) + } +} diff --git a/src/asynch/control.rs b/src/asynch/control.rs index fd658c6..5bc4ed0 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -1,44 +1,101 @@ use core::future::poll_fn; use core::task::Poll; -use atat::asynch::AtatClient; +use atat::{ + asynch::{AtatClient, SimpleClient}, + UrcChannel, UrcSubscription, +}; +use embassy_net::{ + udp::{PacketMetadata, UdpSocket}, + Ipv4Address, +}; use embassy_time::{with_timeout, Duration}; +use crate::command::gpio::{ + types::{GPIOId, GPIOValue}, + WriteGPIO, +}; use crate::command::network::SetNetworkHostName; -use crate::command::security::types::SecurityDataType; -use crate::command::security::SendSecurityDataImport; +use crate::command::system::{RebootDCE, ResetToFactoryDefaults}; use crate::command::wifi::types::{ Authentication, StatusId, WifiStationAction, WifiStationConfig, WifiStatus, WifiStatusVal, }; use crate::command::wifi::{ExecWifiStationAction, GetWifiStatus, SetWifiStationConfig}; use crate::command::OnOff; -use crate::command::{ - gpio::{ - types::{GPIOId, GPIOValue}, - WriteGPIO, - }, - security::PrepareSecurityDataImport, -}; use crate::error::Error; use super::state::LinkState; -use super::{state, AtHandle}; +use super::{at_udp_socket::AtUdpSocket, runner::URC_SUBSCRIBERS}; +use super::{state, UbloxUrc}; const CONFIG_ID: u8 = 0; -pub struct Control<'a, AT: AtatClient> { - state_ch: state::StateRunner<'a>, - at: AtHandle<'a, AT>, +const MAX_COMMAND_LEN: usize = 128; + +// TODO: Can this be made in a more intuitive way? +pub struct ControlResources { + rx_meta: [PacketMetadata; 1], + tx_meta: [PacketMetadata; 1], + socket_rx_buf: [u8; 32], + socket_tx_buf: [u8; 32], + at_buf: [u8; MAX_COMMAND_LEN], } -impl<'a, AT: AtatClient> Control<'a, AT> { - pub(crate) fn new(state_ch: state::StateRunner<'a>, at: AtHandle<'a, AT>) -> Self { - Self { state_ch, at } +impl ControlResources { + pub const fn new() -> Self { + Self { + rx_meta: [PacketMetadata::EMPTY; 1], + tx_meta: [PacketMetadata::EMPTY; 1], + socket_rx_buf: [0u8; 32], + socket_tx_buf: [0u8; 32], + at_buf: [0u8; MAX_COMMAND_LEN], + } + } +} + +pub struct Control<'a, 'r, const URC_CAPACITY: usize> { + state_ch: state::Runner<'a>, + at_client: SimpleClient<'r, AtUdpSocket<'r>, atat::DefaultDigester>, + _urc_subscription: UrcSubscription<'a, UbloxUrc, URC_CAPACITY, URC_SUBSCRIBERS>, +} + +impl<'a, 'r, const URC_CAPACITY: usize> Control<'a, 'r, URC_CAPACITY> { + pub(crate) fn new( + state_ch: state::Runner<'a>, + urc_channel: &'a UrcChannel, + resources: &'r mut ControlResources, + stack: &'r embassy_net::Stack, + ) -> Self { + let mut socket = UdpSocket::new( + stack, + &mut resources.rx_meta, + &mut resources.socket_rx_buf, + &mut resources.tx_meta, + &mut resources.socket_tx_buf, + ); + + info!("Socket bound!"); + socket + .bind((Ipv4Address::new(172, 30, 0, 252), AtUdpSocket::PPP_AT_PORT)) + .unwrap(); + + let at_client = SimpleClient::new( + AtUdpSocket(socket), + atat::AtDigester::::new(), + &mut resources.at_buf, + atat::Config::default(), + ); + + Self { + state_ch, + at_client, + _urc_subscription: urc_channel.subscribe().unwrap(), + } } pub async fn set_hostname(&mut self, hostname: &str) -> Result<(), Error> { - self.at - .send(SetNetworkHostName { + self.at_client + .send(&SetNetworkHostName { host_name: hostname, }) .await?; @@ -47,8 +104,8 @@ impl<'a, AT: AtatClient> Control<'a, AT> { async fn get_wifi_status(&mut self) -> Result { match self - .at - .send(GetWifiStatus { + .at_client + .send(&GetWifiStatus { status_id: StatusId::Status, }) .await? @@ -61,8 +118,8 @@ impl<'a, AT: AtatClient> Control<'a, AT> { async fn get_connected_ssid(&mut self) -> Result, Error> { match self - .at - .send(GetWifiStatus { + .at_client + .send(&GetWifiStatus { status_id: StatusId::SSID, }) .await? @@ -73,6 +130,13 @@ impl<'a, AT: AtatClient> Control<'a, AT> { } } + pub async fn factory_reset(&mut self) -> Result<(), Error> { + self.at_client.send(&ResetToFactoryDefaults).await?; + self.at_client.send(&RebootDCE).await?; + + Ok(()) + } + pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { if matches!(self.get_wifi_status().await?, WifiStatusVal::Connected) { // Wifi already connected. Check if the SSID is the same @@ -84,15 +148,22 @@ impl<'a, AT: AtatClient> Control<'a, AT> { }; } - self.at - .send(SetWifiStationConfig { + self.at_client + .send(&ExecWifiStationAction { + config_id: CONFIG_ID, + action: WifiStationAction::Reset, + }) + .await?; + + self.at_client + .send(&SetWifiStationConfig { config_id: CONFIG_ID, config_param: WifiStationConfig::ActiveOnStartup(OnOff::Off), }) .await?; - self.at - .send(SetWifiStationConfig { + self.at_client + .send(&SetWifiStationConfig { config_id: CONFIG_ID, config_param: WifiStationConfig::SSID( heapless::String::try_from(ssid).map_err(|_| Error::Overflow)?, @@ -100,21 +171,21 @@ impl<'a, AT: AtatClient> Control<'a, AT> { }) .await?; - self.at - .send(SetWifiStationConfig { + self.at_client + .send(&SetWifiStationConfig { config_id: CONFIG_ID, config_param: WifiStationConfig::Authentication(Authentication::Open), }) .await?; - self.at - .send(ExecWifiStationAction { + self.at_client + .send(&ExecWifiStationAction { config_id: CONFIG_ID, action: WifiStationAction::Activate, }) .await?; - with_timeout(Duration::from_secs(10), self.wait_for_join(ssid)) + with_timeout(Duration::from_secs(25), self.wait_for_join(ssid)) .await .map_err(|_| Error::Timeout)??; @@ -132,22 +203,22 @@ impl<'a, AT: AtatClient> Control<'a, AT> { }; } - self.at - .send(ExecWifiStationAction { + self.at_client + .send(&ExecWifiStationAction { config_id: CONFIG_ID, action: WifiStationAction::Reset, }) .await?; - self.at - .send(SetWifiStationConfig { + self.at_client + .send(&SetWifiStationConfig { config_id: CONFIG_ID, config_param: WifiStationConfig::ActiveOnStartup(OnOff::Off), }) .await?; - self.at - .send(SetWifiStationConfig { + self.at_client + .send(&SetWifiStationConfig { config_id: CONFIG_ID, config_param: WifiStationConfig::SSID( heapless::String::try_from(ssid).map_err(|_| Error::Overflow)?, @@ -155,15 +226,15 @@ impl<'a, AT: AtatClient> Control<'a, AT> { }) .await?; - self.at - .send(SetWifiStationConfig { + self.at_client + .send(&SetWifiStationConfig { config_id: CONFIG_ID, config_param: WifiStationConfig::Authentication(Authentication::WpaWpa2Psk), }) .await?; - self.at - .send(SetWifiStationConfig { + self.at_client + .send(&SetWifiStationConfig { config_id: CONFIG_ID, config_param: WifiStationConfig::WpaPskOrPassphrase( heapless::String::try_from(passphrase).map_err(|_| Error::Overflow)?, @@ -171,8 +242,8 @@ impl<'a, AT: AtatClient> Control<'a, AT> { }) .await?; - self.at - .send(ExecWifiStationAction { + self.at_client + .send(&ExecWifiStationAction { config_id: CONFIG_ID, action: WifiStationAction::Activate, }) @@ -189,8 +260,8 @@ impl<'a, AT: AtatClient> Control<'a, AT> { match self.get_wifi_status().await? { WifiStatusVal::Disabled => {} WifiStatusVal::Disconnected | WifiStatusVal::Connected => { - self.at - .send(ExecWifiStationAction { + self.at_client + .send(&ExecWifiStationAction { config_id: CONFIG_ID, action: WifiStationAction::Deactivate, }) @@ -227,11 +298,12 @@ impl<'a, AT: AtatClient> Control<'a, AT> { } pub async fn gpio_set(&mut self, id: GPIOId, value: GPIOValue) -> Result<(), Error> { - self.at.send(WriteGPIO { id, value }).await?; + self.at_client.send(&WriteGPIO { id, value }).await?; Ok(()) } // FIXME: This could probably be improved + #[cfg(feature = "internal-network-stack")] pub async fn import_credentials( &mut self, data_type: SecurityDataType, @@ -243,8 +315,8 @@ impl<'a, AT: AtatClient> Control<'a, AT> { info!("Importing {:?} bytes as {:?}", data.len(), name); - self.at - .send(PrepareSecurityDataImport { + self.at_client + .send(&PrepareSecurityDataImport { data_type, data_size: data.len(), internal_name: name, @@ -253,8 +325,8 @@ impl<'a, AT: AtatClient> Control<'a, AT> { .await?; let import_data = self - .at - .send(SendSecurityDataImport { + .at_client + .send(&SendSecurityDataImport { data: atat::serde_bytes::Bytes::new(data), }) .await?; diff --git a/src/asynch/internal_stack.rs b/src/asynch/internal_stack.rs deleted file mode 100644 index 02cdbac..0000000 --- a/src/asynch/internal_stack.rs +++ /dev/null @@ -1,117 +0,0 @@ -// pub mod ublox_stack; - -use core::mem::MaybeUninit; - -use atat::{asynch::Client, AtatIngress}; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; -use embedded_hal::digital::OutputPin; -use embedded_io_async::{Read, Write}; - -use crate::command::custom_digest::EdmDigester; - -pub use super::resources::UbxResources as Resources; - -use super::{control::Control, runner::Runner, state, AtHandle, UbloxUrc}; - -pub struct InternalRunner< - 'a, - R: Read, - W: Write, - RST: OutputPin, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> { - pub cellular_runner: Runner<'a, Client<'a, W, INGRESS_BUF_SIZE>, RST, URC_CAPACITY>, - pub ingress: atat::Ingress<'a, EdmDigester, UbloxUrc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, - pub reader: R, -} - -impl< - 'a, - R: Read, - W: Write, - RST: OutputPin, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, - > InternalRunner<'a, R, W, RST, INGRESS_BUF_SIZE, URC_CAPACITY> -{ - pub async fn run(&mut self) -> ! { - self.cellular_runner.init().await.unwrap(); - - embassy_futures::join::join( - self.ingress.read_from(&mut self.reader), - self.cellular_runner.run(), - ) - .await; - core::unreachable!() - } -} - -pub fn new_internal< - 'a, - R: Read, - W: Write, - RST: OutputPin, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, ->( - reader: R, - writer: W, - resources: &'a mut Resources, - reset: RST, -) -> ( - state::Device<'a, Client<'a, W, INGRESS_BUF_SIZE>, URC_CAPACITY>, - Control<'a, Client<'a, W, INGRESS_BUF_SIZE>>, - InternalRunner<'a, R, W, RST, INGRESS_BUF_SIZE, URC_CAPACITY>, -) { - // safety: this is a self-referential struct, however: - // - it can't move while the `'a` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let at_client_uninit: *mut MaybeUninit>> = - (&mut resources.at_client - as *mut MaybeUninit>>) - .cast(); - - unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( - writer, - &resources.res_slot, - &mut resources.cmd_buf, - atat::Config::default(), - ))); - - let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; - - let (ch_runner, net_device) = state::new( - &mut resources.ch, - AtHandle(at_client), - resources.urc_channel.subscribe().unwrap(), - ); - - let control = Control::new(ch_runner.state_runner(), AtHandle(at_client)); - - let runner = Runner::new( - ch_runner, - AtHandle(at_client), - reset, - resources.urc_channel.subscribe().unwrap(), - ); - - // runner.init().await.unwrap(); - // control.init().await.unwrap(); - - let ingress = atat::Ingress::new( - EdmDigester, - &mut resources.ingress_buf, - &resources.res_slot, - &resources.urc_channel, - ); - - let runner = InternalRunner { - cellular_runner: runner, - ingress, - reader, - }; - - (net_device, control, runner) -} diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index de8313c..a6ef5fb 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,4 +1,6 @@ +mod at_udp_socket; pub mod control; +pub mod network; mod resources; pub mod runner; #[cfg(feature = "ublox-sockets")] @@ -6,18 +8,12 @@ pub mod ublox_stack; pub(crate) mod state; +pub use resources::Resources; +pub use runner::Runner; + #[cfg(feature = "internal-network-stack")] mod internal_stack; -#[cfg(feature = "internal-network-stack")] -pub use internal_stack::{new_internal, InternalRunner, Resources}; - -#[cfg(feature = "ppp")] -mod ppp; -#[cfg(feature = "ppp")] -pub use ppp::{new_ppp, PPPRunner, Resources}; - -use atat::asynch::AtatClient; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; +use embedded_io_async::{BufRead, Error as _, ErrorKind, Read, Write}; #[cfg(feature = "edm")] pub type UbloxUrc = crate::command::edm::urc::EdmEvent; @@ -25,24 +21,34 @@ pub type UbloxUrc = crate::command::edm::urc::EdmEvent; #[cfg(not(feature = "edm"))] pub type UbloxUrc = crate::command::Urc; -pub struct AtHandle<'d, AT: AtatClient>(&'d Mutex); +pub struct ReadWriteAdapter(pub R, pub W); + +impl embedded_io_async::ErrorType for ReadWriteAdapter { + type Error = ErrorKind; +} + +impl Read for ReadWriteAdapter { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.0.read(buf).await.map_err(|e| e.kind()) + } +} + +impl BufRead for ReadWriteAdapter { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.0.fill_buf().await.map_err(|e| e.kind()) + } -impl<'d, AT: AtatClient> AtHandle<'d, AT> { - #[cfg(feature = "edm")] - async fn send(&mut self, cmd: Cmd) -> Result { - self.send_raw(crate::command::edm::EdmAtCmdWrapper(cmd)) - .await + fn consume(&mut self, amt: usize) { + self.0.consume(amt) } +} - #[cfg(not(feature = "edm"))] - async fn send(&mut self, cmd: Cmd) -> Result { - self.send_raw(cmd).await +impl Write for ReadWriteAdapter { + async fn write(&mut self, buf: &[u8]) -> Result { + self.1.write(buf).await.map_err(|e| e.kind()) } - async fn send_raw( - &mut self, - cmd: Cmd, - ) -> Result { - self.0.lock().await.send_retry::(&cmd).await + async fn flush(&mut self) -> Result<(), Self::Error> { + self.1.flush().await.map_err(|e| e.kind()) } } diff --git a/src/asynch/network.rs b/src/asynch/network.rs new file mode 100644 index 0000000..5b7bcae --- /dev/null +++ b/src/asynch/network.rs @@ -0,0 +1,292 @@ +use core::str::FromStr as _; + +use atat::{asynch::AtatClient, UrcChannel, UrcSubscription}; +use embassy_time::{with_timeout, Duration, Timer}; +use embedded_hal::digital::OutputPin as _; +use no_std_net::{Ipv4Addr, Ipv6Addr}; + +use crate::{ + command::{ + data_mode::{types::PeerConfigParameter, SetPeerConfiguration}, + general::SoftwareVersion, + network::{ + responses::NetworkStatusResponse, + types::{InterfaceType, NetworkStatus, NetworkStatusParameter}, + urc::{NetworkDown, NetworkUp}, + GetNetworkStatus, + }, + system::{RebootDCE, StoreCurrentConfig}, + wifi::{ + types::DisconnectReason, + urc::{WifiLinkConnected, WifiLinkDisconnected}, + }, + Urc, + }, + connection::WiFiState, + error::Error, + network::WifiNetwork, + WifiConfig, +}; + +use super::{runner::URC_SUBSCRIBERS, state, UbloxUrc}; + +pub struct NetDevice<'a, 'b, C, A, const URC_CAPACITY: usize> { + ch: &'b state::Runner<'a>, + config: &'b mut C, + at_client: A, + urc_subscription: UrcSubscription<'a, UbloxUrc, URC_CAPACITY, URC_SUBSCRIBERS>, +} + +impl<'a, 'b, C, A, const URC_CAPACITY: usize> NetDevice<'a, 'b, C, A, URC_CAPACITY> +where + C: WifiConfig<'a>, + A: AtatClient, +{ + pub fn new( + ch: &'b state::Runner<'a>, + config: &'b mut C, + at_client: A, + urc_channel: &'a UrcChannel, + ) -> Self { + Self { + ch, + config, + at_client, + urc_subscription: urc_channel.subscribe().unwrap(), + } + } + + pub(crate) async fn init(&mut self) -> Result<(), Error> { + // Initilize a new ublox device to a known state (set RS232 settings) + debug!("Initializing module"); + // Hard reset module + self.reset().await?; + + self.at_client.send(&SoftwareVersion).await?; + + if let Some(size) = C::TLS_IN_BUFFER_SIZE { + self.at_client + .send(&SetPeerConfiguration { + parameter: PeerConfigParameter::TlsInBuffer(size), + }) + .await?; + } + + if let Some(size) = C::TLS_OUT_BUFFER_SIZE { + self.at_client + .send(&SetPeerConfiguration { + parameter: PeerConfigParameter::TlsOutBuffer(size), + }) + .await?; + } + + Ok(()) + } + + pub async fn run(&mut self) -> ! { + loop { + let event = self.urc_subscription.next_message_pure().await; + + #[cfg(feature = "edm")] + let Some(event) = event.extract_urc() else { + continue; + }; + + self.handle_urc(event).await; + } + } + + async fn handle_urc(&mut self, event: Urc) { + debug!("GOT URC event"); + match event { + Urc::StartUp => { + error!("AT startup event?! Device restarted unintentionally!"); + } + Urc::WifiLinkConnected(WifiLinkConnected { + connection_id: _, + bssid, + channel, + }) => self.ch.update_connection_with(|con| { + con.wifi_state = WiFiState::Connected; + con.network + .replace(WifiNetwork::new_station(bssid, channel)); + con.activated = true; + }), + Urc::WifiLinkDisconnected(WifiLinkDisconnected { reason, .. }) => { + self.ch.update_connection_with(|con| match reason { + DisconnectReason::NetworkDisabled => { + con.wifi_state = WiFiState::Inactive; + } + DisconnectReason::SecurityProblems => { + error!("Wifi Security Problems"); + con.wifi_state = WiFiState::NotConnected; + } + _ => { + con.wifi_state = WiFiState::NotConnected; + } + }) + } + Urc::WifiAPUp(_) => todo!(), + Urc::WifiAPDown(_) => todo!(), + Urc::WifiAPStationConnected(_) => todo!(), + Urc::WifiAPStationDisconnected(_) => todo!(), + Urc::EthernetLinkUp(_) => todo!(), + Urc::EthernetLinkDown(_) => todo!(), + Urc::NetworkUp(NetworkUp { interface_id }) => { + drop(event); + self.network_status_callback(interface_id).await.ok(); + } + Urc::NetworkDown(NetworkDown { interface_id }) => { + drop(event); + self.network_status_callback(interface_id).await.ok(); + } + Urc::NetworkError(_) => todo!(), + _ => {} + } + } + + async fn network_status_callback(&mut self, interface_id: u8) -> Result<(), Error> { + let NetworkStatusResponse { + status: NetworkStatus::InterfaceType(InterfaceType::WifiStation), + .. + } = self + .at_client + .send(&GetNetworkStatus { + interface_id, + status: NetworkStatusParameter::InterfaceType, + }) + .await? + else { + return Err(Error::Network); + }; + + let NetworkStatusResponse { + status: NetworkStatus::IPv4Address(ipv4), + .. + } = self + .at_client + .send(&GetNetworkStatus { + interface_id, + status: NetworkStatusParameter::IPv4Address, + }) + .await? + else { + return Err(Error::Network); + }; + + let ipv4_up = core::str::from_utf8(ipv4.as_slice()) + .ok() + .and_then(|s| Ipv4Addr::from_str(s).ok()) + .map(|ip| !ip.is_unspecified()) + .unwrap_or_default(); + + let NetworkStatusResponse { + status: NetworkStatus::IPv6LinkLocalAddress(ipv6), + .. + } = self + .at_client + .send(&GetNetworkStatus { + interface_id, + status: NetworkStatusParameter::IPv6LinkLocalAddress, + }) + .await? + else { + return Err(Error::Network); + }; + + let ipv6_up = core::str::from_utf8(ipv6.as_slice()) + .ok() + .and_then(|s| Ipv6Addr::from_str(s).ok()) + .map(|ip| !ip.is_unspecified()) + .unwrap_or_default(); + + // Use `ipv4_up` & `ipv6_up` to determine link state + self.ch + .update_connection_with(|con| con.network_up = ipv4_up && ipv6_up); + + Ok(()) + } + + async fn wait_startup(&mut self, timeout: Duration) -> Result<(), Error> { + let fut = async { + loop { + let event = self.urc_subscription.next_message_pure().await; + + #[cfg(feature = "edm")] + let Some(event) = event.extract_urc() else { + continue; + }; + + match event { + Urc::StartUp => return, + _ => {} + } + } + }; + + with_timeout(timeout, fut).await.map_err(|_| Error::Timeout) + } + + pub async fn reset(&mut self) -> Result<(), Error> { + warn!("Hard resetting Ublox Short Range"); + self.config.reset_pin().unwrap().set_low().ok(); + Timer::after(Duration::from_millis(100)).await; + self.config.reset_pin().unwrap().set_high().ok(); + + self.wait_startup(Duration::from_secs(4)).await?; + + #[cfg(feature = "edm")] + self.enter_edm(Duration::from_secs(4)).await?; + + Ok(()) + } + + pub async fn restart(&mut self, store: bool) -> Result<(), Error> { + warn!("Soft resetting Ublox Short Range"); + if store { + self.at_client.send(&StoreCurrentConfig).await?; + } + + self.at_client.send(&RebootDCE).await?; + + self.wait_startup(Duration::from_secs(10)).await?; + + info!("Module started again"); + #[cfg(feature = "edm")] + self.enter_edm(Duration::from_secs(4)).await?; + + Ok(()) + } + + #[cfg(feature = "edm")] + pub async fn enter_edm(&mut self, timeout: Duration) -> Result<(), Error> { + info!("Entering EDM mode"); + + // Switch to EDM on Init. If in EDM, fail and check with autosense + let fut = async { + loop { + // Ignore AT results until we are successful in EDM mode + if let Ok(_) = self.at_client.send(SwitchToEdmCommand).await { + // After executing the data mode command or the extended data + // mode command, a delay of 50 ms is required before start of + // data transmission. + Timer::after(Duration::from_millis(50)).await; + break; + } + Timer::after(Duration::from_millis(10)).await; + } + }; + + with_timeout(timeout, fut) + .await + .map_err(|_| Error::Timeout)?; + + self.at_client + .send(crate::command::system::SetEcho { + on: crate::command::system::types::EchoOn::Off, + }) + .await?; + + Ok(()) + } +} diff --git a/src/asynch/ppp.rs b/src/asynch/ppp.rs deleted file mode 100644 index dfd3588..0000000 --- a/src/asynch/ppp.rs +++ /dev/null @@ -1,338 +0,0 @@ -use core::mem::MaybeUninit; - -use atat::{ - asynch::{AtatClient, Client, SimpleClient}, - AtatIngress, -}; -use embassy_futures::select::Either; -use embassy_net::{ - udp::{PacketMetadata, UdpSocket}, - IpEndpoint, Ipv4Address, -}; -use embassy_sync::{ - blocking_mutex::raw::NoopRawMutex, - mutex::Mutex, - pipe::{Reader, Writer}, -}; -use embassy_time::{Duration, Instant, Timer}; -use embedded_hal::digital::OutputPin; -use embedded_io_async::{BufRead, Read, Write}; - -use crate::command::{ - data_mode::{self, ChangeMode}, - system::{self, SetEcho}, -}; - -use super::{control::Control, resources::UbxResources, runner::Runner, state, AtHandle, UbloxUrc}; - -const PPP_AT_PORT: u16 = 23; -pub const SOCKET_BUF_SIZE: usize = 128; - -pub type Resources< - 'a, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> = UbxResources< - Writer<'a, NoopRawMutex, SOCKET_BUF_SIZE>, - CMD_BUF_SIZE, - INGRESS_BUF_SIZE, - URC_CAPACITY, ->; - -pub fn new_ppp< - 'a, - RST: OutputPin, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, ->( - resources: &'a mut Resources<'a, CMD_BUF_SIZE, INGRESS_BUF_SIZE, URC_CAPACITY>, - reset: RST, -) -> ( - embassy_net_ppp::Device<'a>, - Control<'a, Client<'a, Writer<'a, NoopRawMutex, SOCKET_BUF_SIZE>, INGRESS_BUF_SIZE>>, - PPPRunner<'a, RST, INGRESS_BUF_SIZE, URC_CAPACITY>, -) { - let ch_runner = state::new_ppp(&mut resources.ch); - let state_ch = ch_runner.state_runner(); - - let (control_rx_reader, control_rx_writer) = resources.control_rx.split(); - let (control_tx_reader, control_tx_writer) = resources.control_tx.split(); - - // safety: this is a self-referential struct, however: - // - it can't move while the `'a` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let at_client_uninit: *mut MaybeUninit< - Mutex< - NoopRawMutex, - Client<'a, Writer<'a, NoopRawMutex, SOCKET_BUF_SIZE>, INGRESS_BUF_SIZE>, - >, - > = (&mut resources.at_client - as *mut MaybeUninit< - Mutex< - NoopRawMutex, - Client<'static, Writer<'a, NoopRawMutex, SOCKET_BUF_SIZE>, INGRESS_BUF_SIZE>, - >, - >) - .cast(); - - unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( - control_tx_writer, - &resources.res_slot, - &mut resources.cmd_buf, - atat::Config::default(), - ))); - - let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; - - let wifi_runner = Runner::new( - ch_runner, - AtHandle(at_client), - reset, - resources.urc_channel.subscribe().unwrap(), - ); - - let ingress = atat::Ingress::new( - atat::AtDigester::::new(), - &mut resources.ingress_buf, - &resources.res_slot, - &resources.urc_channel, - ); - - let control = Control::new(state_ch, AtHandle(at_client)); - - let (net_device, ppp_runner) = embassy_net_ppp::new(&mut resources.ppp_state); - - let runner = PPPRunner { - ppp_runner, - wifi_runner, - ingress, - control_rx_reader, - control_rx_writer, - control_tx_reader, - }; - - (net_device, control, runner) -} - -pub struct PPPRunner<'a, RST: OutputPin, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> { - pub ppp_runner: embassy_net_ppp::Runner<'a>, - pub wifi_runner: Runner< - 'a, - Client<'a, Writer<'a, NoopRawMutex, SOCKET_BUF_SIZE>, INGRESS_BUF_SIZE>, - RST, - URC_CAPACITY, - >, - pub ingress: - atat::Ingress<'a, atat::AtDigester, UbloxUrc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, - pub control_rx_reader: Reader<'a, NoopRawMutex, SOCKET_BUF_SIZE>, - pub control_rx_writer: Writer<'a, NoopRawMutex, SOCKET_BUF_SIZE>, - pub control_tx_reader: Reader<'a, NoopRawMutex, SOCKET_BUF_SIZE>, -} - -impl<'a, RST: OutputPin, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> - PPPRunner<'a, RST, INGRESS_BUF_SIZE, URC_CAPACITY> -{ - async fn configure(at_client: &mut A) -> Result<(), atat::Error> { - let _ = at_client - .send(&ChangeMode { - mode: data_mode::types::Mode::CommandMode, - }) - .await; - - at_client - .send(&SetEcho { - on: system::types::EchoOn::Off, - }) - .await?; - - // Initialize `ublox` module to desired baudrate - at_client - .send(&system::SetRS232Settings { - baud_rate: system::types::BaudRate::B115200, - flow_control: system::types::FlowControl::On, - data_bits: 8, - stop_bits: system::types::StopBits::One, - parity: system::types::Parity::None, - change_after_confirm: system::types::ChangeAfterConfirm::ChangeAfterOK, - }) - .await?; - - Ok(()) - } - - pub async fn run( - &mut self, - mut iface: RW, - stack: &embassy_net::Stack>, - ) -> ! { - // self.wifi_runner.init().await.unwrap(); - // Timer::after(Duration::from_secs(4)).await; - - loop { - // Reset modem - self.wifi_runner.reset().await; - - Timer::after(Duration::from_secs(1)).await; - - let control_fut = async { - stack.wait_config_up().await; - - let mut rx_meta = [PacketMetadata::EMPTY; 1]; - let mut tx_meta = [PacketMetadata::EMPTY; 1]; - let mut socket_rx_buf = [0u8; 32]; - let mut socket_tx_buf = [0u8; 32]; - let mut socket = UdpSocket::new( - stack, - &mut rx_meta, - &mut socket_rx_buf, - &mut tx_meta, - &mut socket_tx_buf, - ); - - let endpoint = stack.config_v4().unwrap(); - - info!("Socket bound!"); - socket - .bind((endpoint.address.address(), PPP_AT_PORT)) - .unwrap(); - - let mut tx_buf = [0u8; 32]; - let mut rx_buf = [0u8; 32]; - - loop { - match embassy_futures::select::select( - self.control_tx_reader.read(&mut tx_buf), - socket.recv_from(&mut rx_buf), - ) - .await - { - Either::First(n) => { - socket - .send_to( - &tx_buf[..n], - (Ipv4Address::new(172, 30, 0, 251), PPP_AT_PORT), - ) - .await - .unwrap(); - } - Either::Second(Ok((n, _))) => { - self.control_rx_writer - .write_all(&rx_buf[..n]) - .await - .unwrap(); - } - Either::Second(_) => {} - } - } - }; - - let ppp_fut = async { - let mut fails = 0; - let mut last_start = None; - - loop { - if let Some(last_start) = last_start { - Timer::at(last_start + Duration::from_secs(10)).await; - // Do not attempt to start too fast. - - // If was up stably for at least 1 min, reset fail counter. - if Instant::now() > last_start + Duration::from_secs(60) { - fails = 0; - } else { - fails += 1; - if fails == 10 { - warn!("modem: PPP failed too much, rebooting modem."); - break; - } - } - } - last_start = Some(Instant::now()); - - let mut buf = [0u8; 64]; - let mut at_client = SimpleClient::new( - &mut iface, - atat::AtDigester::::new(), - &mut buf, - atat::Config::default(), - ); - - if let Err(e) = Self::configure(&mut at_client).await { - warn!("modem: configure failed {:?}", e); - continue; - } - - Timer::after(Duration::from_secs(2)).await; - - // Send AT command `ATO3` to enter PPP mode - let res = at_client - .send(&ChangeMode { - mode: data_mode::types::Mode::PPPMode, - }) - .await; - - if let Err(e) = res { - warn!("ppp dial failed {:?}", e); - continue; - } - - drop(at_client); - - // Drain the UART - let _ = embassy_time::with_timeout(Duration::from_secs(2), async { - loop { - iface.read(&mut buf).await.ok(); - } - }) - .await; - - info!("RUNNING PPP"); - let config = embassy_net_ppp::Config { - username: b"", - password: b"", - }; - let res = self - .ppp_runner - .run(&mut iface, config, |ipv4| { - let Some(addr) = ipv4.address else { - warn!("PPP did not provide an IP address."); - return; - }; - let mut dns_servers = heapless::Vec::new(); - for s in ipv4.dns_servers.iter().flatten() { - let _ = - dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); - } - let config = - embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { - address: embassy_net::Ipv4Cidr::new( - embassy_net::Ipv4Address::from_bytes(&addr.0), - 0, - ), - gateway: None, - dns_servers, - }); - - stack.set_config_v4(config); - }) - .await; - - info!("ppp failed"); - } - }; - - let ingress_fut = async { - self.ingress.read_from(&mut self.control_rx_reader).await; - }; - - embassy_futures::select::select4( - ppp_fut, - ingress_fut, - control_fut, - self.wifi_runner.run(), - ) - .await; - } - } -} diff --git a/src/asynch/resources.rs b/src/asynch/resources.rs index d55fb2f..90cbe50 100644 --- a/src/asynch/resources.rs +++ b/src/asynch/resources.rs @@ -1,13 +1,8 @@ -use core::mem::MaybeUninit; +use atat::{ResponseSlot, UrcChannel}; -use atat::{asynch::Client, ResponseSlot, UrcChannel}; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; -use embedded_io_async::Write; +use super::{runner::URC_SUBSCRIBERS, state, UbloxUrc}; -use super::{state, UbloxUrc}; - -pub struct UbxResources< - W: Write, +pub struct Resources< const CMD_BUF_SIZE: usize, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize, @@ -15,27 +10,21 @@ pub struct UbxResources< pub(crate) ch: state::State, pub(crate) res_slot: ResponseSlot, - pub(crate) urc_channel: UrcChannel, + pub(crate) urc_channel: UrcChannel, pub(crate) cmd_buf: [u8; CMD_BUF_SIZE], pub(crate) ingress_buf: [u8; INGRESS_BUF_SIZE], +} - pub(crate) at_client: MaybeUninit>>, - - #[cfg(feature = "ppp")] - pub(crate) ppp_state: embassy_net_ppp::State<2, 2>, - - #[cfg(feature = "ppp")] - pub(crate) control_rx: embassy_sync::pipe::Pipe, - #[cfg(feature = "ppp")] - pub(crate) control_tx: embassy_sync::pipe::Pipe, +impl Default + for Resources +{ + fn default() -> Self { + Self::new() + } } -impl< - W: Write, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, - > UbxResources +impl + Resources { pub fn new() -> Self { Self { @@ -45,16 +34,6 @@ impl< urc_channel: UrcChannel::new(), cmd_buf: [0; CMD_BUF_SIZE], ingress_buf: [0; INGRESS_BUF_SIZE], - - at_client: MaybeUninit::uninit(), - - #[cfg(feature = "ppp")] - ppp_state: embassy_net_ppp::State::new(), - - #[cfg(feature = "ppp")] - control_rx: embassy_sync::pipe::Pipe::new(), - #[cfg(feature = "ppp")] - control_tx: embassy_sync::pipe::Pipe::new(), } } } diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index fb0be9d..335d809 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -1,365 +1,299 @@ -use core::str::FromStr; - use super::{ - state::{self, LinkState}, - UbloxUrc, + control::{Control, ControlResources}, + network::NetDevice, + state, Resources, UbloxUrc, }; #[cfg(feature = "edm")] use crate::command::edm::SwitchToEdmCommand; use crate::{ + asynch::at_udp_socket::AtUdpSocket, command::{ - general::SoftwareVersion, - network::{ - responses::NetworkStatusResponse, - types::{InterfaceType, NetworkStatus, NetworkStatusParameter}, - urc::{NetworkDown, NetworkUp}, - GetNetworkStatus, - }, - system::{ - types::{BaudRate, ChangeAfterConfirm, FlowControl, Parity, StopBits}, - RebootDCE, SetRS232Settings, StoreCurrentConfig, - }, - wifi::{ - types::DisconnectReason, - urc::{WifiLinkConnected, WifiLinkDisconnected}, - }, + data_mode::{self, ChangeMode}, Urc, }, - connection::{WiFiState, WifiConnection}, - error::Error, - network::WifiNetwork, + WifiConfig, +}; +use atat::{ + asynch::{AtatClient, SimpleClient}, + AtatIngress as _, UrcChannel, }; -use atat::{asynch::AtatClient, UrcSubscription}; -use embassy_time::{with_timeout, Duration, Timer}; -use embedded_hal::digital::OutputPin; -use no_std_net::{Ipv4Addr, Ipv6Addr}; +use embassy_futures::select::Either; +use embassy_net::{ + udp::{PacketMetadata, UdpSocket}, + Ipv4Address, +}; +use embassy_time::{Duration, Instant, Timer}; +use embedded_io_async::{BufRead, Read, Write}; -use super::AtHandle; +pub(crate) const URC_SUBSCRIBERS: usize = 3; /// Background runner for the Ublox Module. /// /// You must call `.run()` in a background task for the Ublox Module to operate. -pub struct Runner<'d, AT: AtatClient, RST: OutputPin, const URC_CAPACITY: usize> { - ch: state::Runner<'d>, - at: AtHandle<'d, AT>, - reset: RST, - wifi_connection: Option, - urc_subscription: UrcSubscription<'d, UbloxUrc, URC_CAPACITY, 2>, +pub struct Runner<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> { + iface: (R, W), + + ch: state::Runner<'a>, + config: C, + + pub urc_channel: &'a UrcChannel, + + pub ingress: atat::Ingress< + 'a, + atat::AtDigester, + Urc, + INGRESS_BUF_SIZE, + URC_CAPACITY, + URC_SUBSCRIBERS, + >, + pub cmd_buf: &'a mut [u8], + pub res_slot: &'a atat::ResponseSlot, + + #[cfg(feature = "ppp")] + ppp_runner: Option>, } -impl< - 'd, - AT: AtatClient, - // AT: AtatClient + atat::UartExt, - RST: OutputPin, - const URC_CAPACITY: usize, - > Runner<'d, AT, RST, URC_CAPACITY> +impl<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> + Runner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> +where + R: BufRead + Read, + W: Write, + C: WifiConfig<'a> + 'a, { - pub(crate) fn new( - ch: state::Runner<'d>, - at: AtHandle<'d, AT>, - reset: RST, - urc_subscription: UrcSubscription<'d, UbloxUrc, URC_CAPACITY, 2>, + pub fn new( + iface: (R, W), + resources: &'a mut Resources, + config: C, ) -> Self { + let ch_runner = state::Runner::new(&mut resources.ch); + + let ingress = atat::Ingress::new( + atat::AtDigester::new(), + &mut resources.ingress_buf, + &resources.res_slot, + &resources.urc_channel, + ); + Self { - ch, - at, - reset, - wifi_connection: None, - urc_subscription, + iface, + + ch: ch_runner, + config, + urc_channel: &resources.urc_channel, + + ingress, + cmd_buf: &mut resources.cmd_buf, + res_slot: &resources.res_slot, + + #[cfg(feature = "ppp")] + ppp_runner: None, } } - pub(crate) async fn init(&mut self) -> Result<(), Error> { - // Initilize a new ublox device to a known state (set RS232 settings) - debug!("Initializing module"); - // Hard reset module - self.reset().await?; - - // ## 2.2.6.1 AT request serial settings (EDM mode) - // - // The AT+UMRS command to change serial settings does not work exactly - // the same as in command mode. When executed in the extended data mode, - // it is not possible to change the settings directly using the - // parameter. Instead, the - // parameter must be set to 0 and the serial settings will take effect - // when the module is reset. - self.at - .send(SetRS232Settings { - baud_rate: BaudRate::B115200, - flow_control: FlowControl::On, - data_bits: 8, - stop_bits: StopBits::One, - parity: Parity::None, - change_after_confirm: ChangeAfterConfirm::StoreAndReset, - }) - .await?; - - self.restart(true).await?; - - self.at.send(SoftwareVersion).await?; - - // Move to control - // if let Some(size) = self.config.tls_in_buffer_size { - // self.at - // .send(SetPeerConfiguration { - // parameter: PeerConfigParameter::TlsInBuffer(size), - // }) - // .await?; - // } - - // if let Some(size) = self.config.tls_out_buffer_size { - // self.at - // .send(SetPeerConfiguration { - // parameter: PeerConfigParameter::TlsOutBuffer(size), - // }) - // .await?; - // } - - Ok(()) + pub fn control<'r, D: embassy_net::driver::Driver>( + &self, + resources: &'r mut ControlResources, + stack: &'r embassy_net::Stack, + ) -> Control<'a, 'r, URC_CAPACITY> { + Control::new(self.ch.clone(), &self.urc_channel, resources, stack) } - async fn wait_startup(&mut self, timeout: Duration) -> Result<(), Error> { - let fut = async { - loop { - let event = self.urc_subscription.next_message_pure().await; - - #[cfg(feature = "edm")] - let Some(event) = event.extract_urc() else { - continue; - }; - - match event { - Urc::StartUp => return, - _ => {} - } - } - }; + #[cfg(feature = "ppp")] + pub fn ppp_stack<'d: 'a, const N_RX: usize, const N_TX: usize>( + &mut self, + ppp_state: &'d mut embassy_net_ppp::State, + ) -> embassy_net_ppp::Device<'d> { + let (net_device, ppp_runner) = embassy_net_ppp::new(ppp_state); + self.ppp_runner.replace(ppp_runner); + net_device + } - with_timeout(timeout, fut).await.map_err(|_| Error::Timeout) + #[cfg(feature = "internal-network-stack")] + pub fn internal_stack(&mut self) -> state::Device { + state::Device { + shared: &self.ch.shared, + urc_subscription: self.urc_channel.subscribe().unwrap(), + } } - pub async fn reset(&mut self) -> Result<(), Error> { - warn!("Hard resetting Ublox Short Range"); - self.reset.set_low().ok(); - Timer::after(Duration::from_millis(100)).await; - self.reset.set_high().ok(); + pub async fn run(mut self, stack: &embassy_net::Stack) -> ! { + #[cfg(feature = "ppp")] + let mut ppp_runner = self.ppp_runner.take().unwrap(); - self.wait_startup(Duration::from_secs(4)).await?; + let at_config = atat::Config::default(); + loop { + // Run the cellular device from full power down to the + // `DataEstablished` state, handling power on, module configuration, + // network registration & operator selection and PDP context + // activation along the way. + // + // This is all done directly on the serial line, before setting up + // virtual channels through multiplexing. + { + let at_client = atat::asynch::Client::new( + &mut self.iface.1, + self.res_slot, + self.cmd_buf, + at_config, + ); + let mut wifi_device = + NetDevice::new(&self.ch, &mut self.config, at_client, self.urc_channel); + + // Clean up and start from completely powered off state. Ignore URCs in the process. + self.ingress.clear(); + + match embassy_futures::select::select( + self.ingress.read_from(&mut self.iface.0), + wifi_device.init(), + ) + .await + { + Either::First(_) => { + // This has return type never (`-> !`) + unreachable!() + } + Either::Second(Err(_)) => { + // Reboot the wifi module and try again! + continue; + } + Either::Second(Ok(_)) => { + // All good! We are now ready to start communication services! + } + } + } - #[cfg(feature = "edm")] - self.enter_edm(Duration::from_secs(4)).await?; + #[cfg(feature = "ppp")] + let ppp_fut = async { + let mut iface = super::ReadWriteAdapter(&mut self.iface.0, &mut self.iface.1); - Ok(()) - } + let mut fails = 0; + let mut last_start = None; - pub async fn restart(&mut self, store: bool) -> Result<(), Error> { - warn!("Soft resetting Ublox Short Range"); - if store { - self.at.send(StoreCurrentConfig).await?; - } + loop { + if let Some(last_start) = last_start { + Timer::at(last_start + Duration::from_secs(10)).await; + // Do not attempt to start too fast. - self.at.send(RebootDCE).await?; + // If was up stably for at least 1 min, reset fail counter. + if Instant::now() > last_start + Duration::from_secs(60) { + fails = 0; + } else { + fails += 1; + if fails == 10 { + warn!("modem: PPP failed too much, rebooting modem."); + break; + } + } + } + last_start = Some(Instant::now()); + + { + let mut buf = [0u8; 64]; + + let mut at_client = SimpleClient::new( + &mut iface, + atat::AtDigester::::new(), + &mut buf, + at_config, + ); + + // Send AT command `ATO3` to enter PPP mode + let res = at_client + .send(&ChangeMode { + mode: data_mode::types::Mode::PPPMode, + }) + .await; + + if let Err(e) = res { + warn!("ppp dial failed {:?}", e); + continue; + } - self.wait_startup(Duration::from_secs(10)).await?; + drop(at_client); - info!("Module started again"); - #[cfg(feature = "edm")] - self.enter_edm(Duration::from_secs(4)).await?; + // Drain the UART + let _ = embassy_time::with_timeout(Duration::from_secs(2), async { + loop { + iface.read(&mut buf).await.ok(); + } + }) + .await; - Ok(()) - } + Timer::after(Duration::from_millis(100)).await; + } - #[cfg(feature = "edm")] - pub async fn enter_edm(&mut self, timeout: Duration) -> Result<(), Error> { - info!("Entering EDM mode"); - - // Switch to EDM on Init. If in EDM, fail and check with autosense - let fut = async { - loop { - // Ignore AT results until we are successful in EDM mode - if let Ok(_) = self.at.send(SwitchToEdmCommand).await { - // After executing the data mode command or the extended data - // mode command, a delay of 50 ms is required before start of - // data transmission. - Timer::after(Duration::from_millis(50)).await; - break; + info!("RUNNING PPP"); + let res = ppp_runner + .run(&mut iface, C::PPP_CONFIG, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = heapless::Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = + dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); + } + let config = + embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new( + embassy_net::Ipv4Address::from_bytes(&addr.0), + 0, + ), + gateway: None, + dns_servers, + }); + + stack.set_config_v4(config); + }) + .await; + + info!("ppp failed: {:?}", res); } - Timer::after(Duration::from_millis(10)).await; - } - }; + }; - with_timeout(timeout, fut) - .await - .map_err(|_| Error::Timeout)?; + let network_fut = async { + stack.wait_config_up().await; - self.at - .send(crate::command::system::SetEcho { - on: crate::command::system::types::EchoOn::Off, - }) - .await?; + let mut rx_meta = [PacketMetadata::EMPTY; 1]; + let mut tx_meta = [PacketMetadata::EMPTY; 1]; + let mut socket_rx_buf = [0u8; 64]; + let mut socket_tx_buf = [0u8; 64]; + let mut socket = UdpSocket::new( + stack, + &mut rx_meta, + &mut socket_rx_buf, + &mut tx_meta, + &mut socket_tx_buf, + ); - Ok(()) - } + let endpoint = stack.config_v4().unwrap(); - pub async fn is_link_up(&mut self) -> Result { - // Determine link state - let link_state = match self.wifi_connection { - Some(ref conn) - if conn.network_up && matches!(conn.wifi_state, WiFiState::Connected) => - { - LinkState::Up - } - _ => LinkState::Down, - }; + info!("Socket bound!"); + socket + .bind((Ipv4Address::new(172, 30, 0, 252), AtUdpSocket::PPP_AT_PORT)) + .unwrap(); - self.ch.set_link_state(link_state); + let at_socket = AtUdpSocket(socket); - Ok(link_state == LinkState::Up) - } + let at_client = + atat::asynch::Client::new(&at_socket, self.res_slot, self.cmd_buf, at_config); - pub async fn run(&mut self) -> ! { - loop { - let wait_link_up = { - let event = self.urc_subscription.next_message_pure().await; - - #[cfg(feature = "edm")] - let Some(event) = event.extract_urc() else { - continue; - }; - - match event { - Urc::StartUp => { - error!("AT startup event?! Device restarted unintentionally!"); - false - } - Urc::WifiLinkConnected(WifiLinkConnected { - connection_id: _, - bssid, - channel, - }) => { - if let Some(ref mut con) = self.wifi_connection { - con.wifi_state = WiFiState::Connected; - con.network.bssid = bssid; - con.network.channel = channel; - } else { - debug!("[URC] Active network config discovered"); - self.wifi_connection.replace( - WifiConnection::new( - WifiNetwork::new_station(bssid, channel), - WiFiState::Connected, - 255, - ) - .activate(), - ); - } - true - } - Urc::WifiLinkDisconnected(WifiLinkDisconnected { reason, .. }) => { - if let Some(ref mut con) = self.wifi_connection { - match reason { - DisconnectReason::NetworkDisabled => { - con.wifi_state = WiFiState::Inactive; - } - DisconnectReason::SecurityProblems => { - error!("Wifi Security Problems"); - con.wifi_state = WiFiState::NotConnected; - } - _ => { - con.wifi_state = WiFiState::NotConnected; - } - } - } + let mut wifi_device = + NetDevice::new(&self.ch, &mut self.config, at_client, self.urc_channel); - true - } - Urc::WifiAPUp(_) => todo!(), - Urc::WifiAPDown(_) => todo!(), - Urc::WifiAPStationConnected(_) => todo!(), - Urc::WifiAPStationDisconnected(_) => todo!(), - Urc::EthernetLinkUp(_) => todo!(), - Urc::EthernetLinkDown(_) => todo!(), - Urc::NetworkUp(NetworkUp { interface_id }) => { - drop(event); - self.network_status_callback(interface_id).await.ok(); - true - } - Urc::NetworkDown(NetworkDown { interface_id }) => { - drop(event); - self.network_status_callback(interface_id).await.ok(); - true - } - Urc::NetworkError(_) => todo!(), - _ => false, - } + embassy_futures::join::join(self.ingress.read_from(&at_socket), wifi_device.run()) + .await; }; - if wait_link_up { - self.is_link_up().await.unwrap(); + match embassy_futures::select::select(ppp_fut, network_fut).await { + Either::First(_) => { + warn!("Breaking to reboot module from PPP"); + } + Either::Second(_) => { + warn!("Breaking to reboot module from network runner"); + } } } } - - async fn network_status_callback(&mut self, interface_id: u8) -> Result<(), Error> { - let NetworkStatusResponse { - status: NetworkStatus::InterfaceType(InterfaceType::WifiStation), - .. - } = self - .at - .send(GetNetworkStatus { - interface_id, - status: NetworkStatusParameter::InterfaceType, - }) - .await? - else { - return Err(Error::Network); - }; - - let NetworkStatusResponse { - status: NetworkStatus::Gateway(ipv4), - .. - } = self - .at - .send(GetNetworkStatus { - interface_id, - status: NetworkStatusParameter::Gateway, - }) - .await? - else { - return Err(Error::Network); - }; - - let ipv4_up = core::str::from_utf8(ipv4.as_slice()) - .ok() - .and_then(|s| Ipv4Addr::from_str(s).ok()) - .map(|ip| !ip.is_unspecified()) - .unwrap_or_default(); - - let NetworkStatusResponse { - status: NetworkStatus::IPv6LinkLocalAddress(ipv6), - .. - } = self - .at - .send(GetNetworkStatus { - interface_id, - status: NetworkStatusParameter::IPv6LinkLocalAddress, - }) - .await? - else { - return Err(Error::Network); - }; - - let ipv6_up = core::str::from_utf8(ipv6.as_slice()) - .ok() - .and_then(|s| Ipv6Addr::from_str(s).ok()) - .map(|ip| !ip.is_unspecified()) - .unwrap_or_default(); - - // Use `ipv4_up` & `ipv6_up` to determine link state - if let Some(ref mut con) = self.wifi_connection { - con.network_up = ipv4_up && ipv6_up; - } - - Ok(()) - } } diff --git a/src/asynch/state.rs b/src/asynch/state.rs index 816e1ac..8ec431e 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -1,15 +1,15 @@ #![allow(dead_code)] use core::cell::RefCell; -use core::mem::MaybeUninit; -use core::task::Context; +use core::future::poll_fn; +use core::task::{Context, Poll}; -use atat::asynch::AtatClient; -use atat::UrcSubscription; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::waitqueue::WakerRegistration; +use crate::connection::{WiFiState, WifiConnection}; + /// The link state of a network device. #[derive(PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -20,43 +20,40 @@ pub enum LinkState { Up, } -use super::{AtHandle, UbloxUrc}; - pub struct State { - inner: MaybeUninit, + shared: Mutex>, } impl State { pub const fn new() -> Self { Self { - inner: MaybeUninit::uninit(), + shared: Mutex::new(RefCell::new(Shared { + link_state: LinkState::Down, + wifi_connection: WifiConnection::new(), + state_waker: WakerRegistration::new(), + connection_waker: WakerRegistration::new(), + })), } } } -struct StateInner { - shared: Mutex>, -} - /// State of the LinkState pub struct Shared { link_state: LinkState, - waker: WakerRegistration, + wifi_connection: WifiConnection, + state_waker: WakerRegistration, + connection_waker: WakerRegistration, } +#[derive(Clone)] pub struct Runner<'d> { shared: &'d Mutex>, } -#[derive(Clone, Copy)] -pub struct StateRunner<'d> { - shared: &'d Mutex>, -} - impl<'d> Runner<'d> { - pub fn state_runner(&self) -> StateRunner<'d> { - StateRunner { - shared: self.shared, + pub fn new(state: &'d mut State) -> Self { + Self { + shared: &state.shared, } } @@ -64,96 +61,62 @@ impl<'d> Runner<'d> { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.link_state = state; - s.waker.wake(); + s.state_waker.wake(); }); } -} -impl<'d> StateRunner<'d> { - pub fn set_link_state(&self, state: LinkState) { + pub fn link_state(&mut self, cx: &mut Context) -> LinkState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.link_state = state; - s.waker.wake(); - }); + s.state_waker.register(cx.waker()); + s.link_state + }) } - pub fn link_state(&mut self, cx: &mut Context) -> LinkState { + pub fn update_connection_with(&self, f: impl FnOnce(&mut WifiConnection)) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.waker.register(cx.waker()); - s.link_state + f(&mut s.wifi_connection); + info!( + "Connection status changed! Connected: {:?}", + s.wifi_connection.is_connected() + ); + + if s.wifi_connection.network_up + && matches!(s.wifi_connection.wifi_state, WiFiState::Connected) + { + s.link_state = LinkState::Up; + } else { + s.link_state = LinkState::Down; + } + + s.state_waker.wake(); + s.connection_waker.wake(); }) } -} -pub fn new<'d, AT: AtatClient, const URC_CAPACITY: usize>( - state: &'d mut State, - at: AtHandle<'d, AT>, - urc_subscription: UrcSubscription<'d, UbloxUrc, URC_CAPACITY, 2>, -) -> (Runner<'d>, Device<'d, AT, URC_CAPACITY>) { - // safety: this is a self-referential struct, however: - // - it can't move while the `'d` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let state_uninit: *mut MaybeUninit = - (&mut state.inner as *mut MaybeUninit).cast(); - - let state = unsafe { &mut *state_uninit }.write(StateInner { - shared: Mutex::new(RefCell::new(Shared { - link_state: LinkState::Down, - waker: WakerRegistration::new(), - })), - }); - - ( - Runner { - shared: &state.shared, - }, - Device { - shared: TestShared { - inner: &state.shared, - }, - urc_subscription, - at, - }, - ) -} - -pub fn new_ppp<'d>(state: &'d mut State) -> Runner<'d> { - // safety: this is a self-referential struct, however: - // - it can't move while the `'d` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let state_uninit: *mut MaybeUninit = - (&mut state.inner as *mut MaybeUninit).cast(); - - let state = unsafe { &mut *state_uninit }.write(StateInner { - shared: Mutex::new(RefCell::new(Shared { - link_state: LinkState::Down, - waker: WakerRegistration::new(), - })), - }); - - Runner { - shared: &state.shared, + pub fn is_connected(&self, cx: Option<&mut Context>) -> bool { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + if let Some(cx) = cx { + s.connection_waker.register(cx.waker()); + } + s.wifi_connection.is_connected() + }) } -} - -pub struct TestShared<'d> { - inner: &'d Mutex>, -} - -pub struct Device<'d, AT: AtatClient, const URC_CAPACITY: usize> { - pub(crate) shared: TestShared<'d>, - pub(crate) at: AtHandle<'d, AT>, - pub(crate) urc_subscription: UrcSubscription<'d, UbloxUrc, URC_CAPACITY, 2>, -} -impl<'d> TestShared<'d> { - pub fn link_state(&mut self, cx: &mut Context) -> LinkState { - self.inner.lock(|s| { - let s = &mut *s.borrow_mut(); - s.waker.register(cx.waker()); - s.link_state + pub async fn wait_connection_change(&mut self) -> bool { + let old_state = self + .shared + .lock(|s| s.borrow().wifi_connection.is_connected()); + + poll_fn(|cx| { + let current_state = self.is_connected(Some(cx)); + if current_state != old_state { + return Poll::Ready(current_state); + } + Poll::Pending }) + .await } } diff --git a/src/asynch/ublox_stack/tcp.rs b/src/asynch/ublox_stack/tcp.rs index aef9f89..6283ff6 100644 --- a/src/asynch/ublox_stack/tcp.rs +++ b/src/asynch/ublox_stack/tcp.rs @@ -750,7 +750,7 @@ pub mod client { } } - impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::ErrorType + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::ErrorType for TcpConnection<'d, N, TX_SZ, RX_SZ> { type Error = Error; diff --git a/src/asynch/ublox_stack/tls.rs b/src/asynch/ublox_stack/tls.rs index 55434d2..7caa78a 100644 --- a/src/asynch/ublox_stack/tls.rs +++ b/src/asynch/ublox_stack/tls.rs @@ -406,7 +406,7 @@ pub mod client { } } - impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::ErrorType + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::ErrorType for TlsConnection<'d, N, TX_SZ, RX_SZ> { type Error = Error; diff --git a/src/command/custom_digest.rs b/src/command/custom_digest.rs index d591a08..1db8831 100644 --- a/src/command/custom_digest.rs +++ b/src/command/custom_digest.rs @@ -112,20 +112,6 @@ impl Digester for EdmDigester { // struct MockWriter; -// impl embedded_io::Io for MockWriter { -// type Error = (); -// } - -// impl embedded_io::blocking::Write for MockWriter { -// fn write(&mut self, buf: &[u8]) -> Result { -// Ok(buf.len()) -// } - -// fn flush(&mut self) -> Result<(), Self::Error> { -// Ok(()) -// } -// } - // /// Removed functionality used to change OK responses to empty responses. // #[test] // fn ok_response<'a>() { diff --git a/src/command/network/mod.rs b/src/command/network/mod.rs index c8e401b..81d4c8a 100644 --- a/src/command/network/mod.rs +++ b/src/command/network/mod.rs @@ -23,7 +23,7 @@ pub struct SetNetworkHostName<'a> { /// /// Shows current status of the network interface id. #[derive(Clone, AtatCmd)] -#[at_cmd("+UNSTAT", NetworkStatusResponse, timeout_ms = 3000)] +#[at_cmd("+UNSTAT", NetworkStatusResponse, attempts = 3, timeout_ms = 1000)] pub struct GetNetworkStatus { #[at_arg(position = 0)] pub interface_id: u8, diff --git a/src/command/system/mod.rs b/src/command/system/mod.rs index ec1ac13..d3403a0 100644 --- a/src/command/system/mod.rs +++ b/src/command/system/mod.rs @@ -29,7 +29,7 @@ pub struct SetToDefaultConfig; /// Reset to factory defined defaults. A reboot is required before using the new settings. #[derive(Debug, PartialEq, Clone, AtatCmd)] #[at_cmd("+UFACTORY", NoResponse, timeout_ms = 1000)] -pub struct ResetToFacroryDefaults; +pub struct ResetToFactoryDefaults; /// 4.4 Circuit 108/2 (DTR) behavior &D /// diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..8589684 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,17 @@ +use embedded_hal::digital::OutputPin; + +pub trait WifiConfig<'a> { + type ResetPin: OutputPin; + + const FLOW_CONTROL: bool = false; + + const TLS_IN_BUFFER_SIZE: Option = None; + const TLS_OUT_BUFFER_SIZE: Option = None; + + #[cfg(feature = "ppp")] + const PPP_CONFIG: embassy_net_ppp::Config<'a>; + + fn reset_pin(&mut self) -> Option<&mut Self::ResetPin> { + None + } +} diff --git a/src/connection.rs b/src/connection.rs index c270887..8136e8d 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -14,7 +14,7 @@ pub struct WifiConnection { /// Keeps track of connection state on module pub wifi_state: WiFiState, pub network_up: bool, - pub network: WifiNetwork, + pub network: Option, /// Number from 0-9. 255 used for unknown pub config_id: u8, /// Keeps track of activation of the config by driver @@ -22,30 +22,28 @@ pub struct WifiConnection { } impl WifiConnection { - pub(crate) fn new(network: WifiNetwork, wifi_state: WiFiState, config_id: u8) -> Self { + pub(crate) const fn new() -> Self { WifiConnection { - wifi_state, + wifi_state: WiFiState::Inactive, network_up: false, - network, - config_id, + network: None, + config_id: 255, activated: false, } } pub fn is_station(&self) -> bool { - self.network.mode == WifiMode::Station + match self.network { + Some(ref n) => n.mode == WifiMode::Station, + _ => false, + } } pub fn is_access_point(&self) -> bool { !self.is_station() } - pub(crate) fn activate(mut self) -> Self { - self.activated = true; - self - } - - pub(crate) fn deactivate(&mut self) { - self.activated = false; + pub fn is_connected(&self) -> bool { + self.network_up } } diff --git a/src/lib.rs b/src/lib.rs index 180e4fb..b7375c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,21 @@ #[cfg(all(feature = "ppp", feature = "internal-network-stack"))] compile_error!("You may not enable both `ppp` and `internal-network-stack` features."); +#[cfg(not(any( + feature = "odin_w2xx", + feature = "nina_w1xx", + feature = "nina_b1xx", + feature = "anna_b1xx", + feature = "nina_b2xx", + feature = "nina_b3xx" +)))] +compile_error!("No chip feature activated. You must activate exactly one of the following features: odin_w2xx, nina_w1xx, nina_b1xx, anna_b1xx, nina_b2xx, nina_b3xx"); + mod fmt; pub mod asynch; +mod config; mod connection; mod network; mod peer_builder; @@ -19,16 +30,5 @@ pub use atat; pub mod command; pub mod error; // pub mod wifi; +pub use config::WifiConfig; pub use peer_builder::SecurityCredentials; - -// TODO: -// - UDP stack -// - Secure sockets -// - Network scan -// - AP Mode (control) -// - TCP listener stack -// - -// -// FIXME: -// - PWR/Restart stuff doesn't fully work -// -