diff --git a/Cargo.toml b/Cargo.toml index 850dc8b..41734df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ name = "ublox_cellular" doctest = false [dependencies] -atat = { version = "0.22", features = ["derive", "bytes"] } +atat = { version = "0.23", features = ["derive", "bytes"] } heapless = { version = "^0.8", features = ["serde"] } serde = { version = "^1", default-features = false, features = ["derive"] } #ublox-sockets = { version = "0.5", optional = true } @@ -37,7 +37,7 @@ embedded-nal = "0.8" embedded-nal-async = { version = "0.7" } # embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a", optional = true } -embassy-at-cmux = { path = "../embassy/embassy-at-cmux", optional = true } +embassy-at-cmux = { path = "../embassy/embassy-at-cmux" } embassy-net-ppp = { version = "0.1", optional = true } embassy-net = { version = "0.4", features = [ "proto-ipv4", @@ -87,13 +87,12 @@ context-mapping-required = [] # sock-set-local-port = [] # fota = [] # uart-power-saving = [] -cmux = ["dep:embassy-at-cmux"] # snr-reported = [] authentication-mode-automatic = [] # lwm2m = [] ucged = [] # http = [] -ppp = ["cmux", "dep:embassy-net-ppp", "dep:embassy-net"] +ppp = ["dep:embassy-net-ppp", "dep:embassy-net"] automatic-apn = [] @@ -108,11 +107,11 @@ defmt = [ "heapless/defmt-03", "embassy-time/defmt", "embassy-sync/defmt", + "embassy-at-cmux/defmt", "embassy-futures/defmt", "ublox-sockets?/defmt", "embassy-net-ppp?/defmt", "embassy-net?/defmt", - "embassy-at-cmux?/defmt", ] log = ["dep:log", "ublox-sockets?/log", "atat/log"] @@ -153,5 +152,5 @@ embassy-net = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a #embassy-time = { path = "../embassy/embassy-time" } #embassy-sync = { path = "../embassy/embassy-sync" } #embassy-futures = { path = "../embassy/embassy-futures" } - -atat = { path = "../atat/atat" } +atat = { git = "https://github.com/BlackbirdHQ/atat", rev = "a466836" } +# atat = { path = "../atat/atat" } diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index 12ed225..831d5b6 100644 --- a/examples/embassy-rp2040-example/Cargo.toml +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -56,7 +56,7 @@ panic-probe = { version = "0.3.1", features = ["print-defmt"] } static_cell = { version = "2.0", features = [] } -atat = { version = "0.22", features = ["derive", "bytes", "defmt"] } +atat = { version = "0.23", features = ["derive", "bytes", "defmt"] } ublox-cellular-rs = { version = "0.4.0", path = "../..", features = [ "lara-r6", "defmt", diff --git a/examples/embassy-stm32-example/Cargo.toml b/examples/embassy-stm32-example/Cargo.toml index 997b586..44fc7e0 100644 --- a/examples/embassy-stm32-example/Cargo.toml +++ b/examples/embassy-stm32-example/Cargo.toml @@ -20,7 +20,7 @@ panic-probe = { version = "0.3.1", features = ["print-defmt"] } static_cell = { version = "2.0", features = []} -atat = { version = "0.21.0", features = ["derive", "bytes", "defmt"] } +atat = { version = "0.23.0", features = ["derive", "bytes", "defmt"] } ublox-cellular-rs = {version = "0.4.0", path = "../..", features = ["sara-r5", "defmt"]} [patch.crates-io] diff --git a/examples/tokio-std-example/Cargo.toml b/examples/tokio-std-example/Cargo.toml index 0966950..190e2e5 100644 --- a/examples/tokio-std-example/Cargo.toml +++ b/examples/tokio-std-example/Cargo.toml @@ -38,7 +38,7 @@ log = "0.4.21" static_cell = { version = "2.0" } -atat = { version = "0.22", features = ["derive", "bytes", "log"] } +atat = { version = "0.23", features = ["derive", "bytes", "log"] } ublox-cellular-rs = { version = "0.4.0", path = "../..", features = [ "lara-r6", "log", diff --git a/src/asynch/control.rs b/src/asynch/control.rs index b1c69fc..5104d2f 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -1,62 +1,115 @@ -use super::state::{self, LinkState, OperationState}; +use atat::{ + asynch::{AtatClient, SimpleClient}, + AtDigester, +}; + +use crate::{ + command::{ + general::{types::FirmwareVersion, GetFirmwareVersion}, + gpio::{types::GpioMode, GetGpioConfiguration, SetGpioConfiguration}, + network_service::{ + responses::{OperatorSelection, SignalQuality}, + GetOperatorSelection, GetSignalQuality, + }, + Urc, + }, + error::Error, +}; + +use super::{ + runner::CMUX_CHANNEL_SIZE, + state::{self, LinkState, OperationState}, +}; pub struct Control<'a> { state_ch: state::Runner<'a>, + at_client: + SimpleClient<'a, embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, atat::AtDigester>, } impl<'a> Control<'a> { - pub(crate) fn new(state_ch: state::Runner<'a>) -> Self { - Self { state_ch } + pub(crate) fn new( + state_ch: state::Runner<'a>, + at_client: SimpleClient< + 'a, + embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, + atat::AtDigester, + >, + ) -> Self { + Self { + state_ch, + at_client, + } } - pub fn link_state(&mut self) -> LinkState { + pub fn link_state(&self) -> LinkState { self.state_ch.link_state(None) } - pub fn operation_state(&mut self) -> OperationState { + pub fn operation_state(&self) -> OperationState { self.state_ch.operation_state(None) } - pub fn desired_state(&mut self) -> OperationState { + pub fn desired_state(&self) -> OperationState { self.state_ch.desired_state(None) } - pub fn set_desired_state(&mut self, ps: OperationState) { + pub fn set_desired_state(&self, ps: OperationState) { self.state_ch.set_desired_state(ps); } - pub async fn wait_for_desired_state(&mut self, ps: OperationState) { + pub async fn wait_for_desired_state(&self, ps: OperationState) { self.state_ch.wait_for_desired_state(ps).await } - pub async fn wait_for_operation_state(&mut self, ps: OperationState) { + pub async fn wait_for_operation_state(&self, ps: OperationState) { self.state_ch.wait_for_operation_state(ps).await } - // pub async fn get_signal_quality( - // &mut self, - // ) -> Result { - // self.at - // .send(&crate::command::network_service::GetSignalQuality) - // .await - // .map_err(|e| Error::Atat(e)) - // } - - // pub async fn get_operator( - // &mut self, - // ) -> Result { - // self.at - // .send(&crate::command::network_service::GetOperatorSelection) - // .await - // .map_err(|e| Error::Atat(e)) - // } - - // /// Send an AT command to the modem - // /// This is usefull if you have special configuration but might break the drivers functionality if your settings interfere with the drivers settings - // pub async fn send( - // &mut self, - // cmd: &Cmd, - // ) -> Result { - // self.at.send::(cmd).await - // } + pub async fn get_signal_quality(&mut self) -> Result { + if self.operation_state() == OperationState::PowerDown { + return Err(Error::Uninitialized); + } + + Ok(self.at_client.send_retry(&GetSignalQuality).await?) + } + + pub async fn get_operator(&mut self) -> Result { + if self.operation_state() == OperationState::PowerDown { + return Err(Error::Uninitialized); + } + + Ok(self.at_client.send_retry(&GetOperatorSelection).await?) + } + + pub async fn get_version(&mut self) -> Result { + if self.operation_state() == OperationState::PowerDown { + return Err(Error::Uninitialized); + } + + let res = self.at_client.send_retry(&GetFirmwareVersion).await?; + Ok(res.version) + } + + pub async fn set_gpio_configuration( + &self, + gpio_id: u8, + gpio_mode: GpioMode, + ) -> Result<(), Error> { + if self.operation_state() == OperationState::PowerDown { + return Err(Error::Uninitialized); + } + + self.at_client + .send_retry(&SetGpioConfiguration { gpio_id, gpio_mode }) + .await?; + Ok(()) + } + + /// Send an AT command to the modem This is usefull if you have special + /// configuration but might break the drivers functionality if your settings + /// interfere with the drivers settings + pub async fn send(&mut self, cmd: &Cmd) -> Result { + Ok(self.at_client.send_retry::(cmd).await?) + } } diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index a51d4f9..10d2bd6 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,44 +1,12 @@ pub mod control; mod network; +mod pwr; mod resources; pub mod runner; pub mod state; mod urc_handler; -use embedded_io_async::{BufRead, Error as _, ErrorKind, Read, Write}; pub use resources::Resources; pub use runner::Runner; #[cfg(feature = "internal-network-stack")] pub use state::Device; - -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 5e11dae..6d51ced 100644 --- a/src/asynch/network.rs +++ b/src/asynch/network.rs @@ -1,78 +1,48 @@ -use core::future::poll_fn; -use core::task::Poll; - -use crate::command::control::types::Echo; -use crate::command::control::types::FlowControl; -use crate::command::control::SetEcho; - -use crate::command::dns::ResolveNameIp; -use crate::command::general::responses::FirmwareVersion; -use crate::command::general::GetCIMI; -use crate::command::general::GetFirmwareVersion; -use crate::command::general::IdentificationInformation; -use crate::command::mobile_control::responses::ModuleFunctionality; -use crate::command::mobile_control::types::PowerMode; -use crate::command::mobile_control::GetModuleFunctionality; -use crate::command::network_service::responses::OperatorSelection; -use crate::command::network_service::types::OperatorSelectionMode; -use crate::command::network_service::GetNetworkRegistrationStatus; -use crate::command::network_service::GetOperatorSelection; -use crate::command::network_service::SetChannelAndNetworkEnvDesc; -use crate::command::network_service::SetOperatorSelection; -use crate::command::networking::types::EmbeddedPortFilteringMode; -use crate::command::networking::SetEmbeddedPortFiltering; -use crate::command::psn; -use crate::command::psn::GetEPSNetworkRegistrationStatus; -use crate::command::psn::GetGPRSAttached; -use crate::command::psn::GetGPRSNetworkRegistrationStatus; -use crate::command::psn::GetPDPContextDefinition; -use crate::command::psn::GetPDPContextState; -use crate::command::psn::SetPDPContextState; - -use crate::error::GenericError; -use crate::modules::Generic; -use crate::modules::Module; -use crate::modules::ModuleParams; -use crate::{command::Urc, config::CellularConfig}; +use core::{cmp::Ordering, future::poll_fn, marker::PhantomData, task::Poll}; + +use crate::{ + asynch::state::OperationState, + command::{ + general::GetCIMI, + mobile_control::{ + responses::ModuleFunctionality, + types::{Functionality, PowerMode}, + GetModuleFunctionality, SetModuleFunctionality, + }, + network_service::{ + responses::OperatorSelection, + types::{NetworkRegistrationUrcConfig, OperatorSelectionMode}, + GetNetworkRegistrationStatus, GetOperatorSelection, SetNetworkRegistrationStatus, + SetOperatorSelection, + }, + psn::{ + responses::GPRSAttached, + types::{ + ContextId, EPSNetworkRegistrationUrcConfig, GPRSAttachedState, + GPRSNetworkRegistrationUrcConfig, PDPContextStatus, ProfileId, + }, + GetEPSNetworkRegistrationStatus, GetGPRSAttached, GetGPRSNetworkRegistrationStatus, + GetPDPContextState, SetEPSNetworkRegistrationStatus, SetGPRSNetworkRegistrationStatus, + SetPDPContextState, + }, + }, + config::CellularConfig, + error::Error, + modules::ModuleParams, + registration::ProfileState, +}; use super::state; -use crate::asynch::state::OperationState; -use crate::command::control::types::{Circuit108Behaviour, Circuit109Behaviour}; -use crate::command::control::{SetCircuit108Behaviour, SetCircuit109Behaviour, SetFlowControl}; -use crate::command::device_lock::responses::PinStatus; -use crate::command::device_lock::types::PinStatusCode; -use crate::command::device_lock::GetPinStatus; -use crate::command::general::{GetCCID, GetModelId}; -use crate::command::gpio::types::{GpioInPull, GpioMode, GpioOutValue}; -use crate::command::gpio::SetGpioConfiguration; -use crate::command::mobile_control::types::{Functionality, TerminationErrorMode}; -use crate::command::mobile_control::{SetModuleFunctionality, SetReportMobileTerminationError}; -use crate::command::psn::responses::GPRSAttached; -use crate::command::psn::types::GPRSAttachedState; -use crate::command::psn::types::PDPContextStatus; -use crate::command::system_features::types::PowerSavingMode; -use crate::command::system_features::SetPowerSavingControl; -use crate::command::AT; -use crate::error::Error; - -use atat::UrcChannel; -use atat::{asynch::AtatClient, UrcSubscription}; -use embassy_futures::select::select; - -use embassy_futures::select::Either3; -use embassy_time::{with_timeout, Duration, Timer}; -use embedded_hal::digital::{InputPin, OutputPin}; -use futures_util::FutureExt; - -use crate::command::psn::types::{ContextId, ProfileId}; -use embassy_futures::select::Either; - -const GENERIC_PWR_ON_TIMES: [u16; 2] = [300, 2000]; + +use atat::asynch::AtatClient; +use embassy_futures::select::{select, Either}; + +use embassy_time::{Duration, Timer}; pub struct NetDevice<'a, 'b, C, A> { ch: &'b state::Runner<'a>, - config: &'b mut C, at_client: A, + _config: PhantomData, } impl<'a, 'b, C, A> NetDevice<'a, 'b, C, A> @@ -80,109 +50,11 @@ where C: CellularConfig<'a>, A: AtatClient, { - pub fn new(ch: &'b state::Runner<'a>, config: &'b mut C, at_client: A) -> Self { + pub fn new(ch: &'b state::Runner<'a>, at_client: A) -> Self { Self { ch, - config, at_client, - } - } - - pub async fn is_alive(&mut self) -> Result { - if !self.has_power().await? { - return Err(Error::PoweredDown); - } - - match self.at_client.send(&AT).await { - Ok(_) => Ok(true), - Err(err) => Err(Error::Atat(err)), - } - } - - pub async fn has_power(&mut self) -> Result { - if let Some(pin) = self.config.vint_pin() { - if pin.is_high().map_err(|_| Error::IoPin)? { - Ok(true) - } else { - Ok(false) - } - } else { - info!("No VInt pin configured"); - Ok(true) - } - } - - pub async fn power_up(&mut self) -> Result<(), Error> { - if !self.has_power().await? { - for generic_time in GENERIC_PWR_ON_TIMES { - let pull_time = self - .ch - .module() - .map(|m| m.power_on_pull_time()) - .unwrap_or(Generic.power_on_pull_time()) - .unwrap_or(Duration::from_millis(generic_time as _)); - if let Some(pin) = self.config.power_pin() { - pin.set_low().map_err(|_| Error::IoPin)?; - Timer::after(pull_time).await; - pin.set_high().map_err(|_| Error::IoPin)?; - - Timer::after( - self.ch - .module() - .map(|m| m.boot_wait()) - .unwrap_or(Generic.boot_wait()), - ) - .await; - - if !self.has_power().await? { - if self.ch.module().is_some() { - return Err(Error::PoweredDown); - } - continue; - } - - self.ch.set_operation_state(OperationState::PowerUp); - debug!("Powered up"); - return Ok(()); - } else { - warn!("No power pin configured"); - return Ok(()); - } - } - Err(Error::PoweredDown) - } else { - Ok(()) - } - } - - pub async fn wait_for_desired_state(&mut self, ps: OperationState) { - self.ch.clone().wait_for_desired_state(ps).await - } - - pub async fn power_down(&mut self) -> Result<(), Error> { - if self.has_power().await? { - if let Some(pin) = self.config.power_pin() { - pin.set_low().map_err(|_| Error::IoPin)?; - Timer::after( - self.ch - .module() - .map(|m| m.power_off_pull_time()) - .unwrap_or(Generic.power_off_pull_time()), - ) - .await; - pin.set_high().map_err(|_| Error::IoPin)?; - self.ch.set_operation_state(OperationState::PowerDown); - debug!("Powered down"); - - Timer::after(Duration::from_secs(1)).await; - - Ok(()) - } else { - warn!("No power pin configured"); - Ok(()) - } - } else { - Ok(()) + _config: PhantomData, } } @@ -192,7 +64,7 @@ where /// /// Returns an error if any of the internal network operations fail. /// - pub async fn register_network(&mut self, mcc_mnc: Option<()>) -> Result<(), Error> { + async fn register_network(&mut self, mcc_mnc: Option<()>) -> Result<(), Error> { self.prepare_connect().await?; if mcc_mnc.is_none() { @@ -226,7 +98,7 @@ where }) .await?; - if let Some(_) = mcc_mnc { + if mcc_mnc.is_some() { // TODO: If MCC & MNC is set, register with manual operator selection. // This is currently not supported! @@ -254,24 +126,25 @@ where Ok(()) } - pub(crate) async fn prepare_connect(&mut self) -> Result<(), Error> { + async fn prepare_connect(&mut self) -> Result<(), Error> { // CREG URC - self.at_client.send( - &crate::command::network_service::SetNetworkRegistrationStatus { - n: crate::command::network_service::types::NetworkRegistrationUrcConfig::UrcEnabled, - }).await?; + self.at_client + .send(&SetNetworkRegistrationStatus { + n: NetworkRegistrationUrcConfig::UrcEnabled, + }) + .await?; // CGREG URC self.at_client - .send(&crate::command::psn::SetGPRSNetworkRegistrationStatus { - n: crate::command::psn::types::GPRSNetworkRegistrationUrcConfig::UrcEnabled, + .send(&SetGPRSNetworkRegistrationStatus { + n: GPRSNetworkRegistrationUrcConfig::UrcEnabled, }) .await?; // CEREG URC self.at_client - .send(&crate::command::psn::SetEPSNetworkRegistrationStatus { - n: crate::command::psn::types::EPSNetworkRegistrationUrcConfig::UrcEnabled, + .send(&SetEPSNetworkRegistrationStatus { + n: EPSNetworkRegistrationUrcConfig::UrcEnabled, }) .await?; @@ -280,62 +153,33 @@ where break; } - Timer::after(Duration::from_secs(1)).await; + Timer::after_secs(1).await; } Ok(()) } - /// Reset the module by driving it's `RESET_N` pin low for 50 ms - /// - /// **NOTE** This function will reset NVM settings! - pub async fn reset(&mut self) -> Result<(), Error> { - warn!("Hard resetting Ublox Cellular Module"); - if let Some(pin) = self.config.reset_pin() { - pin.set_low().ok(); - Timer::after( - self.ch - .module() - .map(|m| m.reset_hold()) - .unwrap_or(Generic.reset_hold()), - ) - .await; - pin.set_high().ok(); - Timer::after( - self.ch - .module() - .map(|m| m.boot_wait()) - .unwrap_or(Generic.boot_wait()), - ) - .await; - // self.is_alive().await?; - } else { - warn!("No reset pin configured"); - } - Ok(()) - } - - /// Perform at full factory reset of the module, clearing all NVM sectors in the process - pub async fn factory_reset(&mut self) -> Result<(), Error> { - self.at_client - .send(&crate::command::system_features::SetFactoryConfiguration { - fs_op: crate::command::system_features::types::FSFactoryRestoreType::NoRestore, - nvm_op: - crate::command::system_features::types::NVMFactoryRestoreType::NVMFlashSectors, - }) - .await?; + // Perform at full factory reset of the module, clearing all NVM sectors in the process + // TODO: Should this be moved to control? + // async fn factory_reset(&mut self) -> Result<(), Error> { + // self.at_client + // .send(&SetFactoryConfiguration { + // fs_op: FSFactoryRestoreType::NoRestore, + // nvm_op: NVMFactoryRestoreType::NVMFlashSectors, + // }) + // .await?; - info!("Successfully factory reset modem!"); + // info!("Successfully factory reset modem!"); - if self.soft_reset(true).await.is_err() { - self.reset().await?; - } + // if self.soft_reset(true).await.is_err() { + // self.pwr.reset().await?; + // } - Ok(()) - } + // Ok(()) + // } - /// Reset the module by sending AT CFUN command - pub async fn soft_reset(&mut self, sim_reset: bool) -> Result<(), Error> { + // /// Reset the module by sending AT CFUN command + async fn soft_reset(&mut self, sim_reset: bool) -> Result<(), Error> { trace!( "Attempting to soft reset of the modem with sim reset: {}.", sim_reset @@ -363,19 +207,6 @@ where } } - /// Wait until module is alive (uses `Vint` & `AT` command) - async fn wait_alive(&mut self, timeout: Duration) -> Result { - let fut = async { - loop { - if let Ok(alive) = self.is_alive().await { - return alive; - } - Timer::after(Duration::from_millis(100)).await; - } - }; - Ok(embassy_time::with_timeout(timeout, fut).await?) - } - /// Check if we are registered to a network technology (uses +CxREG family /// commands) async fn wait_network_registered(&mut self, timeout: Duration) -> Result<(), Error> { @@ -384,7 +215,7 @@ where loop { self.update_registration().await; - Timer::after(Duration::from_millis(300)).await; + Timer::after_millis(300).await; } }; @@ -419,185 +250,9 @@ where } } - async fn init_at(&mut self) -> Result<(), Error> { - // Allow auto bauding to kick in - embassy_time::with_timeout( - self.ch - .module() - .map(|m| m.boot_wait()) - .unwrap_or(Generic.boot_wait()) - * 2, - async { - loop { - if let Ok(alive) = self.at_client.send(&AT).await { - break alive; - } - Timer::after(Duration::from_millis(100)).await; - } - }, - ) - .await - .map_err(|_| Error::PoweredDown)?; - - let model_id = self.at_client.send_retry(&GetModelId).await?; - self.ch.set_module(Module::from_model_id(&model_id)); - - let FirmwareVersion { version } = self.at_client.send_retry(&GetFirmwareVersion).await?; - info!( - "Found module to be: {=[u8]:a}, {=[u8]:a}", - model_id.model.as_slice(), - version.as_slice() - ); - - self.at_client - .send_retry(&SetEmbeddedPortFiltering { - mode: C::EMBEDDED_PORT_FILTERING, - }) - .await?; - - // FIXME: The following three GPIO settings should not be here! - self.at_client - .send_retry(&SetGpioConfiguration { - gpio_id: 23, - gpio_mode: GpioMode::NetworkStatus, - }) - .await; - - // Select SIM - self.at_client - .send_retry(&SetGpioConfiguration { - gpio_id: 25, - gpio_mode: GpioMode::Output(GpioOutValue::Low), - }) - .await?; - - #[cfg(feature = "lara-r6")] - self.at_client - .send_retry(&SetGpioConfiguration { - gpio_id: 42, - gpio_mode: GpioMode::Input(GpioInPull::NoPull), - }) - .await?; - - // self.soft_reset(true).await?; - - // self.wait_alive( - // self.ch - // .module() - // .map(|m| m.boot_wait()) - // .unwrap_or(Generic.boot_wait()) - // * 2, - // ) - // .await?; - - // Echo off - self.at_client - .send_retry(&SetEcho { enabled: Echo::Off }) - .await?; - - // Extended errors on - self.at_client - .send_retry(&SetReportMobileTerminationError { - n: TerminationErrorMode::Enabled, - }) - .await?; - - #[cfg(feature = "internal-network-stack")] - if C::HEX_MODE { - self.at_client - .send_retry(&crate::command::ip_transport_layer::SetHexMode { - hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Enabled, - }) - .await?; - } else { - self.at_client - .send_retry(&crate::command::ip_transport_layer::SetHexMode { - hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Disabled, - }) - .await?; - } - - // self.at_client - // .send_retry(&IdentificationInformation { n: 9 }) - // .await?; - - // DCD circuit (109) changes in accordance with the carrier - self.at_client - .send_retry(&SetCircuit109Behaviour { - value: Circuit109Behaviour::AlwaysPresent, - }) - .await?; - - // Ignore changes to DTR - self.at_client - .send_retry(&SetCircuit108Behaviour { - value: Circuit108Behaviour::Ignore, - }) - .await?; - - self.check_sim_status().await?; - - let ccid = self.at_client.send_retry(&GetCCID).await?; - info!("CCID: {}", ccid.ccid); - - #[cfg(all( - feature = "ucged", - any( - feature = "sara-r410m", - feature = "sara-r412m", - feature = "sara-r422", - feature = "lara-r6" - ) - ))] - self.at_client - .send_retry(&SetChannelAndNetworkEnvDesc { - mode: if cfg!(feature = "ucged5") { 5 } else { 2 }, - }) - .await?; - - // Tell module whether we support flow control - if C::FLOW_CONTROL { - self.at_client - .send_retry(&SetFlowControl { - value: FlowControl::RtsCts, - }) - .await?; - } else { - self.at_client - .send_retry(&SetFlowControl { - value: FlowControl::Disabled, - }) - .await?; - } - - // Switch off UART power saving until it is integrated into this API - self.at_client - .send_retry(&SetPowerSavingControl { - mode: PowerSavingMode::Disabled, - timeout: None, - }) - .await?; - - if !self.ch.is_registered(None) { - self.at_client - .send_retry(&SetModuleFunctionality { - fun: self - .ch - .module() - .ok_or(Error::Uninitialized)? - .radio_off_cfun(), - rst: None, - }) - .await?; - } - - Ok(()) - } - async fn radio_off(&mut self) -> Result<(), Error> { #[cfg(not(feature = "use-upsd-context-activation"))] - self.ch - .set_profile_state(crate::registration::ProfileState::ShouldBeDown); + self.ch.set_profile_state(ProfileState::ShouldBeDown); let module_cfun = self .ch @@ -640,71 +295,23 @@ where Err(last_err.unwrap().into()) } - async fn check_sim_status(&mut self) -> Result<(), Error> { - for _ in 0..2 { - match self.at_client.send(&GetPinStatus).await { - Ok(PinStatus { code }) if code == PinStatusCode::Ready => { - debug!("SIM is ready"); - return Ok(()); - } - _ => {} - } - - Timer::after(Duration::from_secs(1)).await; - } - - // There was an error initializing the SIM - // We've seen issues on uBlox-based devices, as a precation, we'll cycle - // the modem here through minimal/full functional state. - self.at_client - .send(&SetModuleFunctionality { - fun: self - .ch - .module() - .ok_or(Error::Uninitialized)? - .radio_off_cfun(), - rst: None, - }) - .await?; - self.at_client - .send(&SetModuleFunctionality { - fun: Functionality::Full, - rst: None, - }) - .await?; - - Ok(()) - } - - pub async fn run(&mut self) -> ! { - match self.has_power().await { - Ok(true) => { - self.ch.set_operation_state(OperationState::PowerUp); - } - Ok(false) | Err(_) => { - self.ch.set_operation_state(OperationState::PowerDown); - } - } + pub async fn run(&mut self) -> Result<(), Error> { + self.run_to_desired().await?; loop { - // FIXME: This does not seem to work as expected? - match embassy_futures::select::select( - self.ch.clone().wait_for_desired_state_change(), - self.ch.clone().wait_registration_change(), + match select( + self.ch.wait_for_desired_state_change(), + self.ch.wait_registration_change(), ) .await { - Either::First(desired_state) => { - info!("Desired state: {:?}", desired_state); - let _ = self.run_to_state(desired_state).await; + Either::First(_) => { + self.run_to_desired().await?; } Either::Second(false) => { warn!("Lost network registration. Setting operating state back to initialized"); - self.ch.set_operation_state(OperationState::Initialized); - let _ = self - .run_to_state(self.ch.clone().operation_state(None)) - .await; + self.run_to_desired().await?; } Either::Second(true) => { // This flag will be set if we had been knocked out @@ -713,77 +320,32 @@ where // queue before any user registratioon status callback // so that everything is sorted for them #[cfg(not(feature = "use-upsd-context-activation"))] - if self.ch.get_profile_state() - == crate::registration::ProfileState::RequiresReactivation - { - self.activate_context(C::CONTEXT_ID, C::PROFILE_ID) - .await - .unwrap(); - self.ch - .set_profile_state(crate::registration::ProfileState::ShouldBeUp); + if self.ch.get_profile_state() == ProfileState::RequiresReactivation { + self.activate_context(C::CONTEXT_ID, C::PROFILE_ID).await?; + self.ch.set_profile_state(ProfileState::ShouldBeUp); } } - _ => {} } } } - pub async fn run_to_state(&mut self, desired_state: OperationState) -> Result<(), Error> { - if 0 >= desired_state as isize - self.ch.clone().operation_state(None) as isize { - debug!( - "Power steps was negative, power down: {}", - desired_state as isize - self.ch.clone().operation_state(None) as isize - ); - self.power_down().await.ok(); - self.ch.set_operation_state(OperationState::PowerDown); - } - let start_state = self.ch.clone().operation_state(None) as isize; - let steps = desired_state as isize - start_state; - for step in 0..=steps { - debug!( - "State transition {} steps: {} -> {}, {}", - steps, - start_state, - start_state + step, - step - ); - let next_state = start_state + step; - match OperationState::try_from(next_state) { - Ok(OperationState::PowerDown) => {} - Ok(OperationState::PowerUp) => match self.power_up().await { - Ok(_) => { - self.ch.set_operation_state(OperationState::PowerUp); - } - Err(err) => { - error!("Error in power_up: {:?}", err); - return Err(err); - } - }, - Ok(OperationState::Initialized) => match self.init_at().await { - Ok(_) => { - self.ch.set_operation_state(OperationState::Initialized); - } - Err(err) => { - error!("Error in init_at: {:?}", err); - return Err(err); - } - }, - Ok(OperationState::Connected) => match self.register_network(None).await { - Ok(_) => match self.wait_network_registered(Duration::from_secs(180)).await { - Ok(_) => { - self.ch.set_operation_state(OperationState::Connected); - } - Err(err) => { - error!("Timeout waiting for network attach: {:?}", err); - return Err(err); - } - }, - Err(err) => { - error!("Error in register_network: {:?}", err); - return Err(err); - } - }, - Ok(OperationState::DataEstablished) => { + async fn run_to_desired(&mut self) -> Result<(), Error> { + loop { + let current_state = self.ch.operation_state(None); + let desired_state = self.ch.desired_state(None); + + info!("State transition: {} -> {}", current_state, desired_state); + + match (current_state, desired_state.cmp(¤t_state)) { + (_, Ordering::Equal) => break, + + (OperationState::Initialized, Ordering::Greater) => { + self.register_network(None).await?; + self.wait_network_registered(Duration::from_secs(180)) + .await?; + self.ch.set_operation_state(OperationState::Connected); + } + (OperationState::Connected, Ordering::Greater) => { match self.connect(C::APN, C::PROFILE_ID, C::CONTEXT_ID).await { Ok(_) => { #[cfg(not(feature = "use-upsd-context-activation"))] @@ -791,21 +353,27 @@ where .set_profile_state(crate::registration::ProfileState::ShouldBeUp); self.ch.set_operation_state(OperationState::DataEstablished); - Timer::after(Duration::from_secs(5)).await; + Timer::after_secs(1).await; } Err(err) => { // Switch radio off after failure let _ = self.radio_off().await; - - error!("Error in connect: {:?}", err); return Err(err); } } } - Err(_) => { - error!("State transition next_state not valid: start_state={}, next_state={}, steps={} ", start_state, next_state, steps); - return Err(Error::InvalidStateTransition); + + // TODO: do proper backwards "single stepping" + (OperationState::Connected, Ordering::Less) => { + self.ch.set_operation_state(OperationState::Initialized); + } + (OperationState::DataEstablished, Ordering::Less) => { + self.ch.set_operation_state(OperationState::Connected); } + + (OperationState::DataEstablished, Ordering::Greater) => unreachable!(), + (OperationState::Initialized, Ordering::Less) => return Err(Error::PoweredDown), + (OperationState::PowerDown, _) => return Err(Error::PoweredDown), } } Ok(()) @@ -834,7 +402,7 @@ where attached = true; break; }; - Timer::after(Duration::from_secs(1)).await; + Timer::after_secs(1).await; } if !attached { return Err(Error::AttachTimeout); diff --git a/src/asynch/pwr.rs b/src/asynch/pwr.rs new file mode 100644 index 0000000..b3a573a --- /dev/null +++ b/src/asynch/pwr.rs @@ -0,0 +1,138 @@ +use embassy_time::{Duration, Timer}; +use embedded_hal::digital::{InputPin as _, OutputPin as _}; + +use crate::{ + asynch::state::OperationState, + config::CellularConfig, + error::Error, + modules::{Generic, ModuleParams as _}, +}; + +use super::state; + +const GENERIC_PWR_ON_TIMES: [u16; 2] = [300, 2000]; + +pub(crate) struct PwrCtrl<'a, 'b, C> { + config: &'b mut C, + ch: &'b state::Runner<'a>, +} + +impl<'a, 'b, C> PwrCtrl<'a, 'b, C> +where + C: CellularConfig<'a>, +{ + pub(crate) fn new(ch: &'b state::Runner<'a>, config: &'b mut C) -> Self { + Self { ch, config } + } + + pub(crate) fn has_power(&mut self) -> Result { + if let Some(pin) = self.config.vint_pin() { + if pin.is_high().map_err(|_| Error::IoPin)? { + Ok(true) + } else { + Ok(false) + } + } else { + info!("No VInt pin configured"); + Ok(true) + } + } + + /// Reset the module by driving it's `RESET_N` pin low for + /// `Module::reset_hold()` duration + /// + /// **NOTE** This function will reset NVM settings! + pub(crate) async fn reset(&mut self) -> Result<(), Error> { + warn!("Hard resetting Ublox Cellular Module"); + if let Some(pin) = self.config.reset_pin() { + pin.set_low().ok(); + Timer::after( + self.ch + .module() + .map(|m| m.reset_hold()) + .unwrap_or(Generic.reset_hold()), + ) + .await; + pin.set_high().ok(); + Timer::after( + self.ch + .module() + .map(|m| m.boot_wait()) + .unwrap_or(Generic.boot_wait()), + ) + .await; + } else { + warn!("No reset pin configured"); + } + Ok(()) + } + + pub(crate) async fn power_up(&mut self) -> Result<(), Error> { + if !self.has_power()? { + debug!("Attempting to power up device"); + + for generic_time in GENERIC_PWR_ON_TIMES { + let pull_time = self + .ch + .module() + .map(|m| m.power_on_pull_time()) + .unwrap_or(Generic.power_on_pull_time()) + .unwrap_or(Duration::from_millis(generic_time as _)); + if let Some(pin) = self.config.power_pin() { + pin.set_low().map_err(|_| Error::IoPin)?; + Timer::after(pull_time).await; + pin.set_high().map_err(|_| Error::IoPin)?; + + Timer::after( + self.ch + .module() + .map(|m| m.boot_wait()) + .unwrap_or(Generic.boot_wait()), + ) + .await; + + if !self.has_power()? { + if self.ch.module().is_some() { + return Err(Error::PoweredDown); + } + continue; + } + + debug!("Powered up"); + return Ok(()); + } else { + warn!("No power pin configured"); + return Ok(()); + } + } + Err(Error::PoweredDown) + } else { + Ok(()) + } + } + + pub(crate) async fn power_down(&mut self) -> Result<(), Error> { + if self.has_power()? { + if let Some(pin) = self.config.power_pin() { + pin.set_low().map_err(|_| Error::IoPin)?; + Timer::after( + self.ch + .module() + .map(|m| m.power_off_pull_time()) + .unwrap_or(Generic.power_off_pull_time()), + ) + .await; + pin.set_high().map_err(|_| Error::IoPin)?; + self.ch.set_operation_state(OperationState::PowerDown); + debug!("Powered down"); + + Timer::after_secs(1).await; + } else { + warn!("No power pin configured"); + } + } else { + self.ch.set_operation_state(OperationState::PowerDown); + } + Ok(()) + } +} diff --git a/src/asynch/resources.rs b/src/asynch/resources.rs index 656b4b0..29da8db 100644 --- a/src/asynch/resources.rs +++ b/src/asynch/resources.rs @@ -1,12 +1,11 @@ use atat::{ResponseSlot, UrcChannel}; +use super::{ + runner::{CMUX_CHANNELS, CMUX_CHANNEL_SIZE, URC_SUBSCRIBERS}, + state, +}; use crate::command::Urc; -use super::{runner::URC_SUBSCRIBERS, state}; - -#[cfg(feature = "cmux")] -use super::runner::{CMUX_CHANNELS, CMUX_CHANNEL_SIZE}; - pub struct Resources< const CMD_BUF_SIZE: usize, const INGRESS_BUF_SIZE: usize, @@ -17,9 +16,9 @@ pub struct Resources< pub(crate) res_slot: ResponseSlot, pub(crate) urc_channel: UrcChannel, pub(crate) cmd_buf: [u8; CMD_BUF_SIZE], + pub(crate) control_cmd_buf: [u8; CMD_BUF_SIZE], pub(crate) ingress_buf: [u8; INGRESS_BUF_SIZE], - #[cfg(feature = "cmux")] pub(crate) mux: embassy_at_cmux::Mux, } @@ -41,9 +40,9 @@ impl { - iface: (R, W), +pub struct Runner<'a, T, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> { + transport: T, pub ch: state::Runner<'a>, pub config: C, @@ -60,35 +75,30 @@ pub struct Runner<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY pub cmd_buf: &'a mut [u8], pub res_slot: &'a atat::ResponseSlot, - #[cfg(feature = "cmux")] pub mux_runner: embassy_at_cmux::Runner<'a, CMUX_CHANNELS, CMUX_CHANNEL_SIZE>, - #[cfg(feature = "cmux")] network_channel: ( embassy_at_cmux::ChannelRx<'a, CMUX_CHANNEL_SIZE>, embassy_at_cmux::ChannelTx<'a, CMUX_CHANNEL_SIZE>, embassy_at_cmux::ChannelLines<'a, CMUX_CHANNEL_SIZE>, ), - - #[cfg(feature = "cmux")] data_channel: embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, #[cfg(feature = "ppp")] pub 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, C: CellularConfig<'a> + 'a, { pub fn new( - iface: (R, W), + transport: T, resources: &'a mut Resources, config: C, - ) -> Self { + ) -> (Self, Control<'a>) { let ch_runner = state::Runner::new(&mut resources.ch); let ingress = atat::Ingress::new( @@ -98,34 +108,43 @@ where &resources.urc_channel, ); - #[cfg(feature = "cmux")] let (mux_runner, channels) = resources.mux.start(); - #[cfg(feature = "cmux")] let mut channel_iter = channels.into_iter(); - Self { - iface, + let network_channel = channel_iter.next().unwrap().split(); + let data_channel = channel_iter.next().unwrap(); + let control_channel = channel_iter.next().unwrap(); + + let control_client = SimpleClient::new( + control_channel, + atat::AtDigester::new(), + &mut resources.control_cmd_buf, + C::AT_CONFIG, + ); + let control = Control::new(ch_runner.clone(), control_client); - ch: ch_runner, - config, - urc_channel: &resources.urc_channel, + ( + Self { + transport, - ingress, - cmd_buf: &mut resources.cmd_buf, - res_slot: &resources.res_slot, + ch: ch_runner, + config, + urc_channel: &resources.urc_channel, - #[cfg(feature = "cmux")] - mux_runner, + ingress, + cmd_buf: &mut resources.cmd_buf, + res_slot: &resources.res_slot, - #[cfg(feature = "cmux")] - network_channel: channel_iter.next().unwrap().split(), + mux_runner, - #[cfg(feature = "cmux")] - data_channel: channel_iter.next().unwrap(), + network_channel, + data_channel, - #[cfg(feature = "ppp")] - ppp_runner: None, - } + #[cfg(feature = "ppp")] + ppp_runner: None, + }, + control, + ) } #[cfg(feature = "ppp")] @@ -140,6 +159,7 @@ where #[cfg(feature = "internal-network-stack")] pub fn internal_stack(&mut self) -> state::Device { + // let data_channel = self.data_channel; state::Device { shared: &self.ch.shared, desired_state_pub_sub: &self.ch.desired_state_pub_sub, @@ -147,86 +167,356 @@ where } } - pub fn control(&self) -> Control<'a> { - Control::new(self.ch.clone()) + /// 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 cellular module using baud rate: {}", + baudrate as u32 + ); + self.transport.set_baudrate(baudrate as u32); + + { + let mut at_client = SimpleClient::new( + &mut self.transport, + atat::AtDigester::::new(), + self.cmd_buf, + C::AT_CONFIG, + ); + + // Allow auto bauding to kick in + embassy_time::with_timeout(Duration::from_secs(5), async { + loop { + if at_client.send(&AT).await.is_ok() { + break; + } + Timer::after(Duration::from_millis(100)).await; + } + }) + .await?; + + // Lets take a shortcut if we successfully probed for the desired + // baudrate + if baudrate == C::BAUD_RATE { + return Ok(()); + } + + at_client + .send_retry(&SetDataRate { rate: C::BAUD_RATE }) + .await?; + } + + self.transport.set_baudrate(C::BAUD_RATE as u32); + + // On the UART AT interface, after the reception of the "OK" result code + // for the +IPR command, the DTE shall wait for at least 100 ms before + // issuing a new AT command; this is to guarantee a proper baud rate + // reconfiguration + Timer::after_millis(100).await; + + // Verify communication + SimpleClient::new( + &mut self.transport, + atat::AtDigester::::new(), + self.cmd_buf, + C::AT_CONFIG, + ) + .send_retry(&AT) + .await?; + + Ok(()) + } + + async fn init(&mut self) -> Result<(), Error> { + // Initialize a new ublox device to a known state (set RS232 settings) + debug!("Initializing cellular module"); + + let mut pwr = PwrCtrl::new(&self.ch, &mut self.config); + if let Err(e) = pwr.power_up().await { + pwr.power_down().await?; + return Err(e); + }; + + // 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::B19200, + BaudRate::B38400, + BaudRate::B57600, + BaudRate::B115200, + BaudRate::B230400, + BaudRate::B460800, + BaudRate::B921600, + BaudRate::B3000000, + ] { + match with_timeout(Duration::from_secs(6), self.probe_baud(baudrate)).await { + Ok(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. + + // TODO: Is this correct? + // Some modules seem to persist baud rates by themselves. + // Nothing to do here for now. + } + found_baudrate = true; + break; + } + _ => {} + } + } + + if !found_baudrate { + // TODO: Attempt to do some better recovery here? + PwrCtrl::new(&self.ch, &mut self.config) + .power_down() + .await?; + + return Err(Error::BaudDetection); + } + + let mut at_client = SimpleClient::new( + &mut self.transport, + atat::AtDigester::::new(), + self.cmd_buf, + C::AT_CONFIG, + ); + + // FIXME: + // // Tell module whether we support flow control + // let flow_control = if C::FLOW_CONTROL { + // FlowControl::RtsCts + // } else { + // FlowControl::Disabled + // }; + + // at_client + // .send_retry(&SetFlowControl { + // value: flow_control, + // }) + // .await?; + + let model_id = at_client.send_retry(&GetModelId).await?; + self.ch.set_module(Module::from_model_id(&model_id)); + + let FirmwareVersion { version } = at_client.send_retry(&GetFirmwareVersion).await?; + info!( + "Found module to be: {=[u8]:a}, {=[u8]:a}", + model_id.model.as_slice(), + version.as_slice() + ); + + at_client + .send_retry(&SetEmbeddedPortFiltering { + mode: C::EMBEDDED_PORT_FILTERING, + }) + .await?; + + // // FIXME: The following three GPIO settings should not be here! + let _ = at_client + .send_retry(&SetGpioConfiguration { + gpio_id: 23, + gpio_mode: GpioMode::NetworkStatus, + }) + .await; + + // Select SIM + at_client + .send_retry(&SetGpioConfiguration { + gpio_id: 25, + gpio_mode: GpioMode::Output(GpioOutValue::High), + }) + .await?; + + #[cfg(feature = "lara-r6")] + at_client + .send_retry(&SetGpioConfiguration { + gpio_id: 42, + gpio_mode: GpioMode::Input(GpioInPull::NoPull), + }) + .await?; + + // self.soft_reset(true).await?; + + // self.wait_alive( + // self.ch + // .module() + // .map(|m| m.boot_wait()) + // .unwrap_or(Generic.boot_wait()) + // * 2, + // ) + // .await?; + + // Echo off + at_client + .send_retry(&SetEcho { enabled: Echo::Off }) + .await?; + + // Extended errors on + at_client + .send_retry(&SetReportMobileTerminationError { + n: TerminationErrorMode::Enabled, + }) + .await?; + + #[cfg(feature = "internal-network-stack")] + if C::HEX_MODE { + at_client + .send_retry(&crate::command::ip_transport_layer::SetHexMode { + hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Enabled, + }) + .await?; + } else { + at_client + .send_retry(&crate::command::ip_transport_layer::SetHexMode { + hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Disabled, + }) + .await?; + } + + // DCD circuit (109) changes in accordance with the carrier + at_client + .send_retry(&SetCircuit109Behaviour { + value: Circuit109Behaviour::AlwaysPresent, + }) + .await?; + + // Ignore changes to DTR + at_client + .send_retry(&SetCircuit108Behaviour { + value: Circuit108Behaviour::Ignore, + }) + .await?; + + // Check sim status + let sim_status = async { + for _ in 0..2 { + if let Ok(PinStatus { + code: PinStatusCode::Ready, + }) = at_client.send(&GetPinStatus).await + { + debug!("SIM is ready"); + return Ok(()); + } + + Timer::after_secs(1).await; + } + + // There was an error initializing the SIM + // We've seen issues on uBlox-based devices, as a precation, we'll cycle + // the modem here through minimal/full functional state. + at_client + .send(&SetModuleFunctionality { + fun: self + .ch + .module() + .ok_or(Error::Uninitialized)? + .radio_off_cfun(), + rst: None, + }) + .await?; + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Full, + rst: None, + }) + .await?; + + Err(Error::SimCard) + }; + + sim_status.await?; + + let ccid = at_client.send_retry(&GetCCID).await?; + info!("CCID: {}", ccid.ccid); + + at_client + .send_retry(&SetResultCodeSelection { + value: ResultCodeSelection::ConnectOnly, + }) + .await?; + + #[cfg(all( + feature = "ucged", + any( + feature = "sara-r410m", + feature = "sara-r412m", + feature = "sara-r422", + feature = "lara-r6" + ) + ))] + at_client + .send_retry(&SetChannelAndNetworkEnvDesc { + mode: if cfg!(feature = "ucged5") { 5 } else { 2 }, + }) + .await?; + + // Switch off UART power saving until it is integrated into this API + at_client + .send_retry(&SetPowerSavingControl { + mode: PowerSavingMode::Disabled, + timeout: None, + }) + .await?; + + if self.ch.desired_state(None) == OperationState::Initialized { + at_client + .send_retry(&SetModuleFunctionality { + fun: self + .ch + .module() + .ok_or(Error::Uninitialized)? + .radio_off_cfun(), + rst: None, + }) + .await?; + } + + self.ch.set_operation_state(OperationState::Initialized); + + Ok(()) } pub async fn run( &mut self, stack: &embassy_net::Stack, ) -> ! { - 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 cell_device = NetDevice::new(&self.ch, &mut self.config, at_client); - let mut urc_handler = UrcHandler::new(&self.ch, self.urc_channel); - // Clean up and start from completely powered off state. Ignore URCs in the process. - self.ingress.clear(); - - warn!("STARTING CELLULAR MODULE!"); - if self.ch.desired_state(None) != OperationState::DataEstablished { - self.ch.wait_for_desired_state_change().await; - } else { - if cell_device - .run_to_state(OperationState::PowerDown) - .await - .is_err() - { - continue; - } - } + let _ = PwrCtrl::new(&self.ch, &mut self.config).power_down().await; - match embassy_futures::select::select3( - self.ingress.read_from(&mut self.iface.0), - urc_handler.run(), - cell_device.run_to_state(OperationState::DataEstablished), - ) - .await - { - Either3::First(_) | Either3::Second(_) => { - // These two both have return type never (`-> !`) - unreachable!() - } - Either3::Third(Err(_)) => { - // Reboot the cellular module and try again! - continue; - } - Either3::Third(Ok(_)) => { - // All good! We are now in `DataEstablished` and ready - // to start communication services! - } - } + // Wait for the desired state to change to anything but `PowerDown` + poll_fn(|cx| match self.ch.desired_state(Some(cx)) { + OperationState::PowerDown => Poll::Pending, + _ => Poll::Ready(()), + }) + .await; + + if self.init().await.is_err() { + continue; } #[cfg(feature = "ppp")] let ppp_fut = async { - #[cfg(not(feature = "cmux"))] - let mut iface = super::ReadWriteAdapter(&mut self.iface.0, &mut self.iface.1); + self.ch + .wait_for_operation_state(OperationState::DataEstablished) + .await; + + Timer::after_secs(1).await; let mut fails = 0; let mut last_start = None; loop { - if self.ch.desired_state(None) != OperationState::DataEstablished { - break; - } - self.ch - .wait_for_operation_state(OperationState::DataEstablished) - .await; - if let Some(last_start) = last_start { Timer::at(last_start + Duration::from_secs(10)).await; // Do not attempt to start too fast. @@ -252,16 +542,10 @@ where &mut self.data_channel, atat::AtDigester::::new(), &mut buf, - at_config, + C::AT_CONFIG, ); - let _ = at_client.send(&DeactivatePDPContext).await; - - // hangup just in case a call was already in progress. - // Ignore errors because this fails if it wasn't. - // let _ = at_client - // .send(&heapless::String::<16>::try_from("ATX0\r\n").unwrap()) - // .await; + // let _ = at_client.send(&DeactivatePDPContext).await; // Send AT command to enter PPP mode let res = at_client.send(&EnterPPP { cid: C::CONTEXT_ID }).await; @@ -275,8 +559,7 @@ where } // Check for CTS low (bit 2) - // #[cfg(feature = "cmux")] - // self.data_channel.set_hangup_detection(0x04, 0x00); + self.data_channel.set_hangup_detection(0x04, 0x00); info!("RUNNING PPP"); let res = self @@ -284,6 +567,8 @@ where .as_mut() .unwrap() .run(&mut self.data_channel, C::PPP_CONFIG, |ipv4| { + debug!("Running on_ipv4_up for cellular!"); + let Some(addr) = ipv4.address else { warn!("PPP did not provide an IP address."); return; @@ -309,35 +594,30 @@ where info!("ppp failed: {:?}", res); - #[cfg(feature = "cmux")] - { - self.data_channel.clear_hangup_detection(); + self.data_channel.clear_hangup_detection(); - // escape back to data mode. - self.data_channel - .set_lines(embassy_at_cmux::Control::from_bits(0x44 << 1), None); - Timer::after(Duration::from_millis(100)).await; - self.data_channel - .set_lines(embassy_at_cmux::Control::from_bits(0x46 << 1), None); + // escape back to data mode. + self.data_channel + .set_lines(embassy_at_cmux::Control::from_bits(0x44 << 1), None); + Timer::after(Duration::from_millis(100)).await; + self.data_channel + .set_lines(embassy_at_cmux::Control::from_bits(0x46 << 1), None); + + if self.ch.desired_state(None) != OperationState::DataEstablished { + break; } } }; - #[cfg(feature = "cmux")] let mux_fut = async { - self.ch - .wait_for_operation_state(OperationState::DataEstablished) - .await; - // Must be large enough to hold 'AT+CMUX=0,0,5,512,10,3,40,10,2\r\n' let mut buf = [0u8; 32]; - let mut interface = super::ReadWriteAdapter(&mut self.iface.0, &mut self.iface.1); { let mut at_client = SimpleClient::new( - &mut interface, + &mut self.transport, atat::AtDigester::::new(), &mut buf, - at_config, + C::AT_CONFIG, ); at_client @@ -359,50 +639,41 @@ where // The UART interface takes around 200 ms to reconfigure itself // after the multiplexer configuration through the +CMUX AT // command. - Timer::after(Duration::from_millis(200)).await; + Timer::after_millis(200).await; // Drain the UART of any leftover AT stuff before setting up multiplexer let _ = embassy_time::with_timeout(Duration::from_millis(100), async { loop { - let _ = interface.read(&mut buf).await; + let _ = self.transport.read(&mut buf).await; } }) .await; + let (mut tx, mut rx) = self.transport.split_ref(); self.mux_runner - .run(&mut self.iface.0, &mut self.iface.1, CMUX_MAX_FRAME_SIZE) + .run(&mut rx, &mut tx, CMUX_MAX_FRAME_SIZE) .await }; - #[cfg(not(all(feature = "ppp", not(feature = "cmux"))))] - let network_fut = async { - #[cfg(not(feature = "cmux"))] - let (mut at_rx, mut at_tx) = self.iface; - - #[cfg(feature = "cmux")] + let device_fut = async { let (at_rx, at_tx, _) = &mut self.network_channel; - - let at_client = - atat::asynch::Client::new(at_tx, self.res_slot, self.cmd_buf, at_config); - let mut cell_device = NetDevice::new(&self.ch, &mut self.config, at_client); + let mut cell_device = NetDevice::::new( + &self.ch, + atat::asynch::Client::new(at_tx, self.res_slot, self.cmd_buf, C::AT_CONFIG), + ); let mut urc_handler = UrcHandler::new(&self.ch, self.urc_channel); - // TODO: Should we set ATE0 and CMEE=1 here, again? - - embassy_futures::join::join3( + select3( self.ingress.read_from(at_rx), - cell_device.run(), urc_handler.run(), + cell_device.run(), ) - .await; + .await }; - #[cfg(all(feature = "ppp", not(feature = "cmux")))] - ppp_fut.await; - - #[cfg(all(feature = "ppp", feature = "cmux"))] - match embassy_futures::select::select3(mux_fut, ppp_fut, network_fut).await { + #[cfg(feature = "ppp")] + match select3(mux_fut, ppp_fut, device_fut).await { Either3::First(_) => { warn!("Breaking to reboot modem from multiplexer"); } @@ -414,8 +685,8 @@ where } } - #[cfg(all(feature = "cmux", not(feature = "ppp")))] - match embassy_futures::select::select(mux_fut, network_fut).await { + #[cfg(not(feature = "ppp"))] + match embassy_futures::select::select(mux_fut, device_fut).await { embassy_futures::select::Either::First(_) => { warn!("Breaking to reboot modem from multiplexer"); } diff --git a/src/asynch/state.rs b/src/asynch/state.rs index 42ee2c3..fbdae44 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -4,10 +4,8 @@ use core::cell::RefCell; use core::future::poll_fn; use core::task::{Context, Poll}; -use atat::UrcSubscription; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; -use embassy_sync::pubsub::PubSubChannel; use embassy_sync::waitqueue::WakerRegistration; /// The link state of a network device. @@ -21,32 +19,15 @@ pub enum LinkState { } /// If the celular modem is up and responding to AT. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OperationState { PowerDown = 0, - PowerUp, - Initialized, - Connected, - DataEstablished, + Initialized = 1, + Connected = 2, + DataEstablished = 3, } -impl TryFrom for OperationState { - fn try_from(state: isize) -> Result { - match state { - 0 => Ok(OperationState::PowerDown), - 1 => Ok(OperationState::PowerUp), - 2 => Ok(OperationState::Initialized), - 3 => Ok(OperationState::Connected), - 4 => Ok(OperationState::DataEstablished), - _ => Err(()), - } - } - type Error = (); -} - -use crate::command::Urc; -use crate::error::Error; use crate::modules::Module; use crate::registration::{ProfileState, RegistrationState}; @@ -54,6 +35,12 @@ pub struct State { shared: Mutex>, } +impl Default for State { + fn default() -> Self { + Self::new() + } +} + impl State { pub const fn new() -> Self { Self { @@ -61,7 +48,7 @@ impl State { link_state: LinkState::Down, operation_state: OperationState::PowerDown, module: None, - desired_state: OperationState::DataEstablished, + desired_state: OperationState::Initialized, registration_state: RegistrationState::new(), state_waker: WakerRegistration::new(), registration_waker: WakerRegistration::new(), @@ -93,11 +80,11 @@ impl<'d> Runner<'d> { } } - pub fn module(&self) -> Option { + pub(crate) fn module(&self) -> Option { self.shared.lock(|s| s.borrow().module) } - pub fn set_module(&self, module: Module) { + pub(crate) fn set_module(&self, module: Module) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.module.replace(module); @@ -126,6 +113,7 @@ impl<'d> Runner<'d> { }) } + #[cfg(not(feature = "use-upsd-context-activation"))] pub fn set_profile_state(&self, state: ProfileState) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); @@ -133,6 +121,7 @@ impl<'d> Runner<'d> { }) } + #[cfg(not(feature = "use-upsd-context-activation"))] pub fn get_profile_state(&self) -> ProfileState { self.shared .lock(|s| s.borrow().registration_state.profile_state) @@ -146,7 +135,7 @@ impl<'d> Runner<'d> { }); } - pub fn link_state(&mut self, cx: Option<&mut Context>) -> LinkState { + pub fn link_state(&self, cx: Option<&mut Context>) -> LinkState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); if let Some(cx) = cx { @@ -174,7 +163,7 @@ impl<'d> Runner<'d> { }) } - pub fn set_desired_state(&mut self, ps: OperationState) { + pub fn set_desired_state(&self, ps: OperationState) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.desired_state = ps; @@ -192,7 +181,7 @@ impl<'d> Runner<'d> { }) } - pub async fn wait_for_desired_state(&mut self, ps: OperationState) { + pub async fn wait_for_desired_state(&self, ps: OperationState) { if self.desired_state(None) == ps { return; } @@ -221,7 +210,7 @@ impl<'d> Runner<'d> { } pub async fn wait_for_desired_state_change(&self) -> OperationState { - let old_desired = self.shared.lock(|s| s.borrow().desired_state); + let old_desired = self.desired_state(None); poll_fn(|cx| { let current_desired = self.desired_state(Some(cx)); @@ -234,9 +223,7 @@ impl<'d> Runner<'d> { } pub async fn wait_registration_change(&self) -> bool { - let old_state = self - .shared - .lock(|s| s.borrow().registration_state.is_registered()); + let old_state = self.is_registered(None); poll_fn(|cx| { let current_state = self.is_registered(Some(cx)); @@ -258,7 +245,7 @@ pub struct Device<'d, const URC_CAPACITY: usize> { #[cfg(feature = "internal-network-stack")] impl<'d, const URC_CAPACITY: usize> Device<'d, URC_CAPACITY> { - pub fn link_state(&mut self, cx: &mut Context) -> LinkState { + pub fn link_state(&self, cx: &mut Context) -> LinkState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.state_waker.register(cx.waker()); @@ -266,7 +253,7 @@ impl<'d, const URC_CAPACITY: usize> Device<'d, URC_CAPACITY> { }) } - pub fn operation_state(&mut self, cx: &mut Context) -> OperationState { + pub fn operation_state(&self, cx: &mut Context) -> OperationState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.state_waker.register(cx.waker()); @@ -274,14 +261,14 @@ impl<'d, const URC_CAPACITY: usize> Device<'d, URC_CAPACITY> { }) } - pub fn link_state(&mut self) -> LinkState { + pub fn link_state(&self) -> LinkState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.link_state }) } - pub fn operation_state(&mut self) -> OperationState { + pub fn operation_state(&self) -> OperationState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.operation_state @@ -296,7 +283,7 @@ impl<'d, const URC_CAPACITY: usize> Device<'d, URC_CAPACITY> { }) } - pub fn set_desired_state(&mut self, ps: OperationState) { + pub fn set_desired_state(&self, ps: OperationState) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.desired_state = ps; @@ -304,7 +291,7 @@ impl<'d, const URC_CAPACITY: usize> Device<'d, URC_CAPACITY> { }); } - pub async fn wait_for_desired_state(&mut self, ps: OperationState) { + pub async fn wait_for_desired_state(&self, ps: OperationState) { poll_fn(|cx| { if self.desired_state(cx) == ps { return Poll::Ready(()); @@ -314,7 +301,7 @@ impl<'d, const URC_CAPACITY: usize> Device<'d, URC_CAPACITY> { .await } - pub async fn wait_for_desired_state_change(&mut self) -> OperationState { + pub async fn wait_for_desired_state_change(&self) -> OperationState { let current_desired = self.shared.lock(|s| s.borrow().desired_state); poll_fn(|cx| { diff --git a/src/command/control/mod.rs b/src/command/control/mod.rs index 3fe7188..24d2a5a 100644 --- a/src/command/control/mod.rs +++ b/src/command/control/mod.rs @@ -9,7 +9,8 @@ use super::NoResponse; use atat::atat_derive::AtatCmd; use responses::DataRate; use types::{ - BaudRate, Circuit108Behaviour, Circuit109Behaviour, Echo, FlowControl, SoftwareFlowControl, + BaudRate, Circuit108Behaviour, Circuit109Behaviour, Echo, FlowControl, ResultCodeSelection, + SoftwareFlowControl, }; /// 15.2 Circuit 109 behavior &C @@ -117,3 +118,14 @@ pub struct SetEcho { #[at_arg(position = 0)] pub enabled: Echo, } + +/// 14.21 Result code selection and call progress monitoring control X +/// +/// In a CS data call, determines how the DCE transmits to the DTE the CONNECT +/// result code. +#[derive(Clone, AtatCmd)] +#[at_cmd("X", NoResponse, value_sep = false)] +pub struct SetResultCodeSelection { + #[at_arg(position = 0)] + pub value: ResultCodeSelection, +} diff --git a/src/command/control/types.rs b/src/command/control/types.rs index f3dc135..7c64b83 100644 --- a/src/command/control/types.rs +++ b/src/command/control/types.rs @@ -21,6 +21,14 @@ pub enum Echo { On = 1, } +#[derive(Clone, PartialEq, Eq, AtatEnum)] +pub enum ResultCodeSelection { + /// 0: CONNECT result code is given upon entering online data state; + ConnectOnly = 0, + /// 1-4: CONNECT result code is given upon entering online data state; + ConnectSpeed = 1, +} + /// Indicates the behavior of circuit 108 #[derive(Clone, PartialEq, Eq, AtatEnum)] pub enum Circuit108Behaviour { @@ -74,7 +82,7 @@ pub enum BaudRate { feature = "sara-g3", feature = "sara-g4" ))] - B0 = 0, + Auto = 0, #[cfg(any(feature = "lisa-u1", feature = "lisa-u2", feature = "sara-u2",))] B1200 = 1200, #[cfg(any( diff --git a/src/command/general/responses.rs b/src/command/general/responses.rs index 49ed0b8..0e88e44 100644 --- a/src/command/general/responses.rs +++ b/src/command/general/responses.rs @@ -1,4 +1,5 @@ //! Responses for General Commands +use super::types; use atat::atat_derive::AtatResp; use atat::heapless_bytes::Bytes; @@ -23,7 +24,7 @@ pub struct ModelId { #[derive(Clone, Debug, AtatResp)] pub struct FirmwareVersion { #[at_arg(position = 0)] - pub version: Bytes<10>, + pub version: types::FirmwareVersion, } /// 4.7 IMEI identification +CGSN diff --git a/src/command/general/types.rs b/src/command/general/types.rs index 0a58407..aea1eeb 100644 --- a/src/command/general/types.rs +++ b/src/command/general/types.rs @@ -12,3 +12,79 @@ pub enum Snt { /// IMEI (not including the spare digit), the check digit and the SVN IMEIExtended = 255, } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FirmwareVersion { + major: u8, + minor: u8, +} + +impl FirmwareVersion { + pub fn new(major: u8, minor: u8) -> Self { + Self { major, minor } + } +} + +impl PartialOrd for FirmwareVersion { + fn partial_cmp(&self, other: &Self) -> Option { + match self.major.partial_cmp(&other.major) { + Some(core::cmp::Ordering::Equal) => {} + ord => return ord, + } + self.minor.partial_cmp(&other.minor) + } +} + +pub struct DeserializeError; + +impl core::fmt::Display for DeserializeError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Failed to deserialize version") + } +} + +impl core::str::FromStr for FirmwareVersion { + type Err = DeserializeError; + fn from_str(s: &str) -> Result { + let mut iter = s.splitn(2, '.'); + let major = iter + .next() + .and_then(|s| s.parse().ok()) + .ok_or(DeserializeError)?; + let minor = iter + .next() + .and_then(|s| s.parse().ok()) + .ok_or(DeserializeError)?; + + Ok(Self { major, minor }) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareVersion { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{}.{}", self.major, self.minor) + } +} + +impl Serialize for FirmwareVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut str = heapless::String::<7>::new(); + str.write_fmt(format_args!("{}.{}", self.major, self.minor)) + .map_err(serde::ser::Error::custom)?; + serializer.serialize_str(&str) + } +} + +impl<'de> Deserialize<'de> for FirmwareVersion { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = heapless::String::<7>::deserialize(deserializer)?; + core::str::FromStr::from_str(&s).map_err(serde::de::Error::custom) + } +} diff --git a/src/command/mod.rs b/src/command/mod.rs index 2cb2c39..2b5e0c5 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -29,7 +29,7 @@ use atat::{ pub struct NoResponse; #[derive(Clone, AtatCmd)] -#[at_cmd("", NoResponse)] +#[at_cmd("", NoResponse, attempts = 3)] pub struct AT; #[derive(Debug, Clone, AtatUrc)] diff --git a/src/command/networking/responses.rs b/src/command/networking/responses.rs index b56b514..10878a3 100644 --- a/src/command/networking/responses.rs +++ b/src/command/networking/responses.rs @@ -1,6 +1,5 @@ //! Responses for Networking Commands use atat::atat_derive::AtatResp; -use heapless::String; /// 34.4 Configure port filtering for embedded applications +UEMBPF #[derive(Debug, Clone, AtatResp)] diff --git a/src/command/networking/types.rs b/src/command/networking/types.rs index 44d73cd..16b9197 100644 --- a/src/command/networking/types.rs +++ b/src/command/networking/types.rs @@ -30,7 +30,7 @@ impl Serialize for EmbeddedPortFilteringMode { atat::serde_at::serde::ser::Serializer::serialize_tuple_variant( serializer, "EmbeddedPortFilteringMode", - 1 as u32, + 1_u32, "Enable", 0, )?; diff --git a/src/command/psn/mod.rs b/src/command/psn/mod.rs index c8aaf4b..8ac2a71 100644 --- a/src/command/psn/mod.rs +++ b/src/command/psn/mod.rs @@ -288,7 +288,6 @@ pub struct GetPDPContextState; "D*99***", NoResponse, value_sep = false, - timeout_ms = 180000, abortable = true, termination = "#\r\n" )] diff --git a/src/config.rs b/src/config.rs index a7f6122..054431d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,14 @@ use core::convert::Infallible; use embedded_hal::digital::{ErrorType, InputPin, OutputPin, PinState}; +use embedded_io_async::{BufRead, Read, Write}; -use crate::command::{ - networking::types::EmbeddedPortFilteringMode, - psn::types::{ContextId, ProfileId}, +use crate::{ + command::{ + control::types::BaudRate, + networking::types::EmbeddedPortFilteringMode, + psn::types::{ContextId, ProfileId}, + }, + DEFAULT_BAUD_RATE, }; pub struct NoPin; @@ -76,7 +81,11 @@ pub trait CellularConfig<'a> { type PowerPin: OutputPin; type VintPin: InputPin; + 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 HEX_MODE: bool = true; @@ -107,6 +116,11 @@ pub trait CellularConfig<'a> { } } +pub trait Transport: Write + Read + BufRead { + fn set_baudrate(&mut self, baudrate: u32); + fn split_ref(&mut self) -> (impl Write, impl Read + BufRead); +} + #[repr(u8)] pub enum OperatorFormat { Long = 0, diff --git a/src/error.rs b/src/error.rs index 3b28f1f..d917893 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,6 +13,7 @@ pub enum GenericError { pub enum Error { // General device errors BaudDetection, + SimCard, Busy, Uninitialized, StateTimeout, diff --git a/src/lib.rs b/src/lib.rs index 3ba2859..528168c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,3 +14,6 @@ mod modules; mod registration; pub mod asynch; + +use command::control::types::BaudRate; +pub const DEFAULT_BAUD_RATE: BaudRate = BaudRate::B115200; diff --git a/src/modules/lara_r6.rs b/src/modules/lara_r6.rs index 670a00f..7b0199b 100644 --- a/src/modules/lara_r6.rs +++ b/src/modules/lara_r6.rs @@ -1,5 +1,4 @@ use super::ModuleParams; -use crate::command::mobile_control::types::Functionality; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] diff --git a/src/registration.rs b/src/registration.rs index db2cf99..f508c20 100644 --- a/src/registration.rs +++ b/src/registration.rs @@ -1,5 +1,3 @@ -use core::default; - use crate::command::{ network_service::{ responses::NetworkRegistrationStatus, @@ -210,6 +208,12 @@ pub struct RegistrationState { pub(crate) profile_state: ProfileState, } +impl Default for RegistrationState { + fn default() -> Self { + Self::new() + } +} + impl RegistrationState { pub const fn new() -> Self { Self {