From 1b9838e9c4f9cb9d5151b5252e8629bb877618ba Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Mon, 15 Jan 2024 00:11:40 +0100 Subject: [PATCH 1/8] add APNInfo to config as const --- src/config.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/config.rs b/src/config.rs index d0e254f..bdfa330 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ use core::convert::Infallible; use embedded_hal::digital::{ErrorType, InputPin, OutputPin, PinState}; +use heapless::String; pub struct NoPin; @@ -75,6 +76,12 @@ pub trait CellularConfig { const HEX_MODE: bool = true; const OPERATOR_FORMAT: OperatorFormat = OperatorFormat::Long; + const APN: APNInfo = APNInfo { + apn: Apn::Automatic, + user_name: None, + password: None, + }; + fn reset_pin(&mut self) -> Option<&mut Self::ResetPin>; fn power_pin(&mut self) -> Option<&mut Self::PowerPin>; fn vint_pin(&mut self) -> Option<&mut Self::VintPin>; @@ -86,3 +93,33 @@ pub enum OperatorFormat { Short = 1, Numeric = 2, } + +#[derive(Debug, Clone)] +pub enum Apn { + Given(String<100>), + Automatic, +} + +impl Default for Apn { + fn default() -> Self { + Self::Automatic + } +} + +#[derive(Debug, Clone, Default)] +pub struct APNInfo { + pub apn: Apn, + pub user_name: Option>, + pub password: Option>, +} + +impl APNInfo { + #[must_use] + pub fn new(apn: &str) -> Self { + Self { + apn: Apn::Given(String::try_from(apn).unwrap()), + user_name: None, + password: None, + } + } +} From b6bfa7c7576af03f97bdac44679e07e22536c626 Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Mon, 15 Jan 2024 18:59:31 +0100 Subject: [PATCH 2/8] change apn settings to enum --- Cargo.toml | 2 ++ src/config.rs | 30 +++++------------------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da4e355..22ebb70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,8 @@ defmt = [ log = ["dep:log", "ublox-sockets?/log", "atat/log"] +automatic-apn = [] + lara-r2 = [] lara-r6 = [] leon-g1 = [] diff --git a/src/config.rs b/src/config.rs index bdfa330..1904b49 100644 --- a/src/config.rs +++ b/src/config.rs @@ -76,11 +76,7 @@ pub trait CellularConfig { const HEX_MODE: bool = true; const OPERATOR_FORMAT: OperatorFormat = OperatorFormat::Long; - const APN: APNInfo = APNInfo { - apn: Apn::Automatic, - user_name: None, - password: None, - }; + const APN: Apn = Apn::None; fn reset_pin(&mut self) -> Option<&mut Self::ResetPin>; fn power_pin(&mut self) -> Option<&mut Self::PowerPin>; @@ -96,30 +92,14 @@ pub enum OperatorFormat { #[derive(Debug, Clone)] pub enum Apn { - Given(String<100>), + None, + Given{ name: String<64>, username: Option>, password: Option>}, + #[cfg(any(feature = "automatic-apn"))] Automatic, } impl Default for Apn { fn default() -> Self { - Self::Automatic - } -} - -#[derive(Debug, Clone, Default)] -pub struct APNInfo { - pub apn: Apn, - pub user_name: Option>, - pub password: Option>, -} - -impl APNInfo { - #[must_use] - pub fn new(apn: &str) -> Self { - Self { - apn: Apn::Given(String::try_from(apn).unwrap()), - user_name: None, - password: None, - } + Self::None } } From 5c9a206a82d835119bd9868b09ac9cf7e0872b48 Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Mon, 22 Jan 2024 22:42:55 +0100 Subject: [PATCH 3/8] update dependencies to new embassy release --- Cargo.toml | 4 ++-- examples/embassy-stm32-example/Cargo.toml | 4 ++-- examples/embassy-stm32-example/src/main.rs | 20 ++++++++++---------- src/config.rs | 6 +++++- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22ebb70..73ad664 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ nb = "^1" serde = { version = "^1", default-features = false, features = ["derive"] } #ublox-sockets = { version = "0.5", optional = true } ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", branch = "feature/async-borrowed-sockets", optional = true } -embassy-time = "0.2" +embassy-time = "0.3" embassy-sync = "0.5" no-std-net = { version = "^0.6", features = ["serde"] } @@ -47,7 +47,7 @@ embedded-io-async = "0.6" [features] default = ["socket-udp", "socket-tcp", "async", "ublox-sockets"] -async = ["dep:embedded-nal-async", "dep:embassy-futures", "ublox-sockets?/async"] +async = ["dep:embedded-nal-async", "dep:embassy-futures"] defmt = [ "dep:defmt", diff --git a/examples/embassy-stm32-example/Cargo.toml b/examples/embassy-stm32-example/Cargo.toml index ffc067e..f34e1f1 100644 --- a/examples/embassy-stm32-example/Cargo.toml +++ b/examples/embassy-stm32-example/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" # Example for the STM32H747I-DISCO board with SARA-R5 modem attached to UART8 [dependencies] embassy-stm32 = { version = "0.1.0", features = ["defmt", "stm32h747xi-cm7", "time-driver-any", "exti", "memory-x", "unstable-pac"] } -embassy-executor = { version = "0.4.0", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.2", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-executor = { version = "0.5.0", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3", features = ["defmt", "defmt-timestamp-uptime"] } embassy-sync = { version = "0.5", features = ["defmt"] } diff --git a/examples/embassy-stm32-example/src/main.rs b/examples/embassy-stm32-example/src/main.rs index 5068f64..17adfdb 100644 --- a/examples/embassy-stm32-example/src/main.rs +++ b/examples/embassy-stm32-example/src/main.rs @@ -42,20 +42,20 @@ const URC_CAPACITY: usize = 2; const URC_SUBSCRIBERS: usize = 2; struct MyCelullarConfig { - reset_pin: Option>, + reset_pin: Option>, // reset_pin: Option, - power_pin: Option>>, + power_pin: Option>>, // power_pin: Option, - vint_pin: Option>, + vint_pin: Option>, // vint_pin: Option } impl CellularConfig for MyCelullarConfig { - type ResetPin = Output<'static, AnyPin>; + type ResetPin = Output<'static, >; // type ResetPin = NoPin; - type PowerPin = ReverseOutputPin>; + type PowerPin = ReverseOutputPin>; // type PowerPin = NoPin; - type VintPin = Input<'static, AnyPin>; + type VintPin = Input<'static, >; // type VintPin = NoPin; const FLOW_CONTROL: bool = false; @@ -134,14 +134,14 @@ async fn main_task(spawner: Spawner) { // let power = Output::new(p.PJ4, Level::High, Speed::VeryHigh).degrade(); // let reset = Output::new(p.PF8, Level::High, Speed::VeryHigh).degrade(); let celullar_config = MyCelullarConfig { - reset_pin: Some(Output::new(p.PF8, Level::High, Speed::Low).degrade()), + reset_pin: Some(Output::new(p.PF8, Level::High, Speed::Low)), power_pin: Some(ReverseOutputPin( - Output::new(p.PJ4, Level::Low, Speed::Low).degrade(), + Output::new(p.PJ4, Level::Low, Speed::Low), )), // reset_pin: Some(OutputOpenDrain::new(p.PF8, Level::High, Speed::Low, Pull::None).degrade()), // power_pin: Some(OutputOpenDrain::new(p.PJ4, Level::High, Speed::Low, Pull::None).degrade()), // power_pin: None, - vint_pin: Some(Input::new(p.PJ3, Pull::Down).degrade()), + vint_pin: Some(Input::new(p.PJ3, Pull::Down)), }; static RES_SLOT: ResponseSlot = ResponseSlot::new(); @@ -247,7 +247,7 @@ async fn cellular_task( #[embassy_executor::task(pool_size = 3)] async fn blinky(mut led: AnyPin){ - let mut output = Output::new(led, Level::High, Speed::Low).degrade(); + let mut output = Output::new(led, Level::High, Speed::Low); loop { output.set_high(); Timer::after(Duration::from_millis(1000)).await; diff --git a/src/config.rs b/src/config.rs index 1904b49..30f2522 100644 --- a/src/config.rs +++ b/src/config.rs @@ -93,7 +93,11 @@ pub enum OperatorFormat { #[derive(Debug, Clone)] pub enum Apn { None, - Given{ name: String<64>, username: Option>, password: Option>}, + Given { + name: String<64>, + username: Option>, + password: Option>, + }, #[cfg(any(feature = "automatic-apn"))] Automatic, } From fdb791ed6ce87e5ee614b89653800e714640a21a Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Fri, 26 Jan 2024 18:10:24 +0100 Subject: [PATCH 4/8] can establish psd context and a dns request works --- examples/embassy-stm32-example/src/main.rs | 12 +- src/asynch/mod.rs | 2 +- src/asynch/runner.rs | 363 +++++++++++++++++++-- src/command/dns/responses.rs | 1 + src/config.rs | 18 +- src/error.rs | 5 + 6 files changed, 368 insertions(+), 33 deletions(-) diff --git a/examples/embassy-stm32-example/src/main.rs b/examples/embassy-stm32-example/src/main.rs index 17adfdb..7d2041c 100644 --- a/examples/embassy-stm32-example/src/main.rs +++ b/examples/embassy-stm32-example/src/main.rs @@ -17,13 +17,14 @@ use embassy_stm32::time::{khz, mhz}; use embassy_stm32::usart::{BufferedUart, BufferedUartRx, BufferedUartTx}; use embassy_stm32::{bind_interrupts, interrupt, peripherals, usart, Config}; use embassy_time::{Duration, Timer}; +use atat::heapless::String; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; // use embedded_hal::digital::{ErrorType, InputPin, OutputPin}; use ublox_cellular; -use ublox_cellular::config::{CellularConfig, ReverseOutputPin}; +use ublox_cellular::config::{Apn, CellularConfig, ReverseOutputPin}; use atat::asynch::AtatClient; use atat::{AtDigester, AtatIngress, DefaultDigester, Ingress, Parser}; @@ -50,7 +51,7 @@ struct MyCelullarConfig { // vint_pin: Option } -impl CellularConfig for MyCelullarConfig { +impl<'a> CellularConfig<'a> for MyCelullarConfig { type ResetPin = Output<'static, >; // type ResetPin = NoPin; type PowerPin = ReverseOutputPin>; @@ -60,6 +61,7 @@ impl CellularConfig for MyCelullarConfig { const FLOW_CONTROL: bool = false; const HEX_MODE: bool = true; + const APN: Apn<'a> = Apn::Given {name: "hologram", username: None, password: None}; fn reset_pin(&mut self) -> Option<&mut Self::ResetPin> { info!("reset_pin"); return self.reset_pin.as_mut(); @@ -182,9 +184,9 @@ async fn main_task(spawner: Spawner) { defmt::unwrap!(spawner.spawn(cellular_task(runner))); Timer::after(Duration::from_millis(1000)).await; loop { - control.set_desired_state(OperationState::Connected).await; + control.set_desired_state(OperationState::DataEstablished).await; info!("set_desired_state(PowerState::Alive)"); - while control.power_state() != OperationState::Connected { + while control.power_state() != OperationState::DataEstablished { Timer::after(Duration::from_millis(1000)).await; } Timer::after(Duration::from_millis(10000)).await; @@ -205,6 +207,8 @@ async fn main_task(spawner: Spawner) { } } } + let dns = control.send(&ublox_cellular::command::dns::ResolveNameIp{resolution_type: ublox_cellular::command::dns::types::ResolutionType::DomainNameToIp, ip_domain_string: "www.google.com"}).await; + debug!("dns: {:?}", dns); Timer::after(Duration::from_millis(10000)).await; control.set_desired_state(OperationState::PowerDown).await; info!("set_desired_state(PowerState::PowerDown)"); diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 940b6fc..386de44 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -35,7 +35,7 @@ impl State { } } -pub async fn new<'a, AT: AtatClient, C: CellularConfig, const URC_CAPACITY: usize>( +pub async fn new<'a, AT: AtatClient, C: CellularConfig<'a>, const URC_CAPACITY: usize>( state: &'a mut State, subscriber: &'a UrcChannel, config: C, diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index de543ef..eb3b53d 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -1,3 +1,10 @@ +use crate::command::psn::types::PacketSwitchedParam; +use crate::command::psn::types::ProtocolType; +use crate::command::psn::GetGPRSAttached; +use crate::command::psn::GetPDPContextState; +use crate::command::psn::GetPacketSwitchedNetworkData; +use crate::command::psn::SetPDPContextState; +use crate::command::psn::SetPacketSwitchedConfig; use core::str::FromStr; use crate::{command::Urc, config::CellularConfig}; @@ -17,6 +24,13 @@ use crate::command::ip_transport_layer::types::HexMode; use crate::command::ip_transport_layer::SetHexMode; use crate::command::mobile_control::types::{Functionality, ResetMode, TerminationErrorMode}; use crate::command::mobile_control::{SetModuleFunctionality, SetReportMobileTerminationError}; +use crate::command::psn::responses::GPRSAttached; +use crate::command::psn::responses::PacketSwitchedNetworkData; +use crate::command::psn::types::GPRSAttachedState; +use crate::command::psn::types::PDPContextStatus; +use crate::command::psn::types::PacketSwitchedAction; +use crate::command::psn::types::PacketSwitchedNetworkDataParam; +use crate::command::psn::SetPacketSwitchedAction; use crate::command::system_features::types::PowerSavingMode; use crate::command::system_features::SetPowerSavingControl; use crate::command::AT; @@ -30,6 +44,9 @@ use embedded_hal::digital::{InputPin, OutputPin}; use heapless::String; use no_std_net::{Ipv4Addr, Ipv6Addr}; +use crate::command::psn::types::{ContextId, ProfileId}; +use crate::config::Apn; +use crate::error::Error::Network; use embassy_futures::select::Either; use super::AtHandle; @@ -37,14 +54,14 @@ use super::AtHandle; /// Background runner for the Ublox Module. /// /// You must call `.run()` in a background task for the Ublox Module to operate. -pub struct Runner<'d, AT: AtatClient, C: CellularConfig, const URC_CAPACITY: usize> { +pub struct Runner<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> { ch: state::Runner<'d>, at: AtHandle<'d, AT>, config: C, urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, 2>, } -impl<'d, AT: AtatClient, C: CellularConfig, const URC_CAPACITY: usize> +impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> Runner<'d, AT, C, URC_CAPACITY> { pub(crate) fn new( @@ -453,6 +470,15 @@ impl<'d, AT: AtatClient, C: CellularConfig, const URC_CAPACITY: usize> } } + async fn is_network_attached_loop(&mut self) -> bool { + loop { + if let Ok(true) = self.is_network_attached().await { + return true; + } + Timer::after(Duration::from_secs(1)).await; + } + } + pub async fn run(mut self) -> ! { match self.has_power().await.ok() { Some(false) => { @@ -550,16 +576,44 @@ impl<'d, AT: AtatClient, C: CellularConfig, const URC_CAPACITY: usize> }, Ok(OperationState::Connected) => match self.init_network().await { Ok(_) => { - self.ch.set_power_state(OperationState::Connected); + match with_timeout( + Duration::from_secs(180), + self.is_network_attached_loop(), + ) + .await + { + Ok(_) => { + debug!("Will set Connected"); + self.ch.set_power_state(OperationState::Connected); + debug!("Set Connected"); + } + Err(err) => { + error!("Timeout waiting for network attach: {:?}", err); + break; + } + } } Err(err) => { error!("Error in init_network: {:?}", err); break; } }, - Ok(OperationState::DataEstablished) => { - todo!() - } + Ok(OperationState::DataEstablished) => match self + .connect( + C::APN, + crate::command::psn::types::ProfileId(C::PROFILE_ID), + crate::command::psn::types::ContextId(C::CONTEXT_ID), + ) + .await + { + Ok(_) => { + self.ch.set_power_state(OperationState::DataEstablished); + } + Err(err) => { + error!("Error in connect: {:?}", err); + break; + } + }, Err(_) => { error!("State transition next_state not valid: start_state={}, next_state={}, steps={} ", start_state, next_state, steps); break; @@ -574,23 +628,290 @@ impl<'d, AT: AtatClient, C: CellularConfig, const URC_CAPACITY: usize> } } - async fn handle_urc(&mut self, event: Urc) -> Error { + async fn handle_urc(&mut self, event: Urc) -> Result<(), Error> { match event { // Handle network URCs - Urc::NetworkDetach => todo!(), - Urc::MobileStationDetach => todo!(), - Urc::NetworkDeactivate => todo!(), - Urc::MobileStationDeactivate => todo!(), - Urc::NetworkPDNDeactivate => todo!(), - Urc::MobileStationPDNDeactivate => todo!(), - Urc::SocketDataAvailable(_) => todo!(), - Urc::SocketDataAvailableUDP(_) => todo!(), - Urc::DataConnectionActivated(_) => todo!(), - Urc::DataConnectionDeactivated(_) => todo!(), - Urc::SocketClosed(_) => todo!(), - Urc::MessageWaitingIndication(_) => todo!(), - Urc::ExtendedPSNetworkRegistration(_) => todo!(), - Urc::HttpResponse(_) => todo!(), + Urc::NetworkDetach => warn!("Network detached"), + Urc::MobileStationDetach => warn!("Mobile station detached"), + Urc::NetworkDeactivate => warn!("Network deactivated"), + Urc::MobileStationDeactivate => warn!("Mobile station deactivated"), + Urc::NetworkPDNDeactivate => warn!("Network PDN deactivated"), + Urc::MobileStationPDNDeactivate => warn!("Mobile station PDN deactivated"), + Urc::SocketDataAvailable(_) => warn!("Socket data available"), + Urc::SocketDataAvailableUDP(_) => warn!("Socket data available UDP"), + Urc::DataConnectionActivated(_) => warn!("Data connection activated"), + Urc::DataConnectionDeactivated(_) => warn!("Data connection deactivated"), + Urc::SocketClosed(_) => warn!("Socket closed"), + Urc::MessageWaitingIndication(_) => warn!("Message waiting indication"), + Urc::ExtendedPSNetworkRegistration(_) => warn!("Extended PS network registration"), + Urc::HttpResponse(_) => warn!("HTTP response"), }; + Ok(()) + } + + #[allow(unused_variables)] + async fn connect( + &mut self, + apn_info: Apn<'_>, + profile_id: ProfileId, + context_id: ContextId, + ) -> Result<(), Error> { + // This step _shouldn't_ be necessary. However, for reasons I don't + // understand, SARA-R4 can be registered but not attached (i.e. AT+CGATT + // returns 0) on both RATs (unh?). Phil Ware, who knows about these + // things, always goes through (a) register, (b) wait for AT+CGATT to + // return 1 and then (c) check that a context is active with AT+CGACT or + // using AT+UPSD (even for EUTRAN). Since this sequence works for both + // RANs, it is best to be consistent. + let mut attached = false; + for _ in 0..10 { + attached = self.is_network_attached().await?; + if attached { + break; + } + } + if !attached { + return Err(Error::AttachTimeout); + } + + // Activate the context + #[cfg(feature = "upsd-context-activation")] + self.activate_context_upsd(profile_id, apn_info).await?; + #[cfg(not(feature = "upsd-context-activation"))] + self.activate_context(context_id, profile_id).await?; + + Ok(()) + } + + // Make sure we are attached to the cellular network. + async fn is_network_attached(&mut self) -> Result { + // Check for AT+CGATT to return 1 + let GPRSAttached { state } = self.at.send(&GetGPRSAttached).await.map_err(Error::from)?; + + if state == GPRSAttachedState::Attached { + return Ok(true); + } + return Ok(false); + + // self.at .send( &SetGPRSAttached { state: + // GPRSAttachedState::Attached, } ).await .map_err(Error::from)?; + } + + /// Activate context using AT+UPSD commands + /// Required for SARA-G3, SARA-U2 SARA-R5 modules. + #[cfg(feature = "upsd-context-activation")] + async fn activate_context_upsd( + &mut self, + profile_id: ProfileId, + apn_info: Apn<'_>, + ) -> Result<(), Error> { + // Check if the PSD profile is activated (param_tag = 1) + let PacketSwitchedNetworkData { param_tag, .. } = self + .at + .send(&GetPacketSwitchedNetworkData { + profile_id, + param: PacketSwitchedNetworkDataParam::PsdProfileStatus, + }) + .await + .map_err(Error::from)?; + + if param_tag == 0 { + // SARA-U2 pattern: everything is done through AT+UPSD + // Set up the APN + if let Apn::Given { + name, + username, + password, + } = apn_info + { + self.at + .send(&SetPacketSwitchedConfig { + profile_id, + param: PacketSwitchedParam::APN(String::<99>::try_from(name).unwrap()), + }) + .await + .map_err(Error::from)?; + + // Set up the user name + if let Some(user_name) = username { + self.at + .send(&SetPacketSwitchedConfig { + profile_id, + param: PacketSwitchedParam::Username( + String::<64>::try_from(user_name).unwrap(), + ), + }) + .await + .map_err(Error::from)?; + } + + // Set up the password + if let Some(password) = password { + self.at + .send(&SetPacketSwitchedConfig { + profile_id, + param: PacketSwitchedParam::Password( + String::<64>::try_from(password).unwrap(), + ), + }) + .await + .map_err(Error::from)?; + } + } + // Set up the dynamic IP address assignment. + #[cfg(not(feature = "sara-r5"))] + self.at + .send(&SetPacketSwitchedConfig { + profile_id, + param: PacketSwitchedParam::IPAddress(Ipv4Addr::unspecified().into()), + }) + .await + .map_err(Error::from)?; + + // Automatic authentication protocol selection + #[cfg(not(feature = "sara-r5"))] + self.at + .send(&SetPacketSwitchedConfig { + profile_id, + param: PacketSwitchedParam::Authentication(AuthenticationType::Auto), + }) + .await + .map_err(Error::from)?; + + #[cfg(not(feature = "sara-r5"))] + self.at + .send(&SetPacketSwitchedConfig { + profile_id, + param: PacketSwitchedParam::IPAddress(Ipv4Addr::unspecified().into()), + }) + .await + .map_err(Error::from)?; + + #[cfg(feature = "sara-r5")] + self.at + .send(&SetPacketSwitchedConfig { + profile_id, + param: PacketSwitchedParam::ProtocolType(ProtocolType::IPv4), + }) + .await + .map_err(Error::from)?; + + #[cfg(feature = "sara-r5")] + self.at + .send(&SetPacketSwitchedConfig { + profile_id, + param: PacketSwitchedParam::MapProfile(ContextId(1)), + }) + .await + .map_err(Error::from)?; + + self.at + .send(&SetPacketSwitchedAction { + profile_id, + action: PacketSwitchedAction::Activate, + }) + .await + .map_err(Error::from)?; + } + + Ok(()) + } + + /// Activate context using 3GPP commands + /// Required for SARA-R4 and TOBY modules. + #[cfg(not(feature = "upsd-context-activation"))] + async fn activate_context( + &mut self, + cid: ContextId, + profile_id: ProfileId, + ) -> Result<(), Error> { + for _ in 0..10 { + let context_states = self + .at + .send(&GetPDPContextState) + .await + .map_err(Error::from)?; + + let activated = context_states + .iter() + .find_map(|state| { + if state.cid == cid { + Some(state.status == PDPContextStatus::Activated) + } else { + None + } + }) + .unwrap_or(false); + + if activated { + // Note: SARA-R4 only supports a single context at any one time and + // so doesn't require/support AT+UPSD. + #[cfg(not(any(feature = "sara-r4", feature = "lara-r6")))] + { + if let psn::responses::PacketSwitchedConfig { + param: psn::types::PacketSwitchedParam::MapProfile(context), + .. + } = self + .at + .send(&psn::GetPacketSwitchedConfig { + profile_id, + param: psn::types::PacketSwitchedParamReq::MapProfile, + }) + .await + .map_err(Error::from)? + { + if context != cid { + self.at + .send(&psn::SetPacketSwitchedConfig { + profile_id, + param: psn::types::PacketSwitchedParam::MapProfile(cid), + }) + .await + .map_err(Error::from)?; + + self.at + .send( + &psn::GetPacketSwitchedNetworkData { + profile_id, + param: psn::types::PacketSwitchedNetworkDataParam::PsdProfileStatus, + }, + ).await + .map_err(Error::from)?; + } + } + + let psn::responses::PacketSwitchedNetworkData { param_tag, .. } = self + .at + .send(&psn::GetPacketSwitchedNetworkData { + profile_id, + param: psn::types::PacketSwitchedNetworkDataParam::PsdProfileStatus, + }) + .await + .map_err(Error::from)?; + + if param_tag == 0 { + self.at + .send(&psn::SetPacketSwitchedAction { + profile_id, + action: psn::types::PacketSwitchedAction::Activate, + }) + .await + .map_err(Error::from)?; + } + } + + return Ok(()); + } else { + self.at + .send(&SetPDPContextState { + status: PDPContextStatus::Activated, + cid: Some(cid), + }) + .await + .map_err(Error::from)?; + Timer::after(Duration::from_secs(1)).await; + } + } + return Err(Error::ContextActivationTimeout); } } diff --git a/src/command/dns/responses.rs b/src/command/dns/responses.rs index ce3096e..4b8c03e 100644 --- a/src/command/dns/responses.rs +++ b/src/command/dns/responses.rs @@ -4,6 +4,7 @@ use heapless::String; /// 24.1 Resolve name / IP number through DNS +UDNSRN #[derive(Clone, PartialEq, Eq, AtatResp)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ResolveNameIpResponse { #[at_arg(position = 0)] pub ip_domain_string: String<128>, diff --git a/src/config.rs b/src/config.rs index 30f2522..a78d0e4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -67,7 +67,7 @@ impl> InputPin for ReverseInputPin

{ } } -pub trait CellularConfig { +pub trait CellularConfig<'a> { type ResetPin: OutputPin; type PowerPin: OutputPin; type VintPin: InputPin; @@ -76,7 +76,11 @@ pub trait CellularConfig { const HEX_MODE: bool = true; const OPERATOR_FORMAT: OperatorFormat = OperatorFormat::Long; - const APN: Apn = Apn::None; + const PROFILE_ID: u8 = 1; + // #[cfg(not(feature = "upsd-context-activation"))] + const CONTEXT_ID: u8 = 1; + + const APN: Apn<'a> = Apn::None; fn reset_pin(&mut self) -> Option<&mut Self::ResetPin>; fn power_pin(&mut self) -> Option<&mut Self::PowerPin>; @@ -91,18 +95,18 @@ pub enum OperatorFormat { } #[derive(Debug, Clone)] -pub enum Apn { +pub enum Apn<'a> { None, Given { - name: String<64>, - username: Option>, - password: Option>, + name: &'a str, + username: Option<&'a str>, + password: Option<&'a str>, }, #[cfg(any(feature = "automatic-apn"))] Automatic, } -impl Default for Apn { +impl Default for Apn<'_> { fn default() -> Self { Self::None } diff --git a/src/error.rs b/src/error.rs index 2d825d2..fd03e57 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,6 +17,8 @@ pub enum Error { Uninitialized, StateTimeout, PoweredDown, + AttachTimeout, + ContextActivationTimeout, // Network errors Network(NetworkError), @@ -44,6 +46,9 @@ impl defmt::Format for Error { Self::Busy => defmt::write!(f, "Busy"), Self::Uninitialized => defmt::write!(f, "Uninitialized"), Self::StateTimeout => defmt::write!(f, "StateTimeout"), + Self::PoweredDown => defmt::write!(f, "PoweredDown"), + Self::AttachTimeout => defmt::write!(f, "AttachTimeout"), + Self::ContextActivationTimeout => defmt::write!(f, "ContextActivationTimeout"), Self::Network(e) => defmt::write!(f, "Network({:?})", e), // Self::DataService(e) => defmt::write!(f, "DataService({:?})", e), Self::Generic(e) => defmt::write!(f, "Generic({:?})", e), From d7e7ba3f2965527642c6e8529ee5b84e8541a973 Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Fri, 26 Jan 2024 22:22:04 +0100 Subject: [PATCH 5/8] move statemachine out of main runner select loop --- examples/embassy-stm32-example/src/main.rs | 12 +- src/asynch/runner.rs | 221 ++++++++++----------- src/error.rs | 2 + 3 files changed, 116 insertions(+), 119 deletions(-) diff --git a/examples/embassy-stm32-example/src/main.rs b/examples/embassy-stm32-example/src/main.rs index 7d2041c..2090ee2 100644 --- a/examples/embassy-stm32-example/src/main.rs +++ b/examples/embassy-stm32-example/src/main.rs @@ -173,12 +173,12 @@ async fn main_task(spawner: Spawner) { .await; // defmt::info!("{:?}", runner.init().await); // control.set_desired_state(PowerState::Connected).await; - control - .send(&crate::command::network_service::SetOperatorSelection { - mode: crate::command::network_service::types::OperatorSelectionMode::Automatic, - format: Some(0), - }) - .await; + // control + // .send(&crate::command::network_service::SetOperatorSelection { + // mode: crate::command::network_service::types::OperatorSelectionMode::Automatic, + // format: Some(0), + // }) + // .await; defmt::unwrap!(spawner.spawn(cellular_task(runner))); diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index eb3b53d..7baa817 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -505,127 +505,122 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> continue; } let desired_state = desired_state.unwrap(); - if 0 >= desired_state as isize - self.ch.state_runner().power_state() as isize { - debug!( - "Power steps was negative, power down: {}", - desired_state as isize - self.ch.state_runner().power_state() as isize - ); - self.power_down().await.ok(); - self.ch.set_power_state(OperationState::PowerDown); + self.change_state_to_desired_state(desired_state).await; + } + Either::Second(event) => { + self.handle_urc(event).await; + } + } + } + } + + async fn change_state_to_desired_state( + &mut self, + desired_state: OperationState, + ) -> Result<(), Error> { + if 0 >= desired_state as isize - self.ch.state_runner().power_state() as isize { + debug!( + "Power steps was negative, power down: {}", + desired_state as isize - self.ch.state_runner().power_state() as isize + ); + self.power_down().await.ok(); + self.ch.set_power_state(OperationState::PowerDown); + } + let start_state = self.ch.state_runner().power_state() 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_power_state(OperationState::PowerUp); + } + Err(err) => { + error!("Error in power_up: {:?}", err); + return Err(err); + } + }, + Ok(OperationState::Alive) => { + match with_timeout(boot_time() * 2, self.check_is_alive_loop()).await { + Ok(true) => { + debug!("Will set Alive"); + self.ch.set_power_state(OperationState::Alive); + debug!("Set Alive"); + } + Ok(false) => { + error!("Error in is_alive: {:?}", Error::PoweredDown); + return Err(Error::PoweredDown); + } + Err(err) => { + error!("Error in is_alive: {:?}", err); + return Err(Error::StateTimeout); + } } - let start_state = self.ch.state_runner().power_state() 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_power_state(OperationState::PowerUp); - } - Err(err) => { - error!("Error in power_up: {:?}", err); - break; - } - }, - Ok(OperationState::Alive) => { - match with_timeout(boot_time() * 2, self.check_is_alive_loop()) - .await - { - Ok(true) => { - debug!("Will set Alive"); - self.ch.set_power_state(OperationState::Alive); - debug!("Set Alive"); - } - Ok(false) => { - error!("Error in is_alive: {:?}", Error::PoweredDown); - break; - } - Err(err) => { - error!("Error in is_alive: {:?}", err); - break; - } - } + } + Ok(OperationState::Initialized) => match self.init_at().await { + Ok(_) => { + self.ch.set_power_state(OperationState::Initialized); + } + Err(err) => { + error!("Error in init_at: {:?}", err); + return Err(err); + } + }, + Ok(OperationState::Connected) => match self.init_network().await { + Ok(_) => { + match with_timeout( + Duration::from_secs(180), + self.is_network_attached_loop(), + ) + .await + { + Ok(_) => { + debug!("Will set Connected"); + self.ch.set_power_state(OperationState::Connected); + debug!("Set Connected"); } - // Ok(OperationState::Alive) => match self.is_alive().await { - // Ok(_) => { - // debug!("Will set Alive"); - // self.ch.set_power_state(OperationState::Alive); - // debug!("Set Alive"); - // } - // Err(err) => { - // error!("Error in is_alive: {:?}", err); - // break; - // } - // }, - Ok(OperationState::Initialized) => match self.init_at().await { - Ok(_) => { - self.ch.set_power_state(OperationState::Initialized); - } - Err(err) => { - error!("Error in init_at: {:?}", err); - break; - } - }, - Ok(OperationState::Connected) => match self.init_network().await { - Ok(_) => { - match with_timeout( - Duration::from_secs(180), - self.is_network_attached_loop(), - ) - .await - { - Ok(_) => { - debug!("Will set Connected"); - self.ch.set_power_state(OperationState::Connected); - debug!("Set Connected"); - } - Err(err) => { - error!("Timeout waiting for network attach: {:?}", err); - break; - } - } - } - Err(err) => { - error!("Error in init_network: {:?}", err); - break; - } - }, - Ok(OperationState::DataEstablished) => match self - .connect( - C::APN, - crate::command::psn::types::ProfileId(C::PROFILE_ID), - crate::command::psn::types::ContextId(C::CONTEXT_ID), - ) - .await - { - Ok(_) => { - self.ch.set_power_state(OperationState::DataEstablished); - } - Err(err) => { - error!("Error in connect: {:?}", err); - break; - } - }, - Err(_) => { - error!("State transition next_state not valid: start_state={}, next_state={}, steps={} ", start_state, next_state, steps); - break; + Err(err) => { + error!("Timeout waiting for network attach: {:?}", err); + return Err(Error::StateTimeout); } } } - } - Either::Second(event) => { - self.handle_urc(event).await; + Err(err) => { + error!("Error in init_network: {:?}", err); + return Err(err); + } + }, + Ok(OperationState::DataEstablished) => match self + .connect( + C::APN, + crate::command::psn::types::ProfileId(C::PROFILE_ID), + crate::command::psn::types::ContextId(C::CONTEXT_ID), + ) + .await + { + Ok(_) => { + self.ch.set_power_state(OperationState::DataEstablished); + } + Err(err) => { + 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); } } } + Ok(()) } async fn handle_urc(&mut self, event: Urc) -> Result<(), Error> { diff --git a/src/error.rs b/src/error.rs index fd03e57..14a440d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,6 +19,7 @@ pub enum Error { PoweredDown, AttachTimeout, ContextActivationTimeout, + InvalidStateTransition, // Network errors Network(NetworkError), @@ -49,6 +50,7 @@ impl defmt::Format for Error { Self::PoweredDown => defmt::write!(f, "PoweredDown"), Self::AttachTimeout => defmt::write!(f, "AttachTimeout"), Self::ContextActivationTimeout => defmt::write!(f, "ContextActivationTimeout"), + Self::InvalidStateTransition => defmt::write!(f, "InvalidStateTransition"), Self::Network(e) => defmt::write!(f, "Network({:?})", e), // Self::DataService(e) => defmt::write!(f, "DataService({:?})", e), Self::Generic(e) => defmt::write!(f, "Generic({:?})", e), From 2296cda69be00f06c17bffee14ce521152d081e2 Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Fri, 26 Jan 2024 23:11:35 +0100 Subject: [PATCH 6/8] a bit of formatting --- examples/embassy-stm32-example/src/main.rs | 47 ++++++++++++++-------- src/asynch/runner.rs | 2 +- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/examples/embassy-stm32-example/src/main.rs b/examples/embassy-stm32-example/src/main.rs index 2090ee2..caf90d7 100644 --- a/examples/embassy-stm32-example/src/main.rs +++ b/examples/embassy-stm32-example/src/main.rs @@ -4,6 +4,7 @@ // #![feature(type_alias_impl_trait)] use atat::asynch::Client; +use atat::heapless::String; use atat::ResponseSlot; use atat::UrcChannel; use core::cell::RefCell; @@ -17,7 +18,6 @@ use embassy_stm32::time::{khz, mhz}; use embassy_stm32::usart::{BufferedUart, BufferedUartRx, BufferedUartTx}; use embassy_stm32::{bind_interrupts, interrupt, peripherals, usart, Config}; use embassy_time::{Duration, Timer}; -use atat::heapless::String; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -43,25 +43,29 @@ const URC_CAPACITY: usize = 2; const URC_SUBSCRIBERS: usize = 2; struct MyCelullarConfig { - reset_pin: Option>, + reset_pin: Option>, // reset_pin: Option, - power_pin: Option>>, + power_pin: Option>>, // power_pin: Option, - vint_pin: Option>, + vint_pin: Option>, // vint_pin: Option } impl<'a> CellularConfig<'a> for MyCelullarConfig { - type ResetPin = Output<'static, >; + type ResetPin = Output<'static>; // type ResetPin = NoPin; - type PowerPin = ReverseOutputPin>; + type PowerPin = ReverseOutputPin>; // type PowerPin = NoPin; - type VintPin = Input<'static, >; + type VintPin = Input<'static>; // type VintPin = NoPin; const FLOW_CONTROL: bool = false; const HEX_MODE: bool = true; - const APN: Apn<'a> = Apn::Given {name: "hologram", username: None, password: None}; + const APN: Apn<'a> = Apn::Given { + name: "hologram", + username: None, + password: None, + }; fn reset_pin(&mut self) -> Option<&mut Self::ResetPin> { info!("reset_pin"); return self.reset_pin.as_mut(); @@ -137,9 +141,7 @@ async fn main_task(spawner: Spawner) { // let reset = Output::new(p.PF8, Level::High, Speed::VeryHigh).degrade(); let celullar_config = MyCelullarConfig { reset_pin: Some(Output::new(p.PF8, Level::High, Speed::Low)), - power_pin: Some(ReverseOutputPin( - Output::new(p.PJ4, Level::Low, Speed::Low), - )), + power_pin: Some(ReverseOutputPin(Output::new(p.PJ4, Level::Low, Speed::Low))), // reset_pin: Some(OutputOpenDrain::new(p.PF8, Level::High, Speed::Low, Pull::None).degrade()), // power_pin: Some(OutputOpenDrain::new(p.PJ4, Level::High, Speed::Low, Pull::None).degrade()), // power_pin: None, @@ -180,24 +182,30 @@ async fn main_task(spawner: Spawner) { // }) // .await; - defmt::unwrap!(spawner.spawn(cellular_task(runner))); Timer::after(Duration::from_millis(1000)).await; loop { - control.set_desired_state(OperationState::DataEstablished).await; + control + .set_desired_state(OperationState::DataEstablished) + .await; info!("set_desired_state(PowerState::Alive)"); while control.power_state() != OperationState::DataEstablished { Timer::after(Duration::from_millis(1000)).await; } Timer::after(Duration::from_millis(10000)).await; + loop { Timer::after(Duration::from_millis(1000)).await; let operator = control.get_operator().await; info!("{}", operator); let signal_quality = control.get_signal_quality().await; info!("{}", signal_quality); + if signal_quality.is_err() { + let desired_state = control.desired_state(); + control.set_desired_state(desired_state).await + } if let Ok(sq) = signal_quality { - if let Ok(op) = operator { + if let Ok(op) = operator { if op.oper == None { continue; } @@ -207,7 +215,13 @@ async fn main_task(spawner: Spawner) { } } } - let dns = control.send(&ublox_cellular::command::dns::ResolveNameIp{resolution_type: ublox_cellular::command::dns::types::ResolutionType::DomainNameToIp, ip_domain_string: "www.google.com"}).await; + let dns = control + .send(&ublox_cellular::command::dns::ResolveNameIp { + resolution_type: + ublox_cellular::command::dns::types::ResolutionType::DomainNameToIp, + ip_domain_string: "www.google.com", + }) + .await; debug!("dns: {:?}", dns); Timer::after(Duration::from_millis(10000)).await; control.set_desired_state(OperationState::PowerDown).await; @@ -218,7 +232,6 @@ async fn main_task(spawner: Spawner) { Timer::after(Duration::from_millis(5000)).await; } - } #[embassy_executor::task] @@ -250,7 +263,7 @@ async fn cellular_task( } #[embassy_executor::task(pool_size = 3)] -async fn blinky(mut led: AnyPin){ +async fn blinky(mut led: AnyPin) { let mut output = Output::new(led, Level::High, Speed::Low); loop { output.set_high(); diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 7baa817..59ff950 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -505,7 +505,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> continue; } let desired_state = desired_state.unwrap(); - self.change_state_to_desired_state(desired_state).await; + let _ = self.change_state_to_desired_state(desired_state).await; } Either::Second(event) => { self.handle_urc(event).await; From 616cc463803f4cc4bc63f1181b9aa8f1de870a0c Mon Sep 17 00:00:00 2001 From: Tobias Breitwieser Date: Thu, 15 Feb 2024 00:35:17 +0100 Subject: [PATCH 7/8] update to newest atat changes --- examples/embassy-stm32-example/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/embassy-stm32-example/src/main.rs b/examples/embassy-stm32-example/src/main.rs index caf90d7..da8fc3e 100644 --- a/examples/embassy-stm32-example/src/main.rs +++ b/examples/embassy-stm32-example/src/main.rs @@ -116,6 +116,7 @@ async fn main_task(spawner: Spawner) { static tx_buf: StaticCell<[u8; 16]> = StaticCell::new(); static rx_buf: StaticCell<[u8; 16]> = StaticCell::new(); + static INGRESS_BUF: StaticCell<[u8; INGRESS_BUF_SIZE]> = StaticCell::new(); let (tx_pin, rx_pin, uart) = (p.PJ8, p.PJ9, p.UART8); let mut uart_config = embassy_stm32::usart::Config::default(); @@ -152,6 +153,7 @@ async fn main_task(spawner: Spawner) { static URC_CHANNEL: UrcChannel = UrcChannel::new(); let ingress = Ingress::new( DefaultDigester::::default(), + INGRESS_BUF.init([0; INGRESS_BUF_SIZE]), &RES_SLOT, &URC_CHANNEL, ); From 3218b5ae624e599732946f8ef7601e9d6eb96e60 Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 15 Feb 2024 11:10:46 +0100 Subject: [PATCH 8/8] Add RP2040 example --- .../embassy-rp2040-example/.cargo/config.toml | 8 + examples/embassy-rp2040-example/.gitignore | 9 + examples/embassy-rp2040-example/Cargo.toml | 47 ++++ examples/embassy-rp2040-example/build.rs | 6 + examples/embassy-rp2040-example/memory.x | 5 + .../rust-toolchain.toml | 14 ++ examples/embassy-rp2040-example/src/main.rs | 237 ++++++++++++++++++ examples/embassy-stm32-example/.gitignore | 9 + 8 files changed, 335 insertions(+) create mode 100644 examples/embassy-rp2040-example/.cargo/config.toml create mode 100644 examples/embassy-rp2040-example/.gitignore create mode 100644 examples/embassy-rp2040-example/Cargo.toml create mode 100644 examples/embassy-rp2040-example/build.rs create mode 100644 examples/embassy-rp2040-example/memory.x create mode 100644 examples/embassy-rp2040-example/rust-toolchain.toml create mode 100644 examples/embassy-rp2040-example/src/main.rs create mode 100644 examples/embassy-stm32-example/.gitignore diff --git a/examples/embassy-rp2040-example/.cargo/config.toml b/examples/embassy-rp2040-example/.cargo/config.toml new file mode 100644 index 0000000..ffed95e --- /dev/null +++ b/examples/embassy-rp2040-example/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv6m-none-eabi] +runner = 'probe-rs run --chip RP2040' + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace,embassy_hal_internal=error" diff --git a/examples/embassy-rp2040-example/.gitignore b/examples/embassy-rp2040-example/.gitignore new file mode 100644 index 0000000..092e4d3 --- /dev/null +++ b/examples/embassy-rp2040-example/.gitignore @@ -0,0 +1,9 @@ +**/*.rs.bk +.#* +.gdb_history +*.fifo +target/ +*.o +**/*secrets* + +.DS_Store diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml new file mode 100644 index 0000000..2c3ca35 --- /dev/null +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "embassy-rp2040-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +embassy-rp = { version = "0.1.0", features = ["defmt", "time-driver", "unstable-pac", "rom-v2-intrinsics", "critical-section-impl"] } +embassy-executor = { version = "0.5.0", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-sync = { version = "0.5", features = ["defmt"] } +portable-atomic = { version = "1.5.1", features = ["critical-section"] } + +cortex-m = { version = "0.7.7", features = ["inline-asm"] } +cortex-m-rt = "0.7.3" + +defmt = "0.3.5" +defmt-rtt = "0.4.0" +panic-probe = { version = "0.3.1", features = ["print-defmt"] } + +static_cell = { version = "2.0", features = []} + +atat = { version = "0.21.0", features = ["derive", "bytes", "defmt"] } +ublox-cellular-rs = {version = "0.4.0", path = "../..", features = ["lara-r6", "defmt", "async"]} + +[patch.crates-io] +ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", branch = "feature/async-borrowed-sockets" } +atat = { git = "https://github.com/BlackbirdHQ/atat", branch = "master" } +no-std-net = { git = "https://github.com/rushmorem/no-std-net", branch = "issue-15" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", branch = "main" } + +#embassy-time = { path = "../../../embassy/embassy-time" } +#embassy-sync = { path = "../../../embassy/embassy-sync" } +#embassy-futures = { path = "../../../embassy/embassy-futures" } +#embassy-executor = { path = "../../../embassy/embassy-executor" } + +#ublox-sockets = { path = "../../../ublox-sockets" } +#atat = { path = "../../../atat/atat" } + +[profile.dev] +opt-level = "s" + +[profile.release] +opt-level = "s" \ No newline at end of file diff --git a/examples/embassy-rp2040-example/build.rs b/examples/embassy-rp2040-example/build.rs new file mode 100644 index 0000000..673f12e --- /dev/null +++ b/examples/embassy-rp2040-example/build.rs @@ -0,0 +1,6 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/embassy-rp2040-example/memory.x b/examples/embassy-rp2040-example/memory.x new file mode 100644 index 0000000..eb8c173 --- /dev/null +++ b/examples/embassy-rp2040-example/memory.x @@ -0,0 +1,5 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 1024K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} \ No newline at end of file diff --git a/examples/embassy-rp2040-example/rust-toolchain.toml b/examples/embassy-rp2040-example/rust-toolchain.toml new file mode 100644 index 0000000..25d771e --- /dev/null +++ b/examples/embassy-rp2040-example/rust-toolchain.toml @@ -0,0 +1,14 @@ +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "1.75" +components = [ "rust-src", "rustfmt", "llvm-tools" ] +targets = [ + "thumbv7em-none-eabi", + "thumbv7m-none-eabi", + "thumbv6m-none-eabi", + "thumbv7em-none-eabihf", + "thumbv8m.main-none-eabihf", + "riscv32imac-unknown-none-elf", + "wasm32-unknown-unknown", +] \ No newline at end of file diff --git a/examples/embassy-rp2040-example/src/main.rs b/examples/embassy-rp2040-example/src/main.rs new file mode 100644 index 0000000..dff7b3d --- /dev/null +++ b/examples/embassy-rp2040-example/src/main.rs @@ -0,0 +1,237 @@ +#![no_std] +#![no_main] +#![allow(stable_features)] + +use atat::asynch::Client; + +use atat::ResponseSlot; +use atat::UrcChannel; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::{Executor, Spawner}; +use embassy_rp::gpio; +use embassy_rp::gpio::Input; + +use embassy_rp::gpio::OutputOpenDrain; +use embassy_rp::uart::BufferedUart; +use embassy_rp::uart::BufferedUartRx; +use embassy_rp::uart::BufferedUartTx; +use embassy_rp::{bind_interrupts, peripherals::UART0, uart::BufferedInterruptHandler}; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +use ublox_cellular::config::{Apn, CellularConfig}; + +use atat::{AtatIngress, DefaultDigester, Ingress}; +use ublox_cellular::asynch::runner::Runner; +use ublox_cellular::asynch::state::OperationState; +use ublox_cellular::asynch::State; +use ublox_cellular::command; +use ublox_cellular::command::Urc; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +const INGRESS_BUF_SIZE: usize = 1024; +const URC_CAPACITY: usize = 2; +const URC_SUBSCRIBERS: usize = 2; + +struct MyCelullarConfig { + reset_pin: Option>, + power_pin: Option>, + vint_pin: Option>, +} + +impl<'a> CellularConfig<'a> for MyCelullarConfig { + type ResetPin = OutputOpenDrain<'static>; + type PowerPin = OutputOpenDrain<'static>; + type VintPin = Input<'static>; + + const FLOW_CONTROL: bool = true; + const HEX_MODE: bool = true; + const APN: Apn<'a> = Apn::Given { + name: "em", + username: None, + password: None, + }; + fn reset_pin(&mut self) -> Option<&mut Self::ResetPin> { + info!("reset_pin"); + self.reset_pin.as_mut() + } + fn power_pin(&mut self) -> Option<&mut Self::PowerPin> { + info!("power_pin"); + self.power_pin.as_mut() + } + fn vint_pin(&mut self) -> Option<&mut Self::VintPin> { + info!("vint_pin = {}", self.vint_pin.as_mut()?.is_high()); + self.vint_pin.as_mut() + } +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let p = { + let config = + embassy_rp::config::Config::new(embassy_rp::clocks::ClockConfig::crystal(12_000_000)); + embassy_rp::init(config) + }; + + static TX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + static INGRESS_BUF: StaticCell<[u8; INGRESS_BUF_SIZE]> = StaticCell::new(); + + let mut cell_uart_config = embassy_rp::uart::Config::default(); + cell_uart_config.baudrate = 115200; + + let cell_uart = BufferedUart::new_with_rtscts( + p.UART0, + Irqs, + p.PIN_0, + p.PIN_1, + p.PIN_3, + p.PIN_2, + TX_BUF.init([0; 16]), + RX_BUF.init([0; 16]), + cell_uart_config, + ); + + let (uart_rx, uart_tx) = cell_uart.split(); + let cell_nrst = gpio::OutputOpenDrain::new(p.PIN_4, gpio::Level::High); + let cell_pwr = gpio::OutputOpenDrain::new(p.PIN_5, gpio::Level::High); + let cell_vint = gpio::Input::new(p.PIN_6, gpio::Pull::None); + + let celullar_config = MyCelullarConfig { + reset_pin: Some(cell_nrst), + power_pin: Some(cell_pwr), + vint_pin: Some(cell_vint), + }; + + static RES_SLOT: ResponseSlot = ResponseSlot::new(); + static URC_CHANNEL: UrcChannel = UrcChannel::new(); + let ingress = Ingress::new( + DefaultDigester::::default(), + INGRESS_BUF.init([0; INGRESS_BUF_SIZE]), + &RES_SLOT, + &URC_CHANNEL, + ); + static BUF: StaticCell<[u8; INGRESS_BUF_SIZE]> = StaticCell::new(); + let client = Client::new( + uart_tx, + &RES_SLOT, + BUF.init([0; INGRESS_BUF_SIZE]), + atat::Config::default(), + ); + + spawner.spawn(ingress_task(ingress, uart_rx)).unwrap(); + + static STATE: StaticCell, INGRESS_BUF_SIZE>>> = + StaticCell::new(); + let (_device, mut control, runner) = ublox_cellular::asynch::new( + STATE.init(State::new(client)), + &URC_CHANNEL, + celullar_config, + ) + .await; + // defmt::info!("{:?}", runner.init().await); + // control.set_desired_state(PowerState::Connected).await; + // control + // .send(&crate::command::network_service::SetOperatorSelection { + // mode: crate::command::network_service::types::OperatorSelectionMode::Automatic, + // format: Some(0), + // }) + // .await; + + defmt::unwrap!(spawner.spawn(cellular_task(runner))); + Timer::after(Duration::from_millis(1000)).await; + loop { + control + .set_desired_state(OperationState::DataEstablished) + .await; + info!("set_desired_state(PowerState::Alive)"); + while control.power_state() != OperationState::DataEstablished { + Timer::after(Duration::from_millis(1000)).await; + } + Timer::after(Duration::from_millis(10000)).await; + + loop { + Timer::after(Duration::from_millis(1000)).await; + let operator = control.get_operator().await; + info!("{}", operator); + let signal_quality = control.get_signal_quality().await; + info!("{}", signal_quality); + if signal_quality.is_err() { + let desired_state = control.desired_state(); + control.set_desired_state(desired_state).await + } + if let Ok(sq) = signal_quality { + if let Ok(op) = operator { + if op.oper.is_none() { + continue; + } + } + if sq.rxlev > 0 && sq.rsrp != 255 { + break; + } + } + } + let dns = control + .send(&ublox_cellular::command::dns::ResolveNameIp { + resolution_type: + ublox_cellular::command::dns::types::ResolutionType::DomainNameToIp, + ip_domain_string: "www.google.com", + }) + .await; + info!("dns: {:?}", dns); + Timer::after(Duration::from_millis(10000)).await; + control.set_desired_state(OperationState::PowerDown).await; + info!("set_desired_state(PowerState::PowerDown)"); + while control.power_state() != OperationState::PowerDown { + Timer::after(Duration::from_millis(1000)).await; + } + + Timer::after(Duration::from_millis(5000)).await; + } +} + +#[embassy_executor::task] +async fn ingress_task( + mut ingress: Ingress< + 'static, + DefaultDigester, + ublox_cellular::command::Urc, + { INGRESS_BUF_SIZE }, + { URC_CAPACITY }, + { URC_SUBSCRIBERS }, + >, + mut reader: BufferedUartRx<'static, UART0>, +) -> ! { + ingress.read_from(&mut reader).await +} + +#[embassy_executor::task] +async fn cellular_task( + runner: Runner< + 'static, + atat::asynch::Client<'_, BufferedUartTx<'static, UART0>, { INGRESS_BUF_SIZE }>, + MyCelullarConfig, + { URC_CAPACITY }, + >, +) -> ! { + runner.run().await +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task(spawner))); + }) +} diff --git a/examples/embassy-stm32-example/.gitignore b/examples/embassy-stm32-example/.gitignore new file mode 100644 index 0000000..092e4d3 --- /dev/null +++ b/examples/embassy-stm32-example/.gitignore @@ -0,0 +1,9 @@ +**/*.rs.bk +.#* +.gdb_history +*.fifo +target/ +*.o +**/*secrets* + +.DS_Store