diff --git a/.vscode/settings.json b/.vscode/settings.json index 1b2bf72..8fc7548 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,7 @@ "rust-analyzer.check.allTargets": false, "rust-analyzer.linkedProjects": [], "rust-analyzer.cargo.features": [ - "odin_w2xx", + "odin-w2xx", // "internal-network-stack" "ppp" ], diff --git a/Cargo.toml b/Cargo.toml index 5d31af1..650b27a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ atat = { version = "0.23", features = ["derive", "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 = { version = "0.5", optional = true } ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", rev = "9f7fe54", optional = true } portable-atomic = "1.6" @@ -50,6 +50,8 @@ default = ["socket-tcp", "socket-udp"] internal-network-stack = ["dep:ublox-sockets", "edm"] edm = ["ublox-sockets?/edm"] +ipv6 = ["embassy-net?/proto-ipv6"] + # PPP mode requires UDP sockets enabled, to be able to do AT commands over UDP port 23 ppp = ["dep:embassy-net-ppp", "dep:embassy-net", "socket-udp"] @@ -66,12 +68,13 @@ defmt = [ ] log = ["dep:log", "ublox-sockets?/log", "atat/log"] -odin_w2xx = [] -nina_w1xx = [] -nina_b1xx = [] -anna_b1xx = [] -nina_b2xx = [] -nina_b3xx = [] +# Supported Ublox modules +odin-w2xx = [] +nina-w1xx = [] +nina-b1xx = [] +anna-b1xx = [] +nina-b2xx = [] +nina-b3xx = [] [workspace] members = [] @@ -81,4 +84,5 @@ exclude = ["examples"] [patch.crates-io] no-std-net = { git = "https://github.com/rushmorem/no-std-net", branch = "issue-15" } -atat = { path = "../atat/atat" } \ No newline at end of file +atat = { git = "https://github.com/BlackbirdHQ/atat", rev = "a466836" } +# atat = { path = "../atat/atat" } \ No newline at end of file diff --git a/README.md b/README.md index 4010a77..402312c 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ A driver crate for AT-command based serial ublox short range modules, built on top of [atat]. The driver aims to be compatible with the ublox short range modules: -- odin_w2xx -- nina_w1xx -- nina_b1xx -- anna_b1xx -- nina_b2xx -- nina_b3xx +- odin-w2xx +- nina-w1xx +- nina-b1xx +- anna-b1xx +- nina-b2xx +- nina-b3xx [atat]: https://crates.io/crates/atat @@ -48,12 +48,12 @@ The samples can be built using `cargo build -p linux_example --target x86_64-unk ## Features - device selection (must select one, and only one!): - - `odin_w2xx` - - `nina_w1xx` - - `nina_b1xx` - - `anna_b1xx` - - `nina_b2xx` - - `nina_b3xx` + - `odin-w2xx` + - `nina-w1xx` + - `nina-b1xx` + - `anna-b1xx` + - `nina-b2xx` + - `nina-b3xx` - `socket-tcp`: Enabled by default. Adds TCP socket capabilities, and implements [`TcpStack`] trait. - `socket-udp`: Enabled by default. Adds UDP socket capabilities, and implements [`UdpStack`] trait. - `defmt-default`: Disabled by default. Add log statements on trace (dev) or info (release) log levels to aid debugging. diff --git a/examples/rpi-pico/Cargo.toml b/examples/rpi-pico/Cargo.toml index 4a9d2a1..3a26943 100644 --- a/examples/rpi-pico/Cargo.toml +++ b/examples/rpi-pico/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] -ublox-short-range-rs = { path = "../../", features = ["odin_w2xx", "defmt"] } +ublox-short-range-rs = { path = "../../", features = ["odin-w2xx", "defmt"] } embassy-executor = { version = "0.5", features = [ "defmt", "integrated-timers", diff --git a/src/asynch/at_udp_socket.rs b/src/asynch/at_udp_socket.rs index 5b3f87f..4428181 100644 --- a/src/asynch/at_udp_socket.rs +++ b/src/asynch/at_udp_socket.rs @@ -1,6 +1,8 @@ use embassy_net::{udp::UdpSocket, Ipv4Address}; use embedded_io_async::{Read, Write}; +use crate::config::Transport; + pub struct AtUdpSocket<'a>(pub(crate) UdpSocket<'a>); impl<'a> AtUdpSocket<'a> { @@ -32,6 +34,16 @@ impl<'a> Write for &AtUdpSocket<'a> { } } +impl<'a> Transport for AtUdpSocket<'a> { + fn set_baudrate(&mut self, _baudrate: u32) { + // Nothing to do here + } + + fn split_ref(&mut self) -> (impl Write, impl Read) { + (&*self, &*self) + } +} + impl<'a> embedded_io_async::ErrorType for AtUdpSocket<'a> { type Error = core::convert::Infallible; } diff --git a/src/asynch/control.rs b/src/asynch/control.rs index ed4206d..8f03d79 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -1,13 +1,26 @@ use core::cell::Cell; +use core::str::FromStr as _; +use atat::AtatCmd; use atat::{asynch::AtatClient, response_slot::ResponseSlotGuard, UrcChannel}; use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Sender}; use embassy_time::{with_timeout, Duration, Timer}; use heapless::Vec; +use no_std_net::Ipv4Addr; +use crate::command::general::responses::SoftwareVersionResponse; +use crate::command::general::types::FirmwareVersion; +use crate::command::general::SoftwareVersion; use crate::command::gpio::responses::ReadGPIOResponse; use crate::command::gpio::types::GPIOMode; use crate::command::gpio::ConfigureGPIO; +use crate::command::network::responses::NetworkStatusResponse; +use crate::command::network::types::{NetworkStatus, NetworkStatusParameter}; +use crate::command::network::GetNetworkStatus; +use crate::command::ping::Ping; +use crate::command::system::responses::LocalAddressResponse; +use crate::command::system::types::InterfaceID; +use crate::command::system::GetLocalAddress; use crate::command::wifi::{ExecWifiStationAction, GetWifiStatus, SetWifiStationConfig}; use crate::command::OnOff; use crate::command::{ @@ -32,13 +45,19 @@ use crate::command::{ system::{RebootDCE, ResetToFactoryDefaults}, wifi::types::AccessPointId, }; -use crate::connection::WiFiState; +use crate::connection::{DnsServers, StaticConfigV4, WiFiState}; use crate::error::Error; use super::runner::{MAX_CMD_LEN, URC_SUBSCRIBERS}; use super::state::LinkState; use super::{state, UbloxUrc}; +enum WifiAuthentication<'a> { + None, + Wpa2Passphrase(&'a str), + Wpa2Psk(&'a [u8; 32]), +} + const CONFIG_ID: u8 = 0; pub(crate) struct ProxyClient<'a, const INGRESS_BUF_SIZE: usize> { @@ -90,9 +109,12 @@ impl<'a, const INGRESS_BUF_SIZE: usize> atat::asynch::AtatClient } // TODO: Guard against race condition! - self.req_sender - .send(Vec::try_from(&buf[..len]).unwrap()) - .await; + with_timeout( + Duration::from_secs(1), + self.req_sender.send(Vec::try_from(&buf[..len]).unwrap()), + ) + .await + .map_err(|_| atat::Error::Timeout)?; self.cooldown_timer.set(Some(Timer::after_millis(20))); @@ -111,7 +133,7 @@ impl<'a, const INGRESS_BUF_SIZE: usize> atat::asynch::AtatClient pub struct Control<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> { state_ch: state::Runner<'a>, at_client: ProxyClient<'a, INGRESS_BUF_SIZE>, - _urc_channel: &'a UrcChannel, + urc_channel: &'a UrcChannel, } impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> @@ -119,21 +141,22 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> { pub(crate) fn new( state_ch: state::Runner<'a>, - urc_channel: &'a UrcChannel, + urc_channel: &'a UrcChannel, req_sender: Sender<'a, NoopRawMutex, Vec, 1>, res_slot: &'a atat::ResponseSlot, ) -> Self { Self { state_ch, at_client: ProxyClient::new(req_sender, res_slot), - _urc_channel: urc_channel, + urc_channel: urc_channel, } } + /// Set the hostname of the device pub async fn set_hostname(&self, hostname: &str) -> Result<(), Error> { self.state_ch.wait_for_initialized().await; - (&(&self).at_client) + (&self.at_client) .send_retry(&SetNetworkHostName { host_name: hostname, }) @@ -141,6 +164,28 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> Ok(()) } + /// Gets the firmware version of the device + pub async fn get_version(&self) -> Result { + self.state_ch.wait_for_initialized().await; + + let SoftwareVersionResponse { version } = + (&self.at_client).send_retry(&SoftwareVersion).await?; + Ok(version) + } + + /// Gets the MAC address of the device + pub async fn hardware_address(&mut self) -> Result<[u8; 6], Error> { + self.state_ch.wait_for_initialized().await; + + let LocalAddressResponse { mac } = (&self.at_client) + .send_retry(&GetLocalAddress { + interface_id: InterfaceID::WiFi, + }) + .await?; + + Ok(mac.to_be_bytes()[2..].try_into().unwrap()) + } + async fn get_wifi_status(&self) -> Result { match (&self.at_client) .send_retry(&GetWifiStatus { @@ -154,7 +199,91 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> } } - async fn get_connected_ssid(&self) -> Result, Error> { + pub async fn wait_for_link_state(&self, link_state: LinkState) { + self.state_ch.wait_for_link_state(link_state).await + } + + pub async fn config_v4(&self) -> Result, Error> { + let NetworkStatusResponse { + status: NetworkStatus::IPv4Address(ipv4), + .. + } = (&self.at_client) + .send_retry(&GetNetworkStatus { + interface_id: 0, + status: NetworkStatusParameter::IPv4Address, + }) + .await? + else { + return Err(Error::Network); + }; + + let ipv4_addr = core::str::from_utf8(ipv4.as_slice()) + .ok() + .and_then(|s| Ipv4Addr::from_str(s).ok()) + .and_then(|ip| (!ip.is_unspecified()).then_some(ip)); + + let NetworkStatusResponse { + status: NetworkStatus::Gateway(gateway), + .. + } = (&self.at_client) + .send_retry(&GetNetworkStatus { + interface_id: 0, + status: NetworkStatusParameter::Gateway, + }) + .await? + else { + return Err(Error::Network); + }; + + let gateway_addr = core::str::from_utf8(gateway.as_slice()) + .ok() + .and_then(|s| Ipv4Addr::from_str(s).ok()) + .and_then(|ip| (!ip.is_unspecified()).then_some(ip)); + + let NetworkStatusResponse { + status: NetworkStatus::PrimaryDNS(primary), + .. + } = (&self.at_client) + .send_retry(&GetNetworkStatus { + interface_id: 0, + status: NetworkStatusParameter::PrimaryDNS, + }) + .await? + else { + return Err(Error::Network); + }; + + let primary = core::str::from_utf8(primary.as_slice()) + .ok() + .and_then(|s| Ipv4Addr::from_str(s).ok()) + .and_then(|ip| (!ip.is_unspecified()).then_some(ip)); + + let NetworkStatusResponse { + status: NetworkStatus::SecondaryDNS(secondary), + .. + } = (&self.at_client) + .send_retry(&GetNetworkStatus { + interface_id: 0, + status: NetworkStatusParameter::SecondaryDNS, + }) + .await? + else { + return Err(Error::Network); + }; + + let secondary = core::str::from_utf8(secondary.as_slice()) + .ok() + .and_then(|s| Ipv4Addr::from_str(s).ok()) + .and_then(|ip| (!ip.is_unspecified()).then_some(ip)); + + Ok(ipv4_addr.map(|address| StaticConfigV4 { + address, + gateway: gateway_addr, + dns_servers: DnsServers { primary, secondary }, + })) + } + + pub async fn get_connected_ssid(&self) -> Result, Error> { match (&self.at_client) .send_retry(&GetWifiStatus { status_id: StatusId::SSID, @@ -178,7 +307,7 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> Ok(()) } - pub async fn start_ap(&self, ssid: &str) -> Result<(), Error> { + async fn start_ap(&self, ssid: &str) -> Result<(), Error> { self.state_ch.wait_for_initialized().await; // Deactivate network id 0 @@ -302,16 +431,32 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> Ok(()) } - pub async fn join_open(&self, ssid: &str) -> Result<(), Error> { + /// Start open access point. + pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { + todo!() + } + + /// Start WPA2 protected access point. + pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) { + todo!() + } + + /// Closes access point. + pub async fn close_ap(&self) -> Result<(), Error> { + todo!() + } + + async fn join_sta(&self, ssid: &str, auth: WifiAuthentication<'_>) -> Result<(), Error> { self.state_ch.wait_for_initialized().await; if matches!(self.get_wifi_status().await?, WifiStatusVal::Connected) { // Wifi already connected. Check if the SSID is the same let current_ssid = self.get_connected_ssid().await?; if current_ssid.as_str() == ssid { + self.state_ch.set_should_connect(true); return Ok(()); } else { - self.disconnect().await?; + self.leave().await?; }; } @@ -338,87 +483,82 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> }) .await?; - (&self.at_client) - .send_retry(&SetWifiStationConfig { - config_id: CONFIG_ID, - config_param: WifiStationConfig::Authentication(Authentication::Open), - }) - .await?; - - (&self.at_client) - .send_retry(&ExecWifiStationAction { - config_id: CONFIG_ID, - action: WifiStationAction::Activate, - }) - .await?; - - self.wait_for_join(ssid, Duration::from_secs(20)).await - } + match auth { + WifiAuthentication::None => { + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::Authentication(Authentication::Open), + }) + .await?; + } + WifiAuthentication::Wpa2Passphrase(passphrase) => { + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::Authentication(Authentication::WpaWpa2Psk), + }) + .await?; - pub async fn join_wpa2(&self, ssid: &str, passphrase: &str) -> Result<(), Error> { - self.state_ch.wait_for_initialized().await; + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::WpaPskOrPassphrase( + heapless::String::try_from(passphrase).map_err(|_| Error::Overflow)?, + ), + }) + .await?; + } + WifiAuthentication::Wpa2Psk(psk) => { + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::Authentication(Authentication::WpaWpa2Psk), + }) + .await?; - if matches!(self.get_wifi_status().await?, WifiStatusVal::Connected) { - // Wifi already connected. Check if the SSID is the same - let current_ssid = self.get_connected_ssid().await?; - if current_ssid.as_str() == ssid { - return Ok(()); - } else { - self.disconnect().await?; - }; + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::WpaPskOrPassphrase(todo!("hex values?!")), + }) + .await?; + } } (&self.at_client) .send_retry(&ExecWifiStationAction { config_id: CONFIG_ID, - action: WifiStationAction::Reset, - }) - .await?; - - (&self.at_client) - .send_retry(&SetWifiStationConfig { - config_id: CONFIG_ID, - config_param: WifiStationConfig::ActiveOnStartup(OnOff::Off), + action: WifiStationAction::Activate, }) .await?; - (&self.at_client) - .send_retry(&SetWifiStationConfig { - config_id: CONFIG_ID, - config_param: WifiStationConfig::SSID( - heapless::String::try_from(ssid).map_err(|_| Error::Overflow)?, - ), - }) - .await?; + self.wait_for_join(ssid, Duration::from_secs(20)).await?; + self.state_ch.set_should_connect(true); - (&self.at_client) - .send_retry(&SetWifiStationConfig { - config_id: CONFIG_ID, - config_param: WifiStationConfig::Authentication(Authentication::WpaWpa2Psk), - }) - .await?; + Ok(()) + } - (&self.at_client) - .send_retry(&SetWifiStationConfig { - config_id: CONFIG_ID, - config_param: WifiStationConfig::WpaPskOrPassphrase( - heapless::String::try_from(passphrase).map_err(|_| Error::Overflow)?, - ), - }) - .await?; + /// Join an unprotected network with the provided ssid. + pub async fn join_open(&self, ssid: &str) -> Result<(), Error> { + self.join_sta(ssid, WifiAuthentication::None).await + } - (&self.at_client) - .send_retry(&ExecWifiStationAction { - config_id: CONFIG_ID, - action: WifiStationAction::Activate, - }) - .await?; + /// Join a protected network with the provided ssid and passphrase. + pub async fn join_wpa2(&self, ssid: &str, passphrase: &str) -> Result<(), Error> { + self.join_sta(ssid, WifiAuthentication::Wpa2Passphrase(passphrase)) + .await + } - self.wait_for_join(ssid, Duration::from_secs(20)).await + /// Join a protected network with the provided ssid and precomputed PSK. + pub async fn join_wpa2_psk(&mut self, ssid: &str, psk: &[u8; 32]) -> Result<(), Error> { + self.join_sta(ssid, WifiAuthentication::Wpa2Psk(psk)).await } - pub async fn disconnect(&self) -> Result<(), Error> { + /// Leave the wifi, with which we are currently associated. + pub async fn leave(&self) -> Result<(), Error> { self.state_ch.wait_for_initialized().await; + self.state_ch.set_should_connect(false); match self.get_wifi_status().await? { WifiStatusVal::Disabled => {} @@ -442,7 +582,7 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> Ok(()) } - async fn wait_for_join(&self, ssid: &str, timeout: Duration) -> Result<(), Error> { + pub async fn wait_for_join(&self, ssid: &str, timeout: Duration) -> Result<(), Error> { match with_timeout(timeout, self.state_ch.wait_for_link_state(LinkState::Up)).await { Ok(_) => { // Check that SSID matches @@ -454,42 +594,81 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> Ok(()) } Err(_) if self.state_ch.wifi_state(None) == WiFiState::SecurityProblems => { + let _ = (&self.at_client) + .send_retry(&ExecWifiStationAction { + config_id: CONFIG_ID, + action: WifiStationAction::Deactivate, + }) + .await; Err(Error::SecurityProblems) } Err(_) => Err(Error::Timeout), } } - pub async fn gpio_configure(&self, id: GPIOId, mode: GPIOMode) -> Result<(), Error> { + // /// Start a wifi scan + // /// + // /// Returns a `Stream` of networks found by the device + // /// + // /// # Note + // /// Device events are currently implemented using a bounded queue. + // /// To not miss any events, you should make sure to always await the stream. + // pub async fn scan(&mut self, scan_opts: ScanOptions) -> Scanner<'_> { + // todo!() + // } + + pub async fn send_at(&self, cmd: &Cmd) -> Result { self.state_ch.wait_for_initialized().await; - (&self.at_client) - .send_retry(&ConfigureGPIO { id, mode }) - .await?; + Ok((&self.at_client).send_retry(cmd).await?) + } + + pub async fn gpio_configure(&self, id: GPIOId, mode: GPIOMode) -> Result<(), Error> { + self.send_at(&ConfigureGPIO { id, mode }).await?; Ok(()) } pub async fn gpio_set(&self, id: GPIOId, value: bool) -> Result<(), Error> { - self.state_ch.wait_for_initialized().await; - let value = if value { GPIOValue::High } else { GPIOValue::Low }; - (&self.at_client) - .send_retry(&WriteGPIO { id, value }) - .await?; + self.send_at(&WriteGPIO { id, value }).await?; Ok(()) } pub async fn gpio_get(&self, id: GPIOId) -> Result { - self.state_ch.wait_for_initialized().await; - - let ReadGPIOResponse { value, .. } = (&self.at_client).send_retry(&ReadGPIO { id }).await?; + let ReadGPIOResponse { value, .. } = self.send_at(&ReadGPIO { id }).await?; Ok(value as u8 != 0) } + #[cfg(feature = "ppp")] + pub async fn ping( + &self, + hostname: &str, + ) -> Result { + let mut urc_sub = self.urc_channel.subscribe().map_err(|_| Error::Overflow)?; + + self.send_at(&Ping { + hostname, + retry_num: 1, + }) + .await?; + + let result_fut = async { + loop { + match urc_sub.next_message_pure().await { + crate::command::Urc::PingResponse(r) => return Ok(r), + crate::command::Urc::PingErrorResponse(e) => return Err(Error::Dns(e.error)), + _ => {} + } + } + }; + + with_timeout(Duration::from_secs(15), result_fut).await? + } + // FIXME: This could probably be improved // #[cfg(feature = "internal-network-stack")] // pub async fn import_credentials( diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 49ace50..afb3f0f 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -13,42 +13,8 @@ pub use resources::Resources; pub use runner::Runner; pub use state::LinkState; -use embedded_io_async::{BufRead, Error as _, ErrorKind, Read, Write}; - #[cfg(feature = "edm")] pub type UbloxUrc = crate::command::edm::urc::EdmEvent; #[cfg(not(feature = "edm"))] pub type UbloxUrc = crate::command::Urc; - -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()) - } - - fn consume(&mut self, amt: usize) { - self.0.consume(amt) - } -} - -impl Write for ReadWriteAdapter { - async fn write(&mut self, buf: &[u8]) -> Result { - self.1.write(buf).await.map_err(|e| e.kind()) - } - - 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 index 4e398fa..79532b0 100644 --- a/src/asynch/network.rs +++ b/src/asynch/network.rs @@ -7,20 +7,18 @@ use no_std_net::{Ipv4Addr, Ipv6Addr}; use crate::{ command::{ - general::SoftwareVersion, network::{ responses::NetworkStatusResponse, types::{InterfaceType, NetworkStatus, NetworkStatusParameter}, urc::{NetworkDown, NetworkUp}, GetNetworkStatus, }, - system::{types::EchoOn, RebootDCE, SetEcho, StoreCurrentConfig}, + system::{RebootDCE, StoreCurrentConfig}, wifi::{ - types::{DisconnectReason, PowerSaveMode, WifiConfig as WifiConfigParam}, + types::DisconnectReason, urc::{WifiLinkConnected, WifiLinkDisconnected}, - SetWifiConfig, }, - OnOff, Urc, + Urc, }, connection::WiFiState, error::Error, @@ -28,13 +26,13 @@ use crate::{ WifiConfig, }; -use super::{runner::URC_SUBSCRIBERS, state, LinkState, UbloxUrc}; +use super::{runner::URC_SUBSCRIBERS, state, UbloxUrc}; pub(crate) 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>, + 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> @@ -46,7 +44,7 @@ where ch: &'b state::Runner<'a>, config: &'b mut C, at_client: A, - urc_channel: &'a UrcChannel, + urc_channel: &'a UrcChannel, ) -> Self { Self { ch, @@ -56,63 +54,7 @@ where } } - pub(crate) async fn init(&mut self) -> Result<(), Error> { - // Initialize a new ublox device to a known state (set RS232 settings) - debug!("Initializing module"); - // Hard reset module - self.reset().await?; - - self.at_client.send_retry(&SoftwareVersion).await?; - self.at_client - .send_retry(&SetEcho { on: EchoOn::Off }) - .await?; - self.at_client - .send_retry(&SetWifiConfig { - config_param: WifiConfigParam::DropNetworkOnLinkLoss(OnOff::On), - }) - .await?; - - // Disable all power savings for now - self.at_client - .send_retry(&SetWifiConfig { - config_param: WifiConfigParam::PowerSaveMode(PowerSaveMode::ActiveMode), - }) - .await?; - - #[cfg(feature = "internal-network-stack")] - if let Some(size) = C::TLS_IN_BUFFER_SIZE { - self.at_client - .send_retry(&crate::command::data_mode::SetPeerConfiguration { - parameter: crate::command::data_mode::types::PeerConfigParameter::TlsInBuffer( - size, - ), - }) - .await?; - } - - #[cfg(feature = "internal-network-stack")] - if let Some(size) = C::TLS_OUT_BUFFER_SIZE { - self.at_client - .send_retry(&crate::command::data_mode::SetPeerConfiguration { - parameter: crate::command::data_mode::types::PeerConfigParameter::TlsOutBuffer( - size, - ), - }) - .await?; - } - - self.ch.mark_initialized(); - - Ok(()) - } - pub async fn run(&mut self) -> Result<(), Error> { - if self.ch.link_state(None) == LinkState::Uninitialized { - self.init().await?; - } - - let mut link_was_up = false; - loop { match embassy_futures::select::select( self.urc_subscription.next_message_pure(), @@ -128,15 +70,11 @@ where self.handle_urc(event).await?; } - embassy_futures::select::Either::Second(_) => {} + _ => {} } - match self.ch.wifi_state(None) { - WiFiState::Inactive if self.ch.connection_down(None) && link_was_up => { - return Ok(()) - } - WiFiState::Connected if self.ch.is_connected(None) => link_was_up = true, - _ => {} + if self.ch.wifi_state(None) == WiFiState::Inactive && self.ch.connection_down(None) { + return Ok(()); } } } @@ -228,13 +166,37 @@ where return Err(Error::Network); }; - let ipv4_addr = core::str::from_utf8(ipv4.as_slice()) + let ipv4_up = core::str::from_utf8(ipv4.as_slice()) .ok() .and_then(|s| Ipv4Addr::from_str(s).ok()) - .and_then(|ip| (!ip.is_unspecified()).then_some(ip)); + .map(|ip| !ip.is_unspecified()) + .unwrap_or_default(); + + #[cfg(feature = "ipv6")] + let ipv6_up = { + let NetworkStatusResponse { + status: NetworkStatus::IPv6Address1(ipv6), + .. + } = self + .at_client + .send_retry(&GetNetworkStatus { + interface_id, + status: NetworkStatusParameter::IPv6Address1, + }) + .await? + else { + return Err(Error::Network); + }; + + core::str::from_utf8(ipv6.as_slice()) + .ok() + .and_then(|s| Ipv6Addr::from_str(s).ok()) + .map(|ip| !ip.is_unspecified()) + .unwrap_or_default() + }; let NetworkStatusResponse { - status: NetworkStatus::IPv6LinkLocalAddress(ipv6), + status: NetworkStatus::IPv6LinkLocalAddress(ipv6_link_local), .. } = self .at_client @@ -247,16 +209,21 @@ where return Err(Error::Network); }; - let ipv6_addr = core::str::from_utf8(ipv6.as_slice()) + let ipv6_link_local_up = core::str::from_utf8(ipv6_link_local.as_slice()) .ok() .and_then(|s| Ipv6Addr::from_str(s).ok()) - .and_then(|ip| (!ip.is_unspecified()).then_some(ip)); + .map(|ip| !ip.is_unspecified()) + .unwrap_or_default(); // Use `ipv4_addr` & `ipv6_addr` to determine link state self.ch.update_connection_with(|con| { - con.ipv4 = ipv4_addr; - con.ipv6 = ipv6_addr; - con.network_up = ipv4_addr.is_some() && ipv6_addr.is_some() + con.ipv6_link_local_up = ipv6_link_local_up; + con.ipv4_up = ipv4_up; + + #[cfg(feature = "ipv6")] + { + con.ipv6_up = ipv6_up + } }); Ok(()) @@ -272,9 +239,8 @@ where continue; }; - match event { - Urc::StartUp => return, - _ => {} + if let Urc::StartUp = event { + return; } } }; @@ -293,7 +259,9 @@ where self.at_client.send_retry(&RebootDCE).await?; } - self.wait_startup(Duration::from_secs(10)).await?; + self.ch.mark_uninitialized(); + + self.wait_startup(Duration::from_secs(5)).await?; #[cfg(feature = "edm")] self.enter_edm(Duration::from_secs(4)).await?; @@ -310,7 +278,9 @@ where self.at_client.send_retry(&RebootDCE).await?; - self.wait_startup(Duration::from_secs(10)).await?; + self.ch.mark_uninitialized(); + + self.wait_startup(Duration::from_secs(5)).await?; info!("Module started again"); #[cfg(feature = "edm")] diff --git a/src/asynch/resources.rs b/src/asynch/resources.rs index f115962..20db742 100644 --- a/src/asynch/resources.rs +++ b/src/asynch/resources.rs @@ -11,7 +11,7 @@ pub struct Resources { pub(crate) res_slot: ResponseSlot, pub(crate) req_slot: Channel, 1>, - pub(crate) urc_channel: UrcChannel, + pub(crate) urc_channel: UrcChannel, pub(crate) ingress_buf: [u8; INGRESS_BUF_SIZE], } diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 8836b8a..5153b77 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -1,21 +1,31 @@ -use super::{ - control::Control, - network::NetDevice, - state::{self, LinkState}, - Resources, UbloxUrc, -}; +use super::{control::Control, network::NetDevice, state, Resources, UbloxUrc}; use crate::{ asynch::control::ProxyClient, - command::data_mode::{self, ChangeMode}, - WifiConfig, + command::{ + data_mode::{self, ChangeMode}, + general::SoftwareVersion, + system::{ + types::{BaudRate, ChangeAfterConfirm, EchoOn, FlowControl, Parity, StopBits}, + SetEcho, SetRS232Settings, + }, + wifi::{ + types::{PowerSaveMode, WifiConfig as WifiConfigParam}, + SetWifiConfig, + }, + OnOff, AT, + }, + config::Transport, + error::Error, + WifiConfig, DEFAULT_BAUD_RATE, }; use atat::{ - asynch::{AtatClient, SimpleClient}, + asynch::{AtatClient as _, SimpleClient}, AtatIngress as _, UrcChannel, }; +use embassy_futures::select::Either; use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel}; use embassy_time::{Duration, Timer}; -use embedded_io_async::{BufRead, Read, Write}; +use embedded_io_async::{BufRead, Write}; #[cfg(feature = "ppp")] pub(crate) const URC_SUBSCRIBERS: usize = 2; @@ -30,8 +40,7 @@ type Digester = crate::command::custom_digest::EdmDigester; pub(crate) const MAX_CMD_LEN: usize = 256; async fn at_bridge<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize>( - mut sink: impl Write, - source: impl Read, + transport: &mut impl Transport, req_slot: &Channel, 1>, ingress: &mut atat::Ingress< 'a, @@ -39,19 +48,21 @@ async fn at_bridge<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> UbloxUrc, INGRESS_BUF_SIZE, URC_CAPACITY, - URC_SUBSCRIBERS, + { URC_SUBSCRIBERS }, >, ) -> ! { ingress.clear(); + let (mut tx, rx) = transport.split_ref(); + let tx_fut = async { loop { let msg = req_slot.receive().await; - let _ = sink.write_all(&msg).await; + let _ = tx.write_all(&msg).await; } }; - embassy_futures::join::join(tx_fut, ingress.read_from(source)).await; + embassy_futures::join::join(tx_fut, ingress.read_from(rx)).await; unreachable!() } @@ -59,16 +70,16 @@ async fn at_bridge<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> /// Background runner for the Ublox Module. /// /// You must call `.run()` in a background task for the Ublox Module to operate. -pub struct Runner<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> { - iface: (R, W), +pub struct Runner<'a, T: Transport, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> { + transport: T, ch: state::Runner<'a>, config: C, - pub urc_channel: &'a UrcChannel, + pub urc_channel: &'a UrcChannel, pub ingress: - atat::Ingress<'a, Digester, UbloxUrc, INGRESS_BUF_SIZE, URC_CAPACITY, URC_SUBSCRIBERS>, + atat::Ingress<'a, Digester, UbloxUrc, INGRESS_BUF_SIZE, URC_CAPACITY, { URC_SUBSCRIBERS }>, pub res_slot: &'a atat::ResponseSlot, pub req_slot: &'a Channel, 1>, @@ -76,18 +87,17 @@ pub struct Runner<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY ppp_runner: Option>, } -impl<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> - Runner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> +impl<'a, T, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> + Runner<'a, T, C, INGRESS_BUF_SIZE, URC_CAPACITY> where - R: BufRead + Read, - W: Write, + T: Transport + BufRead, C: WifiConfig<'a> + 'a, { pub fn new( - iface: (R, W), + transport: T, resources: &'a mut Resources, config: C, - ) -> Self { + ) -> (Self, Control<'a, INGRESS_BUF_SIZE, URC_CAPACITY>) { let ch_runner = state::Runner::new(&mut resources.ch); let ingress = atat::Ingress::new( @@ -97,28 +107,29 @@ where &resources.urc_channel, ); - Self { - iface, + let control = Control::new( + ch_runner.clone(), + &resources.urc_channel, + resources.req_slot.sender(), + &resources.res_slot, + ); - ch: ch_runner, - config, - urc_channel: &resources.urc_channel, + ( + Self { + transport, - ingress, - res_slot: &resources.res_slot, - req_slot: &resources.req_slot, + ch: ch_runner, + config, + urc_channel: &resources.urc_channel, - #[cfg(feature = "ppp")] - ppp_runner: None, - } - } + ingress, + res_slot: &resources.res_slot, + req_slot: &resources.req_slot, - pub fn control(&self) -> Control<'a, INGRESS_BUF_SIZE, URC_CAPACITY> { - Control::new( - self.ch.clone(), - &self.urc_channel, - self.req_slot.sender(), - &self.res_slot, + #[cfg(feature = "ppp")] + ppp_runner: None, + }, + control, ) } @@ -146,38 +157,201 @@ where } } - #[cfg(feature = "internal-network-stack")] - pub async fn run(&mut self) -> ! { - let device_fut = async { - loop { - let mut device = NetDevice::new( - &self.ch, - &mut self.config, - ProxyClient::new(self.req_slot.sender(), &self.res_slot), - self.urc_channel, - ); + /// Probe a given baudrate with the goal of establishing initial + /// communication with the module, so we can reconfigure it for desired + /// baudrate + async fn probe_baud(&mut self, baudrate: BaudRate) -> Result<(), Error> { + info!("Probing wifi module using baud rate: {}", baudrate as u32); + self.transport.set_baudrate(baudrate as u32); - if let Err(e) = device.init().await { - error!("WiFi init failed {:?}", e); - continue; - }; + let baud_fut = async { + let at_client = ProxyClient::new(self.req_slot.sender(), self.res_slot); + + // Hard reset module + NetDevice::new(&self.ch, &mut self.config, &at_client, self.urc_channel) + .reset() + .await?; + + (&at_client).send_retry(&AT).await?; + + // Lets take a shortcut if we are probing for the desired baudrate + if baudrate == C::BAUD_RATE { + info!("Successfully shortcut the baud probing!"); + return Ok(None); + } + + let flow_control = if C::FLOW_CONTROL { + FlowControl::On + } else { + FlowControl::Off + }; + + (&at_client) + .send_retry(&SetRS232Settings { + baud_rate: C::BAUD_RATE, + flow_control, + data_bits: 8, + stop_bits: StopBits::One, + parity: Parity::None, + change_after_confirm: ChangeAfterConfirm::ChangeAfterOK, + }) + .await?; + + Ok::<_, Error>(Some(C::BAUD_RATE)) + }; + + match embassy_futures::select::select( + baud_fut, + at_bridge(&mut self.transport, self.req_slot, &mut self.ingress), + ) + .await + { + Either::First(Ok(Some(baud))) => { + self.transport.set_baudrate(baud as u32); + Timer::after_millis(40).await; + Ok(()) + } + Either::First(r) => r.map(drop), + Either::Second(_) => unreachable!(), + } + } + + async fn init(&mut self) -> Result<(), Error> { + // Initialize a new ublox device to a known state + debug!("Initializing WiFi module"); + + // Probe all possible baudrates with the goal of establishing initial + // communication with the module, so we can reconfigure it for desired + // baudrate. + // + // Start with the two most likely + let mut found_baudrate = false; + + for baudrate in [ + C::BAUD_RATE, + DEFAULT_BAUD_RATE, + BaudRate::B9600, + BaudRate::B14400, + BaudRate::B19200, + BaudRate::B28800, + BaudRate::B38400, + BaudRate::B57600, + BaudRate::B76800, + BaudRate::B115200, + BaudRate::B230400, + BaudRate::B250000, + BaudRate::B460800, + BaudRate::B921600, + BaudRate::B3000000, + BaudRate::B5250000, + ] { + if self.probe_baud(baudrate).await.is_ok() { + if baudrate != C::BAUD_RATE { + // Attempt to store the desired baudrate, so we can shortcut + // this probing next time. Ignore any potential failures, as + // this is purely an optimization. + let _ = embassy_futures::select::select( + NetDevice::new( + &self.ch, + &mut self.config, + &ProxyClient::new(self.req_slot.sender(), self.res_slot), + self.urc_channel, + ) + .restart(true), + at_bridge(&mut self.transport, self.req_slot, &mut self.ingress), + ) + .await; + } + found_baudrate = true; + break; + } + } + + if !found_baudrate { + return Err(Error::BaudDetection); + } + + let at_client = ProxyClient::new(self.req_slot.sender(), self.res_slot); + + let setup_fut = async { + (&at_client).send_retry(&SoftwareVersion).await?; + + (&at_client) + .send_retry(&SetEcho { on: EchoOn::Off }) + .await?; + (&at_client) + .send_retry(&SetWifiConfig { + config_param: WifiConfigParam::DropNetworkOnLinkLoss(OnOff::On), + }) + .await?; + + // Disable all power savings for now + (&at_client) + .send_retry(&SetWifiConfig { + config_param: WifiConfigParam::PowerSaveMode(PowerSaveMode::ActiveMode), + }) + .await?; + + #[cfg(feature = "internal-network-stack")] + if let Some(size) = C::TLS_IN_BUFFER_SIZE { + (&at_client) + .send_retry(&crate::command::data_mode::SetPeerConfiguration { + parameter: crate::command::data_mode::types::PeerConfigParameter::TlsInBuffer( + size, + ), + }) + .await?; + } - let _ = device.run().await; + #[cfg(feature = "internal-network-stack")] + if let Some(size) = C::TLS_OUT_BUFFER_SIZE { + (&at_client) + .send_retry(&crate::command::data_mode::SetPeerConfiguration { + parameter: + crate::command::data_mode::types::PeerConfigParameter::TlsOutBuffer( + size, + ), + }) + .await?; } + + Ok::<(), Error>(()) }; - embassy_futures::join::join( - device_fut, - at_bridge( - &mut self.iface.1, - &mut self.iface.0, - &self.req_slot, - &mut self.ingress, - ), + match embassy_futures::select::select( + setup_fut, + at_bridge(&mut self.transport, self.req_slot, &mut self.ingress), ) - .await; + .await + { + Either::First(r) => r?, + Either::Second(_) => unreachable!(), + } + + self.ch.mark_initialized(); - unreachable!() + Ok(()) + } + + #[cfg(feature = "internal-network-stack")] + pub async fn run(&mut self) -> ! { + loop { + if self.init().await.is_err() { + continue; + } + + embassy_futures::select::select( + NetDevice::new( + &self.ch, + &mut self.config, + &ProxyClient::new(self.req_slot.sender(), &self.res_slot), + self.urc_channel, + ) + .run(), + at_bridge(&mut self.transport, &self.req_slot, &mut self.ingress), + ) + .await; + } } #[cfg(feature = "ppp")] @@ -185,43 +359,33 @@ where &mut self, stack: &embassy_net::Stack, ) -> ! { - let at_config = atat::Config::default(); - loop { + if self.init().await.is_err() { + continue; + } + + debug!("Done initializing WiFi module"); + let network_fut = async { // Allow control to send/receive AT commands directly on the // UART, until we are ready to establish connection using PPP - - // Send "+++" to escape data mode, and enter command mode - warn!("Escaping to command mode!"); - Timer::after_secs(1).await; - self.iface.1.write_all(b"+++").await.ok(); - Timer::after_secs(1).await; - let _ = embassy_futures::select::select( - at_bridge( - &mut self.iface.1, - &mut self.iface.0, - &self.req_slot, - &mut self.ingress, - ), - self.ch.wait_for_link_state(LinkState::Up), + at_bridge(&mut self.transport, self.req_slot, &mut self.ingress), + self.ch.wait_connected(), ) .await; #[cfg(feature = "ppp")] let ppp_fut = async { - let mut iface = super::ReadWriteAdapter(&mut self.iface.0, &mut self.iface.1); - - self.ch.wait_for_link_state(LinkState::Up).await; + self.ch.wait_for_link_state(state::LinkState::Up).await; { let mut buf = [0u8; 8]; let mut at_client = SimpleClient::new( - &mut iface, + &mut self.transport, atat::AtDigester::::new(), &mut buf, - at_config, + C::AT_CONFIG, ); // Send AT command `ATO3` to enter PPP mode @@ -239,18 +403,18 @@ where // Drain the UART let _ = embassy_time::with_timeout(Duration::from_millis(500), async { loop { - iface.read(&mut buf).await.ok(); + self.transport.read(&mut buf).await.ok(); } }) .await; } info!("RUNNING PPP"); - let res = self + let _ = self .ppp_runner .as_mut() .unwrap() - .run(&mut iface, C::PPP_CONFIG, |ipv4| { + .run(&mut self.transport, C::PPP_CONFIG, |ipv4| { debug!("Running on_ipv4_up for wifi!"); let Some(addr) = ipv4.address else { warn!("PPP did not provide an IP address."); @@ -275,7 +439,7 @@ where }) .await; - info!("ppp failed: {:?}", res); + info!("ppp failed"); }; let at_fut = async { @@ -295,25 +459,23 @@ where ); socket.bind(AtUdpSocket::PPP_AT_PORT).unwrap(); - let at_socket = AtUdpSocket(socket); + let mut at_socket = AtUdpSocket(socket); - at_bridge(&at_socket, &at_socket, &self.req_slot, &mut self.ingress).await; + at_bridge(&mut at_socket, self.req_slot, &mut self.ingress).await; }; embassy_futures::select::select(ppp_fut, at_fut).await; }; let device_fut = async { - let at_client = ProxyClient::new(self.req_slot.sender(), &self.res_slot); - let mut device = - NetDevice::new(&self.ch, &mut self.config, &at_client, self.urc_channel); - - if let Err(e) = device.init().await { - error!("WiFi init failed {:?}", e); - return; - }; - - let _ = device.run().await; + let _ = NetDevice::new( + &self.ch, + &mut self.config, + &ProxyClient::new(self.req_slot.sender(), self.res_slot), + self.urc_channel, + ) + .run() + .await; warn!("Breaking to reboot device"); }; diff --git a/src/asynch/state.rs b/src/asynch/state.rs index c01d3b6..4dc3784 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -30,6 +30,7 @@ impl State { pub(crate) const fn new() -> Self { Self { shared: Mutex::new(RefCell::new(Shared { + should_connect: false, link_state: LinkState::Uninitialized, wifi_connection: WifiConnection::new(), state_waker: WakerRegistration::new(), @@ -42,6 +43,7 @@ impl State { /// State of the LinkState pub(crate) struct Shared { link_state: LinkState, + should_connect: bool, wifi_connection: WifiConnection, state_waker: WakerRegistration, connection_waker: WakerRegistration, @@ -67,6 +69,22 @@ impl<'d> Runner<'d> { }) } + pub(crate) fn mark_uninitialized(&self) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = LinkState::Uninitialized; + s.state_waker.wake(); + }) + } + + pub(crate) fn set_should_connect(&self, should_connect: bool) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.connection_waker.wake(); + s.should_connect = should_connect; + }) + } + pub(crate) async fn wait_for_initialized(&self) { if self.link_state(None) != LinkState::Uninitialized { return; @@ -131,7 +149,7 @@ impl<'d> Runner<'d> { if let Some(cx) = cx { s.connection_waker.register(cx.waker()); } - s.wifi_connection.ipv4.is_none() && s.wifi_connection.ipv6.is_none() + !s.wifi_connection.ipv4_up && !s.wifi_connection.ipv6_link_local_up }) } @@ -155,10 +173,24 @@ impl<'d> Runner<'d> { if let Some(cx) = cx { s.connection_waker.register(cx.waker()); } - s.wifi_connection.is_connected() + s.wifi_connection.is_connected() && s.should_connect }) } + pub(crate) async fn wait_connected(&self) { + if self.is_connected(None) { + return; + } + + poll_fn(|cx| { + if self.is_connected(Some(cx)) { + return Poll::Ready(()); + } + Poll::Pending + }) + .await + } + pub(crate) fn wifi_state(&self, cx: Option<&mut Context>) -> WiFiState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); diff --git a/src/asynch/ublox_stack/dns.rs b/src/asynch/ublox_stack/dns.rs index b3aed1e..00550a2 100644 --- a/src/asynch/ublox_stack/dns.rs +++ b/src/asynch/ublox_stack/dns.rs @@ -25,10 +25,10 @@ pub enum Error { /// length is 64 characters. /// Domain name length is 128 for NINA-W13 and NINA-W15 software version 4.0 /// .0 or later. -#[cfg(not(feature = "nina_w1xx"))] +#[cfg(not(feature = "nina-w1xx"))] pub const MAX_DOMAIN_NAME_LENGTH: usize = 64; -#[cfg(feature = "nina_w1xx")] +#[cfg(feature = "nina-w1xx")] pub const MAX_DOMAIN_NAME_LENGTH: usize = 128; pub struct DnsTableEntry { diff --git a/src/command/mod.rs b/src/command/mod.rs index e237b39..864dec2 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -21,7 +21,7 @@ use atat::atat_derive::{AtatCmd, AtatEnum, AtatResp, AtatUrc}; pub struct NoResponse; #[derive(Debug, Clone, AtatCmd)] -#[at_cmd("", NoResponse, timeout_ms = 1000)] +#[at_cmd("", NoResponse, attempts = 3, timeout_ms = 1000)] pub struct AT; #[derive(Debug, PartialEq, Clone, AtatUrc)] diff --git a/src/command/system/mod.rs b/src/command/system/mod.rs index d3403a0..9ff5be2 100644 --- a/src/command/system/mod.rs +++ b/src/command/system/mod.rs @@ -175,7 +175,7 @@ pub struct ModuleStart { #[at_cmd("+UMLA", NoResponse, timeout_ms = 1000)] pub struct SetLocalAddress<'a> { #[at_arg(position = 0)] - pub interface_id: InserfaceID, + pub interface_id: InterfaceID, /// MAC address of the interface id. If the address is set to 000000000000, the local /// address will be restored to factory-programmed value. /// The least significant bit of the first octet of the
must be 0; that is, the @@ -188,10 +188,10 @@ pub struct SetLocalAddress<'a> { /// /// Reads the local address of the interface id. #[derive(Debug, PartialEq, Clone, AtatCmd)] -#[at_cmd("+UMSM", LocalAddressResponse, timeout_ms = 1000)] +#[at_cmd("+UMLA", LocalAddressResponse, timeout_ms = 1000)] pub struct GetLocalAddress { #[at_arg(position = 0)] - pub interface_id: InserfaceID, + pub interface_id: InterfaceID, } /// 4.15 System status +UMSTAT @@ -215,14 +215,6 @@ pub struct SystemStatus { pub struct SetRS232Settings { #[at_arg(position = 0)] pub baud_rate: BaudRate, - // #[at_arg(position = 1)] - // pub settings: Option<( - // FlowControl, - // Option<( - // u8, - // Option<(StopBits, Option<(Parity, Option)>)>, - // )>, - // )>, #[at_arg(position = 1)] pub flow_control: FlowControl, #[at_arg(position = 2)] diff --git a/src/command/system/responses.rs b/src/command/system/responses.rs index da05fd9..fcd5de8 100644 --- a/src/command/system/responses.rs +++ b/src/command/system/responses.rs @@ -1,6 +1,6 @@ //! Responses for System Commands use super::types::*; -use atat::atat_derive::AtatResp; +use atat::{atat_derive::AtatResp, serde_at::HexStr}; use heapless::String; /// 4.11 Software update +UFWUPD @@ -17,7 +17,7 @@ pub struct LocalAddressResponse { /// MAC address of the interface id. If the address is set to 000000000000, the local /// address will be restored to factory-programmed value. #[at_arg(position = 0)] - pub mac: String<64>, + pub mac: HexStr, } /// 4.15 System status +UMSTAT diff --git a/src/command/system/types.rs b/src/command/system/types.rs index b00e393..b00b2a7 100644 --- a/src/command/system/types.rs +++ b/src/command/system/types.rs @@ -81,10 +81,11 @@ pub enum ModuleStartMode { #[derive(Debug, Clone, PartialEq, AtatEnum)] #[repr(u8)] -pub enum InserfaceID { - Bluetooth = 0, - WiFi = 1, - Ethernet = 2, +pub enum InterfaceID { + Bluetooth = 1, + WiFi = 2, + Ethernet = 3, + WiFiAP = 4, } #[derive(Debug, Clone, PartialEq, AtatEnum)] diff --git a/src/config.rs b/src/config.rs index 44fce23..5380f32 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,16 @@ use embedded_hal::digital::OutputPin; +use embedded_io_async::{Read, Write}; + +use crate::{command::system::types::BaudRate, DEFAULT_BAUD_RATE}; pub trait WifiConfig<'a> { type ResetPin: OutputPin; + const AT_CONFIG: atat::Config = atat::Config::new(); + + // Transport settings const FLOW_CONTROL: bool = false; + const BAUD_RATE: BaudRate = DEFAULT_BAUD_RATE; #[cfg(feature = "internal-network-stack")] const TLS_IN_BUFFER_SIZE: Option = None; @@ -17,3 +24,8 @@ pub trait WifiConfig<'a> { None } } + +pub trait Transport: Write + Read { + fn set_baudrate(&mut self, baudrate: u32); + fn split_ref(&mut self) -> (impl Write, impl Read); +} diff --git a/src/connection.rs b/src/connection.rs index eab96f4..b5f7292 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,4 +1,4 @@ -use no_std_net::{Ipv4Addr, Ipv6Addr}; +use no_std_net::Ipv4Addr; use crate::network::{WifiMode, WifiNetwork}; @@ -12,11 +12,29 @@ pub enum WiFiState { Connected, } +/// Static IP address configuration. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticConfigV4 { + /// IP address and subnet mask. + pub address: Ipv4Addr, + /// Default gateway. + pub gateway: Option, + /// DNS servers. + pub dns_servers: DnsServers, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DnsServers { + pub primary: Option, + pub secondary: Option, +} + pub struct WifiConnection { pub wifi_state: WiFiState, - pub network_up: bool, - pub ipv4: Option, - pub ipv6: Option, + pub ipv6_link_local_up: bool, + pub ipv4_up: bool, + #[cfg(feature = "ipv6")] + pub ipv6_up: bool, pub network: Option, } @@ -24,19 +42,20 @@ impl WifiConnection { pub(crate) const fn new() -> Self { WifiConnection { wifi_state: WiFiState::Inactive, - network_up: false, + ipv6_link_local_up: false, network: None, - ipv4: None, - ipv6: None, + ipv4_up: false, + #[cfg(feature = "ipv6")] + ipv6_up: false, } } #[allow(dead_code)] pub fn is_station(&self) -> bool { - match self.network { - Some(ref n) => n.mode == WifiMode::Station, - _ => false, - } + self.network + .as_ref() + .map(|n| n.mode == WifiMode::Station) + .unwrap_or_default() } #[allow(dead_code)] @@ -44,7 +63,25 @@ impl WifiConnection { !self.is_station() } + /// Get whether the network stack has a valid IP configuration. + /// This is true if the network stack has a static IP configuration or if DHCP has completed + pub fn is_config_up(&self) -> bool { + let v6_up; + let v4_up = self.ipv4_up; + + #[cfg(feature = "ipv6")] + { + v6_up = self.ipv6_up; + } + #[cfg(not(feature = "ipv6"))] + { + v6_up = false; + } + + (v4_up || v6_up) && self.ipv6_link_local_up + } + pub fn is_connected(&self) -> bool { - self.network_up && self.wifi_state == WiFiState::Connected + self.is_config_up() && self.wifi_state == WiFiState::Connected } } diff --git a/src/error.rs b/src/error.rs index d655f44..fd38a1b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,6 +43,12 @@ impl From for Error { } } +impl From for Error { + fn from(_: embassy_time::TimeoutError) -> Self { + Error::Timeout + } +} + #[cfg(feature = "internal-network-stack")] impl From for Error { fn from(e: ublox_sockets::Error) -> Self { diff --git a/src/lib.rs b/src/lib.rs index b8e84fa..283875a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,14 +5,14 @@ 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" + 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"); +compile_error!("No module 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; @@ -28,4 +28,7 @@ pub use atat; pub mod command; pub mod error; -pub use config::WifiConfig; +pub use config::{Transport, WifiConfig}; + +use command::system::types::BaudRate; +pub const DEFAULT_BAUD_RATE: BaudRate = BaudRate::B115200; diff --git a/src/network.rs b/src/network.rs index eafa25d..fc0a593 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use crate::command::wifi::types::{OperationMode, ScannedWifiNetwork}; use crate::error::WifiError; use crate::hex::from_hex;