From eaf9d0043060ed03113efe58a4e34fc197072171 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 16 Feb 2024 07:53:31 +0100 Subject: [PATCH 01/22] WIP on adding PPP support through embassy-net-ppp using embassy-at-cmux --- .../embassy-rp2040-example/.cargo/config.toml | 2 +- examples/embassy-rp2040-example/Cargo.toml | 39 +- examples/embassy-rp2040-example/build.rs | 30 ++ .../{main.rs => bin/embassy-pdp-context.rs} | 5 +- .../src/bin/embassy-smoltcp-ppp.rs | 417 ++++++++++++++++++ src/asynch/mod.rs | 30 +- src/asynch/runner.rs | 49 +- src/asynch/state.rs | 38 +- 8 files changed, 539 insertions(+), 71 deletions(-) rename examples/embassy-rp2040-example/src/{main.rs => bin/embassy-pdp-context.rs} (99%) create mode 100644 examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs diff --git a/examples/embassy-rp2040-example/.cargo/config.toml b/examples/embassy-rp2040-example/.cargo/config.toml index ffed95e..0bb5ce6 100644 --- a/examples/embassy-rp2040-example/.cargo/config.toml +++ b/examples/embassy-rp2040-example/.cargo/config.toml @@ -1,4 +1,4 @@ -[target.thumbv6m-none-eabi] +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] runner = 'probe-rs run --chip RP2040' [build] diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index 2c3ca35..a96f4e6 100644 --- a/examples/embassy-rp2040-example/Cargo.toml +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -7,7 +7,16 @@ edition = "2021" 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-futures = { version = "0.1", features = ["defmt"] } embassy-sync = { version = "0.5", features = ["defmt"] } + +embassy-net = { version = "0.4", features = ["defmt", "proto-ipv4", "medium-ip", "tcp", "udp"] } +embassy-net-ppp = { version = "0.1", features = ["defmt"] } +embassy-at-cmux = { path = "../../../embassy/embassy-at-cmux", features = ["defmt"] } +embedded-tls = { path = "../../../embedded-tls", default-features = false, features = ["defmt"] } + +embedded-io-async = { version = "0.6" } +heapless = "0.8" portable-atomic = { version = "1.5.1", features = ["critical-section"] } cortex-m = { version = "0.7.7", features = ["inline-asm"] } @@ -24,18 +33,26 @@ ublox-cellular-rs = {version = "0.4.0", path = "../..", features = ["lara-r6", " [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" } +# 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" } +# 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-net = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +# embassy-net-ppp = { 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-rp = { path = "../../../embassy/embassy-rp" } +embassy-time = { path = "../../../embassy/embassy-time" } +embassy-sync = { path = "../../../embassy/embassy-sync" } +embassy-net = { path = "../../../embassy/embassy-net" } +embassy-net-ppp = { path = "../../../embassy/embassy-net-ppp" } +embassy-futures = { path = "../../../embassy/embassy-futures" } +embassy-executor = { path = "../../../embassy/embassy-executor" } + +atat = { path = "../../../atat/atat" } #ublox-sockets = { path = "../../../ublox-sockets" } #atat = { path = "../../../atat/atat" } diff --git a/examples/embassy-rp2040-example/build.rs b/examples/embassy-rp2040-example/build.rs index 673f12e..3f915f9 100644 --- a/examples/embassy-rp2040-example/build.rs +++ b/examples/embassy-rp2040-example/build.rs @@ -1,4 +1,34 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink.x"); println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); diff --git a/examples/embassy-rp2040-example/src/main.rs b/examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs similarity index 99% rename from examples/embassy-rp2040-example/src/main.rs rename to examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs index dff7b3d..fa0b3b2 100644 --- a/examples/embassy-rp2040-example/src/main.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs @@ -133,8 +133,7 @@ async fn main_task(spawner: Spawner) { STATE.init(State::new(client)), &URC_CHANNEL, celullar_config, - ) - .await; + ); // defmt::info!("{:?}", runner.init().await); // control.set_desired_state(PowerState::Connected).await; // control @@ -213,7 +212,7 @@ async fn ingress_task( #[embassy_executor::task] async fn cellular_task( - runner: Runner< + mut runner: Runner< 'static, atat::asynch::Client<'_, BufferedUartTx<'static, UART0>, { INGRESS_BUF_SIZE }>, MyCelullarConfig, diff --git a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs new file mode 100644 index 0000000..e1caa11 --- /dev/null +++ b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs @@ -0,0 +1,417 @@ +#![no_std] +#![no_main] +#![allow(stable_features)] + +use atat::asynch::Client; + +use atat::asynch::SimpleClient; +use atat::ResponseSlot; +use atat::UrcChannel; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::{Executor, Spawner}; +use embassy_net::ConfigV4; +use embassy_net::Ipv4Address; +use embassy_net::Ipv4Cidr; +use embassy_net::Stack; +use embassy_net::StackResources; +use embassy_rp::gpio; +use embassy_rp::gpio::Input; + +use embassy_rp::gpio::OutputOpenDrain; +use embassy_rp::uart::BufferedUart; +use embassy_rp::{bind_interrupts, peripherals::UART0, uart::BufferedInterruptHandler}; +use embassy_time::Instant; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use ublox_cellular::asynch::state::OperationState; +use {defmt_rtt as _, panic_probe as _}; + +use embassy_at_cmux::Mux; + +use ublox_cellular::config::{Apn, CellularConfig}; + +use atat::{AtDigester, AtatIngress}; +use ublox_cellular::asynch::State; +use ublox_cellular::command::Urc; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +const INGRESS_BUF_SIZE: usize = 512; +const URC_CAPACITY: usize = 2; +const URC_SUBSCRIBERS: usize = 2; + +struct Networking { + ppp: PPP, + mux: GsmMux, + cellular_runner: ublox_cellular::asynch::runner::Runner< + 'static, + Client<'static, embassy_at_cmux::ChannelTx<'static, 256>, INGRESS_BUF_SIZE>, + MyCelullarConfig, + 2, + >, +} + +impl Networking { + pub async fn run(mut self) -> ! { + let ppp_fut = async { + loop { + // Reboot modem and start again + let _ = self.cellular_runner.reset().await; + + embassy_futures::select::select(self.cellular_runner.run(), self.ppp.run()).await; + } + }; + + embassy_futures::join::join(ppp_fut, self.mux.run()).await; + + core::unreachable!() + } +} + +struct PPP { + stack: &'static Stack>, + ppp_runner: embassy_net_ppp::Runner<'static>, + ppp_channel: embassy_at_cmux::Channel<'static, 256>, +} + +impl PPP { + async fn run(&mut self) { + let mut fails = 0; + let mut last_start = None; + + loop { + if let Some(last_start) = last_start { + // Do not attempt to start too fast. + Timer::at(last_start + Duration::from_secs(10)).await; + + // If was up stably for at least 1 min, reset fail counter. + if Instant::now() > last_start + Duration::from_secs(60) { + fails = 0; + } else { + fails += 1; + if fails == 10 { + warn!("modem: PPP failed too much, rebooting modem."); + return; + } + } + } + last_start = Some(Instant::now()); + + let mut buf = [0u8; 64]; + let at_client = SimpleClient::new( + &mut self.ppp_channel, + atat::AtDigester::::new(), + &mut buf, + atat::Config::default(), + ); + + // if let Err(e) = Self::configure(&mut at_client).await { + // warn!("modem: configure failed {:?}", e); + // continue; + // } + + Timer::after(Duration::from_secs(2)).await; + + // Send AT command `ATO3` to enter PPP mode + // let res = at_client + // .send(&ChangeMode { + // mode: data_mode::types::Mode::PPPMode, + // }) + // .await; + + // if let Err(e) = res { + // warn!("ppp dial failed {:?}", e); + // continue; + // } + + drop(at_client); + + // Drain the UART + // embassy_time::with_timeout(Duration::from_secs(2), async { + // loop { + // self.ppp_channel.read(&mut buf).await; + // } + // }) + // .await; + + info!("RUNNING PPP"); + let config = embassy_net_ppp::Config { + username: b"", + password: b"", + }; + let res = self + .ppp_runner + .run(&mut self.ppp_channel, config, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = heapless::Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0)); + } + let config = ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0), + gateway: None, + dns_servers, + }); + self.stack.set_config_v4(config); + }) + .await; + + info!("ppp failed: {:?}", res); + } + } +} + +struct GsmMux { + mux_runner: embassy_at_cmux::Runner<'static, 2, 256>, + iface: BufferedUart<'static, UART0>, +} + +impl GsmMux { + pub async fn run(self) { + let (rx, tx) = self.iface.split(); + self.mux_runner.run(rx, tx).await + } +} + +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() + } +} + +static RES_SLOT: ResponseSlot = ResponseSlot::new(); +static URC_CHANNEL: UrcChannel = UrcChannel::new(); + +#[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(); + + 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 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); + + // Create new `embassy-net-ppp` device and runner pair + static PPP_STATE: StaticCell> = StaticCell::new(); + let (net_device, ppp_runner) = + embassy_net_ppp::new(PPP_STATE.init(embassy_net_ppp::State::new())); + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static RESOURCES: StaticCell> = StaticCell::new(); + + let stack = &*STACK.init(Stack::new( + net_device, + embassy_net::Config::default(), + RESOURCES.init(StackResources::new()), + seed, + )); + + static CMUX: StaticCell> = StaticCell::new(); + + let mux = CMUX.init(Mux::new()); + let (mux_runner, [ch1, ch2]) = mux.start(); + + let (control_rx, control_tx, _) = ch2.split(); + static CMD_BUF: StaticCell<[u8; 128]> = StaticCell::new(); + let at_client = Client::new( + control_tx, + &RES_SLOT, + CMD_BUF.init([0; 128]), + atat::Config::default(), + ); + + spawner.spawn(ingress_task(control_rx)).unwrap(); + + static STATE: StaticCell< + State, INGRESS_BUF_SIZE>>, + > = StaticCell::new(); + let (mut control, runner) = ublox_cellular::asynch::new_ppp( + STATE.init(State::new(at_client)), + &URC_CHANNEL, + MyCelullarConfig { + reset_pin: Some(cell_nrst), + power_pin: Some(cell_pwr), + vint_pin: Some(cell_vint), + }, + ); + + let networking = Networking { + mux: GsmMux { + iface: cell_uart, + mux_runner, + }, + ppp: PPP { + stack, + ppp_runner, + ppp_channel: ch1, + }, + cellular_runner: runner, + }; + + spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(ppp_task(networking)).unwrap(); + + control + .set_desired_state(OperationState::Connected) + .await; + + stack.wait_config_up().await; + + // 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 net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::task] +async fn ppp_task(networking: Networking) -> ! { + networking.run().await +} + +#[embassy_executor::task] +async fn ingress_task(mut rx: embassy_at_cmux::ChannelRx<'static, 256>) -> ! { + let mut buf = [0u8; INGRESS_BUF_SIZE]; + + let mut ingress = + atat::Ingress::new(AtDigester::::new(), &mut buf, &RES_SLOT, &URC_CHANNEL); + loop { + let buf = ingress.write_buf(); + match embedded_io_async::Read::read(&mut rx, buf).await { + Ok(received) => { + // Ignore errors, as they mean the URC channel was full. This will be automatically redriven later + if ingress.try_advance(received).is_err() { + Timer::after(Duration::from_millis(100)).await; + ingress.try_advance(0).ok(); + } + } + Err(e) => { + defmt::error!( + "Got serial read error {:?}", + embedded_io_async::Error::kind(&e) + ); + ingress.clear(); + } + } + } +} + +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/src/asynch/mod.rs b/src/asynch/mod.rs index 386de44..60fbcbe 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<'a>, const URC_CAPACITY: usize>( +pub fn new<'a, AT: AtatClient, C: CellularConfig<'a>, const URC_CAPACITY: usize>( state: &'a mut State, subscriber: &'a UrcChannel, config: C, @@ -51,18 +51,34 @@ pub async fn new<'a, AT: AtatClient, C: CellularConfig<'a>, const URC_CAPACITY: ); let state_ch = ch_runner.state_runner(); - let mut runner = Runner::new( + let runner = Runner::new( ch_runner, AtHandle(&state.at_handle), config, subscriber.subscribe().unwrap(), ); - // FIXME: Unwrapping the init is not nice, maybe return a Result for new()? - // runner.init().await.unwrap(); - - let mut control = Control::new(state_ch, AtHandle(&state.at_handle)); - // control.init().await.unwrap(); + let control = Control::new(state_ch, AtHandle(&state.at_handle)); (net_device, control, runner) } + +pub fn new_ppp<'a, AT: AtatClient, C: CellularConfig<'a>, const URC_CAPACITY: usize>( + state: &'a mut State, + subscriber: &'a atat::UrcChannel, + config: C, +) -> (Control<'a, AT>, Runner<'a, AT, C, URC_CAPACITY>) { + let ch_runner = state::new_ppp(&mut state.ch); + let state_ch = ch_runner.state_runner(); + + let runner = Runner::new( + ch_runner, + AtHandle(&state.at_handle), + config, + subscriber.subscribe().unwrap(), + ); + + let control = Control::new(state_ch, AtHandle(&state.at_handle)); + + (control, runner) +} diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 59ff950..0e761b9 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -1,23 +1,17 @@ -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}; -use super::state::{self, LinkState}; +use super::state; use crate::asynch::state::OperationState; -use crate::asynch::state::OperationState::{PowerDown, PowerUp}; use crate::command::control::types::{Circuit108Behaviour, Circuit109Behaviour, FlowControl}; 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, GetFirmwareVersion, GetModelId, IdentificationInformation}; +use crate::command::general::{GetCCID, GetFirmwareVersion, GetModelId}; use crate::command::gpio::types::{GpioInPull, GpioMode, GpioOutValue}; use crate::command::gpio::SetGpioConfiguration; use crate::command::ip_transport_layer::types::HexMode; @@ -25,28 +19,20 @@ 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; use crate::error::Error; -use crate::error::GenericError::Timeout; use crate::module_timing::{boot_time, reset_time}; use atat::{asynch::AtatClient, UrcSubscription}; use embassy_futures::select::select; use embassy_time::{with_timeout, Duration, Timer}; 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; @@ -94,19 +80,14 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } pub async fn is_alive(&mut self) -> Result { - let has_power = self.has_power().await?; - if !has_power { + if !self.has_power().await? { return Err(Error::PoweredDown); } - let alive = match self.at.send(&AT).await { - Ok(_) => { - return Ok(true); - } - Err(err) => { - return Err(Error::Atat(err)); - } - }; + match self.at.send(&AT).await { + Ok(_) => Ok(true), + Err(err) => Err(Error::Atat(err)), + } } pub async fn has_power(&mut self) -> Result { @@ -187,7 +168,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> }) .await?; - let model_id = self.at.send(&GetModelId).await?; + let _model_id = self.at.send(&GetModelId).await?; // self.at.send( // &IdentificationInformation { @@ -479,7 +460,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } } - pub async fn run(mut self) -> ! { + pub async fn run(&mut self) -> ! { match self.has_power().await.ok() { Some(false) => { self.ch.set_power_state(OperationState::PowerDown); @@ -818,7 +799,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> async fn activate_context( &mut self, cid: ContextId, - profile_id: ProfileId, + _profile_id: ProfileId, ) -> Result<(), Error> { for _ in 0..10 { let context_states = self @@ -849,7 +830,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } = self .at .send(&psn::GetPacketSwitchedConfig { - profile_id, + profile_id: _profile_id, param: psn::types::PacketSwitchedParamReq::MapProfile, }) .await @@ -858,7 +839,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> if context != cid { self.at .send(&psn::SetPacketSwitchedConfig { - profile_id, + profile_id: _profile_id, param: psn::types::PacketSwitchedParam::MapProfile(cid), }) .await @@ -867,7 +848,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> self.at .send( &psn::GetPacketSwitchedNetworkData { - profile_id, + profile_id: _profile_id, param: psn::types::PacketSwitchedNetworkDataParam::PsdProfileStatus, }, ).await @@ -878,7 +859,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> let psn::responses::PacketSwitchedNetworkData { param_tag, .. } = self .at .send(&psn::GetPacketSwitchedNetworkData { - profile_id, + profile_id: _profile_id, param: psn::types::PacketSwitchedNetworkDataParam::PsdProfileStatus, }) .await @@ -887,7 +868,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> if param_tag == 0 { self.at .send(&psn::SetPacketSwitchedAction { - profile_id, + profile_id: _profile_id, action: psn::types::PacketSwitchedAction::Activate, }) .await diff --git a/src/asynch/state.rs b/src/asynch/state.rs index 9feae6e..90a6b67 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -4,7 +4,6 @@ use core::cell::RefCell; use core::mem::MaybeUninit; use core::task::Context; -use crate::asynch::state::OperationState::DataEstablished; use atat::asynch::AtatClient; use atat::UrcSubscription; use embassy_sync::blocking_mutex::raw::NoopRawMutex; @@ -82,8 +81,8 @@ pub struct Shared { } pub struct Runner<'d> { - shared: &'d Mutex>, - desired_state_pub_sub: + pub(crate) shared: &'d Mutex>, + pub(crate) desired_state_pub_sub: &'d PubSubChannel, } @@ -229,6 +228,23 @@ pub fn new<'d, AT: AtatClient, const URC_CAPACITY: usize>( at: AtHandle<'d, AT>, urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, 2>, ) -> (Runner<'d>, Device<'d, AT, URC_CAPACITY>) { + let runner = new_ppp(state); + + let shared = runner.shared; + let desired_state_pub_sub = runner.desired_state_pub_sub; + + ( + runner, + Device { + shared, + urc_subscription, + at, + desired_state_pub_sub, + }, + ) +} + +pub fn new_ppp<'d>(state: &'d mut State) -> Runner<'d> { // safety: this is a self-referential struct, however: // - it can't move while the `'d` borrow is active. // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. @@ -251,18 +267,10 @@ pub fn new<'d, AT: AtatClient, const URC_CAPACITY: usize>( >::new(), }); - ( - Runner { - shared: &state.shared, - desired_state_pub_sub: &state.desired_state_pub_sub, - }, - Device { - shared: &state.shared, - urc_subscription, - at, - desired_state_pub_sub: &state.desired_state_pub_sub, - }, - ) + Runner { + shared: &state.shared, + desired_state_pub_sub: &state.desired_state_pub_sub, + } } pub struct Device<'d, AT: AtatClient, const URC_CAPACITY: usize> { From ccaf5474ac48de6843ce20db95a32230eeca7bad Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 16 Feb 2024 15:46:21 +0100 Subject: [PATCH 02/22] Working naive ppp example --- examples/embassy-rp2040-example/Cargo.toml | 2 +- .../src/bin/embassy-smoltcp-ppp.rs | 211 +++++++++++++++--- src/asynch/runner.rs | 186 +++++++-------- src/command/call_control/mod.rs | 57 +++++ src/command/call_control/types.rs | 14 ++ src/command/ipc/mod.rs | 58 +++++ src/command/mod.rs | 4 +- src/command/psn/mod.rs | 44 ++++ 8 files changed, 448 insertions(+), 128 deletions(-) create mode 100644 src/command/call_control/mod.rs create mode 100644 src/command/call_control/types.rs create mode 100644 src/command/ipc/mod.rs diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index a96f4e6..a485238 100644 --- a/examples/embassy-rp2040-example/Cargo.toml +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -5,7 +5,7 @@ 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-executor = { version = "0.5.0", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers", "nightly"] } embassy-time = { version = "0.3", features = ["defmt", "defmt-timestamp-uptime"] } embassy-futures = { version = "0.1", features = ["defmt"] } embassy-sync = { version = "0.5", features = ["defmt"] } diff --git a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs index e1caa11..5454d94 100644 --- a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs @@ -1,7 +1,9 @@ #![no_std] #![no_main] #![allow(stable_features)] +#![feature(type_alias_impl_trait)] +use atat::asynch::AtatClient; use atat::asynch::Client; use atat::asynch::SimpleClient; @@ -11,6 +13,7 @@ use atat::UrcChannel; use cortex_m_rt::entry; use defmt::*; use embassy_executor::{Executor, Spawner}; +use embassy_futures::join::join; use embassy_net::ConfigV4; use embassy_net::Ipv4Address; use embassy_net::Ipv4Cidr; @@ -24,8 +27,30 @@ use embassy_rp::uart::BufferedUart; use embassy_rp::{bind_interrupts, peripherals::UART0, uart::BufferedInterruptHandler}; use embassy_time::Instant; use embassy_time::{Duration, Timer}; +use embedded_io_async::Read as _; use static_cell::StaticCell; use ublox_cellular::asynch::state::OperationState; +use ublox_cellular::command::control::types::Circuit108Behaviour; +use ublox_cellular::command::control::types::Circuit109Behaviour; +use ublox_cellular::command::control::types::FlowControl; +use ublox_cellular::command::control::SetCircuit108Behaviour; +use ublox_cellular::command::control::SetCircuit109Behaviour; +use ublox_cellular::command::control::SetFlowControl; +use ublox_cellular::command::general::GetCCID; +use ublox_cellular::command::general::GetFirmwareVersion; +use ublox_cellular::command::general::GetModelId; +use ublox_cellular::command::gpio::types::GpioInPull; +use ublox_cellular::command::gpio::types::GpioMode; +use ublox_cellular::command::gpio::types::GpioOutValue; +use ublox_cellular::command::gpio::SetGpioConfiguration; +use ublox_cellular::command::ipc::SetMultiplexing; +use ublox_cellular::command::mobile_control::types::TerminationErrorMode; +use ublox_cellular::command::mobile_control::SetReportMobileTerminationError; +use ublox_cellular::command::psn::types::ContextId; +use ublox_cellular::command::psn::DeactivatePDPContext; +use ublox_cellular::command::psn::EnterPPP; +use ublox_cellular::command::system_features::types::PowerSavingMode; +use ublox_cellular::command::system_features::SetPowerSavingControl; use {defmt_rtt as _, panic_probe as _}; use embassy_at_cmux::Mux; @@ -57,18 +82,16 @@ struct Networking { impl Networking { pub async fn run(mut self) -> ! { - let ppp_fut = async { + let fut = async { loop { + // let _ = self.cellular_runner.reset().await; // Reboot modem and start again - let _ = self.cellular_runner.reset().await; - + Timer::after(Duration::from_secs(5)).await; embassy_futures::select::select(self.cellular_runner.run(), self.ppp.run()).await; } }; - - embassy_futures::join::join(ppp_fut, self.mux.run()).await; - - core::unreachable!() + join(self.mux.run(), fut).await; + core::unreachable!(); } } @@ -84,9 +107,11 @@ impl PPP { let mut last_start = None; loop { + Timer::after(Duration::from_secs(15)).await; + if let Some(last_start) = last_start { - // Do not attempt to start too fast. Timer::at(last_start + Duration::from_secs(10)).await; + // Do not attempt to start too fast. // If was up stably for at least 1 min, reset fail counter. if Instant::now() > last_start + Duration::from_secs(60) { @@ -102,7 +127,7 @@ impl PPP { last_start = Some(Instant::now()); let mut buf = [0u8; 64]; - let at_client = SimpleClient::new( + let mut at_client = SimpleClient::new( &mut self.ppp_channel, atat::AtDigester::::new(), &mut buf, @@ -116,27 +141,22 @@ impl PPP { Timer::after(Duration::from_secs(2)).await; - // Send AT command `ATO3` to enter PPP mode - // let res = at_client - // .send(&ChangeMode { - // mode: data_mode::types::Mode::PPPMode, - // }) - // .await; + // hangup just in case a call was already in progress. + // Ignore errors because this fails if it wasn't. + let _ = at_client.send(&DeactivatePDPContext).await; - // if let Err(e) = res { - // warn!("ppp dial failed {:?}", e); - // continue; - // } + // Send AT command to enter PPP mode + let res = at_client.send(&EnterPPP { cid: ContextId(1) }).await; + + if let Err(e) = res { + warn!("ppp dial failed {:?}", e); + continue; + } drop(at_client); - // Drain the UART - // embassy_time::with_timeout(Duration::from_secs(2), async { - // loop { - // self.ppp_channel.read(&mut buf).await; - // } - // }) - // .await; + // Check for CTS low (bit 2) + // self.ppp_channel.set_hangup_detection(0x04, 0x00); info!("RUNNING PPP"); let config = embassy_net_ppp::Config { @@ -164,6 +184,13 @@ impl PPP { .await; info!("ppp failed: {:?}", res); + + self.ppp_channel.clear_hangup_detection(); + + // escape back to data mode. + self.ppp_channel.set_lines(0x44); + Timer::after(Duration::from_millis(100)).await; + self.ppp_channel.set_lines(0x46); } } } @@ -174,9 +201,126 @@ struct GsmMux { } impl GsmMux { - pub async fn run(self) { - let (rx, tx) = self.iface.split(); - self.mux_runner.run(rx, tx).await + async fn init(&mut self) -> Result<(), atat::Error> { + let mut buf = [0u8; 64]; + let mut at_client = SimpleClient::new( + &mut self.iface, + atat::AtDigester::::new(), + &mut buf, + atat::Config::default(), + ); + + at_client + .send(&SetReportMobileTerminationError { + n: TerminationErrorMode::Enabled, + }) + .await?; + + // Select SIM + at_client + .send(&SetGpioConfiguration { + gpio_id: 25, + gpio_mode: GpioMode::Output(GpioOutValue::High), + }) + .await?; + + at_client + .send(&SetGpioConfiguration { + gpio_id: 42, + gpio_mode: GpioMode::Input(GpioInPull::NoPull), + }) + .await?; + + let _model_id = at_client.send(&GetModelId).await?; + + // at_client.send( + // &IdentificationInformation { + // n: 9 + // }, + // ).await?; + + at_client.send(&GetFirmwareVersion).await?; + + // self.select_sim_card().await?; + + let ccid = at_client.send(&GetCCID).await?; + info!("CCID: {}", ccid.ccid); + + // DCD circuit (109) changes in accordance with the carrier + at_client + .send(&SetCircuit109Behaviour { + value: Circuit109Behaviour::ChangesWithCarrier, + }) + .await?; + + // Ignore changes to DTR + at_client + .send(&SetCircuit108Behaviour { + value: Circuit108Behaviour::Ignore, + }) + .await?; + + // Switch off UART power saving until it is integrated into this API + at_client + .send(&SetPowerSavingControl { + mode: PowerSavingMode::Disabled, + timeout: None, + }) + .await?; + + at_client + .send(&SetFlowControl { + value: FlowControl::RtsCts, + }) + .await?; + + at_client + .send(&SetMultiplexing { + mode: 0, + subset: None, + port_speed: None, + n1: None, + t1: None, + n2: None, + t2: None, + t3: None, + k: None, + }) + .await?; + + Timer::after(Duration::from_secs(1)).await; + + Ok(()) + } + pub async fn run(mut self) { + let mut fails = 0; + let mut last_start = None; + + loop { + if let Some(last_start) = last_start { + // Do not attempt to start too fast. + Timer::at(last_start + Duration::from_secs(10)).await; + + // If was up stably for at least 1 min, reset fail counter. + if Instant::now() > last_start + Duration::from_secs(60) { + fails = 0; + } else { + fails += 1; + if fails == 10 { + warn!("modem: CMUX failed too much, rebooting modem."); + // return; + } + } + } + last_start = Some(Instant::now()); + + if self.init().await.is_err() { + continue; + } + + let (mut rx, mut tx) = self.iface.split(); + self.mux_runner.run(&mut rx, &mut tx).await + } } } @@ -192,12 +336,13 @@ impl<'a> CellularConfig<'a> for MyCelullarConfig { 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() @@ -309,9 +454,9 @@ async fn main_task(spawner: Spawner) { spawner.spawn(net_task(stack)).unwrap(); spawner.spawn(ppp_task(networking)).unwrap(); - control - .set_desired_state(OperationState::Connected) - .await; + Timer::after(Duration::from_secs(15)).await; + + control.set_desired_state(OperationState::Connected).await; stack.wait_config_up().await; diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 0e761b9..05b4c42 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -141,99 +141,99 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } pub async fn init_at(&mut self) -> Result<(), Error> { - if !self.is_alive().await? { - return Err(Error::PoweredDown); - } - - // Extended errors on - self.at - .send(&SetReportMobileTerminationError { - n: TerminationErrorMode::Enabled, - }) - .await?; - - // Select SIM - self.at - .send(&SetGpioConfiguration { - gpio_id: 25, - gpio_mode: GpioMode::Output(GpioOutValue::High), - }) - .await?; - - #[cfg(any(feature = "lara-r6"))] - self.at - .send(&SetGpioConfiguration { - gpio_id: 42, - gpio_mode: GpioMode::Input(GpioInPull::NoPull), - }) - .await?; - - let _model_id = self.at.send(&GetModelId).await?; - - // self.at.send( - // &IdentificationInformation { - // n: 9 - // }, - // ).await?; - - self.at.send(&GetFirmwareVersion).await?; - - self.select_sim_card().await?; - - let ccid = self.at.send(&GetCCID).await?; - info!("CCID: {}", ccid.ccid); - - // DCD circuit (109) changes in accordance with the carrier - self.at - .send(&SetCircuit109Behaviour { - value: Circuit109Behaviour::ChangesWithCarrier, - }) - .await?; - - // Ignore changes to DTR - self.at - .send(&SetCircuit108Behaviour { - value: Circuit108Behaviour::Ignore, - }) - .await?; - - // Switch off UART power saving until it is integrated into this API - self.at - .send(&SetPowerSavingControl { - mode: PowerSavingMode::Disabled, - timeout: None, - }) - .await?; - - if C::HEX_MODE { - self.at - .send(&SetHexMode { - hex_mode_disable: HexMode::Enabled, - }) - .await?; - } else { - self.at - .send(&SetHexMode { - hex_mode_disable: HexMode::Disabled, - }) - .await?; - } - - // Tell module whether we support flow control - // FIXME: Use AT+IFC=2,2 instead of AT&K here - if C::FLOW_CONTROL { - self.at - .send(&SetFlowControl { - value: FlowControl::RtsCts, - }) - .await?; - } else { - self.at - .send(&SetFlowControl { - value: FlowControl::Disabled, - }) - .await?; - } + // if !self.is_alive().await? { + // return Err(Error::PoweredDown); + // } + + // // Extended errors on + // self.at + // .send(&SetReportMobileTerminationError { + // n: TerminationErrorMode::Enabled, + // }) + // .await?; + + // // Select SIM + // self.at + // .send(&SetGpioConfiguration { + // gpio_id: 25, + // gpio_mode: GpioMode::Output(GpioOutValue::High), + // }) + // .await?; + + // #[cfg(any(feature = "lara-r6"))] + // self.at + // .send(&SetGpioConfiguration { + // gpio_id: 42, + // gpio_mode: GpioMode::Input(GpioInPull::NoPull), + // }) + // .await?; + + // let _model_id = self.at.send(&GetModelId).await?; + + // // self.at.send( + // // &IdentificationInformation { + // // n: 9 + // // }, + // // ).await?; + + // self.at.send(&GetFirmwareVersion).await?; + + // self.select_sim_card().await?; + + // let ccid = self.at.send(&GetCCID).await?; + // info!("CCID: {}", ccid.ccid); + + // // DCD circuit (109) changes in accordance with the carrier + // self.at + // .send(&SetCircuit109Behaviour { + // value: Circuit109Behaviour::ChangesWithCarrier, + // }) + // .await?; + + // // Ignore changes to DTR + // self.at + // .send(&SetCircuit108Behaviour { + // value: Circuit108Behaviour::Ignore, + // }) + // .await?; + + // // Switch off UART power saving until it is integrated into this API + // self.at + // .send(&SetPowerSavingControl { + // mode: PowerSavingMode::Disabled, + // timeout: None, + // }) + // .await?; + + // if C::HEX_MODE { + // self.at + // .send(&SetHexMode { + // hex_mode_disable: HexMode::Enabled, + // }) + // .await?; + // } else { + // self.at + // .send(&SetHexMode { + // hex_mode_disable: HexMode::Disabled, + // }) + // .await?; + // } + + // // Tell module whether we support flow control + // // FIXME: Use AT+IFC=2,2 instead of AT&K here + // if C::FLOW_CONTROL { + // self.at + // .send(&SetFlowControl { + // value: FlowControl::RtsCts, + // }) + // .await?; + // } else { + // self.at + // .send(&SetFlowControl { + // value: FlowControl::Disabled, + // }) + // .await?; + // } Ok(()) } /// Initializes the network only valid after `init_at`. diff --git a/src/command/call_control/mod.rs b/src/command/call_control/mod.rs new file mode 100644 index 0000000..6e8e038 --- /dev/null +++ b/src/command/call_control/mod.rs @@ -0,0 +1,57 @@ +//! ### 6 - Call control +mod types; + +use atat::atat_derive::AtatCmd; + +use self::types::AddressType; + +use super::NoResponse; + +/// 6.1 Select type of address +CSTA +/// +/// Selects the type of number for further dialling commands (D) according to +/// 3GPP specifications. +/// +/// **NOTES:** +/// - The type of address is automatically detected from the dialling string +/// thus the +CSTA command has no effect. +#[derive(Clone, AtatCmd)] +#[at_cmd("+CSTA", NoResponse)] +pub struct SetAddressType { + #[at_arg(position = 0)] + pub typ: AddressType, +} + +/// 6.2 Dial command D +/// +/// Lists characters that may be used in a dialling string for making a call +/// (voice, data or fax call) or controlling supplementary services in +/// accordance with 3GPP TS 22.030 [77] and initiates the indicated kind of +/// call. No further commands may follow in the command line in case of data or +/// fax calls. +/// +/// **NOTES:** +/// - **LARA-L6 / LARA-R6**: Supplementary services strings are not supported in +/// the dial command. Set the DTR line to ON state before making a data call +/// - **LARA-L6004D / LARA-R6001D / LARA-R6401D**: Voice calls are not +/// supported. +#[derive(Clone, AtatCmd)] +#[at_cmd( + "D", + NoResponse, + abortable = true, + timeout_ms = 180000, + value_sep = false +)] +pub struct Dial<'a> { + /// Dial string; the allowed characters are: 1 2 3 4 5 6 7 8 9 0 * # + A B C + /// D , T P ! W @ (see the 3GPP TS 27.007 [75]). The following characters + /// are ignored: , T ! W @. + /// + /// **NOTE**: The first occurrence of P is interpreted as pause and + /// separator between the dialling number and the DTMF string. The following + /// occurrences are interpreted only as pause. The use of P as pause has + /// been introduced for AT&T certification. + #[at_arg(position = 0, len = 32)] + pub number: &'a str, +} diff --git a/src/command/call_control/types.rs b/src/command/call_control/types.rs new file mode 100644 index 0000000..26ff082 --- /dev/null +++ b/src/command/call_control/types.rs @@ -0,0 +1,14 @@ +//! Argument and parameter types used by Call Control Commands and Responses + +use atat::atat_derive::AtatEnum; + +/// Type of address in integer format +#[derive(Clone, Default, PartialEq, Eq, AtatEnum)] +pub enum AddressType { + /// 145: dialing string includes international access code character '+' + IncludeNationalAccessCode = 145, + + /// 129 (default value): national coded dialing string + #[default] + NationalCodedString = 129, +} diff --git a/src/command/ipc/mod.rs b/src/command/ipc/mod.rs new file mode 100644 index 0000000..b391cf6 --- /dev/null +++ b/src/command/ipc/mod.rs @@ -0,0 +1,58 @@ +//! ### 3 - IPC - Inter Processor Communication +use atat::atat_derive::AtatCmd; + +use super::NoResponse; + +/// 3.1 Multiplexing mode +CMUX +/// +/// Enables the multiplexing protocol control channel as defined in 3GPP TS +/// 27.010 [104]. The command sets the parameters for the control channel. The +/// result code is returned using the old interface speed. The parameters become +/// active only after sending the OK result code. The usage of +CMUX set command +/// during the multiplexing is not allowed. +#[derive(Clone, AtatCmd)] +#[at_cmd("+CMUX", NoResponse)] +pub struct SetMultiplexing { + /// Multiplexer transparency mechanism: + #[at_arg(position = 0)] + pub mode: u8, + + /// The way in which the multiplexer control channel is set up: + #[at_arg(position = 1)] + pub subset: Option, + + /// Transmission rate. The allowed range is 0-7. + /// 0, 9600, 19200, 38400, 57600, 115200, 230400, 460800 + #[at_arg(position = 2)] + pub port_speed: Option, + + /// Maximum frame size + /// + /// - Allowed range is 1-1509. + /// - The default value is 31. + #[at_arg(position = 3)] + pub n1: Option, + + /// Acknowledgement timer in units of ten milliseconds. + /// + /// - The allowed range is 1-255 + #[at_arg(position = 4)] + pub t1: Option, + + /// Maximum number of re-transmissions + #[at_arg(position = 5)] + pub n2: Option, + + /// Response timer for the multiplexer control channel in units of ten + /// milliseconds. + #[at_arg(position = 6)] + pub t2: Option, + + /// Wake up response timer. + #[at_arg(position = 7)] + pub t3: Option, + + /// Window size, for advanced operation with Error Recovery options. + #[at_arg(position = 8)] + pub k: Option, +} diff --git a/src/command/mod.rs b/src/command/mod.rs index b7a588f..0756522 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -1,6 +1,7 @@ //! AT Commands for u-blox cellular module family\ -//! Following the [u-blox cellular modules AT commands manual](https://www.u-blox.com/sites/default/files/u-blox-CEL_ATCommands_%28UBX-13002752%29.pdf) +//! Following the [u-blox cellular modules AT commands manual](https://content.u-blox.com/sites/default/files/u-blox-CEL_ATCommands_UBX-13002752.pdf) +pub mod call_control; pub mod control; pub mod device_data_security; pub mod device_lock; @@ -10,6 +11,7 @@ pub mod general; pub mod gpio; pub mod http; pub mod ip_transport_layer; +pub mod ipc; pub mod mobile_control; pub mod network_service; pub mod psn; diff --git a/src/command/psn/mod.rs b/src/command/psn/mod.rs index 091f077..0d3162a 100644 --- a/src/command/psn/mod.rs +++ b/src/command/psn/mod.rs @@ -265,6 +265,34 @@ pub struct SetPDPContextState { #[at_cmd("+CGACT?", heapless::Vec, attempts = 1, timeout_ms = 150000, abortable = true)] pub struct GetPDPContextState; +/// 18.21 Enter PPP state/GPRS dial-up D* +/// +/// The V.24 dial command "D", similar to the command with the syntax +/// AT+CGDATA="PPP",, causes the MT to perform the necessary actions to +/// establish the communication between the DTE and the external PDP network +/// through the PPP protocol. This can include performing a PS attach and, if +/// the PPP server on the DTE side starts communication, PDP context activation +/// on the specified PDP context identifier (if not already requested by means +/// of +CGATT and +CGACT commands). +/// +/// If the command is accepted and the preliminary PS procedures have succeeded, +/// the "CONNECT" intermediate result code is returned, the MT enters the +/// V.25ter online data state and the PPP L2 protocol between the MT and the DTE +/// is started. +#[derive(Clone, AtatCmd)] +#[at_cmd( + "D*99***", + NoResponse, + value_sep = false, + timeout_ms = 180000, + abortable = true, + termination = "#\r\n" +)] +pub struct EnterPPP { + #[at_arg(position = 0)] + pub cid: ContextId, +} + /// 18.26 Packet switched event reporting +CGEREP /// /// Configures sending of URCs from MT to the DTE, in case of certain events @@ -347,6 +375,22 @@ pub struct SetExtendedPSNetworkRegistrationStatus { #[at_cmd("+UREG?", ExtendedPSNetworkRegistrationStatus)] pub struct GetExtendedPSNetworkRegistrationStatus; +/// 18.29 Manual deactivation of a PDP context H +/// +/// Deactivates an active PDP context with PPP L2 protocol in online command +/// mode. The MT responds with a final result code. For a detailed description, +/// see the H command description. For additional information about OLCM, see +/// the AT command settings. +/// +/// **NOTES**: +/// - In GPRS online command mode, entered by typing the escape sequence "+++" +/// or "~+++" (see &D), the ATH command is needed to terminate the connection. +/// Alternatively, in data transfer mode, DTE originated DTR toggling or PPP +/// disconnection may be used. +#[derive(Clone, AtatCmd)] +#[at_cmd("H", NoResponse)] +pub struct DeactivatePDPContext; + /// 18.36 EPS network registration status +CEREG /// /// Configures the network registration URC related to EPS domain. The URC From 80c4c8deba1ada1f5ac2a297330f08bdeb6d93bf Mon Sep 17 00:00:00 2001 From: Mathias Date: Tue, 20 Feb 2024 19:33:11 +0100 Subject: [PATCH 03/22] TCP working in PPP example --- .../src/bin/embassy-smoltcp-ppp.rs | 153 ++++++++---------- 1 file changed, 67 insertions(+), 86 deletions(-) diff --git a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs index 5454d94..b5e7ce9 100644 --- a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs @@ -14,6 +14,7 @@ use cortex_m_rt::entry; use defmt::*; use embassy_executor::{Executor, Spawner}; use embassy_futures::join::join; +use embassy_net::tcp::TcpSocket; use embassy_net::ConfigV4; use embassy_net::Ipv4Address; use embassy_net::Ipv4Cidr; @@ -27,7 +28,6 @@ use embassy_rp::uart::BufferedUart; use embassy_rp::{bind_interrupts, peripherals::UART0, uart::BufferedInterruptHandler}; use embassy_time::Instant; use embassy_time::{Duration, Timer}; -use embedded_io_async::Read as _; use static_cell::StaticCell; use ublox_cellular::asynch::state::OperationState; use ublox_cellular::command::control::types::Circuit108Behaviour; @@ -44,11 +44,15 @@ use ublox_cellular::command::gpio::types::GpioMode; use ublox_cellular::command::gpio::types::GpioOutValue; use ublox_cellular::command::gpio::SetGpioConfiguration; use ublox_cellular::command::ipc::SetMultiplexing; +use ublox_cellular::command::mobile_control::types::Functionality; +use ublox_cellular::command::mobile_control::types::ResetMode; use ublox_cellular::command::mobile_control::types::TerminationErrorMode; +use ublox_cellular::command::mobile_control::SetModuleFunctionality; use ublox_cellular::command::mobile_control::SetReportMobileTerminationError; use ublox_cellular::command::psn::types::ContextId; use ublox_cellular::command::psn::DeactivatePDPContext; use ublox_cellular::command::psn::EnterPPP; +use ublox_cellular::command::psn::SetPDPContextDefinition; use ublox_cellular::command::system_features::types::PowerSavingMode; use ublox_cellular::command::system_features::SetPowerSavingControl; use {defmt_rtt as _, panic_probe as _}; @@ -78,16 +82,23 @@ struct Networking { MyCelullarConfig, 2, >, + btn: embassy_rp::gpio::Input<'static>, } impl Networking { pub async fn run(mut self) -> ! { let fut = async { loop { - // let _ = self.cellular_runner.reset().await; // Reboot modem and start again Timer::after(Duration::from_secs(5)).await; - embassy_futures::select::select(self.cellular_runner.run(), self.ppp.run()).await; + embassy_futures::select::select3( + self.cellular_runner.run(), + self.ppp.run(), + self.btn.wait_for_any_edge(), + ) + .await; + + let _ = self.cellular_runner.reset().await; } }; join(self.mux.run(), fut).await; @@ -102,6 +113,31 @@ struct PPP { } impl PPP { + async fn configure(at_client: &mut A) -> Result<(), atat::Error> { + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Minimum, + rst: Some(ResetMode::DontReset), + }) + .await?; + + at_client + .send(&SetPDPContextDefinition { + cid: ContextId(1), + pdp_type: "IP", + apn: "em", + }) + .await?; + + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Full, + rst: Some(ResetMode::DontReset), + }) + .await?; + Ok(()) + } + async fn run(&mut self) { let mut fails = 0; let mut last_start = None; @@ -134,10 +170,10 @@ impl PPP { atat::Config::default(), ); - // if let Err(e) = Self::configure(&mut at_client).await { - // warn!("modem: configure failed {:?}", e); - // continue; - // } + if let Err(e) = Self::configure(&mut at_client).await { + warn!("modem: configure failed {:?}", e); + continue; + } Timer::after(Duration::from_secs(2)).await; @@ -156,7 +192,7 @@ impl PPP { drop(at_client); // Check for CTS low (bit 2) - // self.ppp_channel.set_hangup_detection(0x04, 0x00); + self.ppp_channel.set_hangup_detection(0x04, 0x00); info!("RUNNING PPP"); let config = embassy_net_ppp::Config { @@ -288,39 +324,13 @@ impl GsmMux { }) .await?; - Timer::after(Duration::from_secs(1)).await; - Ok(()) } pub async fn run(mut self) { - let mut fails = 0; - let mut last_start = None; - - loop { - if let Some(last_start) = last_start { - // Do not attempt to start too fast. - Timer::at(last_start + Duration::from_secs(10)).await; + self.init().await.unwrap(); - // If was up stably for at least 1 min, reset fail counter. - if Instant::now() > last_start + Duration::from_secs(60) { - fails = 0; - } else { - fails += 1; - if fails == 10 { - warn!("modem: CMUX failed too much, rebooting modem."); - // return; - } - } - } - last_start = Some(Instant::now()); - - if self.init().await.is_err() { - continue; - } - - let (mut rx, mut tx) = self.iface.split(); - self.mux_runner.run(&mut rx, &mut tx).await - } + let (mut rx, mut tx) = self.iface.split(); + self.mux_runner.run(&mut rx, &mut tx).await } } @@ -438,6 +448,8 @@ async fn main_task(spawner: Spawner) { }, ); + control.set_desired_state(OperationState::Alive).await; + let networking = Networking { mux: GsmMux { iface: cell_uart, @@ -449,6 +461,7 @@ async fn main_task(spawner: Spawner) { ppp_channel: ch1, }, cellular_runner: runner, + btn: gpio::Input::new(p.PIN_27, gpio::Pull::Up), }; spawner.spawn(net_task(stack)).unwrap(); @@ -460,55 +473,23 @@ async fn main_task(spawner: Spawner) { stack.wait_config_up().await; - // 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; - // } + info!("WE have network!"); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(Duration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(93, 184, 216, 34), 80); + info!("connecting to {:?}...", remote_endpoint); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + warn!("connect error: {:?}", e); + return; + } + info!("TCP connected!"); } #[embassy_executor::task] From 942b93e777fd2a5b8c07793921b73284be7810cb Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 21 Feb 2024 10:43:49 +0100 Subject: [PATCH 04/22] Simplify initialization of both ppp mode and ublox mode, by providing batteries included new functions that sets up ATAT and all related resources --- Cargo.toml | 27 +- .../src/bin/embassy-pdp-context.rs | 110 ++--- .../src/bin/embassy-smoltcp-ppp.rs | 452 ++---------------- src/asynch/mod.rs | 430 +++++++++++++++-- src/asynch/runner.rs | 235 ++++----- src/asynch/state.rs | 52 +- src/config.rs | 12 +- 7 files changed, 641 insertions(+), 677 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 73ad664..6bc2214 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,9 +30,6 @@ log = { version = "^0.4", default-features = false, optional = true } defmt = { version = "^0.3", optional = true } futures-util = { version = "0.3.29", default-features = false } -#futures = { version = "0.3.17", default-features = false, features = [ -# "async-await", -#] } embassy-futures = { version = "0.1", optional = true } @@ -40,13 +37,25 @@ embedded-hal = "1.0.0" embedded-nal = "0.8" embedded-nal-async = { version = "0.7", optional = true } +embassy-at-cmux = { path = "../embassy/embassy-at-cmux", optional = true } +embassy-net-ppp = { version = "0.1", optional = true } +embassy-net = { version = "0.4", features = [ + "proto-ipv4", + "medium-ip", +], optional = true } embedded-io = "0.6" embedded-io-async = "0.6" +[dev-dependencies] +atat = { version = "*", features = ["heapless"] } + + [features] default = ["socket-udp", "socket-tcp", "async", "ublox-sockets"] +ppp = ["dep:embassy-at-cmux", "dep:embassy-net-ppp", "dep:embassy-net"] + async = ["dep:embedded-nal-async", "dep:embassy-futures"] defmt = [ @@ -57,6 +66,9 @@ defmt = [ "embassy-time/defmt", "embassy-sync/defmt", "embassy-futures?/defmt", + "embassy-net-ppp?/defmt", + "embassy-net?/defmt", + "embassy-at-cmux?/defmt", ] log = ["dep:log", "ublox-sockets?/log", "atat/log"] @@ -79,8 +91,8 @@ toby-l4 = [] upsd-context-activation = [] -socket-tcp = ["ublox-sockets?/socket-tcp"] -socket-udp = ["ublox-sockets?/socket-udp"] +socket-tcp = ["ublox-sockets?/socket-tcp", "embassy-net?/tcp"] +socket-udp = ["ublox-sockets?/socket-udp", "embassy-net?/udp"] [workspace] members = [] @@ -91,11 +103,14 @@ exclude = ["examples"] #ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", branch = "feature/async-borrowed-sockets" } no-std-net = { git = "https://github.com/rushmorem/no-std-net", branch = "issue-15" } #ublox-sockets = { path = "../ublox-sockets" } -atat = { git = "https://github.com/BlackbirdHQ/atat", branch = "master" } +# atat = { git = "https://github.com/BlackbirdHQ/atat", branch = "master" } 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-net-ppp = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +embassy-net = { 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" } +atat = { path = "../atat/atat" } diff --git a/examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs b/examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs index fa0b3b2..48f48a4 100644 --- a/examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs @@ -1,15 +1,10 @@ #![no_std] #![no_main] #![allow(stable_features)] +#![feature(type_alias_impl_trait)] -use atat::asynch::Client; - -use atat::ResponseSlot; -use atat::UrcChannel; - -use cortex_m_rt::entry; use defmt::*; -use embassy_executor::{Executor, Spawner}; +use embassy_executor::Spawner; use embassy_rp::gpio; use embassy_rp::gpio::Input; @@ -20,24 +15,21 @@ use embassy_rp::uart::BufferedUartTx; use embassy_rp::{bind_interrupts, peripherals::UART0, uart::BufferedInterruptHandler}; use embassy_time::{Duration, Timer}; use static_cell::StaticCell; +use ublox_cellular::asynch::Resources; +use ublox_cellular::asynch::UbloxRunner; 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 CMD_BUF_SIZE: usize = 128; const INGRESS_BUF_SIZE: usize = 1024; const URC_CAPACITY: usize = 2; -const URC_SUBSCRIBERS: usize = 2; struct MyCelullarConfig { reset_pin: Option>, @@ -71,8 +63,8 @@ impl<'a> CellularConfig<'a> for MyCelullarConfig { } } -#[embassy_executor::task] -async fn main_task(spawner: Spawner) { +#[embassy_executor::main] +async fn main(spawner: Spawner) { let p = { let config = embassy_rp::config::Config::new(embassy_rp::clocks::ClockConfig::crystal(12_000_000)); @@ -81,10 +73,6 @@ 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 mut cell_uart_config = embassy_rp::uart::Config::default(); - cell_uart_config.baudrate = 115200; let cell_uart = BufferedUart::new_with_rtscts( p.UART0, @@ -95,7 +83,7 @@ async fn main_task(spawner: Spawner) { p.PIN_2, TX_BUF.init([0; 16]), RX_BUF.init([0; 16]), - cell_uart_config, + embassy_rp::uart::Config::default(), ); let (uart_rx, uart_tx) = cell_uart.split(); @@ -103,37 +91,21 @@ async fn main_task(spawner: Spawner) { 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 RESOURCES: StaticCell< + Resources, CMD_BUF_SIZE, INGRESS_BUF_SIZE, URC_CAPACITY>, + > = StaticCell::new(); - 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( + let (_net_device, mut control, runner) = ublox_cellular::asynch::new( + uart_rx, uart_tx, - &RES_SLOT, - BUF.init([0; INGRESS_BUF_SIZE]), - atat::Config::default(), + RESOURCES.init(Resources::new()), + MyCelullarConfig { + reset_pin: Some(cell_nrst), + power_pin: Some(cell_pwr), + vint_pin: Some(cell_vint), + }, ); - 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, - ); // defmt::info!("{:?}", runner.init().await); // control.set_desired_state(PowerState::Connected).await; // control @@ -143,7 +115,8 @@ async fn main_task(spawner: Spawner) { // }) // .await; - defmt::unwrap!(spawner.spawn(cellular_task(runner))); + defmt::unwrap!(spawner.spawn(cell_task(runner))); + Timer::after(Duration::from_millis(1000)).await; loop { control @@ -195,42 +168,21 @@ async fn main_task(spawner: Spawner) { } } -#[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 net_task(stack: &'static Stack>) -> ! { +// stack.run().await +// } #[embassy_executor::task] -async fn cellular_task( - mut runner: Runner< +async fn cell_task( + mut runner: UbloxRunner< 'static, - atat::asynch::Client<'_, BufferedUartTx<'static, UART0>, { INGRESS_BUF_SIZE }>, + BufferedUartRx<'static, UART0>, + BufferedUartTx<'static, UART0>, MyCelullarConfig, - { URC_CAPACITY }, + INGRESS_BUF_SIZE, + 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-rp2040-example/src/bin/embassy-smoltcp-ppp.rs b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs index b5e7ce9..4082e70 100644 --- a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs @@ -1,23 +1,14 @@ +#![cfg(feature = "ppp")] + #![no_std] #![no_main] #![allow(stable_features)] #![feature(type_alias_impl_trait)] -use atat::asynch::AtatClient; -use atat::asynch::Client; - -use atat::asynch::SimpleClient; -use atat::ResponseSlot; -use atat::UrcChannel; - -use cortex_m_rt::entry; use defmt::*; -use embassy_executor::{Executor, Spawner}; -use embassy_futures::join::join; +use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::ConfigV4; use embassy_net::Ipv4Address; -use embassy_net::Ipv4Cidr; use embassy_net::Stack; use embassy_net::StackResources; use embassy_rp::gpio; @@ -26,313 +17,22 @@ use embassy_rp::gpio::Input; use embassy_rp::gpio::OutputOpenDrain; use embassy_rp::uart::BufferedUart; use embassy_rp::{bind_interrupts, peripherals::UART0, uart::BufferedInterruptHandler}; -use embassy_time::Instant; -use embassy_time::{Duration, Timer}; +use embassy_time::Duration; use static_cell::StaticCell; use ublox_cellular::asynch::state::OperationState; -use ublox_cellular::command::control::types::Circuit108Behaviour; -use ublox_cellular::command::control::types::Circuit109Behaviour; -use ublox_cellular::command::control::types::FlowControl; -use ublox_cellular::command::control::SetCircuit108Behaviour; -use ublox_cellular::command::control::SetCircuit109Behaviour; -use ublox_cellular::command::control::SetFlowControl; -use ublox_cellular::command::general::GetCCID; -use ublox_cellular::command::general::GetFirmwareVersion; -use ublox_cellular::command::general::GetModelId; -use ublox_cellular::command::gpio::types::GpioInPull; -use ublox_cellular::command::gpio::types::GpioMode; -use ublox_cellular::command::gpio::types::GpioOutValue; -use ublox_cellular::command::gpio::SetGpioConfiguration; -use ublox_cellular::command::ipc::SetMultiplexing; -use ublox_cellular::command::mobile_control::types::Functionality; -use ublox_cellular::command::mobile_control::types::ResetMode; -use ublox_cellular::command::mobile_control::types::TerminationErrorMode; -use ublox_cellular::command::mobile_control::SetModuleFunctionality; -use ublox_cellular::command::mobile_control::SetReportMobileTerminationError; -use ublox_cellular::command::psn::types::ContextId; -use ublox_cellular::command::psn::DeactivatePDPContext; -use ublox_cellular::command::psn::EnterPPP; -use ublox_cellular::command::psn::SetPDPContextDefinition; -use ublox_cellular::command::system_features::types::PowerSavingMode; -use ublox_cellular::command::system_features::SetPowerSavingControl; +use ublox_cellular::asynch::PPPRunner; +use ublox_cellular::asynch::Resources; use {defmt_rtt as _, panic_probe as _}; -use embassy_at_cmux::Mux; - use ublox_cellular::config::{Apn, CellularConfig}; -use atat::{AtDigester, AtatIngress}; -use ublox_cellular::asynch::State; -use ublox_cellular::command::Urc; - bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; }); +const CMD_BUF_SIZE: usize = 128; const INGRESS_BUF_SIZE: usize = 512; const URC_CAPACITY: usize = 2; -const URC_SUBSCRIBERS: usize = 2; - -struct Networking { - ppp: PPP, - mux: GsmMux, - cellular_runner: ublox_cellular::asynch::runner::Runner< - 'static, - Client<'static, embassy_at_cmux::ChannelTx<'static, 256>, INGRESS_BUF_SIZE>, - MyCelullarConfig, - 2, - >, - btn: embassy_rp::gpio::Input<'static>, -} - -impl Networking { - pub async fn run(mut self) -> ! { - let fut = async { - loop { - // Reboot modem and start again - Timer::after(Duration::from_secs(5)).await; - embassy_futures::select::select3( - self.cellular_runner.run(), - self.ppp.run(), - self.btn.wait_for_any_edge(), - ) - .await; - - let _ = self.cellular_runner.reset().await; - } - }; - join(self.mux.run(), fut).await; - core::unreachable!(); - } -} - -struct PPP { - stack: &'static Stack>, - ppp_runner: embassy_net_ppp::Runner<'static>, - ppp_channel: embassy_at_cmux::Channel<'static, 256>, -} - -impl PPP { - async fn configure(at_client: &mut A) -> Result<(), atat::Error> { - at_client - .send(&SetModuleFunctionality { - fun: Functionality::Minimum, - rst: Some(ResetMode::DontReset), - }) - .await?; - - at_client - .send(&SetPDPContextDefinition { - cid: ContextId(1), - pdp_type: "IP", - apn: "em", - }) - .await?; - - at_client - .send(&SetModuleFunctionality { - fun: Functionality::Full, - rst: Some(ResetMode::DontReset), - }) - .await?; - Ok(()) - } - - async fn run(&mut self) { - let mut fails = 0; - let mut last_start = None; - - loop { - Timer::after(Duration::from_secs(15)).await; - - if let Some(last_start) = last_start { - Timer::at(last_start + Duration::from_secs(10)).await; - // Do not attempt to start too fast. - - // If was up stably for at least 1 min, reset fail counter. - if Instant::now() > last_start + Duration::from_secs(60) { - fails = 0; - } else { - fails += 1; - if fails == 10 { - warn!("modem: PPP failed too much, rebooting modem."); - return; - } - } - } - last_start = Some(Instant::now()); - - let mut buf = [0u8; 64]; - let mut at_client = SimpleClient::new( - &mut self.ppp_channel, - atat::AtDigester::::new(), - &mut buf, - atat::Config::default(), - ); - - if let Err(e) = Self::configure(&mut at_client).await { - warn!("modem: configure failed {:?}", e); - continue; - } - - Timer::after(Duration::from_secs(2)).await; - - // hangup just in case a call was already in progress. - // Ignore errors because this fails if it wasn't. - let _ = at_client.send(&DeactivatePDPContext).await; - - // Send AT command to enter PPP mode - let res = at_client.send(&EnterPPP { cid: ContextId(1) }).await; - - if let Err(e) = res { - warn!("ppp dial failed {:?}", e); - continue; - } - - drop(at_client); - - // Check for CTS low (bit 2) - self.ppp_channel.set_hangup_detection(0x04, 0x00); - - info!("RUNNING PPP"); - let config = embassy_net_ppp::Config { - username: b"", - password: b"", - }; - let res = self - .ppp_runner - .run(&mut self.ppp_channel, config, |ipv4| { - let Some(addr) = ipv4.address else { - warn!("PPP did not provide an IP address."); - return; - }; - let mut dns_servers = heapless::Vec::new(); - for s in ipv4.dns_servers.iter().flatten() { - let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0)); - } - let config = ConfigV4::Static(embassy_net::StaticConfigV4 { - address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0), - gateway: None, - dns_servers, - }); - self.stack.set_config_v4(config); - }) - .await; - - info!("ppp failed: {:?}", res); - - self.ppp_channel.clear_hangup_detection(); - - // escape back to data mode. - self.ppp_channel.set_lines(0x44); - Timer::after(Duration::from_millis(100)).await; - self.ppp_channel.set_lines(0x46); - } - } -} - -struct GsmMux { - mux_runner: embassy_at_cmux::Runner<'static, 2, 256>, - iface: BufferedUart<'static, UART0>, -} - -impl GsmMux { - async fn init(&mut self) -> Result<(), atat::Error> { - let mut buf = [0u8; 64]; - let mut at_client = SimpleClient::new( - &mut self.iface, - atat::AtDigester::::new(), - &mut buf, - atat::Config::default(), - ); - - at_client - .send(&SetReportMobileTerminationError { - n: TerminationErrorMode::Enabled, - }) - .await?; - - // Select SIM - at_client - .send(&SetGpioConfiguration { - gpio_id: 25, - gpio_mode: GpioMode::Output(GpioOutValue::High), - }) - .await?; - - at_client - .send(&SetGpioConfiguration { - gpio_id: 42, - gpio_mode: GpioMode::Input(GpioInPull::NoPull), - }) - .await?; - - let _model_id = at_client.send(&GetModelId).await?; - - // at_client.send( - // &IdentificationInformation { - // n: 9 - // }, - // ).await?; - - at_client.send(&GetFirmwareVersion).await?; - - // self.select_sim_card().await?; - - let ccid = at_client.send(&GetCCID).await?; - info!("CCID: {}", ccid.ccid); - - // DCD circuit (109) changes in accordance with the carrier - at_client - .send(&SetCircuit109Behaviour { - value: Circuit109Behaviour::ChangesWithCarrier, - }) - .await?; - - // Ignore changes to DTR - at_client - .send(&SetCircuit108Behaviour { - value: Circuit108Behaviour::Ignore, - }) - .await?; - - // Switch off UART power saving until it is integrated into this API - at_client - .send(&SetPowerSavingControl { - mode: PowerSavingMode::Disabled, - timeout: None, - }) - .await?; - - at_client - .send(&SetFlowControl { - value: FlowControl::RtsCts, - }) - .await?; - - at_client - .send(&SetMultiplexing { - mode: 0, - subset: None, - port_speed: None, - n1: None, - t1: None, - n2: None, - t2: None, - t3: None, - k: None, - }) - .await?; - - Ok(()) - } - pub async fn run(mut self) { - self.init().await.unwrap(); - - let (mut rx, mut tx) = self.iface.split(); - self.mux_runner.run(&mut rx, &mut tx).await - } -} struct MyCelullarConfig { reset_pin: Option>, @@ -353,25 +53,29 @@ impl<'a> CellularConfig<'a> for MyCelullarConfig { password: None, }; + const PPP_CONFIG: embassy_net_ppp::Config<'a> = embassy_net_ppp::Config { + username: b"", + password: b"", + }; + 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() } } -static RES_SLOT: ResponseSlot = ResponseSlot::new(); -static URC_CHANNEL: UrcChannel = UrcChannel::new(); - -#[embassy_executor::task] -async fn main_task(spawner: Spawner) { +#[embassy_executor::main] +async fn main(spawner: Spawner) { let p = { let config = embassy_rp::config::Config::new(embassy_rp::clocks::ClockConfig::crystal(12_000_000)); @@ -381,9 +85,6 @@ async fn main_task(spawner: Spawner) { static TX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); static RX_BUF: StaticCell<[u8; 16]> = 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, @@ -393,87 +94,47 @@ async fn main_task(spawner: Spawner) { p.PIN_2, TX_BUF.init([0; 16]), RX_BUF.init([0; 16]), - cell_uart_config, + embassy_rp::uart::Config::default(), ); 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); - // Create new `embassy-net-ppp` device and runner pair - static PPP_STATE: StaticCell> = StaticCell::new(); - let (net_device, ppp_runner) = - embassy_net_ppp::new(PPP_STATE.init(embassy_net_ppp::State::new())); + static RESOURCES: StaticCell> = + StaticCell::new(); + + let (net_device, mut control, runner) = ublox_cellular::asynch::new_ppp( + RESOURCES.init(Resources::new()), + MyCelullarConfig { + reset_pin: Some(cell_nrst), + power_pin: Some(cell_pwr), + vint_pin: Some(cell_vint), + }, + ); // Generate random seed let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. // Init network stack static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); + static STACK_RESOURCES: StaticCell> = StaticCell::new(); let stack = &*STACK.init(Stack::new( net_device, embassy_net::Config::default(), - RESOURCES.init(StackResources::new()), + STACK_RESOURCES.init(StackResources::new()), seed, )); - static CMUX: StaticCell> = StaticCell::new(); - - let mux = CMUX.init(Mux::new()); - let (mux_runner, [ch1, ch2]) = mux.start(); - - let (control_rx, control_tx, _) = ch2.split(); - static CMD_BUF: StaticCell<[u8; 128]> = StaticCell::new(); - let at_client = Client::new( - control_tx, - &RES_SLOT, - CMD_BUF.init([0; 128]), - atat::Config::default(), - ); - - spawner.spawn(ingress_task(control_rx)).unwrap(); - - static STATE: StaticCell< - State, INGRESS_BUF_SIZE>>, - > = StaticCell::new(); - let (mut control, runner) = ublox_cellular::asynch::new_ppp( - STATE.init(State::new(at_client)), - &URC_CHANNEL, - MyCelullarConfig { - reset_pin: Some(cell_nrst), - power_pin: Some(cell_pwr), - vint_pin: Some(cell_vint), - }, - ); - - control.set_desired_state(OperationState::Alive).await; - - let networking = Networking { - mux: GsmMux { - iface: cell_uart, - mux_runner, - }, - ppp: PPP { - stack, - ppp_runner, - ppp_channel: ch1, - }, - cellular_runner: runner, - btn: gpio::Input::new(p.PIN_27, gpio::Pull::Up), - }; - spawner.spawn(net_task(stack)).unwrap(); - spawner.spawn(ppp_task(networking)).unwrap(); - - Timer::after(Duration::from_secs(15)).await; + spawner.spawn(ppp_task(runner, cell_uart, &stack)).unwrap(); control.set_desired_state(OperationState::Connected).await; stack.wait_config_up().await; - info!("WE have network!"); + info!("We have network!"); // Then we can use it! let mut rx_buffer = [0; 4096]; @@ -498,46 +159,11 @@ async fn net_task(stack: &'static Stack>) -> ! } #[embassy_executor::task] -async fn ppp_task(networking: Networking) -> ! { - networking.run().await -} - -#[embassy_executor::task] -async fn ingress_task(mut rx: embassy_at_cmux::ChannelRx<'static, 256>) -> ! { - let mut buf = [0u8; INGRESS_BUF_SIZE]; - - let mut ingress = - atat::Ingress::new(AtDigester::::new(), &mut buf, &RES_SLOT, &URC_CHANNEL); - loop { - let buf = ingress.write_buf(); - match embedded_io_async::Read::read(&mut rx, buf).await { - Ok(received) => { - // Ignore errors, as they mean the URC channel was full. This will be automatically redriven later - if ingress.try_advance(received).is_err() { - Timer::after(Duration::from_millis(100)).await; - ingress.try_advance(0).ok(); - } - } - Err(e) => { - defmt::error!( - "Got serial read error {:?}", - embedded_io_async::Error::kind(&e) - ); - ingress.clear(); - } - } - } -} - -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))); - }) +async fn ppp_task( + mut runner: PPPRunner<'static, MyCelullarConfig, INGRESS_BUF_SIZE, URC_CAPACITY>, + interface: BufferedUart<'static, UART0>, + stack: &'static embassy_net::Stack>, +) -> ! { + let (rx, tx) = interface.split(); + runner.run(rx, tx, stack).await } diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 60fbcbe..c142d50 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -5,11 +5,27 @@ pub mod ublox_stack; pub mod state; -use crate::{command::Urc, config::CellularConfig}; -use atat::{asynch::AtatClient, UrcChannel}; +use core::mem::MaybeUninit; + +use crate::{ + command::{ + mobile_control::{ + types::{Functionality, ResetMode}, + SetModuleFunctionality, + }, + psn::{DeactivatePDPContext, EnterPPP, SetPDPContextDefinition}, + Urc, + }, + config::{Apn, CellularConfig}, +}; +use atat::{ + asynch::{AtatClient, Client, SimpleClient}, + AtatIngress, UrcChannel, +}; use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; +use embassy_time::{Duration, Instant, Timer}; +use embedded_io_async::{BufRead, Write}; use runner::Runner; -use state::Device; use self::control::Control; @@ -21,64 +37,416 @@ impl<'d, AT: AtatClient> AtHandle<'d, AT> { } } -pub struct State { +#[cfg(feature = "ppp")] +pub type Resources< + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +> = Resources< + embassy_at_cmux::ChannelTx<'static, 256>, + CMD_BUF_SIZE, + INGRESS_BUF_SIZE, + URC_CAPACITY, +>; + +// #[cfg(not(feature = "ppp"))] +// pub use self::Resources; + +pub struct Resources< + W: Write, + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +> { ch: state::State, - at_handle: Mutex, + + res_slot: atat::ResponseSlot, + urc_channel: UrcChannel, + cmd_buf: [u8; CMD_BUF_SIZE], + ingress_buf: [u8; INGRESS_BUF_SIZE], + + at_client: MaybeUninit>>, + + #[cfg(feature = "ppp")] + ppp_state: embassy_net_ppp::State<2, 2>, + + #[cfg(feature = "ppp")] + mux: embassy_at_cmux::Mux<2, 256>, } -impl State { - pub fn new(at_handle: AT) -> Self { +impl< + W: Write, + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, + > Resources +{ + pub fn new() -> Self { Self { ch: state::State::new(), - at_handle: Mutex::new(at_handle), + + res_slot: atat::ResponseSlot::new(), + urc_channel: atat::UrcChannel::new(), + cmd_buf: [0; CMD_BUF_SIZE], + ingress_buf: [0; INGRESS_BUF_SIZE], + + at_client: MaybeUninit::uninit(), + + #[cfg(feature = "ppp")] + ppp_state: embassy_net_ppp::State::new(), + + #[cfg(feature = "ppp")] + mux: embassy_at_cmux::Mux::new(), } } } -pub fn new<'a, AT: AtatClient, C: CellularConfig<'a>, const URC_CAPACITY: usize>( - state: &'a mut State, - subscriber: &'a UrcChannel, +pub fn new< + 'a, + R: embedded_io_async::Read, + W: embedded_io_async::Write, + C: CellularConfig<'a>, + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +>( + reader: R, + writer: W, + resources: &'a mut Resources, config: C, ) -> ( - Device<'a, AT, URC_CAPACITY>, - Control<'a, AT>, - Runner<'a, AT, C, URC_CAPACITY>, + state::Device<'a, Client<'a, W, INGRESS_BUF_SIZE>, URC_CAPACITY>, + Control<'a, Client<'a, W, INGRESS_BUF_SIZE>>, + UbloxRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY>, ) { + // safety: this is a self-referential struct, however: + // - it can't move while the `'a` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let at_client_uninit: *mut MaybeUninit>> = + (&mut resources.at_client + as *mut MaybeUninit>>) + .cast(); + + unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( + writer, + &resources.res_slot, + &mut resources.cmd_buf, + atat::Config::default(), + ))); + + let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; + let (ch_runner, net_device) = state::new( - &mut state.ch, - AtHandle(&state.at_handle), - subscriber.subscribe().unwrap(), + &mut resources.ch, + AtHandle(at_client), + resources.urc_channel.subscribe().unwrap(), ); - let state_ch = ch_runner.state_runner(); + + let control = Control::new(ch_runner.state_runner(), AtHandle(at_client)); let runner = Runner::new( ch_runner, - AtHandle(&state.at_handle), + AtHandle(at_client), config, - subscriber.subscribe().unwrap(), + resources.urc_channel.subscribe().unwrap(), + ); + + let ingress = atat::Ingress::new( + atat::AtDigester::::new(), + &mut resources.ingress_buf, + &resources.res_slot, + &resources.urc_channel, ); - let control = Control::new(state_ch, AtHandle(&state.at_handle)); + let runner = UbloxRunner { + cellular_runner: runner, + ingress, + reader, + }; (net_device, control, runner) } -pub fn new_ppp<'a, AT: AtatClient, C: CellularConfig<'a>, const URC_CAPACITY: usize>( - state: &'a mut State, - subscriber: &'a atat::UrcChannel, +pub struct UbloxRunner< + 'a, + R: embedded_io_async::Read, + W: embedded_io_async::Write, + C: CellularConfig<'a>, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +> { + pub cellular_runner: Runner<'a, Client<'a, W, INGRESS_BUF_SIZE>, C, URC_CAPACITY>, + pub ingress: atat::Ingress<'a, atat::AtDigester, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, + pub reader: R, +} + +impl< + 'a, + R: embedded_io_async::Read, + W: embedded_io_async::Write, + C: CellularConfig<'a>, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, + > UbloxRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> +{ + pub async fn run(&mut self) -> ! { + embassy_futures::join::join( + self.ingress.read_from(&mut self.reader), + self.cellular_runner.run(), + ) + .await; + core::unreachable!() + } +} + +#[cfg(feature = "ppp")] +pub fn new_ppp< + 'a, + C: CellularConfig<'a>, + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +>( + resources: &'a mut Resources, config: C, -) -> (Control<'a, AT>, Runner<'a, AT, C, URC_CAPACITY>) { - let ch_runner = state::new_ppp(&mut state.ch); +) -> ( + embassy_net_ppp::Device<'a>, + Control<'a, Client<'a, embassy_at_cmux::ChannelTx<'a, 256>, INGRESS_BUF_SIZE>>, + PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY>, +) { + let ch_runner = state::new_ppp(&mut resources.ch); let state_ch = ch_runner.state_runner(); - let runner = Runner::new( + let (mux_runner, [ppp_channel, control_channel]) = resources.mux.start(); + let (control_rx, control_tx, _) = control_channel.split(); + + // safety: this is a self-referential struct, however: + // - it can't move while the `'a` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let at_client_uninit: *mut MaybeUninit< + Mutex, INGRESS_BUF_SIZE>>, + > = (&mut resources.at_client + as *mut MaybeUninit< + Mutex< + NoopRawMutex, + Client<'static, embassy_at_cmux::ChannelTx<'static, 256>, INGRESS_BUF_SIZE>, + >, + >) + .cast(); + + unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( + control_tx, + &resources.res_slot, + &mut resources.cmd_buf, + atat::Config::default(), + ))); + + let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; + + let cellular_runner = Runner::new( ch_runner, - AtHandle(&state.at_handle), + AtHandle(at_client), config, - subscriber.subscribe().unwrap(), + resources.urc_channel.subscribe().unwrap(), ); - let control = Control::new(state_ch, AtHandle(&state.at_handle)); + let ingress = atat::Ingress::new( + atat::AtDigester::::new(), + &mut resources.ingress_buf, + &resources.res_slot, + &resources.urc_channel, + ); + + let control = Control::new(state_ch, AtHandle(at_client)); + + let (net_device, ppp_runner) = embassy_net_ppp::new(&mut resources.ppp_state); + + let runner = PPPRunner { + ppp_runner, + cellular_runner, + ingress, + ppp_channel, + control_rx, + mux_runner, + }; - (control, runner) + (net_device, control, runner) +} + +#[cfg(feature = "ppp")] +pub struct PPPRunner< + 'a, + C: CellularConfig<'a>, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +> { + pub ppp_runner: embassy_net_ppp::Runner<'a>, + pub cellular_runner: Runner< + 'a, + Client<'a, embassy_at_cmux::ChannelTx<'a, 256>, INGRESS_BUF_SIZE>, + C, + URC_CAPACITY, + >, + pub ingress: atat::Ingress<'a, atat::AtDigester, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, + pub ppp_channel: embassy_at_cmux::Channel<'a, 256>, + pub control_rx: embassy_at_cmux::ChannelRx<'a, 256>, + pub mux_runner: embassy_at_cmux::Runner<'a, 2, 256>, +} + +#[cfg(feature = "ppp")] +impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> + PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY> +{ + async fn configure_apn(at_client: &mut A) -> Result<(), atat::Error> { + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Minimum, + rst: Some(ResetMode::DontReset), + }) + .await?; + + let apn = match C::APN { + Apn::None => "", + Apn::Given { name, .. } => name, + }; + + at_client + .send(&SetPDPContextDefinition { + cid: C::CONTEXT_ID, + pdp_type: "IP", + apn, + }) + .await?; + + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Full, + rst: Some(ResetMode::DontReset), + }) + .await?; + Ok(()) + } + + pub async fn run( + &mut self, + mut rx: R, + mut tx: W, + stack: &embassy_net::Stack>, + ) -> ! { + loop { + // Reset modem + + // Do AT init and enter CMUX mode using interface + + let ppp_fut = async { + let mut fails = 0; + let mut last_start = None; + + loop { + Timer::after(Duration::from_secs(15)).await; + + if let Some(last_start) = last_start { + Timer::at(last_start + Duration::from_secs(10)).await; + // Do not attempt to start too fast. + + // If was up stably for at least 1 min, reset fail counter. + if Instant::now() > last_start + Duration::from_secs(60) { + fails = 0; + } else { + fails += 1; + if fails == 10 { + warn!("modem: PPP failed too much, rebooting modem."); + break; + } + } + } + last_start = Some(Instant::now()); + + let mut buf = [0u8; 64]; + let mut at_client = SimpleClient::new( + &mut self.ppp_channel, + atat::AtDigester::::new(), + &mut buf, + atat::Config::default(), + ); + + if let Err(e) = Self::configure_apn(&mut at_client).await { + warn!("modem: configure failed {:?}", e); + continue; + } + + Timer::after(Duration::from_secs(2)).await; + + // hangup just in case a call was already in progress. + // Ignore errors because this fails if it wasn't. + let _ = at_client.send(&DeactivatePDPContext).await; + + // Send AT command to enter PPP mode + let res = at_client.send(&EnterPPP { cid: C::CONTEXT_ID }).await; + + if let Err(e) = res { + warn!("ppp dial failed {:?}", e); + continue; + } + + drop(at_client); + + // Check for CTS low (bit 2) + self.ppp_channel.set_hangup_detection(0x04, 0x00); + + info!("RUNNING PPP"); + let res = self + .ppp_runner + .run(&mut self.ppp_channel, C::PPP_CONFIG, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = heapless::Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = + dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); + } + let config = + embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new( + embassy_net::Ipv4Address::from_bytes(&addr.0), + 0, + ), + gateway: None, + dns_servers, + }); + + stack.set_config_v4(config); + }) + .await; + + info!("ppp failed: {:?}", res); + + self.ppp_channel.clear_hangup_detection(); + + // escape back to data mode. + self.ppp_channel.set_lines(0x44); + Timer::after(Duration::from_millis(100)).await; + self.ppp_channel.set_lines(0x46); + } + }; + + let ingress_fut = async { + self.ingress.read_from(&mut self.control_rx).await; + }; + + let mux_fut = async { + self.mux_runner.run(&mut rx, &mut tx).await; + }; + + embassy_futures::select::select4( + ppp_fut, + ingress_fut, + self.cellular_runner.run(), + mux_fut, + ) + .await; + } + } } diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 05b4c42..9efa713 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -141,99 +141,99 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } pub async fn init_at(&mut self) -> Result<(), Error> { - // if !self.is_alive().await? { - // return Err(Error::PoweredDown); - // } - - // // Extended errors on - // self.at - // .send(&SetReportMobileTerminationError { - // n: TerminationErrorMode::Enabled, - // }) - // .await?; - - // // Select SIM - // self.at - // .send(&SetGpioConfiguration { - // gpio_id: 25, - // gpio_mode: GpioMode::Output(GpioOutValue::High), - // }) - // .await?; - - // #[cfg(any(feature = "lara-r6"))] - // self.at - // .send(&SetGpioConfiguration { - // gpio_id: 42, - // gpio_mode: GpioMode::Input(GpioInPull::NoPull), - // }) - // .await?; - - // let _model_id = self.at.send(&GetModelId).await?; - - // // self.at.send( - // // &IdentificationInformation { - // // n: 9 - // // }, - // // ).await?; - - // self.at.send(&GetFirmwareVersion).await?; - - // self.select_sim_card().await?; - - // let ccid = self.at.send(&GetCCID).await?; - // info!("CCID: {}", ccid.ccid); - - // // DCD circuit (109) changes in accordance with the carrier - // self.at - // .send(&SetCircuit109Behaviour { - // value: Circuit109Behaviour::ChangesWithCarrier, - // }) - // .await?; - - // // Ignore changes to DTR - // self.at - // .send(&SetCircuit108Behaviour { - // value: Circuit108Behaviour::Ignore, - // }) - // .await?; - - // // Switch off UART power saving until it is integrated into this API - // self.at - // .send(&SetPowerSavingControl { - // mode: PowerSavingMode::Disabled, - // timeout: None, - // }) - // .await?; - - // if C::HEX_MODE { - // self.at - // .send(&SetHexMode { - // hex_mode_disable: HexMode::Enabled, - // }) - // .await?; - // } else { - // self.at - // .send(&SetHexMode { - // hex_mode_disable: HexMode::Disabled, - // }) - // .await?; - // } - - // // Tell module whether we support flow control - // // FIXME: Use AT+IFC=2,2 instead of AT&K here - // if C::FLOW_CONTROL { - // self.at - // .send(&SetFlowControl { - // value: FlowControl::RtsCts, - // }) - // .await?; - // } else { - // self.at - // .send(&SetFlowControl { - // value: FlowControl::Disabled, - // }) - // .await?; - // } + if !self.is_alive().await? { + return Err(Error::PoweredDown); + } + + // Extended errors on + self.at + .send(&SetReportMobileTerminationError { + n: TerminationErrorMode::Enabled, + }) + .await?; + + // Select SIM + self.at + .send(&SetGpioConfiguration { + gpio_id: 25, + gpio_mode: GpioMode::Output(GpioOutValue::High), + }) + .await?; + + #[cfg(any(feature = "lara-r6"))] + self.at + .send(&SetGpioConfiguration { + gpio_id: 42, + gpio_mode: GpioMode::Input(GpioInPull::NoPull), + }) + .await?; + + let _model_id = self.at.send(&GetModelId).await?; + + // self.at.send( + // &IdentificationInformation { + // n: 9 + // }, + // ).await?; + + self.at.send(&GetFirmwareVersion).await?; + + self.select_sim_card().await?; + + let ccid = self.at.send(&GetCCID).await?; + info!("CCID: {}", ccid.ccid); + + // DCD circuit (109) changes in accordance with the carrier + self.at + .send(&SetCircuit109Behaviour { + value: Circuit109Behaviour::ChangesWithCarrier, + }) + .await?; + + // Ignore changes to DTR + self.at + .send(&SetCircuit108Behaviour { + value: Circuit108Behaviour::Ignore, + }) + .await?; + + // Switch off UART power saving until it is integrated into this API + self.at + .send(&SetPowerSavingControl { + mode: PowerSavingMode::Disabled, + timeout: None, + }) + .await?; + + if C::HEX_MODE { + self.at + .send(&SetHexMode { + hex_mode_disable: HexMode::Enabled, + }) + .await?; + } else { + self.at + .send(&SetHexMode { + hex_mode_disable: HexMode::Disabled, + }) + .await?; + } + + // Tell module whether we support flow control + // FIXME: Use AT+IFC=2,2 instead of AT&K here + if C::FLOW_CONTROL { + self.at + .send(&SetFlowControl { + value: FlowControl::RtsCts, + }) + .await?; + } else { + self.at + .send(&SetFlowControl { + value: FlowControl::Disabled, + }) + .await?; + } Ok(()) } /// Initializes the network only valid after `init_at`. @@ -546,15 +546,23 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } } } - Ok(OperationState::Initialized) => match self.init_at().await { - Ok(_) => { - self.ch.set_power_state(OperationState::Initialized); + Ok(OperationState::Initialized) => { + #[cfg(not(feature = "ppp"))] + match self.init_at().await { + Ok(_) => { + self.ch.set_power_state(OperationState::Initialized); + } + Err(err) => { + error!("Error in init_at: {:?}", err); + return Err(err); + } } - Err(err) => { - error!("Error in init_at: {:?}", err); - return Err(err); + + #[cfg(feature = "ppp")] + { + self.ch.set_power_state(OperationState::Initialized); } - }, + } Ok(OperationState::Connected) => match self.init_network().await { Ok(_) => { match with_timeout( @@ -579,22 +587,18 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> 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); + #[cfg(not(feature = "ppp"))] + Ok(OperationState::DataEstablished) => { + match self.connect(C::APN, C::PROFILE_ID, 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); @@ -626,6 +630,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } #[allow(unused_variables)] + #[cfg(not(feature = "ppp"))] async fn connect( &mut self, apn_info: Apn<'_>, diff --git a/src/asynch/state.rs b/src/asynch/state.rs index 90a6b67..dd1995b 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -1,7 +1,6 @@ #![allow(dead_code)] use core::cell::RefCell; -use core::mem::MaybeUninit; use core::task::Context; use atat::asynch::AtatClient; @@ -32,6 +31,7 @@ pub enum OperationState { Alive, Initialized, Connected, + #[cfg(not(feature = "ppp"))] DataEstablished, } @@ -43,6 +43,7 @@ impl TryFrom for OperationState { 2 => Ok(OperationState::Alive), 3 => Ok(OperationState::Initialized), 4 => Ok(OperationState::Connected), + #[cfg(not(feature = "ppp"))] 5 => Ok(OperationState::DataEstablished), _ => Err(()), } @@ -56,22 +57,30 @@ use crate::error::Error; use super::AtHandle; pub struct State { - inner: MaybeUninit, + shared: Mutex>, + desired_state_pub_sub: PubSubChannel, } impl State { pub const fn new() -> Self { Self { - inner: MaybeUninit::uninit(), + shared: Mutex::new(RefCell::new(Shared { + link_state: LinkState::Down, + power_state: OperationState::PowerDown, + desired_state: OperationState::PowerDown, + waker: WakerRegistration::new(), + })), + desired_state_pub_sub: PubSubChannel::< + NoopRawMutex, + OperationState, + 1, + MAX_STATE_LISTENERS, + 1, + >::new(), } } } -struct StateInner { - shared: Mutex>, - desired_state_pub_sub: PubSubChannel, -} - /// State of the LinkState pub struct Shared { link_state: LinkState, @@ -228,7 +237,10 @@ pub fn new<'d, AT: AtatClient, const URC_CAPACITY: usize>( at: AtHandle<'d, AT>, urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, 2>, ) -> (Runner<'d>, Device<'d, AT, URC_CAPACITY>) { - let runner = new_ppp(state); + let runner = Runner { + shared: &state.shared, + desired_state_pub_sub: &state.desired_state_pub_sub, + }; let shared = runner.shared; let desired_state_pub_sub = runner.desired_state_pub_sub; @@ -245,28 +257,6 @@ pub fn new<'d, AT: AtatClient, const URC_CAPACITY: usize>( } pub fn new_ppp<'d>(state: &'d mut State) -> Runner<'d> { - // safety: this is a self-referential struct, however: - // - it can't move while the `'d` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let state_uninit: *mut MaybeUninit = - (&mut state.inner as *mut MaybeUninit).cast(); - - let state = unsafe { &mut *state_uninit }.write(StateInner { - shared: Mutex::new(RefCell::new(Shared { - link_state: LinkState::Down, - power_state: OperationState::PowerDown, - desired_state: OperationState::PowerDown, - waker: WakerRegistration::new(), - })), - desired_state_pub_sub: PubSubChannel::< - NoopRawMutex, - OperationState, - 1, - MAX_STATE_LISTENERS, - 1, - >::new(), - }); - Runner { shared: &state.shared, desired_state_pub_sub: &state.desired_state_pub_sub, diff --git a/src/config.rs b/src/config.rs index a78d0e4..f553ccb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,8 @@ use core::convert::Infallible; use embedded_hal::digital::{ErrorType, InputPin, OutputPin, PinState}; use heapless::String; +use crate::command::psn::types::{ContextId, ProfileId}; + pub struct NoPin; impl ErrorType for NoPin { @@ -72,16 +74,22 @@ pub trait CellularConfig<'a> { type PowerPin: OutputPin; type VintPin: InputPin; + // const INGRESS_BUF_SIZE: usize; + // const URC_CAPACITY: usize; + const FLOW_CONTROL: bool = false; const HEX_MODE: bool = true; const OPERATOR_FORMAT: OperatorFormat = OperatorFormat::Long; - const PROFILE_ID: u8 = 1; + const PROFILE_ID: ProfileId = ProfileId(1); // #[cfg(not(feature = "upsd-context-activation"))] - const CONTEXT_ID: u8 = 1; + const CONTEXT_ID: ContextId = ContextId(1); const APN: Apn<'a> = Apn::None; + #[cfg(feature = "ppp")] + const PPP_CONFIG: embassy_net_ppp::Config<'a>; + 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>; From 67adb8322590ff261745e14946275730fbb1aa0f Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 21 Feb 2024 14:57:21 +0100 Subject: [PATCH 05/22] Change feature flag naming --- Cargo.toml | 3 +- examples/embassy-rp2040-example/Cargo.toml | 4 + ...p-context.rs => embassy-internal-stack.rs} | 6 +- src/asynch/mod.rs | 108 +++++++++++++++--- src/asynch/runner.rs | 18 +-- src/command/mod.rs | 9 +- src/config.rs | 3 + src/lib.rs | 3 + 8 files changed, 125 insertions(+), 29 deletions(-) rename examples/embassy-rp2040-example/src/bin/{embassy-pdp-context.rs => embassy-internal-stack.rs} (98%) diff --git a/Cargo.toml b/Cargo.toml index 6bc2214..8303afa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,9 +52,10 @@ atat = { version = "*", features = ["heapless"] } [features] -default = ["socket-udp", "socket-tcp", "async", "ublox-sockets"] +default = ["socket-udp", "socket-tcp", "async"] ppp = ["dep:embassy-at-cmux", "dep:embassy-net-ppp", "dep:embassy-net"] +internal-network-stack = ["dep:ublox-sockets"] async = ["dep:embedded-nal-async", "dep:embassy-futures"] diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index a485238..20afe1e 100644 --- a/examples/embassy-rp2040-example/Cargo.toml +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -31,6 +31,10 @@ 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"]} +[features] +ppp = ["ublox-cellular-rs/ppp"] +internal-network-stack = ["ublox-cellular-rs/internal-network-stack"] + [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" } diff --git a/examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs b/examples/embassy-rp2040-example/src/bin/embassy-internal-stack.rs similarity index 98% rename from examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs rename to examples/embassy-rp2040-example/src/bin/embassy-internal-stack.rs index 48f48a4..3429b73 100644 --- a/examples/embassy-rp2040-example/src/bin/embassy-pdp-context.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-internal-stack.rs @@ -15,8 +15,8 @@ use embassy_rp::uart::BufferedUartTx; use embassy_rp::{bind_interrupts, peripherals::UART0, uart::BufferedInterruptHandler}; use embassy_time::{Duration, Timer}; use static_cell::StaticCell; +use ublox_cellular::asynch::InternalRunner; use ublox_cellular::asynch::Resources; -use ublox_cellular::asynch::UbloxRunner; use {defmt_rtt as _, panic_probe as _}; use ublox_cellular::config::{Apn, CellularConfig}; @@ -95,7 +95,7 @@ async fn main(spawner: Spawner) { Resources, CMD_BUF_SIZE, INGRESS_BUF_SIZE, URC_CAPACITY>, > = StaticCell::new(); - let (_net_device, mut control, runner) = ublox_cellular::asynch::new( + let (_net_device, mut control, runner) = ublox_cellular::asynch::new_internal( uart_rx, uart_tx, RESOURCES.init(Resources::new()), @@ -175,7 +175,7 @@ async fn main(spawner: Spawner) { #[embassy_executor::task] async fn cell_task( - mut runner: UbloxRunner< + mut runner: InternalRunner< 'static, BufferedUartRx<'static, UART0>, BufferedUartTx<'static, UART0>, diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index c142d50..c012f7f 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,6 +1,6 @@ pub mod control; pub mod runner; -#[cfg(feature = "ublox-sockets")] +#[cfg(feature = "internal-network-stack")] pub mod ublox_stack; pub mod state; @@ -9,9 +9,11 @@ use core::mem::MaybeUninit; use crate::{ command::{ + control::{types::FlowControl, SetFlowControl}, + ipc::SetMultiplexing, mobile_control::{ - types::{Functionality, ResetMode}, - SetModuleFunctionality, + types::{Functionality, ResetMode, TerminationErrorMode}, + SetModuleFunctionality, SetReportMobileTerminationError, }, psn::{DeactivatePDPContext, EnterPPP, SetPDPContextDefinition}, Urc, @@ -24,7 +26,8 @@ use atat::{ }; use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; use embassy_time::{Duration, Instant, Timer}; -use embedded_io_async::{BufRead, Write}; +use embedded_io::Error; +use embedded_io_async::{BufRead, Read, Write}; use runner::Runner; use self::control::Control; @@ -42,17 +45,17 @@ pub type Resources< const CMD_BUF_SIZE: usize, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize, -> = Resources< +> = UbxResources< embassy_at_cmux::ChannelTx<'static, 256>, CMD_BUF_SIZE, INGRESS_BUF_SIZE, URC_CAPACITY, >; -// #[cfg(not(feature = "ppp"))] -// pub use self::Resources; +#[cfg(feature = "internal-network-stack")] +pub use self::UbxResources as Resources; -pub struct Resources< +pub struct UbxResources< W: Write, const CMD_BUF_SIZE: usize, const INGRESS_BUF_SIZE: usize, @@ -79,7 +82,7 @@ impl< const CMD_BUF_SIZE: usize, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize, - > Resources + > UbxResources { pub fn new() -> Self { Self { @@ -101,7 +104,8 @@ impl< } } -pub fn new< +#[cfg(feature = "internal-network-stack")] +pub fn new_internal< 'a, R: embedded_io_async::Read, W: embedded_io_async::Write, @@ -117,7 +121,7 @@ pub fn new< ) -> ( state::Device<'a, Client<'a, W, INGRESS_BUF_SIZE>, URC_CAPACITY>, Control<'a, Client<'a, W, INGRESS_BUF_SIZE>>, - UbloxRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY>, + InternalRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY>, ) { // safety: this is a self-referential struct, however: // - it can't move while the `'a` borrow is active. @@ -158,7 +162,7 @@ pub fn new< &resources.urc_channel, ); - let runner = UbloxRunner { + let runner = InternalRunner { cellular_runner: runner, ingress, reader, @@ -167,7 +171,8 @@ pub fn new< (net_device, control, runner) } -pub struct UbloxRunner< +#[cfg(feature = "internal-network-stack")] +pub struct InternalRunner< 'a, R: embedded_io_async::Read, W: embedded_io_async::Write, @@ -180,6 +185,7 @@ pub struct UbloxRunner< pub reader: R, } +#[cfg(feature = "internal-network-stack")] impl< 'a, R: embedded_io_async::Read, @@ -187,7 +193,7 @@ impl< C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize, - > UbloxRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> + > InternalRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> { pub async fn run(&mut self) -> ! { embassy_futures::join::join( @@ -273,6 +279,28 @@ pub fn new_ppp< (net_device, control, runner) } +pub struct ReadWriteAdapter(pub R, pub W); + +impl embedded_io_async::ErrorType for ReadWriteAdapter { + type Error = embedded_io::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 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()) + } +} + #[cfg(feature = "ppp")] pub struct PPPRunner< 'a, @@ -327,7 +355,45 @@ impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACIT Ok(()) } - pub async fn run( + async fn init(rx: &mut R, tx: &mut W) -> Result<(), atat::Error> { + let mut buf = [0u8; 64]; + let mut at_client = SimpleClient::new( + ReadWriteAdapter(rx, tx), + atat::AtDigester::::new(), + &mut buf, + atat::Config::default(), + ); + + at_client + .send(&SetReportMobileTerminationError { + n: TerminationErrorMode::Enabled, + }) + .await?; + + at_client + .send(&SetFlowControl { + value: FlowControl::RtsCts, + }) + .await?; + + at_client + .send(&SetMultiplexing { + mode: 0, + subset: None, + port_speed: None, + n1: None, + t1: None, + n2: None, + t2: None, + t3: None, + k: None, + }) + .await?; + + Ok(()) + } + + pub async fn run( &mut self, mut rx: R, mut tx: W, @@ -335,8 +401,20 @@ impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACIT ) -> ! { loop { // Reset modem + // if self.cellular_runner.init().await.is_err() { + // Timer::after(Duration::from_secs(5)).await; + // continue; + // } + + // Timer::after(Duration::from_secs(5)).await; // Do AT init and enter CMUX mode using interface + if Self::init(&mut rx, &mut tx).await.is_err() { + Timer::after(Duration::from_secs(5)).await; + continue; + }; + + Timer::after(Duration::from_secs(1)).await; let ppp_fut = async { let mut fails = 0; diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 9efa713..db7bf61 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -14,8 +14,6 @@ use crate::command::device_lock::GetPinStatus; use crate::command::general::{GetCCID, GetFirmwareVersion, GetModelId}; use crate::command::gpio::types::{GpioInPull, GpioMode, GpioOutValue}; use crate::command::gpio::SetGpioConfiguration; -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; @@ -74,7 +72,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> self.power_up().await?; }; self.reset().await?; - self.is_alive().await?; + // self.is_alive().await?; Ok(()) } @@ -205,16 +203,17 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> }) .await?; + #[cfg(feature = "internal-network-stack")] if C::HEX_MODE { self.at - .send(&SetHexMode { - hex_mode_disable: HexMode::Enabled, + .send(&crate::command::ip_transport_layer::SetHexMode { + hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Enabled, }) .await?; } else { self.at - .send(&SetHexMode { - hex_mode_disable: HexMode::Disabled, + .send(&crate::command::ip_transport_layer::SetHexMode { + hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Disabled, }) .await?; } @@ -379,7 +378,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> Timer::after(reset_time()).await; pin.set_high().ok(); Timer::after(boot_time()).await; - self.is_alive().await?; + // self.is_alive().await?; } else { warn!("No reset pin configured"); } @@ -617,10 +616,13 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> Urc::MobileStationDeactivate => warn!("Mobile station deactivated"), Urc::NetworkPDNDeactivate => warn!("Network PDN deactivated"), Urc::MobileStationPDNDeactivate => warn!("Mobile station PDN deactivated"), + #[cfg(feature = "internal-network-stack")] Urc::SocketDataAvailable(_) => warn!("Socket data available"), + #[cfg(feature = "internal-network-stack")] Urc::SocketDataAvailableUDP(_) => warn!("Socket data available UDP"), Urc::DataConnectionActivated(_) => warn!("Data connection activated"), Urc::DataConnectionDeactivated(_) => warn!("Data connection deactivated"), + #[cfg(feature = "internal-network-stack")] Urc::SocketClosed(_) => warn!("Socket closed"), Urc::MessageWaitingIndication(_) => warn!("Message waiting indication"), Urc::ExtendedPSNetworkRegistration(_) => warn!("Extended PS network registration"), diff --git a/src/command/mod.rs b/src/command/mod.rs index 0756522..0db4b01 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -10,6 +10,7 @@ pub mod file_system; pub mod general; pub mod gpio; pub mod http; +#[cfg(feature = "internal-network-stack")] pub mod ip_transport_layer; pub mod ipc; pub mod mobile_control; @@ -42,16 +43,20 @@ pub enum Urc { #[at_urc("+CGEV: ME PDN DEACT")] MobileStationPDNDeactivate, + #[cfg(feature = "internal-network-stack")] #[at_urc("+UUSORD")] SocketDataAvailable(ip_transport_layer::urc::SocketDataAvailable), + #[cfg(feature = "internal-network-stack")] #[at_urc("+UUSORF")] SocketDataAvailableUDP(ip_transport_layer::urc::SocketDataAvailable), + #[cfg(feature = "internal-network-stack")] + #[at_urc("+UUSOCL")] + SocketClosed(ip_transport_layer::urc::SocketClosed), + #[at_urc("+UUPSDA")] DataConnectionActivated(psn::urc::DataConnectionActivated), #[at_urc("+UUPSDD")] DataConnectionDeactivated(psn::urc::DataConnectionDeactivated), - #[at_urc("+UUSOCL")] - SocketClosed(ip_transport_layer::urc::SocketClosed), #[at_urc("+UMWI")] MessageWaitingIndication(sms::urc::MessageWaitingIndication), diff --git a/src/config.rs b/src/config.rs index f553ccb..3e8cbdf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -78,7 +78,10 @@ pub trait CellularConfig<'a> { // const URC_CAPACITY: usize; const FLOW_CONTROL: bool = false; + + #[cfg(feature = "internal-network-stack")] const HEX_MODE: bool = true; + const OPERATOR_FORMAT: OperatorFormat = OperatorFormat::Long; const PROFILE_ID: ProfileId = ProfileId(1); diff --git a/src/lib.rs b/src/lib.rs index 312a885..3451008 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,9 @@ // #![cfg_attr(feature = "async", feature(async_fn_in_trait))] // #![cfg_attr(feature = "async", feature(type_alias_impl_trait))] +#[cfg(all(feature = "ppp", feature = "internal-network-stack"))] +compile_error!("You may not enable both `ppp` and `internal-network-stack` features."); + // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; From 6a7a03160549da02727227b309da5b3062b6a296 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 21 Feb 2024 15:27:31 +0100 Subject: [PATCH 06/22] Remove old unused examples --- examples/README.md | 6 - examples/common_lib/Cargo.toml | 26 --- examples/common_lib/src/gpio.rs | 83 -------- examples/common_lib/src/lib.rs | 3 - examples/common_lib/src/serial.rs | 42 ---- examples/common_lib/src/timer.rs | 87 -------- examples/linux_jobs/Cargo.toml | 27 --- examples/linux_jobs/src/file_handler.rs | 71 ------- examples/linux_jobs/src/main.rs | 266 ------------------------ examples/linux_mqtt/Cargo.toml | 25 --- examples/linux_mqtt/src/main.rs | 215 ------------------- examples/sockets/Cargo.toml | 21 -- examples/sockets/src/main.rs | 217 ------------------- 13 files changed, 1089 deletions(-) delete mode 100644 examples/README.md delete mode 100644 examples/common_lib/Cargo.toml delete mode 100644 examples/common_lib/src/gpio.rs delete mode 100644 examples/common_lib/src/lib.rs delete mode 100644 examples/common_lib/src/serial.rs delete mode 100644 examples/common_lib/src/timer.rs delete mode 100644 examples/linux_jobs/Cargo.toml delete mode 100644 examples/linux_jobs/src/file_handler.rs delete mode 100644 examples/linux_jobs/src/main.rs delete mode 100644 examples/linux_mqtt/Cargo.toml delete mode 100644 examples/linux_mqtt/src/main.rs delete mode 100644 examples/sockets/Cargo.toml delete mode 100644 examples/sockets/src/main.rs diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 0ff523d..0000000 --- a/examples/README.md +++ /dev/null @@ -1,6 +0,0 @@ -## Examples - -This folder contains a number of examples, most of which a made to run on linux using a serial port. - -**NOTES** -- `linux_jobs` requires device certificates to run! diff --git a/examples/common_lib/Cargo.toml b/examples/common_lib/Cargo.toml deleted file mode 100644 index 9e2c78e..0000000 --- a/examples/common_lib/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "common_lib" -version = "0.0.1" -authors = ["Mathias Koch "] -description = "Common implementations for ublox-cellular examples" -readme = "../../README.md" -keywords = ["arm", "cortex-m", "ublox", "cellular"] -categories = ["embedded", "no-std"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/BlackbirdHQ/ublox-cellular-rs" -edition = "2018" - -[lib] -name = "common" -doctest = false - -[dependencies] -serialport = "4" -embedded-hal = "=1.0.0-alpha.9" -embedded-hal-nb = "=1.0.0-alpha.1" -nb = "1" -log = { version = "0.4", default-features = false } -ublox-cellular-rs = { path = "../../ublox-cellular" } - -[target.'cfg(target_os = "linux")'.dependencies] -linux-embedded-hal = "0.3" diff --git a/examples/common_lib/src/gpio.rs b/examples/common_lib/src/gpio.rs deleted file mode 100644 index 2c2b1b7..0000000 --- a/examples/common_lib/src/gpio.rs +++ /dev/null @@ -1,83 +0,0 @@ -use embedded_hal::digital::{InputPin, OutputPin}; - -#[cfg(target_os = "linux")] -mod linux { - use super::*; - use linux_embedded_hal::{sysfs_gpio, Pin}; - - // implement newest embedded_hal traits - // linux_embedded_hal uses old ones - pub struct ExtPin(Pin); - - impl OutputPin for ExtPin { - type Error = sysfs_gpio::Error; - - fn set_low(&mut self) -> Result<(), Self::Error> { - if self.0.get_active_low()? { - self.0.set_value(1) - } else { - self.0.set_value(0) - } - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - if self.0.get_active_low()? { - self.0.set_value(0) - } else { - self.0.set_value(1) - } - } - } - - impl InputPin for ExtPin { - type Error = sysfs_gpio::Error; - - fn is_high(&self) -> Result { - if !self.0.get_active_low()? { - self.0.get_value().map(|val| val != 0) - } else { - self.0.get_value().map(|val| val == 0) - } - } - - fn is_low(&self) -> Result { - self.is_high().map(|val| !val) - } - } -} - -#[cfg(not(target_os = "linux"))] -mod other { - use super::*; - - pub struct ExtPin; - - impl OutputPin for ExtPin { - type Error = std::convert::Infallible; - - fn set_low(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - fn set_high(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - } - - impl InputPin for ExtPin { - type Error = std::convert::Infallible; - - fn is_high(&self) -> Result { - Ok(true) - } - - fn is_low(&self) -> Result { - self.is_high().map(|val| !val) - } - } -} - -#[cfg(target_os = "linux")] -pub use linux::ExtPin; -#[cfg(not(target_os = "linux"))] -pub use other::ExtPin; diff --git a/examples/common_lib/src/lib.rs b/examples/common_lib/src/lib.rs deleted file mode 100644 index 3ca8d92..0000000 --- a/examples/common_lib/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod gpio; -pub mod serial; -pub mod timer; diff --git a/examples/common_lib/src/serial.rs b/examples/common_lib/src/serial.rs deleted file mode 100644 index 6ab5d73..0000000 --- a/examples/common_lib/src/serial.rs +++ /dev/null @@ -1,42 +0,0 @@ -use serialport; -use std::io::{ErrorKind as IoErrorKind, Read, Write}; -use embedded_hal_nb::nb; - -pub struct Serial(pub Box); - -/// Helper to convert std::io::Error to the nb::Error -fn translate_io_errors(err: std::io::Error) -> nb::Error { - match err.kind() { - IoErrorKind::WouldBlock | IoErrorKind::TimedOut | IoErrorKind::Interrupted => { - nb::Error::WouldBlock - } - _err => nb::Error::Other(embedded_hal::serial::ErrorKind::Other), - } -} - -impl embedded_hal_nb::serial::Read for Serial { - type Error = embedded_hal::serial::ErrorKind; - - fn read(&mut self) -> nb::Result { - let mut buffer = [0; 1]; - let bytes_read = self.0.read(&mut buffer).map_err(translate_io_errors)?; - if bytes_read == 1 { - Ok(buffer[0]) - } else { - Err(nb::Error::WouldBlock) - } - } -} - -impl embedded_hal_nb::serial::Write for Serial { - type Error = embedded_hal::serial::ErrorKind; - - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.0.write(&[word]).map_err(translate_io_errors)?; - Ok(()) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.0.flush().map_err(translate_io_errors) - } -} diff --git a/examples/common_lib/src/timer.rs b/examples/common_lib/src/timer.rs deleted file mode 100644 index 3b6b3d0..0000000 --- a/examples/common_lib/src/timer.rs +++ /dev/null @@ -1,87 +0,0 @@ -use ublox_cellular::fugit; -use ublox_cellular::prelude::*; - -pub struct SysTimer { - description: String, - monotonic: std::time::Instant, - start: Option, - duration: fugit::TimerDurationU32, -} - -impl SysTimer { - pub fn new(description: &str) -> Self { - Self { - description: description.into(), - monotonic: std::time::Instant::now(), - start: None, - duration: fugit::TimerDurationU32::millis(0), - } - } -} - -impl Clock for SysTimer { - type Error = std::convert::Infallible; - - fn now(&mut self) -> fugit::TimerInstantU32 { - let millis = self.monotonic.elapsed().as_millis(); - fugit::TimerInstantU32::from_ticks(millis as u32) - } - - fn start(&mut self, duration: fugit::TimerDurationU32) -> Result<(), Self::Error> { - self.start = Some(std::time::Instant::now()); - self.duration = duration.convert(); - log::debug!( - "[{}] start {:?} duration {:?}", - self.description, - self.start, - self.duration - ); - Ok(()) - } - - fn cancel(&mut self) -> Result<(), Self::Error> { - if self.start.is_some() { - self.start = None; - } - Ok(()) - } - - fn wait(&mut self) -> nb::Result<(), Self::Error> { - if let Some(start) = self.start { - if std::time::Instant::now() - start - > std::time::Duration::from_millis(self.duration.ticks() as u64) - { - log::debug!( - "[{}] now {:?} start {:?} duration {:?} {:?}", - self.description, - std::time::Instant::now(), - self.start, - self.duration, - std::time::Duration::from_millis(self.duration.ticks() as u64) - ); - Ok(()) - } else { - Err(nb::Error::WouldBlock) - } - } else { - Ok(()) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use fugit::ExtU32; - - #[test] - fn sys_timer() { - let now = std::time::Instant::now(); - - let mut t: SysTimer<1000> = SysTimer::new(""); - t.start(1.secs()).unwrap(); - nb::block!(t.wait()).unwrap(); - - assert!(now.elapsed().as_millis() >= 1000); - } -} diff --git a/examples/linux_jobs/Cargo.toml b/examples/linux_jobs/Cargo.toml deleted file mode 100644 index d0634a1..0000000 --- a/examples/linux_jobs/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "linux_jobs_example" -version = "0.0.1" -authors = ["Mathias Koch "] -description = "Example for running ublox-cellular with mqttrust & rustot in a linux environment" -readme = "../../README.md" -keywords = ["arm", "cortex-m", "ublox", "cellular", "example"] -categories = ["embedded", "no-std"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/BlackbirdHQ/ublox-cellular-rs" -edition = "2018" - -[dependencies] -embedded-hal = "0.2.3" -atat = { version = "^0.4.1", features = ["derive"] } -log = { version = "0.4", default-features = false } -env_logger = "0.7.1" -linux-embedded-hal = "0.3.0" -serialport = "3.3.0" -nb = "0.1.2" -heapless = { version = "^0.5", features = ["serde"] } -rust-crypto = "^0.2" -mqttrust = { git = "https://github.com/BlackbirdHQ/mqttrust", rev = "2cdffdb" } -rustot = { git = "https://github.com/BlackbirdHQ/rustot", rev = "46d573a" } - -common_lib = { path = "../common_lib" } -ublox-cellular-rs = { path = "../../ublox-cellular", features = ["logging"] } diff --git a/examples/linux_jobs/src/file_handler.rs b/examples/linux_jobs/src/file_handler.rs deleted file mode 100644 index aec1f94..0000000 --- a/examples/linux_jobs/src/file_handler.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crypto::digest::Digest; -use crypto::sha1::Sha1; -use rustot::{ - jobs::FileDescription, - ota::{ - ota::ImageState, - pal::{OtaPal, OtaPalError, PalImageState}, - }, -}; -use std::io::{Cursor, Write}; - -pub struct FileHandler { - filebuf: Option>>, -} - -impl FileHandler { - pub fn new() -> Self { - FileHandler { filebuf: None } - } -} - -impl OtaPal for FileHandler { - type Error = (); - fn abort(&mut self, _file: &FileDescription) -> Result<(), OtaPalError> { - Ok(()) - } - fn create_file_for_rx( - &mut self, - file: &FileDescription, - ) -> Result<(), OtaPalError> { - self.filebuf = Some(Cursor::new(Vec::with_capacity(file.filesize))); - Ok(()) - } - fn get_platform_image_state(&mut self) -> Result> { - unimplemented!() - } - fn set_platform_image_state( - &mut self, - _image_state: ImageState, - ) -> Result<(), OtaPalError> { - unimplemented!() - } - fn reset_device(&mut self) -> Result<(), OtaPalError> { - Ok(()) - } - fn close_file(&mut self, _file: &FileDescription) -> Result<(), OtaPalError> { - if let Some(ref mut buf) = &mut self.filebuf { - let mut hasher = Sha1::new(); - hasher.input(buf.get_ref()); - log::info!("Sha1 is {:}!", hasher.result_str()); - Ok(()) - } else { - Err(OtaPalError::BadFileHandle) - } - } - fn write_block( - &mut self, - _file: &FileDescription, - block_offset: usize, - block_payload: &[u8], - ) -> Result> { - if let Some(ref mut buf) = &mut self.filebuf { - buf.set_position(block_offset as u64); - buf.write(block_payload) - .map_err(|_e| OtaPalError::FileWriteFailed)?; - Ok(block_payload.len()) - } else { - Err(OtaPalError::BadFileHandle) - } - } -} diff --git a/examples/linux_jobs/src/main.rs b/examples/linux_jobs/src/main.rs deleted file mode 100644 index b604065..0000000 --- a/examples/linux_jobs/src/main.rs +++ /dev/null @@ -1,266 +0,0 @@ -extern crate alloc; - -mod file_handler; - -use serialport; -use std::io; -use std::thread; - -use file_handler::FileHandler; - -use ublox_cellular::gprs::APNInfo; -use ublox_cellular::prelude::*; -use ublox_cellular::sockets::Ipv4Addr; -use ublox_cellular::{error::Error as GSMError, Config, GsmClient}; - -use atat::blocking::AtatClient; -use embedded_hal::digital::v2::OutputPin; -use linux_embedded_hal::Pin; -use mqttrust::{MqttClient, MqttEvent, MqttOptions, Notification, Request}; - -use rustot::{ - jobs::{is_job_message, IotJobsData, JobAgent, JobDetails, JobStatus}, - ota::ota::{is_ota_message, OtaAgent, OtaConfig}, -}; - -use heapless::{consts, spsc::Queue, ArrayLength}; - -use common::{serial::Serial, timer::SysTimer}; -use std::time::Duration; - -fn attach_gprs(gsm: &GsmClient) -> Result<(), GSMError> -where - C: AtatClient, - RST: OutputPin, - DTR: OutputPin, -{ - gsm.init(true)?; - - // Load certificates - gsm.import_root_ca( - 0, - "Verisign", - include_bytes!("../secrets_mini_2/Verisign.pem"), - )?; - gsm.import_certificate( - 0, - "cert", - include_bytes!("../secrets_mini_2/certificate.pem.crt"), - )?; - gsm.import_private_key( - 0, - "key", - include_bytes!("../secrets_mini_2/private.pem.key"), - None, - )?; - - gsm.begin("").unwrap(); - gsm.attach_gprs(APNInfo::new("em")).unwrap(); - Ok(()) -} - -static mut Q: Queue = Queue(heapless::i::Queue::new()); - -static mut URC_READY: bool = false; - -struct NvicUrcMatcher {} - -impl NvicUrcMatcher { - pub fn new() -> Self { - NvicUrcMatcher {} - } -} - -impl> atat::UrcMatcher for NvicUrcMatcher { - fn process(&mut self, buf: &mut heapless::Vec) -> atat::UrcMatcherResult { - if let Some(line) = atat::get_line(buf, &[b'\r'], b'\r', b'\n', false, false) { - unsafe { URC_READY = true }; - atat::UrcMatcherResult::Complete(line) - } else { - atat::UrcMatcherResult::Incomplete - } - } -} - -type AtatRxBufLen = consts::U2048; - -fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Debug) - .init(); - - // Serial port settings - let settings = serialport::SerialPortSettings { - baud_rate: 115_200, - data_bits: serialport::DataBits::Eight, - parity: serialport::Parity::None, - stop_bits: serialport::StopBits::One, - flow_control: serialport::FlowControl::None, - timeout: Duration::from_millis(5000), - }; - - // Open serial port - let serial_tx = serialport::open_with_settings("/dev/ttyUSB0", &settings) - .expect("Could not open serial port"); - let mut serial_rx = serial_tx.try_clone().expect("Failed to clone serial port"); - - static mut RES_QUEUE: atat::ResQueue = Queue(heapless::i::Queue::u8()); - static mut URC_QUEUE: atat::UrcQueue = Queue(heapless::i::Queue::u8()); - static mut COM_QUEUE: atat::ComQueue = Queue(heapless::i::Queue::u8()); - let (res_p, res_c) = unsafe { RES_QUEUE.split() }; - let (urc_p, urc_c) = unsafe { URC_QUEUE.split() }; - let (com_p, com_c) = unsafe { COM_QUEUE.split() }; - - let at_config = atat::Config::new(atat::Mode::Timeout); - let mut ingress = atat::IngressManager::with_custom_urc_matcher( - res_p, - urc_p, - com_c, - at_config, - Some(NvicUrcMatcher::new()), - ); - let cell_client = atat::Client::new( - Serial(serial_tx), - res_c, - urc_c, - com_p, - SysTimer::new(), - at_config, - ); - - let gsm = GsmClient::<_, Pin, Pin>::new(cell_client, Config::new()); - - let (p, c) = unsafe { Q.split() }; - - let thing_name = heapless::String::::from("test_mini_2"); - - // Connect to AWS IoT - let mut mqtt_eventloop = MqttEvent::new( - c, - SysTimer::new(), - MqttOptions::new(thing_name.as_str(), Ipv4Addr::new(52, 208, 158, 107), 8883) - .set_max_packet_size(2048), - ); - - let mqtt_client = MqttClient::new(p, thing_name); - - let file_handler = FileHandler::new(); - let mut job_agent = JobAgent::new(); - let mut ota_agent = OtaAgent::new( - file_handler, - SysTimer::new(), - OtaConfig::default().set_block_size(512), - ); - - // Launch reading thread - thread::Builder::new() - .name("serial_read".to_string()) - .spawn(move || loop { - let mut buffer = [0; 32]; - match serial_rx.read(&mut buffer[..]) { - Ok(0) => {} - Ok(bytes_read) => { - ingress.write(&buffer[0..bytes_read]); - ingress.digest(); - ingress.digest(); - // gsm.spin(); - } - Err(e) => match e.kind() { - io::ErrorKind::WouldBlock - | io::ErrorKind::TimedOut - | io::ErrorKind::Interrupted => { - // Ignore - } - _ => { - log::error!("Serial reading thread error while reading: {}", e); - } - }, - } - }) - .unwrap(); - - if attach_gprs(&gsm).is_ok() { - loop { - match mqtt_eventloop.connect(&gsm) { - Ok(_) => { - break; - } - Err(nb::Error::Other(_e)) => panic!("Failed to connect to MQTT"), - Err(nb::Error::WouldBlock) => {} - } - if unsafe { URC_READY } { - gsm.spin().unwrap(); - unsafe { URC_READY = false }; - } - thread::sleep(Duration::from_millis(100)); - } - - job_agent.subscribe_to_jobs(&mqtt_client).unwrap(); - - job_agent - .describe_job_execution(&mqtt_client, "$next", None, None) - .unwrap(); - - loop { - if unsafe { URC_READY } { - log::info!("Spinning from URC_RDY"); - gsm.spin().unwrap(); - unsafe { URC_READY = false }; - } - - ota_agent.request_timer_irq(&mqtt_client); - - match mqtt_eventloop.yield_event(&gsm) { - Ok(Notification::Publish(publish)) => { - if is_job_message(&publish.topic_name) { - match job_agent.handle_message(&mqtt_client, &publish) { - Ok(None) => {} - Ok(Some(job)) => { - log::debug!("Accepted a new JOB! {:?}", job); - match job.details { - JobDetails::OtaJob(otajob) => { - ota_agent.process_ota_job(&mqtt_client, otajob).unwrap() - } - _ => {} - } - } - Err(e) => { - log::error!( - "[{}, {:?}]: {:?}", - publish.topic_name, - publish.qospid, - e - ); - } - } - } else if is_ota_message(&publish.topic_name) { - match ota_agent.handle_message(&mqtt_client, &publish) { - Ok(progress) => { - log::info!("OTA Progress: {}%", progress); - if progress == 100 { - job_agent - .update_job_execution(&mqtt_client, JobStatus::Succeeded) - .unwrap(); - } - } - Err(e) => { - log::error!( - "[{}, {:?}]: {:?}", - publish.topic_name, - publish.qospid, - e - ); - } - } - } else { - log::info!("Got some other incoming message {:?}", publish); - } - } - _ => { - // log::debug!("{:?}", n); - } - } - thread::sleep(Duration::from_millis(100)); - } - } -} diff --git a/examples/linux_mqtt/Cargo.toml b/examples/linux_mqtt/Cargo.toml deleted file mode 100644 index 934d4b6..0000000 --- a/examples/linux_mqtt/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "linux_mqtt_example" -version = "0.0.1" -authors = ["Mathias Koch "] -description = "Example for running ublox-cellular with mqttrust in a linux environment" -readme = "../../README.md" -keywords = ["arm", "cortex-m", "ublox", "cellular", "example"] -categories = ["embedded", "no-std"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/BlackbirdHQ/ublox-cellular-rs" -edition = "2018" - -[dependencies] -embedded-hal = "0.2.3" -log = { version = "0.4", default-features = false } -atat = { version = "^0.4.1", features = ["derive"] } -env_logger = "0.7.1" -linux-embedded-hal = "0.3.0" -serialport = "3.3.0" -nb = "0.1.2" -heapless = { version = "^0.5", features = ["serde"] } -mqttrust = { git = "https://github.com/BlackbirdHQ/mqttrust", rev = "2cdffdb", features = ["alloc"] } - -common_lib = { path = "../common_lib" } -ublox-cellular-rs = { path = "../../ublox-cellular", features = ["logging"] } diff --git a/examples/linux_mqtt/src/main.rs b/examples/linux_mqtt/src/main.rs deleted file mode 100644 index 5fcaf48..0000000 --- a/examples/linux_mqtt/src/main.rs +++ /dev/null @@ -1,215 +0,0 @@ -extern crate alloc; - -use serialport; -use std::io; -use std::thread; - -use ublox_cellular::gprs::APNInfo; -use ublox_cellular::prelude::*; -use ublox_cellular::{error::Error as GSMError, sockets::{SocketSet, Socket}, Config, GsmClient}; - -use atat::{self, AtatClient, ClientBuilder, ComQueue, Queues, ResQueue, UrcQueue}; -use embedded_hal::digital::v2::OutputPin; -use linux_embedded_hal::Pin; -use mqttrust::{ - MqttEvent, MqttOptions, Notification, PublishRequest, QoS, Request, SubscribeRequest, - SubscribeTopic, -}; - -use heapless::{consts, spsc::Queue, ArrayLength, String, Vec}; - -use common::{serial::Serial, timer::SysTimer}; -use std::time::Duration; - -fn attach_gprs(gsm: &GsmClient) -> Result<(), GSMError> -where - C: AtatClient, - RST: OutputPin, - DTR: OutputPin, - N: ArrayLength>>, - L: ArrayLength, -{ - gsm.init(true)?; - gsm.begin().unwrap(); - gsm.attach_gprs().unwrap(); - Ok(()) -} - -static mut Q: Queue>, consts::U10, u8> = Queue(heapless::i::Queue::u8()); - -static mut SOCKET_SET: Option> = None; - -static mut URC_READY: bool = false; - -struct NvicUrcMatcher {} - -impl NvicUrcMatcher { - pub fn new() -> Self { - NvicUrcMatcher {} - } -} - -impl> atat::UrcMatcher for NvicUrcMatcher { - fn process(&mut self, buf: &mut heapless::Vec) -> atat::UrcMatcherResult { - if let Some(line) = atat::get_line(buf, &[b'\r'], b'\r', b'\n', false, false) { - unsafe { URC_READY = true }; - atat::UrcMatcherResult::Complete(line) - } else { - atat::UrcMatcherResult::Incomplete - } - } -} - -type AtatRxBufLen = consts::U2048; - -fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Debug) - .init(); - - // Serial port settings - let settings = serialport::SerialPortSettings { - baud_rate: 230_400, - data_bits: serialport::DataBits::Eight, - parity: serialport::Parity::None, - stop_bits: serialport::StopBits::One, - flow_control: serialport::FlowControl::None, - timeout: Duration::from_millis(5000), - }; - - // Open serial port - let serial_tx = serialport::open_with_settings("/dev/ttyUSB0", &settings) - .expect("Could not open serial port"); - let mut serial_rx = serial_tx.try_clone().expect("Failed to clone serial port"); - - static mut RES_QUEUE: ResQueue = Queue(heapless::i::Queue::u8()); - static mut URC_QUEUE: UrcQueue = Queue(heapless::i::Queue::u8()); - static mut COM_QUEUE: ComQueue = Queue(heapless::i::Queue::u8()); - - let queues = Queues { - res_queue: unsafe { RES_QUEUE.split() }, - urc_queue: unsafe { URC_QUEUE.split() }, - com_queue: unsafe { COM_QUEUE.split() }, - }; - - let (cell_client, mut ingress) = ClientBuilder::new( - Serial(serial_tx), - SysTimer::new(), - atat::Config::new(atat::Mode::Timeout), - ) - .with_custom_urc_matcher(NvicUrcMatcher::new()) - .build(queues); - - unsafe { - SOCKET_SET = Some(SocketSet::new()); - } - - let gsm = GsmClient::<_, Pin, Pin, _, _>::new( - cell_client, - unsafe { SOCKET_SET.as_mut().unwrap() }, - Config::new(APNInfo::new("em")), - ); - - let (mut p, c) = unsafe { Q.split() }; - - // Connect to broker.hivemq.com:1883 - let mut mqtt_eventloop = MqttEvent::new( - c, - SysTimer::new(), - MqttOptions::new("test_mini_1", "broker.hivemq.com".into(), 1883), - ); - - log::info!("{:?}", mqtt_eventloop.options.broker()); - - // Launch reading thread - thread::Builder::new() - .name("serial_read".to_string()) - .spawn(move || loop { - let mut buffer = [0; 32]; - match serial_rx.read(&mut buffer[..]) { - Ok(0) => {} - Ok(bytes_read) => { - ingress.write(&buffer[0..bytes_read]); - ingress.digest(); - ingress.digest(); - // gsm.spin(); - } - Err(e) => match e.kind() { - io::ErrorKind::WouldBlock - | io::ErrorKind::TimedOut - | io::ErrorKind::Interrupted => { - // Ignore - } - _ => { - log::error!("Serial reading thread error while reading: {}", e); - } - }, - } - }) - .unwrap(); - - if attach_gprs(&gsm).is_ok() { - nb::block!(mqtt_eventloop.connect(&gsm)).expect("Failed to connect to MQTT"); - - // Publish @ http://www.hivemq.com/demos/websocket-client/ - p.enqueue( - SubscribeRequest { - topics: Vec::from_slice(&[ - SubscribeTopic { - topic_path: String::try_from("mqttrust/tester/subscriber").unwrap(), - qos: QoS::AtLeastOnce, - }, - SubscribeTopic { - topic_path: String::try_from("mqttrust/tester/subscriber2").unwrap(), - qos: QoS::AtLeastOnce, - }, - ]) - .unwrap(), - } - .into(), - ) - .expect("Failed to subscribe!"); - - thread::Builder::new() - .name("eventloop".to_string()) - .spawn(move || { - let mut cnt = 0; - loop { - // Subscribe @ http://www.hivemq.com/demos/websocket-client/ - p.enqueue( - PublishRequest::new( - String::try_from("fbmini/input/test_mini_1").unwrap(), - format!("{{\"key\": \"Hello World from Factbird Mini - {}!\"}}", cnt) - .as_bytes() - .to_owned(), - ) - .into(), - ) - .expect("Failed to publish!"); - cnt += 1; - thread::sleep(Duration::from_millis(5000)); - } - }) - .unwrap(); - - loop { - if unsafe { URC_READY } { - gsm.spin().unwrap(); - } - match nb::block!(mqtt_eventloop.yield_event(&gsm)) { - Ok(Notification::Publish(_publish)) => { - log::debug!( - "[{}, {:?}]: {:?}", - _publish.topic_name, - _publish.qospid, - String::from_utf8(_publish.payload).unwrap() - ); - } - _ => { - // log::debug!("{:?}", n); - } - } - thread::sleep(Duration::from_millis(500)); - } - } -} diff --git a/examples/sockets/Cargo.toml b/examples/sockets/Cargo.toml deleted file mode 100644 index ac10e6e..0000000 --- a/examples/sockets/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "sockets" -version = "0.1.0" -authors = ["Mathias Koch ", "Andres Vahter "] -description = "Example for running ublox-cellular in a linux environment" -readme = "../../README.md" -keywords = ["arm", "cortex-m", "ublox", "cellular", "example"] -categories = ["embedded", "no-std"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/BlackbirdHQ/ublox-cellular-rs" -edition = "2018" - -[dependencies] -log = { version = "0.4", default-features = false } -env_logger = "0.9" -serialport = "4" -structopt = "0.3" -nb = "1" - -common_lib = { path = "../common_lib" } -ublox-cellular-rs = { path = "../../ublox-cellular", default-features = false, features = ["log", "sara-n3", "socket-udp", "socket-tcp"] } diff --git a/examples/sockets/src/main.rs b/examples/sockets/src/main.rs deleted file mode 100644 index 1138d51..0000000 --- a/examples/sockets/src/main.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::thread; -use std::time::Duration; - -use atat::bbqueue::BBBuffer; -use atat::heapless::spsc::Queue; -use common::{gpio::ExtPin, serial::Serial, timer::SysTimer}; -use serialport; -use structopt::StructOpt; -use ublox_cellular::atat; -use ublox_cellular::prelude::*; -use ublox_cellular::sockets::{SocketHandle, SocketSet}; -use ublox_cellular::{APNInfo, Config, GsmClient}; - -// TODO: better naming and explanations -const RX_BUF_LEN: usize = 256; -const RES_CAPACITY: usize = 256; -const URC_CAPACITY: usize = 256; -const TIMER_HZ: u32 = 1000; -const MAX_SOCKET_COUNT: usize = 6; -const SOCKET_RING_BUFFER_LEN: usize = 1024; - -static mut SOCKET_SET: Option> = None; - -#[derive(StructOpt, Debug)] -struct Opt { - /// Serial port device - #[structopt(short, long, default_value = "/dev/ttyUSB0")] - port: String, - - /// Serial port baudrate - #[structopt(short, long, default_value = "115200")] - baud: u32, -} - -#[derive(Debug)] -enum NetworkError { - SocketOpen, - SocketConnect, - SocketClosed, -} - -fn connect + ?Sized>( - socket: &mut Option, - network: &mut N, - socket_addr: SocketAddr, -) -> Result<(), NetworkError> { - let sock = match socket.as_mut() { - None => { - let sock = network.socket().map_err(|_e| NetworkError::SocketOpen)?; - socket.get_or_insert(sock) - } - Some(sock) => sock, - }; - - nb::block!(network.connect(sock, socket_addr)).map_err(|_| { - socket.take(); - NetworkError::SocketConnect - }) -} - -fn is_connected + ?Sized>( - socket: &Option, - network: &mut N, -) -> Result { - match socket { - Some(ref socket) => network - .is_connected(socket) - .map_err(|_e| NetworkError::SocketClosed), - None => Err(NetworkError::SocketClosed), - } -} - -fn main() { - let opt = Opt::from_args(); - - // different log levels can be set using RUST_LOG env variable - // this sets common_lib to info and all others to debug: - // RUST_LOG=common=info,debug ./target/debug/sockets --port /dev/tty.usbserial-01028661 - // use comma separate list to add specific log levels to other modules: - // RUST_LOG=common=info,atat=info,debug ./target/debug/sockets --port /dev/tty.usbserial-01028661 - env_logger::builder().format_timestamp_millis().init(); - - let serial_tx = serialport::new(opt.port, opt.baud) - .timeout(Duration::from_millis(10)) - .open() - .expect("Could not open serial port"); - - let mut serial_rx = serial_tx.try_clone().expect("Failed to clone serial port"); - - static mut RES_QUEUE: BBBuffer = BBBuffer::new(); - static mut URC_QUEUE: BBBuffer = BBBuffer::new(); - static mut COM_QUEUE: atat::ComQueue = Queue::new(); - - let queues = atat::Queues { - res_queue: unsafe { RES_QUEUE.try_split_framed().unwrap() }, - urc_queue: unsafe { URC_QUEUE.try_split_framed().unwrap() }, - com_queue: unsafe { COM_QUEUE.split() }, - }; - - let (cell_client, mut ingress) = - atat::ClientBuilder::<_, _, _, _, TIMER_HZ, RX_BUF_LEN, RES_CAPACITY, URC_CAPACITY>::new( - Serial(serial_tx), - SysTimer::new("RX"), - atat::Config::new(atat::Mode::Timeout), - ) - .build(queues); - - unsafe { - SOCKET_SET = Some(SocketSet::new()); - } - - let mut cell_client = GsmClient::< - _, - _, - ExtPin, - ExtPin, - ExtPin, - ExtPin, - TIMER_HZ, - MAX_SOCKET_COUNT, - SOCKET_RING_BUFFER_LEN, - >::new( - cell_client, - SysTimer::new("CELL"), - Config::new("").with_apn_info(APNInfo::new("internet.tele2.ee")), - ); - - cell_client.set_socket_storage(unsafe { SOCKET_SET.as_mut().unwrap() }); - - // spawn serial reading thread - thread::Builder::new() - .spawn(move || loop { - ingress.digest(); - - let mut buffer = [0; 32]; - match serial_rx.read(&mut buffer[..]) { - Ok(0) => {} - Ok(bytes_read) => { - //ingress.digest(); - // log::info!( - // "rx: {:?}", - // std::str::from_utf8(&buffer[..bytes_read]).unwrap() - // ); - ingress.write(&buffer[0..bytes_read]); - ingress.digest(); - } - Err(e) => match e.kind() { - std::io::ErrorKind::Interrupted => {} - _ => { - //log::error!("Serial reading thread error while reading: {}", e); - } - }, - } - //ingress.digest(); - }) - .unwrap(); - - let mut socket: Option = None; - let mut count = 0; - - // notice that `.data_service` must be called continuously to tick modem state machine - loop { - cell_client - .data_service(&APNInfo::new("em")) - .and_then(|mut service| { - match is_connected(&socket, &mut service) { - Ok(false) => { - // socket is present, but not connected - // usually this implies that the socket is closed for writes - // close and recycle the socket - let sock = socket.take().unwrap(); - TcpClientStack::close(&mut service, sock).expect("cannot close socket"); - } - Err(_) => { - // socket not available, try to create and connect - if let Err(e) = connect( - &mut socket, - &mut service, - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(195, 34, 89, 241)), 7), - ) { - log::error!("cannot connect {:?}", e); - } - } - Ok(true) => { - // socket is available, and connected. - } - } - - // socket can be used if connected - socket.as_mut().and_then(|sock| { - if let Err(e) = nb::block!(TcpClientStack::send( - &mut service, - sock, - format!("Whatup {}", count).as_bytes() - )) { - log::error!("cannot send {:?}", e); - } - - let mut buf = [0; 32]; - match nb::block!(TcpClientStack::receive(&mut service, sock, &mut buf)) { - Ok(count) => { - log::info!("received {} bytes: {:?}", count, &buf[..count]); - } - Err(e) => { - log::error!("cannot receive {:?}", e); - } - } - Some(()) - }); - - Ok(()) - }) - .ok(); - - count += 1; - } -} From 25e398a24c9b10768352d316022ebb1f75761673 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 21 Feb 2024 15:58:35 +0100 Subject: [PATCH 07/22] Move ppp and internal-stack to separate modules --- Cargo.toml | 2 +- src/asynch/control.rs | 4 - src/asynch/internal_stack.rs | 111 ++++++++ src/asynch/mod.rs | 527 +---------------------------------- src/asynch/ppp.rs | 357 ++++++++++++++++++++++++ src/asynch/resources.rs | 58 ++++ src/asynch/runner.rs | 3 +- src/config.rs | 1 - 8 files changed, 539 insertions(+), 524 deletions(-) create mode 100644 src/asynch/internal_stack.rs create mode 100644 src/asynch/ppp.rs create mode 100644 src/asynch/resources.rs diff --git a/Cargo.toml b/Cargo.toml index 8303afa..d66207c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ atat = { version = "*", features = ["heapless"] } [features] -default = ["socket-udp", "socket-tcp", "async"] +default = ["socket-udp", "socket-tcp", "async", "ppp"] ppp = ["dep:embassy-at-cmux", "dep:embassy-net-ppp", "dep:embassy-net"] internal-network-stack = ["dep:ublox-sockets"] diff --git a/src/asynch/control.rs b/src/asynch/control.rs index de38986..98c52e7 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -1,8 +1,4 @@ -use core::future::poll_fn; -use core::task::Poll; - use atat::asynch::AtatClient; -use embassy_time::{with_timeout, Duration}; use crate::error::Error; diff --git a/src/asynch/internal_stack.rs b/src/asynch/internal_stack.rs new file mode 100644 index 0000000..18a4b87 --- /dev/null +++ b/src/asynch/internal_stack.rs @@ -0,0 +1,111 @@ +// pub mod ublox_stack; + +use core::mem::MaybeUninit; + +use atat::{asynch::Client, AtatIngress}; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; +use embedded_io_async::{Read, Write}; + +use crate::{command::Urc, config::CellularConfig}; + +pub use super::resources::UbxResources as Resources; + +use super::{control::Control, runner::Runner, state, AtHandle}; + +pub fn new_internal< + 'a, + R: Read, + W: Write, + C: CellularConfig<'a>, + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +>( + reader: R, + writer: W, + resources: &'a mut Resources, + config: C, +) -> ( + state::Device<'a, Client<'a, W, INGRESS_BUF_SIZE>, URC_CAPACITY>, + Control<'a, Client<'a, W, INGRESS_BUF_SIZE>>, + InternalRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY>, +) { + // safety: this is a self-referential struct, however: + // - it can't move while the `'a` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let at_client_uninit: *mut MaybeUninit>> = + (&mut resources.at_client + as *mut MaybeUninit>>) + .cast(); + + unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( + writer, + &resources.res_slot, + &mut resources.cmd_buf, + atat::Config::default(), + ))); + + let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; + + let (ch_runner, net_device) = state::new( + &mut resources.ch, + AtHandle(at_client), + resources.urc_channel.subscribe().unwrap(), + ); + + let control = Control::new(ch_runner.state_runner(), AtHandle(at_client)); + + let runner = Runner::new( + ch_runner, + AtHandle(at_client), + config, + resources.urc_channel.subscribe().unwrap(), + ); + + let ingress = atat::Ingress::new( + atat::AtDigester::::new(), + &mut resources.ingress_buf, + &resources.res_slot, + &resources.urc_channel, + ); + + let runner = InternalRunner { + cellular_runner: runner, + ingress, + reader, + }; + + (net_device, control, runner) +} + +pub struct InternalRunner< + 'a, + R: Read, + W: Write, + C: CellularConfig<'a>, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +> { + pub cellular_runner: Runner<'a, Client<'a, W, INGRESS_BUF_SIZE>, C, URC_CAPACITY>, + pub ingress: atat::Ingress<'a, atat::AtDigester, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, + pub reader: R, +} + +impl< + 'a, + R: Read, + W: Write, + C: CellularConfig<'a>, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, + > InternalRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> +{ + pub async fn run(&mut self) -> ! { + embassy_futures::join::join( + self.ingress.read_from(&mut self.reader), + self.cellular_runner.run(), + ) + .await; + core::unreachable!() + } +} diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index c012f7f..aede837 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,530 +1,25 @@ pub mod control; +mod resources; pub mod runner; -#[cfg(feature = "internal-network-stack")] -pub mod ublox_stack; - pub mod state; -use core::mem::MaybeUninit; - -use crate::{ - command::{ - control::{types::FlowControl, SetFlowControl}, - ipc::SetMultiplexing, - mobile_control::{ - types::{Functionality, ResetMode, TerminationErrorMode}, - SetModuleFunctionality, SetReportMobileTerminationError, - }, - psn::{DeactivatePDPContext, EnterPPP, SetPDPContextDefinition}, - Urc, - }, - config::{Apn, CellularConfig}, -}; -use atat::{ - asynch::{AtatClient, Client, SimpleClient}, - AtatIngress, UrcChannel, -}; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; -use embassy_time::{Duration, Instant, Timer}; -use embedded_io::Error; -use embedded_io_async::{BufRead, Read, Write}; -use runner::Runner; - -use self::control::Control; - -pub struct AtHandle<'d, AT: AtatClient>(&'d Mutex); - -impl<'d, AT: AtatClient> AtHandle<'d, AT> { - async fn send(&mut self, cmd: &Cmd) -> Result { - self.0.lock().await.send_retry::(cmd).await - } -} - -#[cfg(feature = "ppp")] -pub type Resources< - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> = UbxResources< - embassy_at_cmux::ChannelTx<'static, 256>, - CMD_BUF_SIZE, - INGRESS_BUF_SIZE, - URC_CAPACITY, ->; - -#[cfg(feature = "internal-network-stack")] -pub use self::UbxResources as Resources; - -pub struct UbxResources< - W: Write, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> { - ch: state::State, - - res_slot: atat::ResponseSlot, - urc_channel: UrcChannel, - cmd_buf: [u8; CMD_BUF_SIZE], - ingress_buf: [u8; INGRESS_BUF_SIZE], - - at_client: MaybeUninit>>, - - #[cfg(feature = "ppp")] - ppp_state: embassy_net_ppp::State<2, 2>, - - #[cfg(feature = "ppp")] - mux: embassy_at_cmux::Mux<2, 256>, -} - -impl< - W: Write, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, - > UbxResources -{ - pub fn new() -> Self { - Self { - ch: state::State::new(), - - res_slot: atat::ResponseSlot::new(), - urc_channel: atat::UrcChannel::new(), - cmd_buf: [0; CMD_BUF_SIZE], - ingress_buf: [0; INGRESS_BUF_SIZE], - - at_client: MaybeUninit::uninit(), - - #[cfg(feature = "ppp")] - ppp_state: embassy_net_ppp::State::new(), - - #[cfg(feature = "ppp")] - mux: embassy_at_cmux::Mux::new(), - } - } -} - #[cfg(feature = "internal-network-stack")] -pub fn new_internal< - 'a, - R: embedded_io_async::Read, - W: embedded_io_async::Write, - C: CellularConfig<'a>, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, ->( - reader: R, - writer: W, - resources: &'a mut Resources, - config: C, -) -> ( - state::Device<'a, Client<'a, W, INGRESS_BUF_SIZE>, URC_CAPACITY>, - Control<'a, Client<'a, W, INGRESS_BUF_SIZE>>, - InternalRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY>, -) { - // safety: this is a self-referential struct, however: - // - it can't move while the `'a` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let at_client_uninit: *mut MaybeUninit>> = - (&mut resources.at_client - as *mut MaybeUninit>>) - .cast(); - - unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( - writer, - &resources.res_slot, - &mut resources.cmd_buf, - atat::Config::default(), - ))); - - let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; - - let (ch_runner, net_device) = state::new( - &mut resources.ch, - AtHandle(at_client), - resources.urc_channel.subscribe().unwrap(), - ); - - let control = Control::new(ch_runner.state_runner(), AtHandle(at_client)); - - let runner = Runner::new( - ch_runner, - AtHandle(at_client), - config, - resources.urc_channel.subscribe().unwrap(), - ); - - let ingress = atat::Ingress::new( - atat::AtDigester::::new(), - &mut resources.ingress_buf, - &resources.res_slot, - &resources.urc_channel, - ); - - let runner = InternalRunner { - cellular_runner: runner, - ingress, - reader, - }; - - (net_device, control, runner) -} - +mod internal_stack; #[cfg(feature = "internal-network-stack")] -pub struct InternalRunner< - 'a, - R: embedded_io_async::Read, - W: embedded_io_async::Write, - C: CellularConfig<'a>, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> { - pub cellular_runner: Runner<'a, Client<'a, W, INGRESS_BUF_SIZE>, C, URC_CAPACITY>, - pub ingress: atat::Ingress<'a, atat::AtDigester, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, - pub reader: R, -} - -#[cfg(feature = "internal-network-stack")] -impl< - 'a, - R: embedded_io_async::Read, - W: embedded_io_async::Write, - C: CellularConfig<'a>, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, - > InternalRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> -{ - pub async fn run(&mut self) -> ! { - embassy_futures::join::join( - self.ingress.read_from(&mut self.reader), - self.cellular_runner.run(), - ) - .await; - core::unreachable!() - } -} - -#[cfg(feature = "ppp")] -pub fn new_ppp< - 'a, - C: CellularConfig<'a>, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, ->( - resources: &'a mut Resources, - config: C, -) -> ( - embassy_net_ppp::Device<'a>, - Control<'a, Client<'a, embassy_at_cmux::ChannelTx<'a, 256>, INGRESS_BUF_SIZE>>, - PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY>, -) { - let ch_runner = state::new_ppp(&mut resources.ch); - let state_ch = ch_runner.state_runner(); - - let (mux_runner, [ppp_channel, control_channel]) = resources.mux.start(); - let (control_rx, control_tx, _) = control_channel.split(); - - // safety: this is a self-referential struct, however: - // - it can't move while the `'a` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let at_client_uninit: *mut MaybeUninit< - Mutex, INGRESS_BUF_SIZE>>, - > = (&mut resources.at_client - as *mut MaybeUninit< - Mutex< - NoopRawMutex, - Client<'static, embassy_at_cmux::ChannelTx<'static, 256>, INGRESS_BUF_SIZE>, - >, - >) - .cast(); - - unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( - control_tx, - &resources.res_slot, - &mut resources.cmd_buf, - atat::Config::default(), - ))); - - let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; - - let cellular_runner = Runner::new( - ch_runner, - AtHandle(at_client), - config, - resources.urc_channel.subscribe().unwrap(), - ); - - let ingress = atat::Ingress::new( - atat::AtDigester::::new(), - &mut resources.ingress_buf, - &resources.res_slot, - &resources.urc_channel, - ); - - let control = Control::new(state_ch, AtHandle(at_client)); - - let (net_device, ppp_runner) = embassy_net_ppp::new(&mut resources.ppp_state); - - let runner = PPPRunner { - ppp_runner, - cellular_runner, - ingress, - ppp_channel, - control_rx, - mux_runner, - }; - - (net_device, control, runner) -} - -pub struct ReadWriteAdapter(pub R, pub W); - -impl embedded_io_async::ErrorType for ReadWriteAdapter { - type Error = embedded_io::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 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()) - } -} +pub use internal_stack::{new_internal, InternalRunner, Resources}; #[cfg(feature = "ppp")] -pub struct PPPRunner< - 'a, - C: CellularConfig<'a>, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> { - pub ppp_runner: embassy_net_ppp::Runner<'a>, - pub cellular_runner: Runner< - 'a, - Client<'a, embassy_at_cmux::ChannelTx<'a, 256>, INGRESS_BUF_SIZE>, - C, - URC_CAPACITY, - >, - pub ingress: atat::Ingress<'a, atat::AtDigester, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, - pub ppp_channel: embassy_at_cmux::Channel<'a, 256>, - pub control_rx: embassy_at_cmux::ChannelRx<'a, 256>, - pub mux_runner: embassy_at_cmux::Runner<'a, 2, 256>, -} - +mod ppp; #[cfg(feature = "ppp")] -impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> - PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY> -{ - async fn configure_apn(at_client: &mut A) -> Result<(), atat::Error> { - at_client - .send(&SetModuleFunctionality { - fun: Functionality::Minimum, - rst: Some(ResetMode::DontReset), - }) - .await?; - - let apn = match C::APN { - Apn::None => "", - Apn::Given { name, .. } => name, - }; - - at_client - .send(&SetPDPContextDefinition { - cid: C::CONTEXT_ID, - pdp_type: "IP", - apn, - }) - .await?; - - at_client - .send(&SetModuleFunctionality { - fun: Functionality::Full, - rst: Some(ResetMode::DontReset), - }) - .await?; - Ok(()) - } - - async fn init(rx: &mut R, tx: &mut W) -> Result<(), atat::Error> { - let mut buf = [0u8; 64]; - let mut at_client = SimpleClient::new( - ReadWriteAdapter(rx, tx), - atat::AtDigester::::new(), - &mut buf, - atat::Config::default(), - ); - - at_client - .send(&SetReportMobileTerminationError { - n: TerminationErrorMode::Enabled, - }) - .await?; - - at_client - .send(&SetFlowControl { - value: FlowControl::RtsCts, - }) - .await?; - - at_client - .send(&SetMultiplexing { - mode: 0, - subset: None, - port_speed: None, - n1: None, - t1: None, - n2: None, - t2: None, - t3: None, - k: None, - }) - .await?; - - Ok(()) - } - - pub async fn run( - &mut self, - mut rx: R, - mut tx: W, - stack: &embassy_net::Stack>, - ) -> ! { - loop { - // Reset modem - // if self.cellular_runner.init().await.is_err() { - // Timer::after(Duration::from_secs(5)).await; - // continue; - // } - - // Timer::after(Duration::from_secs(5)).await; - - // Do AT init and enter CMUX mode using interface - if Self::init(&mut rx, &mut tx).await.is_err() { - Timer::after(Duration::from_secs(5)).await; - continue; - }; - - Timer::after(Duration::from_secs(1)).await; - - let ppp_fut = async { - let mut fails = 0; - let mut last_start = None; - - loop { - Timer::after(Duration::from_secs(15)).await; - - if let Some(last_start) = last_start { - Timer::at(last_start + Duration::from_secs(10)).await; - // Do not attempt to start too fast. - - // If was up stably for at least 1 min, reset fail counter. - if Instant::now() > last_start + Duration::from_secs(60) { - fails = 0; - } else { - fails += 1; - if fails == 10 { - warn!("modem: PPP failed too much, rebooting modem."); - break; - } - } - } - last_start = Some(Instant::now()); - - let mut buf = [0u8; 64]; - let mut at_client = SimpleClient::new( - &mut self.ppp_channel, - atat::AtDigester::::new(), - &mut buf, - atat::Config::default(), - ); - - if let Err(e) = Self::configure_apn(&mut at_client).await { - warn!("modem: configure failed {:?}", e); - continue; - } - - Timer::after(Duration::from_secs(2)).await; - - // hangup just in case a call was already in progress. - // Ignore errors because this fails if it wasn't. - let _ = at_client.send(&DeactivatePDPContext).await; - - // Send AT command to enter PPP mode - let res = at_client.send(&EnterPPP { cid: C::CONTEXT_ID }).await; +pub use ppp::{new_ppp, PPPRunner, Resources}; - if let Err(e) = res { - warn!("ppp dial failed {:?}", e); - continue; - } - - drop(at_client); - - // Check for CTS low (bit 2) - self.ppp_channel.set_hangup_detection(0x04, 0x00); - - info!("RUNNING PPP"); - let res = self - .ppp_runner - .run(&mut self.ppp_channel, C::PPP_CONFIG, |ipv4| { - let Some(addr) = ipv4.address else { - warn!("PPP did not provide an IP address."); - return; - }; - let mut dns_servers = heapless::Vec::new(); - for s in ipv4.dns_servers.iter().flatten() { - let _ = - dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); - } - let config = - embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { - address: embassy_net::Ipv4Cidr::new( - embassy_net::Ipv4Address::from_bytes(&addr.0), - 0, - ), - gateway: None, - dns_servers, - }); - - stack.set_config_v4(config); - }) - .await; - - info!("ppp failed: {:?}", res); - - self.ppp_channel.clear_hangup_detection(); - - // escape back to data mode. - self.ppp_channel.set_lines(0x44); - Timer::after(Duration::from_millis(100)).await; - self.ppp_channel.set_lines(0x46); - } - }; - - let ingress_fut = async { - self.ingress.read_from(&mut self.control_rx).await; - }; +use atat::asynch::AtatClient; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; - let mux_fut = async { - self.mux_runner.run(&mut rx, &mut tx).await; - }; +pub struct AtHandle<'d, AT: AtatClient>(&'d Mutex); - embassy_futures::select::select4( - ppp_fut, - ingress_fut, - self.cellular_runner.run(), - mux_fut, - ) - .await; - } +impl<'d, AT: AtatClient> AtHandle<'d, AT> { + async fn send(&mut self, cmd: &Cmd) -> Result { + self.0.lock().await.send_retry::(cmd).await } } diff --git a/src/asynch/ppp.rs b/src/asynch/ppp.rs new file mode 100644 index 0000000..02828b0 --- /dev/null +++ b/src/asynch/ppp.rs @@ -0,0 +1,357 @@ +use core::mem::MaybeUninit; + +use crate::{ + command::{ + control::{types::FlowControl, SetFlowControl}, + ipc::SetMultiplexing, + mobile_control::{ + types::{Functionality, ResetMode, TerminationErrorMode}, + SetModuleFunctionality, SetReportMobileTerminationError, + }, + psn::{DeactivatePDPContext, EnterPPP, SetPDPContextDefinition}, + Urc, + }, + config::{Apn, CellularConfig}, +}; +use atat::{ + asynch::{AtatClient, Client, SimpleClient}, + AtatIngress, +}; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; +use embassy_time::{Duration, Instant, Timer}; +use embedded_io::Error; +use embedded_io_async::{BufRead, Read, Write}; + +use super::{control::Control, resources::UbxResources, runner::Runner, state, AtHandle}; + +pub type Resources< + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +> = UbxResources< + embassy_at_cmux::ChannelTx<'static, 256>, + CMD_BUF_SIZE, + INGRESS_BUF_SIZE, + URC_CAPACITY, +>; + +pub fn new_ppp< + 'a, + C: CellularConfig<'a>, + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +>( + resources: &'a mut Resources, + config: C, +) -> ( + embassy_net_ppp::Device<'a>, + Control<'a, Client<'a, embassy_at_cmux::ChannelTx<'a, 256>, INGRESS_BUF_SIZE>>, + PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY>, +) { + let ch_runner = state::new_ppp(&mut resources.ch); + let state_ch = ch_runner.state_runner(); + + let (mux_runner, [ppp_channel, control_channel]) = resources.mux.start(); + let (control_rx, control_tx, _) = control_channel.split(); + + // safety: this is a self-referential struct, however: + // - it can't move while the `'a` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let at_client_uninit: *mut MaybeUninit< + Mutex, INGRESS_BUF_SIZE>>, + > = (&mut resources.at_client + as *mut MaybeUninit< + Mutex< + NoopRawMutex, + Client<'static, embassy_at_cmux::ChannelTx<'static, 256>, INGRESS_BUF_SIZE>, + >, + >) + .cast(); + + unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( + control_tx, + &resources.res_slot, + &mut resources.cmd_buf, + atat::Config::default(), + ))); + + let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; + + let cellular_runner = Runner::new( + ch_runner, + AtHandle(at_client), + config, + resources.urc_channel.subscribe().unwrap(), + ); + + let ingress = atat::Ingress::new( + atat::AtDigester::::new(), + &mut resources.ingress_buf, + &resources.res_slot, + &resources.urc_channel, + ); + + let control = Control::new(state_ch, AtHandle(at_client)); + + let (net_device, ppp_runner) = embassy_net_ppp::new(&mut resources.ppp_state); + + let runner = PPPRunner { + ppp_runner, + cellular_runner, + ingress, + ppp_channel, + control_rx, + mux_runner, + }; + + (net_device, control, runner) +} + +pub struct PPPRunner< + 'a, + C: CellularConfig<'a>, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +> { + pub ppp_runner: embassy_net_ppp::Runner<'a>, + pub cellular_runner: Runner< + 'a, + Client<'a, embassy_at_cmux::ChannelTx<'a, 256>, INGRESS_BUF_SIZE>, + C, + URC_CAPACITY, + >, + pub ingress: atat::Ingress<'a, atat::AtDigester, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, + pub ppp_channel: embassy_at_cmux::Channel<'a, 256>, + pub control_rx: embassy_at_cmux::ChannelRx<'a, 256>, + pub mux_runner: embassy_at_cmux::Runner<'a, 2, 256>, +} + +impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> + PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY> +{ + async fn configure_apn(at_client: &mut A) -> Result<(), atat::Error> { + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Minimum, + rst: Some(ResetMode::DontReset), + }) + .await?; + + let apn = match C::APN { + Apn::None => "", + Apn::Given { name, .. } => name, + }; + + at_client + .send(&SetPDPContextDefinition { + cid: C::CONTEXT_ID, + pdp_type: "IP", + apn, + }) + .await?; + + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Full, + rst: Some(ResetMode::DontReset), + }) + .await?; + Ok(()) + } + + async fn init(rx: &mut R, tx: &mut W) -> Result<(), atat::Error> { + let mut buf = [0u8; 64]; + let mut at_client = SimpleClient::new( + ReadWriteAdapter(rx, tx), + atat::AtDigester::::new(), + &mut buf, + atat::Config::default(), + ); + + at_client + .send(&SetReportMobileTerminationError { + n: TerminationErrorMode::Enabled, + }) + .await?; + + at_client + .send(&SetFlowControl { + value: FlowControl::RtsCts, + }) + .await?; + + at_client + .send(&SetMultiplexing { + mode: 0, + subset: None, + port_speed: None, + n1: None, + t1: None, + n2: None, + t2: None, + t3: None, + k: None, + }) + .await?; + + Ok(()) + } + + pub async fn run( + &mut self, + mut rx: R, + mut tx: W, + stack: &embassy_net::Stack>, + ) -> ! { + loop { + // Reset modem + // if self.cellular_runner.init().await.is_err() { + // Timer::after(Duration::from_secs(5)).await; + // continue; + // } + + // Timer::after(Duration::from_secs(5)).await; + + // Do AT init and enter CMUX mode using interface + if Self::init(&mut rx, &mut tx).await.is_err() { + Timer::after(Duration::from_secs(5)).await; + continue; + }; + + Timer::after(Duration::from_secs(1)).await; + + let ppp_fut = async { + let mut fails = 0; + let mut last_start = None; + + loop { + Timer::after(Duration::from_secs(15)).await; + + if let Some(last_start) = last_start { + Timer::at(last_start + Duration::from_secs(10)).await; + // Do not attempt to start too fast. + + // If was up stably for at least 1 min, reset fail counter. + if Instant::now() > last_start + Duration::from_secs(60) { + fails = 0; + } else { + fails += 1; + if fails == 10 { + warn!("modem: PPP failed too much, rebooting modem."); + break; + } + } + } + last_start = Some(Instant::now()); + + let mut buf = [0u8; 64]; + let mut at_client = SimpleClient::new( + &mut self.ppp_channel, + atat::AtDigester::::new(), + &mut buf, + atat::Config::default(), + ); + + if let Err(e) = Self::configure_apn(&mut at_client).await { + warn!("modem: configure failed {:?}", e); + continue; + } + + Timer::after(Duration::from_secs(2)).await; + + // hangup just in case a call was already in progress. + // Ignore errors because this fails if it wasn't. + let _ = at_client.send(&DeactivatePDPContext).await; + + // Send AT command to enter PPP mode + let res = at_client.send(&EnterPPP { cid: C::CONTEXT_ID }).await; + + if let Err(e) = res { + warn!("ppp dial failed {:?}", e); + continue; + } + + drop(at_client); + + // Check for CTS low (bit 2) + self.ppp_channel.set_hangup_detection(0x04, 0x00); + + info!("RUNNING PPP"); + let res = self + .ppp_runner + .run(&mut self.ppp_channel, C::PPP_CONFIG, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = heapless::Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = + dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); + } + let config = + embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new( + embassy_net::Ipv4Address::from_bytes(&addr.0), + 0, + ), + gateway: None, + dns_servers, + }); + + stack.set_config_v4(config); + }) + .await; + + info!("ppp failed: {:?}", res); + + self.ppp_channel.clear_hangup_detection(); + + // escape back to data mode. + self.ppp_channel.set_lines(0x44); + Timer::after(Duration::from_millis(100)).await; + self.ppp_channel.set_lines(0x46); + } + }; + + let ingress_fut = async { + self.ingress.read_from(&mut self.control_rx).await; + }; + + let mux_fut = async { + self.mux_runner.run(&mut rx, &mut tx).await; + }; + + embassy_futures::select::select4( + ppp_fut, + ingress_fut, + self.cellular_runner.run(), + mux_fut, + ) + .await; + } + } +} + +pub struct ReadWriteAdapter(pub R, pub W); + +impl embedded_io_async::ErrorType for ReadWriteAdapter { + type Error = embedded_io::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 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/resources.rs b/src/asynch/resources.rs new file mode 100644 index 0000000..5b2d7a8 --- /dev/null +++ b/src/asynch/resources.rs @@ -0,0 +1,58 @@ +use core::mem::MaybeUninit; + +use atat::{asynch::Client, ResponseSlot, UrcChannel}; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; +use embedded_io_async::Write; + +use crate::command::Urc; + +use super::state; + +pub struct UbxResources< + W: Write, + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, +> { + pub(crate) ch: state::State, + + pub(crate) res_slot: ResponseSlot, + pub(crate) urc_channel: UrcChannel, + pub(crate) cmd_buf: [u8; CMD_BUF_SIZE], + pub(crate) ingress_buf: [u8; INGRESS_BUF_SIZE], + + pub(crate) at_client: MaybeUninit>>, + + #[cfg(feature = "ppp")] + pub(crate) ppp_state: embassy_net_ppp::State<2, 2>, + + #[cfg(feature = "ppp")] + pub(crate) mux: embassy_at_cmux::Mux<2, 256>, +} + +impl< + W: Write, + const CMD_BUF_SIZE: usize, + const INGRESS_BUF_SIZE: usize, + const URC_CAPACITY: usize, + > UbxResources +{ + pub fn new() -> Self { + Self { + ch: state::State::new(), + + res_slot: ResponseSlot::new(), + urc_channel: UrcChannel::new(), + cmd_buf: [0; CMD_BUF_SIZE], + ingress_buf: [0; INGRESS_BUF_SIZE], + + at_client: MaybeUninit::uninit(), + + #[cfg(feature = "ppp")] + ppp_state: embassy_net_ppp::State::new(), + + #[cfg(feature = "ppp")] + mux: embassy_at_cmux::Mux::new(), + } + } +} diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index db7bf61..1d979dc 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -30,7 +30,6 @@ use embassy_time::{with_timeout, Duration, Timer}; use embedded_hal::digital::{InputPin, OutputPin}; use crate::command::psn::types::{ContextId, ProfileId}; -use crate::config::Apn; use embassy_futures::select::Either; use super::AtHandle; @@ -635,7 +634,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> #[cfg(not(feature = "ppp"))] async fn connect( &mut self, - apn_info: Apn<'_>, + apn_info: crate::config::Apn<'_>, profile_id: ProfileId, context_id: ContextId, ) -> Result<(), Error> { diff --git a/src/config.rs b/src/config.rs index 3e8cbdf..e6b8d0b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,5 @@ use core::convert::Infallible; use embedded_hal::digital::{ErrorType, InputPin, OutputPin, PinState}; -use heapless::String; use crate::command::psn::types::{ContextId, ProfileId}; From 89dfd1d6ae3e008ae07a1cb5ae5ef678e3e141da Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 23 Feb 2024 19:22:39 +0100 Subject: [PATCH 08/22] Remove async feature --- Cargo.toml | 26 +++++++++------------- examples/embassy-rp2040-example/Cargo.toml | 2 +- examples/embassy-stm32-example/Cargo.toml | 2 +- src/lib.rs | 6 +---- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d66207c..7d7f783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,11 +31,11 @@ defmt = { version = "^0.3", optional = true } futures-util = { version = "0.3.29", default-features = false } -embassy-futures = { version = "0.1", optional = true } +embassy-futures = { version = "0.1" } embedded-hal = "1.0.0" embedded-nal = "0.8" -embedded-nal-async = { version = "0.7", optional = true } +embedded-nal-async = { version = "0.7" } embassy-at-cmux = { path = "../embassy/embassy-at-cmux", optional = true } embassy-net-ppp = { version = "0.1", optional = true } @@ -52,30 +52,31 @@ atat = { version = "*", features = ["heapless"] } [features] -default = ["socket-udp", "socket-tcp", "async", "ppp"] +default = ["socket-udp", "socket-tcp", "ppp"] -ppp = ["dep:embassy-at-cmux", "dep:embassy-net-ppp", "dep:embassy-net"] internal-network-stack = ["dep:ublox-sockets"] +ppp = ["dep:embassy-at-cmux", "dep:embassy-net-ppp", "dep:embassy-net"] -async = ["dep:embedded-nal-async", "dep:embassy-futures"] +automatic-apn = [] +upsd-context-activation = [] + +socket-tcp = ["ublox-sockets?/socket-tcp", "embassy-net?/tcp"] +socket-udp = ["ublox-sockets?/socket-udp", "embassy-net?/udp"] defmt = [ "dep:defmt", - "ublox-sockets?/defmt", "atat/defmt", "heapless/defmt-03", "embassy-time/defmt", "embassy-sync/defmt", - "embassy-futures?/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"] -automatic-apn = [] - lara-r2 = [] lara-r6 = [] leon-g1 = [] @@ -90,11 +91,6 @@ toby-l2 = [] toby-r2 = [] toby-l4 = [] -upsd-context-activation = [] - -socket-tcp = ["ublox-sockets?/socket-tcp", "embassy-net?/tcp"] -socket-udp = ["ublox-sockets?/socket-udp", "embassy-net?/udp"] - [workspace] members = [] default-members = ["."] diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index 20afe1e..56cc77c 100644 --- a/examples/embassy-rp2040-example/Cargo.toml +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -29,7 +29,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"] } -ublox-cellular-rs = {version = "0.4.0", path = "../..", features = ["lara-r6", "defmt", "async"]} +ublox-cellular-rs = {version = "0.4.0", path = "../..", features = ["lara-r6", "defmt"]} [features] ppp = ["ublox-cellular-rs/ppp"] diff --git a/examples/embassy-stm32-example/Cargo.toml b/examples/embassy-stm32-example/Cargo.toml index f34e1f1..997b586 100644 --- a/examples/embassy-stm32-example/Cargo.toml +++ b/examples/embassy-stm32-example/Cargo.toml @@ -21,7 +21,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"] } -ublox-cellular-rs = {version = "0.4.0", path = "../..", features = ["sara-r5", "defmt", "async"]} +ublox-cellular-rs = {version = "0.4.0", path = "../..", features = ["sara-r5", "defmt"]} [patch.crates-io] ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", branch = "feature/async-borrowed-sockets" } diff --git a/src/lib.rs b/src/lib.rs index 3451008..ffc82e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,5 @@ #![cfg_attr(not(test), no_std)] -#![cfg_attr(feature = "async", allow(incomplete_features))] -// #![cfg_attr(feature = "async", feature(generic_const_exprs))] -// #![cfg_attr(feature = "async", feature(async_fn_in_trait))] -// #![cfg_attr(feature = "async", feature(type_alias_impl_trait))] +#![allow(async_fn_in_trait)] #[cfg(all(feature = "ppp", feature = "internal-network-stack"))] compile_error!("You may not enable both `ppp` and `internal-network-stack` features."); @@ -15,5 +12,4 @@ pub mod config; pub mod error; mod module_timing; -#[cfg(feature = "async")] pub mod asynch; From 8206f138d3dbf0139937044b265fbf7144cd9e58 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 15 Mar 2024 12:58:23 +0100 Subject: [PATCH 09/22] Working PPP + CMUX implementation --- .vscode/settings.json | 1 + Cargo.toml | 6 +- .../.vscode/settings.json | 18 + examples/embassy-rp2040-example/Cargo.toml | 60 +++- .../rust-toolchain.toml | 8 +- .../src/bin/embassy-smoltcp-ppp.rs | 166 ++++++--- examples/tokio-std-example/.cargo/config.toml | 5 + examples/tokio-std-example/.gitignore | 9 + .../tokio-std-example/.vscode/settings.json | 17 + examples/tokio-std-example/Cargo.toml | 81 +++++ .../tokio-std-example/rust-toolchain.toml | 8 + .../src/bin/embassy-internal-stack.rs | 188 ++++++++++ .../src/bin/tokio-smoltcp-ppp.rs | 198 +++++++++++ rust-toolchain.toml | 2 +- src/asynch/control.rs | 7 + src/asynch/internal_stack.rs | 15 +- src/asynch/mod.rs | 10 + src/asynch/ppp.rs | 258 ++++++-------- src/asynch/resources.rs | 7 +- src/asynch/runner.rs | 326 +++++++++--------- src/asynch/state.rs | 12 +- src/asynch/ublox_stack/tcp.rs | 21 +- src/command/control/mod.rs | 40 ++- src/command/control/responses.rs | 10 + src/command/control/types.rs | 16 +- src/config.rs | 16 +- 26 files changed, 1082 insertions(+), 423 deletions(-) create mode 100644 examples/embassy-rp2040-example/.vscode/settings.json create mode 100644 examples/tokio-std-example/.cargo/config.toml create mode 100644 examples/tokio-std-example/.gitignore create mode 100644 examples/tokio-std-example/.vscode/settings.json create mode 100644 examples/tokio-std-example/Cargo.toml create mode 100644 examples/tokio-std-example/rust-toolchain.toml create mode 100644 examples/tokio-std-example/src/bin/embassy-internal-stack.rs create mode 100644 examples/tokio-std-example/src/bin/tokio-smoltcp-ppp.rs create mode 100644 src/command/control/responses.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index d19761f..7f9a29e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ ], "rust-analyzer.cargo.features": [ "lara-r6", + "embassy-embedded-hal" ], "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", "rust-analyzer.diagnostics.disabled": [ diff --git a/Cargo.toml b/Cargo.toml index 7d7f783..917ad7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,14 +43,10 @@ embassy-net = { version = "0.4", features = [ "proto-ipv4", "medium-ip", ], optional = true } +embassy-embedded-hal = { version = "0.1", optional = true } -embedded-io = "0.6" embedded-io-async = "0.6" -[dev-dependencies] -atat = { version = "*", features = ["heapless"] } - - [features] default = ["socket-udp", "socket-tcp", "ppp"] diff --git a/examples/embassy-rp2040-example/.vscode/settings.json b/examples/embassy-rp2040-example/.vscode/settings.json new file mode 100644 index 0000000..b51df5c --- /dev/null +++ b/examples/embassy-rp2040-example/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + // override the default setting (`cargo check --all-targets`) which produces the following error + // "can't find crate for `test`" when the default compilation target is a no_std target + // with these changes RA will call `cargo check --bins` on save + "rust-analyzer.checkOnSave.allTargets": false, + "rust-analyzer.procMacro.enable": true, + "rust-analyzer.cargo.target": "thumbv6m-none-eabi", + // "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", + "rust-analyzer.cargo.features": [ + "ppp", + ], + "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.cargoRunner": null, + "rust-analyzer.experimental.procAttrMacros": false, + "rust-analyzer.diagnostics.disabled": [ + "macro-error" + ], +} \ No newline at end of file diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index 56cc77c..e2ce233 100644 --- a/examples/embassy-rp2040-example/Cargo.toml +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -4,16 +4,44 @@ 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", "nightly"] } -embassy-time = { version = "0.3", features = ["defmt", "defmt-timestamp-uptime"] } +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", + "nightly", +] } +embassy-time = { version = "0.3", features = [ + "defmt", + "defmt-timestamp-uptime", +] } embassy-futures = { version = "0.1", features = ["defmt"] } embassy-sync = { version = "0.5", features = ["defmt"] } -embassy-net = { version = "0.4", features = ["defmt", "proto-ipv4", "medium-ip", "tcp", "udp"] } +embassy-net = { version = "0.4", features = [ + "defmt", + "packet-trace", + "proto-ipv4", + "medium-ip", + "tcp", + "udp", + "dns", +] } embassy-net-ppp = { version = "0.1", features = ["defmt"] } -embassy-at-cmux = { path = "../../../embassy/embassy-at-cmux", features = ["defmt"] } -embedded-tls = { path = "../../../embedded-tls", default-features = false, features = ["defmt"] } +embassy-at-cmux = { path = "../../../embassy/embassy-at-cmux", features = [ + "defmt", +] } + +embedded-tls = { path = "https://github.com/drogue-iot/embedded-tls", rev = "f788e02", default-features = false, features = [ + "defmt", +] } embedded-io-async = { version = "0.6" } heapless = "0.8" @@ -26,10 +54,21 @@ defmt = "0.3.5" defmt-rtt = "0.4.0" panic-probe = { version = "0.3.1", features = ["print-defmt"] } -static_cell = { version = "2.0", features = []} +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"]} +ublox-cellular-rs = { version = "0.4.0", path = "../..", features = [ + "lara-r6", + "defmt", +] } +reqwless = { git = "https://github.com/drogue-iot/reqwless", features = [ + "defmt", +] } +smoltcp = { version = "*", default-features = false, features = [ + "dns-max-server-count-4", +] } +rand_chacha = { version = "0.3", default-features = false } + [features] ppp = ["ublox-cellular-rs/ppp"] @@ -58,11 +97,8 @@ embassy-executor = { path = "../../../embassy/embassy-executor" } atat = { path = "../../../atat/atat" } -#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 +opt-level = "s" diff --git a/examples/embassy-rp2040-example/rust-toolchain.toml b/examples/embassy-rp2040-example/rust-toolchain.toml index 25d771e..d1bbca4 100644 --- a/examples/embassy-rp2040-example/rust-toolchain.toml +++ b/examples/embassy-rp2040-example/rust-toolchain.toml @@ -1,14 +1,8 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "1.75" +channel = "nightly-2024-02-01" 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/bin/embassy-smoltcp-ppp.rs b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs index 4082e70..8f8b287 100644 --- a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs @@ -1,5 +1,4 @@ #![cfg(feature = "ppp")] - #![no_std] #![no_main] #![allow(stable_features)] @@ -7,8 +6,10 @@ use defmt::*; use embassy_executor::Spawner; +use embassy_net::dns::DnsSocket; +use embassy_net::tcp::client::TcpClient; +use embassy_net::tcp::client::TcpClientState; use embassy_net::tcp::TcpSocket; -use embassy_net::Ipv4Address; use embassy_net::Stack; use embassy_net::StackResources; use embassy_rp::gpio; @@ -17,11 +18,29 @@ use embassy_rp::gpio::Input; use embassy_rp::gpio::OutputOpenDrain; use embassy_rp::uart::BufferedUart; use embassy_rp::{bind_interrupts, peripherals::UART0, uart::BufferedInterruptHandler}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::Duration; +use embassy_time::Timer; +use embedded_mqtt::transport::embedded_tls::TlsNalTransport; +use embedded_mqtt::transport::embedded_tls::TlsState; +use embedded_mqtt::DomainBroker; +use embedded_tls::Aes128GcmSha256; +use embedded_tls::Certificate; +use embedded_tls::TlsConfig; +use embedded_tls::TlsConnection; +use embedded_tls::TlsContext; +use embedded_tls::UnsecureProvider; +use rand_chacha::rand_core::SeedableRng; +use rand_chacha::ChaCha8Rng; +use reqwless::headers::ContentType; +use reqwless::request::Request; +use reqwless::request::RequestBuilder as _; +use reqwless::response::Response; use static_cell::StaticCell; use ublox_cellular::asynch::state::OperationState; use ublox_cellular::asynch::PPPRunner; use ublox_cellular::asynch::Resources; +use ublox_cellular::config::NoPin; use {defmt_rtt as _, panic_probe as _}; use ublox_cellular::config::{Apn, CellularConfig}; @@ -82,8 +101,8 @@ async fn main(spawner: Spawner) { embassy_rp::init(config) }; - static TX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); - static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + static TX_BUF: StaticCell<[u8; 256]> = StaticCell::new(); + static RX_BUF: StaticCell<[u8; 256]> = StaticCell::new(); let cell_uart = BufferedUart::new_with_rtscts( p.UART0, @@ -92,8 +111,8 @@ async fn main(spawner: Spawner) { p.PIN_1, p.PIN_3, p.PIN_2, - TX_BUF.init([0; 16]), - RX_BUF.init([0; 16]), + TX_BUF.init([0; 256]), + RX_BUF.init([0; 256]), embassy_rp::uart::Config::default(), ); @@ -103,15 +122,12 @@ async fn main(spawner: Spawner) { static RESOURCES: StaticCell> = StaticCell::new(); - - let (net_device, mut control, runner) = ublox_cellular::asynch::new_ppp( - RESOURCES.init(Resources::new()), - MyCelullarConfig { + let (net_device, mut cell_control, mut runner) = + ublox_cellular::asynch::new_ppp(RESOURCES.init(Resources::new()), MyCelullarConfig { reset_pin: Some(cell_nrst), power_pin: Some(cell_pwr), - vint_pin: Some(cell_vint), - }, - ); + vint_pin: Some(cell_vint) + }); // Generate random seed let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. @@ -127,43 +143,93 @@ async fn main(spawner: Spawner) { seed, )); - spawner.spawn(net_task(stack)).unwrap(); - spawner.spawn(ppp_task(runner, cell_uart, &stack)).unwrap(); - - control.set_desired_state(OperationState::Connected).await; - - stack.wait_config_up().await; - - info!("We have network!"); - - // Then we can use it! - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; - let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(Duration::from_secs(10))); - - let remote_endpoint = (Ipv4Address::new(93, 184, 216, 34), 80); - info!("connecting to {:?}...", remote_endpoint); - let r = socket.connect(remote_endpoint).await; - if let Err(e) = r { - warn!("connect error: {:?}", e); - return; - } - info!("TCP connected!"); -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await -} + let http_fut = async { + stack.wait_config_up().await; + + info!("We have network!"); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + let hostname = "ecdsa-test.germancoding.com"; + + let mut remote = stack + .dns_query(hostname, smoltcp::wire::DnsQueryType::A) + .await + .unwrap(); + let remote_endpoint = (remote.pop().unwrap(), 443); + info!("connecting to {:?}...", remote_endpoint); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + warn!("connect error: {:?}", e); + return; + } + info!("TCP connected!"); + + let mut read_record_buffer = [0; 16384]; + let mut write_record_buffer = [0; 16384]; + let config = TlsConfig::new().with_server_name(hostname); + let mut tls = TlsConnection::new(socket, &mut read_record_buffer, &mut write_record_buffer); + + tls.open(TlsContext::new( + &config, + UnsecureProvider::new::(ChaCha8Rng::seed_from_u64(seed)), + )) + .await + .expect("error establishing TLS connection"); + + info!("TLS Established!"); + + let request = Request::get("/") + .host(hostname) + .content_type(ContentType::TextPlain) + .build(); + request.write(&mut tls).await.unwrap(); + + let mut rx_buf = [0; 4096]; + let response = Response::read(&mut tls, reqwless::request::Method::GET, &mut rx_buf) + .await + .unwrap(); + + // let mut buf = vec![0; 16384]; + // let len = response + // .body() + // .reader() + // .read_to_end(&mut buf) + // .await + // .unwrap(); + // info!("{:?}", core::str::from_utf8(&buf[..len])); + }; -#[embassy_executor::task] -async fn ppp_task( - mut runner: PPPRunner<'static, MyCelullarConfig, INGRESS_BUF_SIZE, URC_CAPACITY>, - interface: BufferedUart<'static, UART0>, - stack: &'static embassy_net::Stack>, -) -> ! { - let (rx, tx) = interface.split(); - runner.run(rx, tx, stack).await + let (rx, tx) = cell_uart.split(); + embassy_futures::join::join3( + stack.run(), + runner.run(rx, tx, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = heapless::Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); + } + let config = embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new( + embassy_net::Ipv4Address::from_bytes(&addr.0), + 0, + ), + gateway: None, + dns_servers, + }); + + stack.set_config_v4(config); + }), + http_fut, + ) + .await; + + core::unreachable!(); } diff --git a/examples/tokio-std-example/.cargo/config.toml b/examples/tokio-std-example/.cargo/config.toml new file mode 100644 index 0000000..38c661e --- /dev/null +++ b/examples/tokio-std-example/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "x86_64-unknown-linux-gnu" + +[env] +RUST_LOG = "trace,embassy_hal_internal=error" diff --git a/examples/tokio-std-example/.gitignore b/examples/tokio-std-example/.gitignore new file mode 100644 index 0000000..092e4d3 --- /dev/null +++ b/examples/tokio-std-example/.gitignore @@ -0,0 +1,9 @@ +**/*.rs.bk +.#* +.gdb_history +*.fifo +target/ +*.o +**/*secrets* + +.DS_Store diff --git a/examples/tokio-std-example/.vscode/settings.json b/examples/tokio-std-example/.vscode/settings.json new file mode 100644 index 0000000..64a95fd --- /dev/null +++ b/examples/tokio-std-example/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + // override the default setting (`cargo check --all-targets`) which produces the following error + // "can't find crate for `test`" when the default compilation target is a no_std target + // with these changes RA will call `cargo check --bins` on save + "rust-analyzer.checkOnSave.allTargets": false, + "rust-analyzer.procMacro.enable": true, + "rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu", + "rust-analyzer.cargo.features": [ + "ppp", + ], + "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.cargoRunner": null, + "rust-analyzer.experimental.procAttrMacros": false, + "rust-analyzer.diagnostics.disabled": [ + "macro-error" + ], +} \ No newline at end of file diff --git a/examples/tokio-std-example/Cargo.toml b/examples/tokio-std-example/Cargo.toml new file mode 100644 index 0000000..e264ceb --- /dev/null +++ b/examples/tokio-std-example/Cargo.toml @@ -0,0 +1,81 @@ +[package] +name = "tokio-std-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1", features = ["full"] } +tokio-serial = "5.4.1" + +embassy-time = { version = "0.3", features = ["std", "generic-queue"] } +embassy-futures = { version = "0.1", features = ["log"] } +embassy-sync = { version = "0.5", features = ["log"] } + +embassy-net = { version = "0.4", features = [ + "log", + "packet-trace", + "proto-ipv4", + "medium-ip", + "tcp", + "udp", + "dns", +] } +embassy-net-ppp = { version = "0.1", features = ["log"] } +embassy-at-cmux = { path = "../../../embassy/embassy-at-cmux", features = [ + "log", +] } + +embedded-tls = { git = "https://github.com/drogue-iot/embedded-tls", rev = "f788e02", default-features = false, features = [ + "log", +] } + +embedded-io-adapters = { version = "0.6", features = ["tokio-1"] } +embedded-io-async = { version = "0.6" } +heapless = "0.8" + +env_logger = "0.11" +log = "0.4.21" + +static_cell = { version = "2.0" } + +atat = { version = "0.21.0", features = ["derive", "bytes", "log"] } +ublox-cellular-rs = { version = "0.4.0", path = "../..", features = [ + "lara-r6", + "log", +] } +reqwless = { git = "https://github.com/drogue-iot/reqwless", features = [ + "log", +] } +smoltcp = { version = "*", default-features = false, features = [ + "dns-max-server-count-4", +] } +rand_chacha = { version = "0.3", default-features = false } + +[features] +ppp = ["ublox-cellular-rs/ppp"] +internal-network-stack = ["ublox-cellular-rs/internal-network-stack"] + +[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-time = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +# embassy-sync = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +# embassy-net = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +# embassy-net-ppp = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +# embassy-futures = { git = "https://github.com/embassy-rs/embassy", branch = "main" } + +embassy-time = { path = "../../../embassy/embassy-time" } +embassy-sync = { path = "../../../embassy/embassy-sync" } +embassy-net = { path = "../../../embassy/embassy-net" } +embassy-net-ppp = { path = "../../../embassy/embassy-net-ppp" } +embassy-futures = { path = "../../../embassy/embassy-futures" } + +atat = { path = "../../../atat/atat" } + +[profile.dev] +opt-level = "s" + +[profile.release] +opt-level = "s" diff --git a/examples/tokio-std-example/rust-toolchain.toml b/examples/tokio-std-example/rust-toolchain.toml new file mode 100644 index 0000000..f39e558 --- /dev/null +++ b/examples/tokio-std-example/rust-toolchain.toml @@ -0,0 +1,8 @@ +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "nightly-2024-02-01" +components = [ "rust-src", "rustfmt", "llvm-tools" ] +targets = [ + "x86_64-unknown-linux-gnu", +] \ No newline at end of file diff --git a/examples/tokio-std-example/src/bin/embassy-internal-stack.rs b/examples/tokio-std-example/src/bin/embassy-internal-stack.rs new file mode 100644 index 0000000..3429b73 --- /dev/null +++ b/examples/tokio-std-example/src/bin/embassy-internal-stack.rs @@ -0,0 +1,188 @@ +#![no_std] +#![no_main] +#![allow(stable_features)] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_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 ublox_cellular::asynch::InternalRunner; +use ublox_cellular::asynch::Resources; +use {defmt_rtt as _, panic_probe as _}; + +use ublox_cellular::config::{Apn, CellularConfig}; + +use ublox_cellular::asynch::state::OperationState; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +const CMD_BUF_SIZE: usize = 128; +const INGRESS_BUF_SIZE: usize = 1024; +const URC_CAPACITY: 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::main] +async fn main(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(); + + 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]), + embassy_rp::uart::Config::default(), + ); + + 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); + + static RESOURCES: StaticCell< + Resources, CMD_BUF_SIZE, INGRESS_BUF_SIZE, URC_CAPACITY>, + > = StaticCell::new(); + + let (_net_device, mut control, runner) = ublox_cellular::asynch::new_internal( + uart_rx, + uart_tx, + RESOURCES.init(Resources::new()), + MyCelullarConfig { + reset_pin: Some(cell_nrst), + power_pin: Some(cell_pwr), + vint_pin: Some(cell_vint), + }, + ); + + // 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(cell_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 net_task(stack: &'static Stack>) -> ! { +// stack.run().await +// } + +#[embassy_executor::task] +async fn cell_task( + mut runner: InternalRunner< + 'static, + BufferedUartRx<'static, UART0>, + BufferedUartTx<'static, UART0>, + MyCelullarConfig, + INGRESS_BUF_SIZE, + URC_CAPACITY, + >, +) -> ! { + runner.run().await +} diff --git a/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp.rs b/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp.rs new file mode 100644 index 0000000..77e7ba6 --- /dev/null +++ b/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp.rs @@ -0,0 +1,198 @@ +#![cfg(feature = "ppp")] + +use atat::asynch::AtatClient as _; +use atat::asynch::SimpleClient; +use atat::AtatIngress as _; +use embassy_net::dns::DnsSocket; +use embassy_net::tcp::client::TcpClient; +use embassy_net::tcp::client::TcpClientState; +use embassy_net::tcp::TcpSocket; +use embassy_net::Stack; +use embassy_net::StackResources; + +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_time::Duration; +use embassy_time::Instant; +use embassy_time::Timer; +use embedded_mqtt::transport::embedded_tls::TlsNalTransport; +use embedded_mqtt::transport::embedded_tls::TlsState; +use embedded_mqtt::DomainBroker; +use embedded_tls::Aes128GcmSha256; +use embedded_tls::Certificate; +use embedded_tls::TlsConfig; +use embedded_tls::TlsConnection; +use embedded_tls::TlsContext; +use embedded_tls::UnsecureProvider; +use log::*; +use rand_chacha::rand_core::SeedableRng; +use rand_chacha::ChaCha8Rng; +use reqwless::headers::ContentType; +use reqwless::request::Request; +use reqwless::request::RequestBuilder as _; +use reqwless::response::Response; +use static_cell::StaticCell; +use tokio_serial::SerialPort; +use tokio_serial::SerialPortBuilderExt; +use ublox_cellular::asynch::state::OperationState; +use ublox_cellular::asynch::Resources; + +use ublox_cellular::command::control::SetDataRate; +use ublox_cellular::command::control::SetFlowControl; +use ublox_cellular::command::general::GetModelId; +use ublox_cellular::command::ipc::SetMultiplexing; +use ublox_cellular::command::psn::DeactivatePDPContext; +use ublox_cellular::command::psn::EnterPPP; +use ublox_cellular::command::Urc; +use ublox_cellular::command::AT; +use ublox_cellular::config::NoPin; +use ublox_cellular::config::{Apn, CellularConfig}; + +const CMD_BUF_SIZE: usize = 128; +const INGRESS_BUF_SIZE: usize = 512; +const URC_CAPACITY: usize = 2; + +struct MyCelullarConfig; + +impl<'a> CellularConfig<'a> for MyCelullarConfig { + type ResetPin = NoPin; + type PowerPin = NoPin; + type VintPin = NoPin; + + const FLOW_CONTROL: bool = true; + + const APN: Apn<'a> = Apn::Given { + name: "em", + username: None, + password: None, + }; + + const PPP_CONFIG: embassy_net_ppp::Config<'a> = embassy_net_ppp::Config { + username: b"", + password: b"", + }; +} + +const TTY: &str = "/dev/ttyUSB0"; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + info!("HELLO"); + + let mut ppp_iface = tokio_serial::new(TTY, 115200).open_native_async()?; + ppp_iface + .set_flow_control(tokio_serial::FlowControl::Hardware) + .unwrap(); + + static RESOURCES: StaticCell> = + StaticCell::new(); + + let (net_device, mut cell_control, mut runner) = + ublox_cellular::asynch::new_ppp(RESOURCES.init(Resources::new()), MyCelullarConfig); + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static STACK_RESOURCES: StaticCell> = StaticCell::new(); + + let stack = &*STACK.init(Stack::new( + net_device, + embassy_net::Config::default(), + STACK_RESOURCES.init(StackResources::new()), + seed, + )); + + let http_fut = async { + stack.wait_config_up().await; + + info!("We have network!"); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + let hostname = "ecdsa-test.germancoding.com"; + + let mut remote = stack + .dns_query(hostname, smoltcp::wire::DnsQueryType::A) + .await + .unwrap(); + let remote_endpoint = (remote.pop().unwrap(), 443); + info!("connecting to {:?}...", remote_endpoint); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + warn!("connect error: {:?}", e); + return; + } + info!("TCP connected!"); + + let mut read_record_buffer = [0; 16384]; + let mut write_record_buffer = [0; 16384]; + let config = TlsConfig::new().with_server_name(hostname); + let mut tls = TlsConnection::new(socket, &mut read_record_buffer, &mut write_record_buffer); + + tls.open(TlsContext::new( + &config, + UnsecureProvider::new::(ChaCha8Rng::seed_from_u64(seed)), + )) + .await + .expect("error establishing TLS connection"); + + info!("TLS Established!"); + + let request = Request::get("/") + .host(hostname) + .content_type(ContentType::TextPlain) + .build(); + request.write(&mut tls).await.unwrap(); + + let mut rx_buf = [0; 4096]; + let response = Response::read(&mut tls, reqwless::request::Method::GET, &mut rx_buf) + .await + .unwrap(); + + let mut buf = vec![0; 16384]; + let len = response + .body() + .reader() + .read_to_end(&mut buf) + .await + .unwrap(); + info!("{:?}", core::str::from_utf8(&buf[..len])); + }; + + let (rx, tx) = tokio::io::split(ppp_iface); + let rx = embedded_io_adapters::tokio_1::FromTokio::new(tokio::io::BufReader::new(rx)); + let tx = embedded_io_adapters::tokio_1::FromTokio::new(tx); + embassy_futures::join::join3( + stack.run(), + runner.run(rx, tx, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = heapless::Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); + } + let config = embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new( + embassy_net::Ipv4Address::from_bytes(&addr.0), + 0, + ), + gateway: None, + dns_servers, + }); + + stack.set_config_v4(config); + }), + http_fut, + ) + .await; + + unreachable!(); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index cab8465..696df2c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] channel = "1.75" -components = [ "rust-src", "rustfmt", "llvm-tools-preview", "clippy" ] +components = [ "rust-src", "rustfmt", "llvm-tools", "clippy" ] targets = [ "x86_64-unknown-linux-gnu", "thumbv7em-none-eabihf" diff --git a/src/asynch/control.rs b/src/asynch/control.rs index 98c52e7..562b960 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -37,6 +37,13 @@ impl<'a, AT: AtatClient> Control<'a, AT> { self.state_ch.set_desired_state(ps).await; } + pub async fn wait_for_desired_state( + &mut self, + ps: OperationState, + ) -> Result { + self.state_ch.wait_for_desired_state(ps).await + } + pub async fn get_signal_quality( &mut self, ) -> Result { diff --git a/src/asynch/internal_stack.rs b/src/asynch/internal_stack.rs index 18a4b87..23f8609 100644 --- a/src/asynch/internal_stack.rs +++ b/src/asynch/internal_stack.rs @@ -10,7 +10,11 @@ use crate::{command::Urc, config::CellularConfig}; pub use super::resources::UbxResources as Resources; -use super::{control::Control, runner::Runner, state, AtHandle}; +use super::{ + control::Control, + runner::{Runner, URC_SUBSCRIBERS}, + state, AtHandle, +}; pub fn new_internal< 'a, @@ -87,7 +91,14 @@ pub struct InternalRunner< const URC_CAPACITY: usize, > { pub cellular_runner: Runner<'a, Client<'a, W, INGRESS_BUF_SIZE>, C, URC_CAPACITY>, - pub ingress: atat::Ingress<'a, atat::AtDigester, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, + pub ingress: atat::Ingress< + 'a, + atat::AtDigester, + Urc, + INGRESS_BUF_SIZE, + URC_CAPACITY, + URC_SUBSCRIBERS, + >, pub reader: R, } diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index aede837..d0cd66a 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -13,6 +13,16 @@ mod ppp; #[cfg(feature = "ppp")] pub use ppp::{new_ppp, PPPRunner, Resources}; +#[cfg(feature = "ppp")] +pub type Control<'d, const INGRESS_BUF_SIZE: usize> = control::Control< + 'd, + atat::asynch::Client< + 'd, + embassy_at_cmux::ChannelTx<'d, { ppp::CMUX_CHANNEL_SIZE }>, + INGRESS_BUF_SIZE, + >, +>; + use atat::asynch::AtatClient; use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; diff --git a/src/asynch/ppp.rs b/src/asynch/ppp.rs index 02828b0..affb3c3 100644 --- a/src/asynch/ppp.rs +++ b/src/asynch/ppp.rs @@ -2,16 +2,12 @@ use core::mem::MaybeUninit; use crate::{ command::{ - control::{types::FlowControl, SetFlowControl}, ipc::SetMultiplexing, - mobile_control::{ - types::{Functionality, ResetMode, TerminationErrorMode}, - SetModuleFunctionality, SetReportMobileTerminationError, - }, - psn::{DeactivatePDPContext, EnterPPP, SetPDPContextDefinition}, + psn::{DeactivatePDPContext, EnterPPP}, Urc, }, - config::{Apn, CellularConfig}, + config::CellularConfig, + module_timing::boot_time, }; use atat::{ asynch::{AtatClient, Client, SimpleClient}, @@ -19,17 +15,25 @@ use atat::{ }; use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; use embassy_time::{Duration, Instant, Timer}; -use embedded_io::Error; -use embedded_io_async::{BufRead, Read, Write}; +use embedded_io_async::{BufRead, Error, ErrorKind, Read, Write}; -use super::{control::Control, resources::UbxResources, runner::Runner, state, AtHandle}; +use super::{ + control::Control, + resources::UbxResources, + runner::{Runner, URC_SUBSCRIBERS}, + state, AtHandle, +}; + +pub const CMUX_MAX_FRAME_SIZE: usize = 512; +pub const CMUX_CHANNEL_SIZE: usize = CMUX_MAX_FRAME_SIZE * 2; +pub const CMUX_CHANNELS: usize = 2; // AT Control + PPP data pub type Resources< const CMD_BUF_SIZE: usize, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize, > = UbxResources< - embassy_at_cmux::ChannelTx<'static, 256>, + embassy_at_cmux::ChannelTx<'static, CMUX_CHANNEL_SIZE>, CMD_BUF_SIZE, INGRESS_BUF_SIZE, URC_CAPACITY, @@ -46,25 +50,32 @@ pub fn new_ppp< config: C, ) -> ( embassy_net_ppp::Device<'a>, - Control<'a, Client<'a, embassy_at_cmux::ChannelTx<'a, 256>, INGRESS_BUF_SIZE>>, + Control<'a, Client<'a, embassy_at_cmux::ChannelTx<'a, CMUX_CHANNEL_SIZE>, INGRESS_BUF_SIZE>>, PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY>, ) { let ch_runner = state::new_ppp(&mut resources.ch); let state_ch = ch_runner.state_runner(); - let (mux_runner, [ppp_channel, control_channel]) = resources.mux.start(); + let (mux_runner, [control_channel, ppp_channel]) = resources.mux.start(); let (control_rx, control_tx, _) = control_channel.split(); // safety: this is a self-referential struct, however: // - it can't move while the `'a` borrow is active. // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. let at_client_uninit: *mut MaybeUninit< - Mutex, INGRESS_BUF_SIZE>>, + Mutex< + NoopRawMutex, + Client<'a, embassy_at_cmux::ChannelTx<'a, CMUX_CHANNEL_SIZE>, INGRESS_BUF_SIZE>, + >, > = (&mut resources.at_client as *mut MaybeUninit< Mutex< NoopRawMutex, - Client<'static, embassy_at_cmux::ChannelTx<'static, 256>, INGRESS_BUF_SIZE>, + Client< + 'static, + embassy_at_cmux::ChannelTx<'static, CMUX_CHANNEL_SIZE>, + INGRESS_BUF_SIZE, + >, >, >) .cast(); @@ -97,6 +108,7 @@ pub fn new_ppp< let (net_device, ppp_runner) = embassy_net_ppp::new(&mut resources.ppp_state); let runner = PPPRunner { + powered: false, ppp_runner, cellular_runner, ingress, @@ -114,87 +126,69 @@ pub struct PPPRunner< const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize, > { + pub powered: bool, pub ppp_runner: embassy_net_ppp::Runner<'a>, pub cellular_runner: Runner< 'a, - Client<'a, embassy_at_cmux::ChannelTx<'a, 256>, INGRESS_BUF_SIZE>, + Client<'a, embassy_at_cmux::ChannelTx<'a, CMUX_CHANNEL_SIZE>, INGRESS_BUF_SIZE>, C, URC_CAPACITY, >, - pub ingress: atat::Ingress<'a, atat::AtDigester, Urc, INGRESS_BUF_SIZE, URC_CAPACITY, 2>, - pub ppp_channel: embassy_at_cmux::Channel<'a, 256>, - pub control_rx: embassy_at_cmux::ChannelRx<'a, 256>, - pub mux_runner: embassy_at_cmux::Runner<'a, 2, 256>, + pub ingress: atat::Ingress< + 'a, + atat::AtDigester, + Urc, + INGRESS_BUF_SIZE, + URC_CAPACITY, + URC_SUBSCRIBERS, + >, + pub ppp_channel: embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, + pub control_rx: embassy_at_cmux::ChannelRx<'a, CMUX_CHANNEL_SIZE>, + pub mux_runner: embassy_at_cmux::Runner<'a, CMUX_CHANNELS, CMUX_CHANNEL_SIZE>, } impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY> { - async fn configure_apn(at_client: &mut A) -> Result<(), atat::Error> { - at_client - .send(&SetModuleFunctionality { - fun: Functionality::Minimum, - rst: Some(ResetMode::DontReset), - }) - .await?; - - let apn = match C::APN { - Apn::None => "", - Apn::Given { name, .. } => name, - }; - - at_client - .send(&SetPDPContextDefinition { - cid: C::CONTEXT_ID, - pdp_type: "IP", - apn, - }) - .await?; - - at_client - .send(&SetModuleFunctionality { - fun: Functionality::Full, - rst: Some(ResetMode::DontReset), - }) - .await?; - Ok(()) - } - - async fn init(rx: &mut R, tx: &mut W) -> Result<(), atat::Error> { + async fn init_multiplexer( + rx: &mut R, + tx: &mut W, + ) -> Result<(), crate::error::Error> { let mut buf = [0u8; 64]; + let mut interface = ReadWriteAdapter(rx, tx); let mut at_client = SimpleClient::new( - ReadWriteAdapter(rx, tx), + &mut interface, atat::AtDigester::::new(), &mut buf, atat::Config::default(), ); - at_client - .send(&SetReportMobileTerminationError { - n: TerminationErrorMode::Enabled, - }) - .await?; - - at_client - .send(&SetFlowControl { - value: FlowControl::RtsCts, - }) - .await?; + super::runner::init_at(&mut at_client, C::FLOW_CONTROL).await?; at_client .send(&SetMultiplexing { mode: 0, - subset: None, - port_speed: None, - n1: None, - t1: None, - n2: None, - t2: None, - t3: None, - k: None, + subset: Some(0), + port_speed: Some(5), + n1: Some(CMUX_MAX_FRAME_SIZE as u16), + t1: None, //Some(10), + n2: None, //Some(3), + t2: None, //Some(30), + t3: None, //Some(10), + k: None, //Some(2), }) .await?; + drop(at_client); + + // 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; + } + }) + .await; + Ok(()) } @@ -202,32 +196,36 @@ impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACIT &mut self, mut rx: R, mut tx: W, - stack: &embassy_net::Stack>, + on_ipv4_up: impl FnMut(embassy_net_ppp::Ipv4Status) + Copy, ) -> ! { loop { - // Reset modem - // if self.cellular_runner.init().await.is_err() { - // Timer::after(Duration::from_secs(5)).await; - // continue; - // } - - // Timer::after(Duration::from_secs(5)).await; + if !self.powered { + // Reset modem + self.cellular_runner + .change_state_to_desired_state(state::OperationState::PowerDown) + .await; + self.cellular_runner + .change_state_to_desired_state(state::OperationState::PowerUp) + .await; + + Timer::after(boot_time()).await; + } // Do AT init and enter CMUX mode using interface - if Self::init(&mut rx, &mut tx).await.is_err() { + if Self::init_multiplexer(&mut rx, &mut tx).await.is_err() { Timer::after(Duration::from_secs(5)).await; continue; }; - Timer::after(Duration::from_secs(1)).await; + self.cellular_runner + .change_state_to_desired_state(state::OperationState::DataEstablished) + .await; let ppp_fut = async { let mut fails = 0; let mut last_start = None; loop { - Timer::after(Duration::from_secs(15)).await; - if let Some(last_start) = last_start { Timer::at(last_start + Duration::from_secs(10)).await; // Do not attempt to start too fast. @@ -245,63 +243,37 @@ impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACIT } last_start = Some(Instant::now()); - let mut buf = [0u8; 64]; - let mut at_client = SimpleClient::new( - &mut self.ppp_channel, - atat::AtDigester::::new(), - &mut buf, - atat::Config::default(), - ); - - if let Err(e) = Self::configure_apn(&mut at_client).await { - warn!("modem: configure failed {:?}", e); - continue; - } - - Timer::after(Duration::from_secs(2)).await; - - // hangup just in case a call was already in progress. - // Ignore errors because this fails if it wasn't. - let _ = at_client.send(&DeactivatePDPContext).await; - - // Send AT command to enter PPP mode - let res = at_client.send(&EnterPPP { cid: C::CONTEXT_ID }).await; + { + let mut buf = [0u8; 16]; // Enough room for "ATD*99***1#\r\n" + let mut at_client = SimpleClient::new( + &mut self.ppp_channel, + atat::AtDigester::::new(), + &mut buf, + atat::Config::default(), + ); + + // hangup just in case a call was already in progress. + // Ignore errors because this fails if it wasn't. + let _ = at_client.send(&DeactivatePDPContext).await; + + // Send AT command to enter PPP mode + let res = at_client.send(&EnterPPP { cid: C::CONTEXT_ID }).await; + + if let Err(e) = res { + warn!("ppp dial failed {:?}", e); + continue; + } - if let Err(e) = res { - warn!("ppp dial failed {:?}", e); - continue; + Timer::after(Duration::from_millis(100)).await; } - drop(at_client); - // Check for CTS low (bit 2) - self.ppp_channel.set_hangup_detection(0x04, 0x00); + // self.ppp_channel.set_hangup_detection(0x04, 0x00); info!("RUNNING PPP"); let res = self .ppp_runner - .run(&mut self.ppp_channel, C::PPP_CONFIG, |ipv4| { - let Some(addr) = ipv4.address else { - warn!("PPP did not provide an IP address."); - return; - }; - let mut dns_servers = heapless::Vec::new(); - for s in ipv4.dns_servers.iter().flatten() { - let _ = - dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); - } - let config = - embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { - address: embassy_net::Ipv4Cidr::new( - embassy_net::Ipv4Address::from_bytes(&addr.0), - 0, - ), - gateway: None, - dns_servers, - }); - - stack.set_config_v4(config); - }) + .run(&mut self.ppp_channel, C::PPP_CONFIG, on_ipv4_up) .await; info!("ppp failed: {:?}", res); @@ -309,27 +281,25 @@ impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACIT self.ppp_channel.clear_hangup_detection(); // escape back to data mode. - self.ppp_channel.set_lines(0x44); + self.ppp_channel + .set_lines(embassy_at_cmux::Control::from_bits(0x44 << 1), None); Timer::after(Duration::from_millis(100)).await; - self.ppp_channel.set_lines(0x46); + self.ppp_channel + .set_lines(embassy_at_cmux::Control::from_bits(0x46 << 1), None); } }; - let ingress_fut = async { - self.ingress.read_from(&mut self.control_rx).await; - }; - - let mux_fut = async { - self.mux_runner.run(&mut rx, &mut tx).await; - }; + self.ingress.clear(); embassy_futures::select::select4( + self.mux_runner.run(&mut rx, &mut tx, CMUX_MAX_FRAME_SIZE), ppp_fut, - ingress_fut, + self.ingress.read_from(&mut self.control_rx), self.cellular_runner.run(), - mux_fut, ) .await; + + self.powered = false; } } } @@ -337,7 +307,7 @@ impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACIT pub struct ReadWriteAdapter(pub R, pub W); impl embedded_io_async::ErrorType for ReadWriteAdapter { - type Error = embedded_io::ErrorKind; + type Error = ErrorKind; } impl Read for ReadWriteAdapter { diff --git a/src/asynch/resources.rs b/src/asynch/resources.rs index 5b2d7a8..3698c74 100644 --- a/src/asynch/resources.rs +++ b/src/asynch/resources.rs @@ -6,7 +6,7 @@ use embedded_io_async::Write; use crate::command::Urc; -use super::state; +use super::{runner::URC_SUBSCRIBERS, state}; pub struct UbxResources< W: Write, @@ -17,7 +17,7 @@ pub struct UbxResources< pub(crate) ch: state::State, pub(crate) res_slot: ResponseSlot, - pub(crate) urc_channel: UrcChannel, + pub(crate) urc_channel: UrcChannel, pub(crate) cmd_buf: [u8; CMD_BUF_SIZE], pub(crate) ingress_buf: [u8; INGRESS_BUF_SIZE], @@ -27,7 +27,8 @@ pub struct UbxResources< pub(crate) ppp_state: embassy_net_ppp::State<2, 2>, #[cfg(feature = "ppp")] - pub(crate) mux: embassy_at_cmux::Mux<2, 256>, + pub(crate) mux: + embassy_at_cmux::Mux<{ super::ppp::CMUX_CHANNELS }, { super::ppp::CMUX_CHANNEL_SIZE }>, } impl< diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 1d979dc..df4f7d3 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -1,3 +1,5 @@ +use crate::command::control::types::Echo; +use crate::command::control::SetEcho; use crate::command::psn::GetGPRSAttached; use crate::command::psn::GetPDPContextState; use crate::command::psn::SetPDPContextState; @@ -34,6 +36,12 @@ use embassy_futures::select::Either; use super::AtHandle; +#[cfg(feature = "ppp")] +pub(crate) const URC_SUBSCRIBERS: usize = 2; + +#[cfg(feature = "internal-network-stack")] +pub(crate) const URC_SUBSCRIBERS: usize = 2; + /// Background runner for the Ublox Module. /// /// You must call `.run()` in a background task for the Ublox Module to operate. @@ -41,7 +49,7 @@ pub struct Runner<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: ch: state::Runner<'d>, at: AtHandle<'d, AT>, config: C, - urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, 2>, + urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, URC_SUBSCRIBERS>, } impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> @@ -51,7 +59,7 @@ impl<'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>, + urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, URC_SUBSCRIBERS>, ) -> Self { Self { ch, @@ -71,7 +79,6 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> self.power_up().await?; }; self.reset().await?; - // self.is_alive().await?; Ok(()) } @@ -119,6 +126,13 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } } + pub async fn wait_for_desired_state( + &mut self, + ps: OperationState, + ) -> Result { + self.ch.state_runner().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() { @@ -127,6 +141,10 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> pin.set_high().map_err(|_| Error::IoPin)?; self.ch.set_power_state(OperationState::PowerDown); debug!("Powered down"); + + // FIXME: Is this needed? + Timer::after(Duration::from_millis(1000)).await; + Ok(()) } else { warn!("No power pin configured"); @@ -137,103 +155,6 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } } - pub async fn init_at(&mut self) -> Result<(), Error> { - if !self.is_alive().await? { - return Err(Error::PoweredDown); - } - - // Extended errors on - self.at - .send(&SetReportMobileTerminationError { - n: TerminationErrorMode::Enabled, - }) - .await?; - - // Select SIM - self.at - .send(&SetGpioConfiguration { - gpio_id: 25, - gpio_mode: GpioMode::Output(GpioOutValue::High), - }) - .await?; - - #[cfg(any(feature = "lara-r6"))] - self.at - .send(&SetGpioConfiguration { - gpio_id: 42, - gpio_mode: GpioMode::Input(GpioInPull::NoPull), - }) - .await?; - - let _model_id = self.at.send(&GetModelId).await?; - - // self.at.send( - // &IdentificationInformation { - // n: 9 - // }, - // ).await?; - - self.at.send(&GetFirmwareVersion).await?; - - self.select_sim_card().await?; - - let ccid = self.at.send(&GetCCID).await?; - info!("CCID: {}", ccid.ccid); - - // DCD circuit (109) changes in accordance with the carrier - self.at - .send(&SetCircuit109Behaviour { - value: Circuit109Behaviour::ChangesWithCarrier, - }) - .await?; - - // Ignore changes to DTR - self.at - .send(&SetCircuit108Behaviour { - value: Circuit108Behaviour::Ignore, - }) - .await?; - - // Switch off UART power saving until it is integrated into this API - self.at - .send(&SetPowerSavingControl { - mode: PowerSavingMode::Disabled, - timeout: None, - }) - .await?; - - #[cfg(feature = "internal-network-stack")] - if C::HEX_MODE { - self.at - .send(&crate::command::ip_transport_layer::SetHexMode { - hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Enabled, - }) - .await?; - } else { - self.at - .send(&crate::command::ip_transport_layer::SetHexMode { - hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Disabled, - }) - .await?; - } - - // Tell module whether we support flow control - // FIXME: Use AT+IFC=2,2 instead of AT&K here - if C::FLOW_CONTROL { - self.at - .send(&SetFlowControl { - value: FlowControl::RtsCts, - }) - .await?; - } else { - self.at - .send(&SetFlowControl { - value: FlowControl::Disabled, - }) - .await?; - } - Ok(()) - } /// Initializes the network only valid after `init_at`. /// /// # Errors @@ -331,42 +252,6 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> Ok(()) } - pub async fn select_sim_card(&mut self) -> Result<(), Error> { - for _ in 0..2 { - match self.at.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 - .send(&SetModuleFunctionality { - fun: Functionality::Minimum, - // SARA-R5 This parameter can be used only when is 1, 4 or 19 - #[cfg(feature = "sara-r5")] - rst: None, - #[cfg(not(feature = "sara-r5"))] - rst: Some(ResetMode::DontReset), - }) - .await?; - self.at - .send(&SetModuleFunctionality { - fun: Functionality::Full, - rst: Some(ResetMode::DontReset), - }) - .await?; - - Ok(()) - } - /// Reset the module by driving it's `RESET_N` pin low for 50 ms /// /// **NOTE** This function will reset NVM settings! @@ -460,13 +345,10 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> pub async fn run(&mut self) -> ! { match self.has_power().await.ok() { - Some(false) => { - self.ch.set_power_state(OperationState::PowerDown); - } Some(true) => { self.ch.set_power_state(OperationState::PowerUp); } - None => { + Some(false) | None => { self.ch.set_power_state(OperationState::PowerDown); } } @@ -493,7 +375,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } } - async fn change_state_to_desired_state( + pub async fn change_state_to_desired_state( &mut self, desired_state: OperationState, ) -> Result<(), Error> { @@ -527,26 +409,9 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> 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); - } - } - } Ok(OperationState::Initialized) => { #[cfg(not(feature = "ppp"))] - match self.init_at().await { + match init_at(&mut self.at, C::FLOW_CONTROL).await { Ok(_) => { self.ch.set_power_state(OperationState::Initialized); } @@ -585,7 +450,6 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> return Err(err); } }, - #[cfg(not(feature = "ppp"))] Ok(OperationState::DataEstablished) => { match self.connect(C::APN, C::PROFILE_ID, C::CONTEXT_ID).await { Ok(_) => { @@ -631,7 +495,6 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> } #[allow(unused_variables)] - #[cfg(not(feature = "ppp"))] async fn connect( &mut self, apn_info: crate::config::Apn<'_>, @@ -668,7 +531,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> // 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)?; + let GPRSAttached { state } = self.at.send(&GetGPRSAttached).await?; if state == GPRSAttachedState::Attached { return Ok(true); @@ -897,3 +760,142 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> return Err(Error::ContextActivationTimeout); } } + +pub(crate) async fn init_at( + at_client: &mut A, + enable_flow_control: bool, +) -> Result<(), Error> { + // Allow auto bauding to kick in + embassy_time::with_timeout(boot_time() * 2, async { + loop { + if let Ok(alive) = at_client.send(&AT).await { + break alive; + } + Timer::after(Duration::from_millis(100)).await; + } + }) + .await + .map_err(|_| Error::PoweredDown)?; + + // Extended errors on + at_client + .send(&SetReportMobileTerminationError { + n: TerminationErrorMode::Enabled, + }) + .await?; + + // Echo off + at_client.send(&SetEcho { enabled: Echo::Off }).await?; + + // Select SIM + at_client + .send(&SetGpioConfiguration { + gpio_id: 25, + gpio_mode: GpioMode::Output(GpioOutValue::High), + }) + .await?; + + #[cfg(any(feature = "lara-r6"))] + at_client + .send(&SetGpioConfiguration { + gpio_id: 42, + gpio_mode: GpioMode::Input(GpioInPull::NoPull), + }) + .await?; + + let _model_id = at_client.send(&GetModelId).await?; + + // at_client.send( + // &IdentificationInformation { + // n: 9 + // }, + // ).await?; + + at_client.send(&GetFirmwareVersion).await?; + + select_sim_card(at_client).await?; + + let ccid = at_client.send(&GetCCID).await?; + info!("CCID: {}", ccid.ccid); + + // DCD circuit (109) changes in accordance with the carrier + at_client + .send(&SetCircuit109Behaviour { + value: Circuit109Behaviour::ChangesWithCarrier, + }) + .await?; + + // Ignore changes to DTR + at_client + .send(&SetCircuit108Behaviour { + value: Circuit108Behaviour::Ignore, + }) + .await?; + + // Switch off UART power saving until it is integrated into this API + at_client + .send(&SetPowerSavingControl { + mode: PowerSavingMode::Disabled, + timeout: None, + }) + .await?; + + #[cfg(feature = "internal-network-stack")] + if C::HEX_MODE { + at_client + .send(&crate::command::ip_transport_layer::SetHexMode { + hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Enabled, + }) + .await?; + } else { + at_client + .send(&crate::command::ip_transport_layer::SetHexMode { + hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Disabled, + }) + .await?; + } + + // Tell module whether we support flow control + if enable_flow_control { + at_client.send(&SetFlowControl).await?; + } else { + at_client.send(&SetFlowControl).await?; + } + Ok(()) +} + +pub(crate) async fn select_sim_card(at_client: &mut A) -> Result<(), Error> { + for _ in 0..2 { + match 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. + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Minimum, + // SARA-R5 This parameter can be used only when is 1, 4 or 19 + #[cfg(feature = "sara-r5")] + rst: None, + #[cfg(not(feature = "sara-r5"))] + rst: Some(ResetMode::DontReset), + }) + .await?; + at_client + .send(&SetModuleFunctionality { + fun: Functionality::Full, + rst: Some(ResetMode::DontReset), + }) + .await?; + + Ok(()) +} diff --git a/src/asynch/state.rs b/src/asynch/state.rs index dd1995b..e02ecd6 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -23,15 +23,13 @@ pub enum LinkState { } /// If the celular modem is up and responding to AT. -#[derive(PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OperationState { PowerDown = 0, PowerUp, - Alive, Initialized, Connected, - #[cfg(not(feature = "ppp"))] DataEstablished, } @@ -40,11 +38,9 @@ impl TryFrom for OperationState { match state { 0 => Ok(OperationState::PowerDown), 1 => Ok(OperationState::PowerUp), - 2 => Ok(OperationState::Alive), - 3 => Ok(OperationState::Initialized), - 4 => Ok(OperationState::Connected), - #[cfg(not(feature = "ppp"))] - 5 => Ok(OperationState::DataEstablished), + 2 => Ok(OperationState::Initialized), + 3 => Ok(OperationState::Connected), + 4 => Ok(OperationState::DataEstablished), _ => Err(()), } } diff --git a/src/asynch/ublox_stack/tcp.rs b/src/asynch/ublox_stack/tcp.rs index 52c9cc3..b01fff1 100644 --- a/src/asynch/ublox_stack/tcp.rs +++ b/src/asynch/ublox_stack/tcp.rs @@ -292,23 +292,22 @@ impl<'d> TcpIo<'d> { } } -// #[cfg(feature = "nightly")] mod embedded_io_impls { use super::*; - impl embedded_io::Error for ConnectError { - fn kind(&self) -> embedded_io::ErrorKind { - embedded_io::ErrorKind::Other + impl embedded_io_async::Error for ConnectError { + fn kind(&self) -> embedded_io_async::ErrorKind { + embedded_io_async::ErrorKind::Other } } - impl embedded_io::Error for Error { - fn kind(&self) -> embedded_io::ErrorKind { - embedded_io::ErrorKind::Other + impl embedded_io_async::Error for Error { + fn kind(&self) -> embedded_io_async::ErrorKind { + embedded_io_async::ErrorKind::Other } } - impl<'d> embedded_io::ErrorType for TcpSocket<'d> { + impl<'d> embedded_io_async::ErrorType for TcpSocket<'d> { type Error = Error; } @@ -328,7 +327,7 @@ mod embedded_io_impls { } } - impl<'d> embedded_io::ErrorType for TcpReader<'d> { + impl<'d> embedded_io_async::ErrorType for TcpReader<'d> { type Error = Error; } @@ -338,7 +337,7 @@ mod embedded_io_impls { } } - impl<'d> embedded_io::ErrorType for TcpWriter<'d> { + impl<'d> embedded_io_async::ErrorType for TcpWriter<'d> { type Error = Error; } @@ -459,7 +458,7 @@ pub mod client { } } - impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::ErrorType + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::ErrorType for TcpConnection<'d, N, TX_SZ, RX_SZ> { type Error = Error; diff --git a/src/command/control/mod.rs b/src/command/control/mod.rs index 6d75312..6a3bef9 100644 --- a/src/command/control/mod.rs +++ b/src/command/control/mod.rs @@ -2,13 +2,15 @@ //! These commands, unless specifically stated, do not implement set syntax using "=", read ("?"), or test ("=?"). //! If such commands are used, the "+CME ERROR: unknown" or "+CME ERROR: 100" error result code is provided //! (depending on the +CMEE AT command setting). -// pub mod responses; +pub mod responses; pub mod types; -use atat::atat_derive::AtatCmd; -use types::{BaudRate, Circuit108Behaviour, Circuit109Behaviour, FlowControl, SoftwareFlowControl}; - use super::NoResponse; +use atat::atat_derive::AtatCmd; +use responses::DataRate; +use types::{ + BaudRate, Circuit108Behaviour, Circuit109Behaviour, Echo, FlowControl, SoftwareFlowControl, +}; /// 15.2 Circuit 109 behavior &C /// @@ -44,11 +46,8 @@ pub struct SetCircuit108Behaviour { /// - HW flow control also referred with RTS / CTS flow control /// - SW flow control also referred with XON / XOFF flow control #[derive(Clone, AtatCmd)] -#[at_cmd("&K", NoResponse, value_sep = false)] -pub struct SetFlowControl { - #[at_arg(position = 0)] - pub value: FlowControl, -} +#[at_cmd("+IFC=2,2", NoResponse, value_sep = false)] +pub struct SetFlowControl; /// 15.8 Set flow control \Q /// @@ -81,6 +80,14 @@ pub struct SetDataRate { pub rate: BaudRate, } +/// 15.9 UART data rate configuration +IPR +/// +/// Specifies the data rate at which the DCE accepts commands on the UART +/// interface. The full range of data rates depends on HW or other criteria. +#[derive(Clone, AtatCmd)] +#[at_cmd("+IPR?", DataRate)] +pub struct GetDataRate; + /// 15.25 Set to factory defined configuration &F /// /// Resets the current profile to factory-programmed setting. Other NVM @@ -92,3 +99,18 @@ pub struct SetDataRate { #[derive(Clone, AtatCmd)] #[at_cmd("&F", NoResponse)] pub struct FactoryResetConfig; + +/// 15.25 Set to factory defined configuration &F +/// +/// Resets the current profile to factory-programmed setting. Other NVM +/// settings, not included in the profiles, are not affected. In case of +/// success, the response is issued using the configuration of the result codes +/// format (Q, V, S3, S4 AT commands) loaded from the factory-programmed +/// profile. The other DCE settings are applied after the response has been +/// sent. +#[derive(Clone, AtatCmd)] +#[at_cmd("E", NoResponse, value_sep = false)] +pub struct SetEcho { + #[at_arg(position = 0)] + pub enabled: Echo, +} diff --git a/src/command/control/responses.rs b/src/command/control/responses.rs new file mode 100644 index 0000000..0c16269 --- /dev/null +++ b/src/command/control/responses.rs @@ -0,0 +1,10 @@ +//! Responses for Control Commands +use super::types::BaudRate; +use atat::atat_derive::AtatResp; + +#[derive(Clone, Debug, PartialEq, Eq, AtatResp)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataRate { + #[at_arg(position = 0)] + pub rate: BaudRate, +} diff --git a/src/command/control/types.rs b/src/command/control/types.rs index a035b83..f3dc135 100644 --- a/src/command/control/types.rs +++ b/src/command/control/types.rs @@ -13,6 +13,14 @@ pub enum Circuit109Behaviour { ChangesWithCarrier = 1, } +#[derive(Clone, PartialEq, Eq, AtatEnum)] +pub enum Echo { + /// 0: Echo off + Off = 0, + /// 1 (default value and factory-programmed value): Echo on + On = 1, +} + /// Indicates the behavior of circuit 108 #[derive(Clone, PartialEq, Eq, AtatEnum)] pub enum Circuit108Behaviour { @@ -51,7 +59,8 @@ pub enum SoftwareFlowControl { Circuit105_106 = 3, } -#[derive(Clone, PartialEq, Eq, AtatEnum)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, AtatEnum)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[at_enum(u32)] pub enum BaudRate { #[cfg(any( @@ -100,6 +109,7 @@ pub enum BaudRate { feature = "sara-u2", feature = "toby-r2", feature = "lara-r2", + feature = "lara-r6", feature = "toby-l4", ))] B230400 = 230_400, @@ -111,6 +121,7 @@ pub enum BaudRate { feature = "sara-u2", feature = "toby-r2", feature = "lara-r2", + feature = "lara-r6", feature = "toby-l4", ))] B460800 = 460_800, @@ -122,10 +133,11 @@ pub enum BaudRate { feature = "sara-u2", feature = "toby-r2", feature = "lara-r2", + feature = "lara-r6", feature = "toby-l4", ))] B921600 = 921_600, - #[cfg(any(feature = "toby-r2", feature = "lara-r2",))] + #[cfg(any(feature = "toby-r2", feature = "lara-r2", feature = "lara-r6"))] B3000000 = 3_000_000, #[cfg(any(feature = "toby-r2", feature = "lara-r2",))] B3250000 = 3_250_000, diff --git a/src/config.rs b/src/config.rs index e6b8d0b..2e5f41c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -73,9 +73,6 @@ pub trait CellularConfig<'a> { type PowerPin: OutputPin; type VintPin: InputPin; - // const INGRESS_BUF_SIZE: usize; - // const URC_CAPACITY: usize; - const FLOW_CONTROL: bool = false; #[cfg(feature = "internal-network-stack")] @@ -84,7 +81,6 @@ pub trait CellularConfig<'a> { const OPERATOR_FORMAT: OperatorFormat = OperatorFormat::Long; const PROFILE_ID: ProfileId = ProfileId(1); - // #[cfg(not(feature = "upsd-context-activation"))] const CONTEXT_ID: ContextId = ContextId(1); const APN: Apn<'a> = Apn::None; @@ -92,9 +88,15 @@ pub trait CellularConfig<'a> { #[cfg(feature = "ppp")] const PPP_CONFIG: embassy_net_ppp::Config<'a>; - 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>; + fn reset_pin(&mut self) -> Option<&mut Self::ResetPin> { + None + } + fn power_pin(&mut self) -> Option<&mut Self::PowerPin> { + None + } + fn vint_pin(&mut self) -> Option<&mut Self::VintPin> { + None + } } #[repr(u8)] From 0f459d2bd78de11918e8e41cff9c0c3cd4e6bf68 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 3 Apr 2024 14:33:36 +0200 Subject: [PATCH 10/22] Before comparison with ubxlib --- Cargo.toml | 59 +++++++++++++++++++--- examples/embassy-rp2040-example/Cargo.toml | 3 +- examples/tokio-std-example/Cargo.toml | 5 +- src/asynch/ppp.rs | 16 +++--- src/asynch/runner.rs | 58 +++++++++++++++++++-- src/command/psn/mod.rs | 5 ++ 6 files changed, 122 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 917ad7c..cad7573 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ name = "ublox_cellular" doctest = false [dependencies] -atat = { version = "0.21.0", features = ["derive", "bytes"] } +atat = { version = "0.22", features = ["derive", "bytes"] } heapless = { version = "^0.8", features = ["serde"] } nb = "^1" serde = { version = "^1", default-features = false, features = ["derive"] } @@ -50,11 +50,55 @@ embedded-io-async = "0.6" [features] default = ["socket-udp", "socket-tcp", "ppp"] -internal-network-stack = ["dep:ublox-sockets"] -ppp = ["dep:embassy-at-cmux", "dep:embassy-net-ppp", "dep:embassy-net"] + +### Cellular feature list from ubxlib: +use-upsd-context-activation = [] +# mno-profile = [] +# cscon = [] +# root-of-trust = [] +# async-sock-close = [] +# data-counters = [] +# security-tls-iana-numbering = [] +# security-tls-server-name-indication = [] +# security-tls-psk-as-hex = [] +# mqtt = [] +# mqtt-sara-r4-old-syntax = [] +# mqtt-set-local-port = [] +# mqtt-session-retain = [] +# mqtt-binary-publish = [] +# mqtt-will = [] +# mqtt-keep-alive = [] +# mqtt-security = [] +# ucged5 = [] +# context-mapping-required = [] +# security-tls-cipher-list = [] +# auto-bauding = [] +# at-profiles = [] +# security-ztp = [] +# file-system-tag = [] +# dtr-power-saving = [] +# 3gpp-power-saving = [] +# 3gpp-power-saving-paging-window-set = [] +# deep-sleep-urc = [] +# edrx = [] +# mqttsn = [] +# mqttsn-security = [] +# cts-control = [] +# sock-set-local-port = [] +# fota = [] +# uart-power-saving = [] +cmux = ["dep:embassy-at-cmux"] +# cmux-channel-close = [] +# snr-reported = [] +# authentication-mode-automatic = [] +# lwm2m = [] +# ucged = [] +# http = [] +ppp = ["cmux", "dep:embassy-net-ppp", "dep:embassy-net"] + automatic-apn = [] -upsd-context-activation = [] +internal-network-stack = ["dep:ublox-sockets"] socket-tcp = ["ublox-sockets?/socket-tcp", "embassy-net?/tcp"] socket-udp = ["ublox-sockets?/socket-udp", "embassy-net?/udp"] @@ -73,6 +117,7 @@ defmt = [ ] log = ["dep:log", "ublox-sockets?/log", "atat/log"] +# Modules lara-r2 = [] lara-r6 = [] leon-g1 = [] @@ -80,9 +125,9 @@ lisa-u2 = [] mpci-l2 = [] sara-g3 = [] sara-g4 = [] -sara-r5 = ["upsd-context-activation"] +sara-r5 = ["use-upsd-context-activation"] sara-u1 = [] -sara-u2 = ["upsd-context-activation"] +sara-u2 = ["use-upsd-context-activation"] toby-l2 = [] toby-r2 = [] toby-l4 = [] @@ -96,7 +141,6 @@ exclude = ["examples"] #ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", branch = "feature/async-borrowed-sockets" } no-std-net = { git = "https://github.com/rushmorem/no-std-net", branch = "issue-15" } #ublox-sockets = { path = "../ublox-sockets" } -# atat = { git = "https://github.com/BlackbirdHQ/atat", branch = "master" } 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" } @@ -106,4 +150,3 @@ embassy-net = { 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" } -atat = { path = "../atat/atat" } diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index e2ce233..e3def29 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.21.0", features = ["derive", "bytes", "defmt"] } +atat = { version = "0.22", features = ["derive", "bytes", "defmt"] } ublox-cellular-rs = { version = "0.4.0", path = "../..", features = [ "lara-r6", "defmt", @@ -95,7 +95,6 @@ embassy-net-ppp = { path = "../../../embassy/embassy-net-ppp" } embassy-futures = { path = "../../../embassy/embassy-futures" } embassy-executor = { path = "../../../embassy/embassy-executor" } -atat = { path = "../../../atat/atat" } [profile.dev] opt-level = "s" diff --git a/examples/tokio-std-example/Cargo.toml b/examples/tokio-std-example/Cargo.toml index e264ceb..17cdb4b 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.21.0", features = ["derive", "bytes", "log"] } +atat = { version = "0.22", features = ["derive", "bytes", "log"] } ublox-cellular-rs = { version = "0.4.0", path = "../..", features = [ "lara-r6", "log", @@ -57,7 +57,6 @@ internal-network-stack = ["ublox-cellular-rs/internal-network-stack"] [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-time = { git = "https://github.com/embassy-rs/embassy", branch = "main" } @@ -72,8 +71,6 @@ embassy-net = { path = "../../../embassy/embassy-net" } embassy-net-ppp = { path = "../../../embassy/embassy-net-ppp" } embassy-futures = { path = "../../../embassy/embassy-futures" } -atat = { path = "../../../atat/atat" } - [profile.dev] opt-level = "s" diff --git a/src/asynch/ppp.rs b/src/asynch/ppp.rs index affb3c3..d7f423f 100644 --- a/src/asynch/ppp.rs +++ b/src/asynch/ppp.rs @@ -3,7 +3,7 @@ use core::mem::MaybeUninit; use crate::{ command::{ ipc::SetMultiplexing, - psn::{DeactivatePDPContext, EnterPPP}, + psn::{types::ContextId, EnterPPP}, Urc, }, config::CellularConfig, @@ -217,14 +217,12 @@ impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACIT continue; }; - self.cellular_runner - .change_state_to_desired_state(state::OperationState::DataEstablished) - .await; - let ppp_fut = async { let mut fails = 0; let mut last_start = None; + Timer::after(Duration::from_secs(10)).await; + loop { if let Some(last_start) = last_start { Timer::at(last_start + Duration::from_secs(10)).await; @@ -295,7 +293,13 @@ impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACIT self.mux_runner.run(&mut rx, &mut tx, CMUX_MAX_FRAME_SIZE), ppp_fut, self.ingress.read_from(&mut self.control_rx), - self.cellular_runner.run(), + async { + self.cellular_runner + .change_state_to_desired_state(state::OperationState::DataEstablished) + .await; + + self.cellular_runner.run().await + }, ) .await; diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index df4f7d3..457f83e 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -519,15 +519,65 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> return Err(Error::AttachTimeout); } + #[cfg(not(feature = "use-upsd-context-activation"))] + self.define_context(context_id, apn_info).await?; + // Activate the context - #[cfg(feature = "upsd-context-activation")] + #[cfg(feature = "use-upsd-context-activation")] self.activate_context_upsd(profile_id, apn_info).await?; - #[cfg(not(feature = "upsd-context-activation"))] + #[cfg(not(feature = "use-upsd-context-activation"))] self.activate_context(context_id, profile_id).await?; Ok(()) } + /// Define a PDP context + #[cfg(not(feature = "use-upsd-context-activation"))] + async fn define_context(&mut self, cid: ContextId, apn_info: crate::config::Apn<'_>) -> Result<(), Error> { + use crate::command::psn::SetPDPContextDefinition; + + self.at.send( + &SetModuleFunctionality { + fun: Functionality::Minimum, + // SARA-R5: this parameter can be used only when is 1, 4 or 19 + #[cfg(feature = "sara-r5")] + rst: None, + #[cfg(not(feature = "sara-r5"))] + rst: Some(ResetMode::DontReset), + }, + ).await?; + + if let crate::config::Apn::Given { name, .. } = apn_info { + self.at.send( + &SetPDPContextDefinition { + cid, + pdp_type: "IP", + apn: name, + }, + ).await?; + } + + // self.at.send( + // &SetAuthParameters { + // cid, + // auth_type: AuthenticationType::Auto, + // username: &apn_info.clone().user_name.unwrap_or_default(), + // password: &apn_info.clone().password.unwrap_or_default(), + // }, + // true, + // ).await?; + + self.at.send( + &SetModuleFunctionality { + fun: Functionality::Full, + rst: Some(ResetMode::DontReset), + }, + ).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 @@ -544,7 +594,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> /// Activate context using AT+UPSD commands /// Required for SARA-G3, SARA-U2 SARA-R5 modules. - #[cfg(feature = "upsd-context-activation")] + #[cfg(feature = "use-upsd-context-activation")] async fn activate_context_upsd( &mut self, profile_id: ProfileId, @@ -664,7 +714,7 @@ impl<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> /// Activate context using 3GPP commands /// Required for SARA-R4 and TOBY modules. - #[cfg(not(feature = "upsd-context-activation"))] + #[cfg(not(feature = "use-upsd-context-activation"))] async fn activate_context( &mut self, cid: ContextId, diff --git a/src/command/psn/mod.rs b/src/command/psn/mod.rs index 0d3162a..17180c5 100644 --- a/src/command/psn/mod.rs +++ b/src/command/psn/mod.rs @@ -93,6 +93,11 @@ pub struct SetPDPContextDefinition<'a> { pub apn: &'a str, } +#[derive(Clone, AtatCmd)] +#[at_cmd("+CGDCONT?", NoResponse)] +pub struct GetPDPContextDefinition; + + /// 18.7 Set Packet switched data configuration +UPSD /// /// Sets all the parameters in a specific packet switched data (PSD) profile. From 049e4620a7df93bc796e5d97ddfd23af0a91ff78 Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 22 Apr 2024 15:07:25 +0200 Subject: [PATCH 11/22] Add registration management, meticulously derived from ubxlib --- Cargo.toml | 40 +- examples/embassy-rp2040-example/Cargo.toml | 5 +- .../src/bin/embassy-internal-stack.rs | 4 +- .../src/bin/embassy-smoltcp-ppp.rs | 185 +-- examples/embassy-stm32-example/src/main.rs | 4 +- examples/tokio-std-example/Cargo.toml | 7 + .../src/bin/embassy-internal-stack.rs | 4 +- .../src/bin/tokio-smoltcp-ppp-mqtt.rs | 168 +++ .../src/bin/tokio-smoltcp-ppp.rs | 40 +- src/asynch/control.rs | 56 +- src/asynch/internal_stack.rs | 122 -- src/asynch/mod.rs | 65 +- src/asynch/network.rs | 1045 +++++++++++++++ src/asynch/ppp.rs | 331 ----- src/asynch/resources.rs | 43 +- src/asynch/runner.rs | 1176 +++++------------ src/asynch/state.rs | 293 ++-- src/asynch/urc_handler.rs | 73 + src/command/control/mod.rs | 7 +- src/command/device_lock/impl_.rs | 8 +- src/command/gpio/types.rs | 7 - src/command/mobile_control/types.rs | 4 +- src/command/mod.rs | 89 +- src/command/network_service/mod.rs | 17 + src/command/network_service/types.rs | 2 +- src/command/psn/mod.rs | 41 +- src/config.rs | 2 + src/error.rs | 6 + src/lib.rs | 3 +- src/module_timing.rs | 62 - src/modules/lara_r6.rs | 30 + src/modules/lena_r8.rs | 27 + src/modules/mod.rs | 179 +++ src/modules/sara_r410m.rs | 21 + src/modules/sara_r412m.rs | 28 + src/modules/sara_r422.rs | 24 + src/modules/sara_r5.rs | 33 + src/modules/sara_u201.rs | 27 + src/modules/toby_r2.rs | 25 + src/registration.rs | 354 +++++ 40 files changed, 2865 insertions(+), 1792 deletions(-) create mode 100644 examples/tokio-std-example/src/bin/tokio-smoltcp-ppp-mqtt.rs delete mode 100644 src/asynch/internal_stack.rs create mode 100644 src/asynch/network.rs delete mode 100644 src/asynch/ppp.rs create mode 100644 src/asynch/urc_handler.rs delete mode 100644 src/module_timing.rs create mode 100644 src/modules/lara_r6.rs create mode 100644 src/modules/lena_r8.rs create mode 100644 src/modules/mod.rs create mode 100644 src/modules/sara_r410m.rs create mode 100644 src/modules/sara_r412m.rs create mode 100644 src/modules/sara_r422.rs create mode 100644 src/modules/sara_r5.rs create mode 100644 src/modules/sara_u201.rs create mode 100644 src/modules/toby_r2.rs create mode 100644 src/registration.rs diff --git a/Cargo.toml b/Cargo.toml index cad7573..fee7a9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,8 +69,8 @@ use-upsd-context-activation = [] # mqtt-will = [] # mqtt-keep-alive = [] # mqtt-security = [] -# ucged5 = [] -# context-mapping-required = [] +ucged5 = [] +context-mapping-required = [] # security-tls-cipher-list = [] # auto-bauding = [] # at-profiles = [] @@ -88,11 +88,10 @@ use-upsd-context-activation = [] # fota = [] # uart-power-saving = [] cmux = ["dep:embassy-at-cmux"] -# cmux-channel-close = [] # snr-reported = [] -# authentication-mode-automatic = [] +authentication-mode-automatic = [] # lwm2m = [] -# ucged = [] +ucged = [] # http = [] ppp = ["cmux", "dep:embassy-net-ppp", "dep:embassy-net"] @@ -117,20 +116,23 @@ defmt = [ ] log = ["dep:log", "ublox-sockets?/log", "atat/log"] -# Modules -lara-r2 = [] -lara-r6 = [] -leon-g1 = [] -lisa-u2 = [] -mpci-l2 = [] -sara-g3 = [] -sara-g4 = [] -sara-r5 = ["use-upsd-context-activation"] -sara-u1 = [] -sara-u2 = ["use-upsd-context-activation"] -toby-l2 = [] +# The supported list of cellular modules. +# +# Note: if you add a new module type here, you also need to add it in +# `modules.rs` +lara-r6 = ["ucged"] +lena-r8 = [] +sara-r410m = ["ucged", "ucged5"] +sara-r412m = ["ucged", "ucged5"] +sara-r422 = ["context-mapping-required", "ucged"] +sara-r5 = ["context-mapping-required", "ucged", "authentication-mode-automatic"] +sara-u201 = [ + "use-upsd-context-activation", + "context-mapping-required", + "ucged", + "authentication-mode-automatic", +] toby-r2 = [] -toby-l4 = [] [workspace] members = [] @@ -150,3 +152,5 @@ embassy-net = { 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" } + +atat = { path = "../atat/atat" } \ No newline at end of file diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index e3def29..238845f 100644 --- a/examples/embassy-rp2040-example/Cargo.toml +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -39,7 +39,7 @@ embassy-at-cmux = { path = "../../../embassy/embassy-at-cmux", features = [ "defmt", ] } -embedded-tls = { path = "https://github.com/drogue-iot/embedded-tls", rev = "f788e02", default-features = false, features = [ +embedded-tls = { git = "https://github.com/drogue-iot/embedded-tls", rev = "f788e02", default-features = false, features = [ "defmt", ] } @@ -65,7 +65,7 @@ reqwless = { git = "https://github.com/drogue-iot/reqwless", features = [ "defmt", ] } smoltcp = { version = "*", default-features = false, features = [ - "dns-max-server-count-4", + "dns-max-server-count-4" ] } rand_chacha = { version = "0.3", default-features = false } @@ -94,6 +94,7 @@ embassy-net = { path = "../../../embassy/embassy-net" } embassy-net-ppp = { path = "../../../embassy/embassy-net-ppp" } embassy-futures = { path = "../../../embassy/embassy-futures" } embassy-executor = { path = "../../../embassy/embassy-executor" } +atat = { path = "../../../atat/atat" } [profile.dev] diff --git a/examples/embassy-rp2040-example/src/bin/embassy-internal-stack.rs b/examples/embassy-rp2040-example/src/bin/embassy-internal-stack.rs index 3429b73..5485175 100644 --- a/examples/embassy-rp2040-example/src/bin/embassy-internal-stack.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-internal-stack.rs @@ -123,7 +123,7 @@ async fn main(spawner: Spawner) { .set_desired_state(OperationState::DataEstablished) .await; info!("set_desired_state(PowerState::Alive)"); - while control.power_state() != OperationState::DataEstablished { + while control.operation_state() != OperationState::DataEstablished { Timer::after(Duration::from_millis(1000)).await; } Timer::after(Duration::from_millis(10000)).await; @@ -160,7 +160,7 @@ async fn main(spawner: Spawner) { 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 { + while control.operation_state() != OperationState::PowerDown { Timer::after(Duration::from_millis(1000)).await; } diff --git a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs index 8f8b287..b205511 100644 --- a/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs +++ b/examples/embassy-rp2040-example/src/bin/embassy-smoltcp-ppp.rs @@ -3,12 +3,10 @@ #![no_main] #![allow(stable_features)] #![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] use defmt::*; use embassy_executor::Spawner; -use embassy_net::dns::DnsSocket; -use embassy_net::tcp::client::TcpClient; -use embassy_net::tcp::client::TcpClientState; use embassy_net::tcp::TcpSocket; use embassy_net::Stack; use embassy_net::StackResources; @@ -17,15 +15,11 @@ 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_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::Duration; -use embassy_time::Timer; -use embedded_mqtt::transport::embedded_tls::TlsNalTransport; -use embedded_mqtt::transport::embedded_tls::TlsState; -use embedded_mqtt::DomainBroker; use embedded_tls::Aes128GcmSha256; -use embedded_tls::Certificate; use embedded_tls::TlsConfig; use embedded_tls::TlsConnection; use embedded_tls::TlsContext; @@ -37,10 +31,7 @@ use reqwless::request::Request; use reqwless::request::RequestBuilder as _; use reqwless::response::Response; use static_cell::StaticCell; -use ublox_cellular::asynch::state::OperationState; -use ublox_cellular::asynch::PPPRunner; use ublox_cellular::asynch::Resources; -use ublox_cellular::config::NoPin; use {defmt_rtt as _, panic_probe as _}; use ublox_cellular::config::{Apn, CellularConfig}; @@ -51,7 +42,7 @@ bind_interrupts!(struct Irqs { const CMD_BUF_SIZE: usize = 128; const INGRESS_BUF_SIZE: usize = 512; -const URC_CAPACITY: usize = 2; +const URC_CAPACITY: usize = 16; struct MyCelullarConfig { reset_pin: Option>, @@ -67,7 +58,7 @@ impl<'a> CellularConfig<'a> for MyCelullarConfig { const FLOW_CONTROL: bool = true; const APN: Apn<'a> = Apn::Given { - name: "em", + name: "onomondo", username: None, password: None, }; @@ -122,12 +113,18 @@ async fn main(spawner: Spawner) { static RESOURCES: StaticCell> = StaticCell::new(); - let (net_device, mut cell_control, mut runner) = - ublox_cellular::asynch::new_ppp(RESOURCES.init(Resources::new()), MyCelullarConfig { + let mut runner = ublox_cellular::asynch::Runner::new( + cell_uart.split(), + RESOURCES.init(Resources::new()), + MyCelullarConfig { reset_pin: Some(cell_nrst), power_pin: Some(cell_pwr), - vint_pin: Some(cell_vint) - }); + vint_pin: Some(cell_vint), + }, + ); + + static PPP_STATE: StaticCell> = StaticCell::new(); + let net_device = runner.ppp_stack(PPP_STATE.init(embassy_net_ppp::State::new())); // Generate random seed let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. @@ -143,73 +140,103 @@ async fn main(spawner: Spawner) { seed, )); + spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(cell_task(runner, stack)).unwrap(); + + stack.wait_config_up().await; + + // embassy_time::Timer::after(Duration::from_secs(2)).await; - let http_fut = async { - stack.wait_config_up().await; + info!("We have network!"); - info!("We have network!"); + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(20))); - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; - let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(Duration::from_secs(10))); + + let hostname = "ecdsa-test.germancoding.com"; + // let hostname = "eohkv57m7xxdr4m.m.pipedream.net"; + info!("looking up {:?}...", hostname); + + let mut remote = stack + .dns_query(hostname, smoltcp::wire::DnsQueryType::A) + .await + .unwrap(); + let remote_endpoint = (remote.pop().unwrap(), 443); + info!("connecting to {:?}...", remote_endpoint); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + warn!("connect error: {:?}", e); + return; + } + info!("TCP connected!"); + + let mut read_record_buffer = [0; 16640]; + let mut write_record_buffer = [0; 16640]; + let config = TlsConfig::new().with_server_name(hostname); + let mut tls = TlsConnection::new(socket, &mut read_record_buffer, &mut write_record_buffer); + + tls.open(TlsContext::new( + &config, + UnsecureProvider::new::(ChaCha8Rng::seed_from_u64(seed)), + )) + .await + .expect("error establishing TLS connection"); + + info!("TLS Established!"); + + let request = Request::get("/") + .host(hostname) + .content_type(ContentType::TextPlain) + .build(); + request.write(&mut tls).await.unwrap(); + + let mut rx_buf = [0; 4096]; + let response = Response::read(&mut tls, reqwless::request::Method::GET, &mut rx_buf) + .await + .unwrap(); - let hostname = "ecdsa-test.germancoding.com"; + { + info!("Got resp! {=[u8]:a}", &rx_buf[..512]); - let mut remote = stack - .dns_query(hostname, smoltcp::wire::DnsQueryType::A) - .await - .unwrap(); - let remote_endpoint = (remote.pop().unwrap(), 443); - info!("connecting to {:?}...", remote_endpoint); - let r = socket.connect(remote_endpoint).await; - if let Err(e) = r { - warn!("connect error: {:?}", e); - return; } - info!("TCP connected!"); - let mut read_record_buffer = [0; 16384]; - let mut write_record_buffer = [0; 16384]; - let config = TlsConfig::new().with_server_name(hostname); - let mut tls = TlsConnection::new(socket, &mut read_record_buffer, &mut write_record_buffer); + // let mut buf = [0; 16384]; + // let len = response + // .body() + // .reader() + // .read_to_end(&mut buf) + // .await + // .unwrap(); + // info!("{=[u8]:a}", &buf[..len]); + + loop { + embassy_time::Timer::after(Duration::from_secs(1)).await + } +} - tls.open(TlsContext::new( - &config, - UnsecureProvider::new::(ChaCha8Rng::seed_from_u64(seed)), - )) - .await - .expect("error establishing TLS connection"); - - info!("TLS Established!"); - - let request = Request::get("/") - .host(hostname) - .content_type(ContentType::TextPlain) - .build(); - request.write(&mut tls).await.unwrap(); - - let mut rx_buf = [0; 4096]; - let response = Response::read(&mut tls, reqwless::request::Method::GET, &mut rx_buf) - .await - .unwrap(); - - // let mut buf = vec![0; 16384]; - // let len = response - // .body() - // .reader() - // .read_to_end(&mut buf) - // .await - // .unwrap(); - // info!("{:?}", core::str::from_utf8(&buf[..len])); - }; +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} - let (rx, tx) = cell_uart.split(); - embassy_futures::join::join3( - stack.run(), - runner.run(rx, tx, |ipv4| { +#[embassy_executor::task] +async fn cell_task( + runner: ublox_cellular::asynch::Runner< + 'static, + BufferedUartRx<'static, UART0>, + BufferedUartTx<'static, UART0>, + MyCelullarConfig, + INGRESS_BUF_SIZE, + URC_CAPACITY, + >, + stack: &'static embassy_net::Stack>, +) -> ! { + runner + .run(|ipv4| { let Some(addr) = ipv4.address else { - warn!("PPP did not provide an IP address."); + defmt::warn!("PPP did not provide an IP address."); return; }; let mut dns_servers = heapless::Vec::new(); @@ -226,10 +253,6 @@ async fn main(spawner: Spawner) { }); stack.set_config_v4(config); - }), - http_fut, - ) - .await; - - core::unreachable!(); + }) + .await } diff --git a/examples/embassy-stm32-example/src/main.rs b/examples/embassy-stm32-example/src/main.rs index da8fc3e..88b4ef0 100644 --- a/examples/embassy-stm32-example/src/main.rs +++ b/examples/embassy-stm32-example/src/main.rs @@ -191,7 +191,7 @@ async fn main_task(spawner: Spawner) { .set_desired_state(OperationState::DataEstablished) .await; info!("set_desired_state(PowerState::Alive)"); - while control.power_state() != OperationState::DataEstablished { + while control.operation_state() != OperationState::DataEstablished { Timer::after(Duration::from_millis(1000)).await; } Timer::after(Duration::from_millis(10000)).await; @@ -228,7 +228,7 @@ async fn main_task(spawner: Spawner) { 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 { + while control.operation_state() != OperationState::PowerDown { Timer::after(Duration::from_millis(1000)).await; } diff --git a/examples/tokio-std-example/Cargo.toml b/examples/tokio-std-example/Cargo.toml index 17cdb4b..fc30306 100644 --- a/examples/tokio-std-example/Cargo.toml +++ b/examples/tokio-std-example/Cargo.toml @@ -51,6 +51,9 @@ smoltcp = { version = "*", default-features = false, features = [ ] } rand_chacha = { version = "0.3", default-features = false } +embedded-mqtt = { path = "../../../embedded-mqtt", features = ["log", "embedded-tls"] } + + [features] ppp = ["ublox-cellular-rs/ppp"] internal-network-stack = ["ublox-cellular-rs/internal-network-stack"] @@ -70,6 +73,10 @@ embassy-sync = { path = "../../../embassy/embassy-sync" } embassy-net = { path = "../../../embassy/embassy-net" } embassy-net-ppp = { path = "../../../embassy/embassy-net-ppp" } embassy-futures = { path = "../../../embassy/embassy-futures" } +atat = { path = "../../../atat/atat" } + +[patch."https://github.com/drogue-iot/embedded-tls"] +embedded-tls = { path = "../../../embedded-tls" } [profile.dev] opt-level = "s" diff --git a/examples/tokio-std-example/src/bin/embassy-internal-stack.rs b/examples/tokio-std-example/src/bin/embassy-internal-stack.rs index 3429b73..5485175 100644 --- a/examples/tokio-std-example/src/bin/embassy-internal-stack.rs +++ b/examples/tokio-std-example/src/bin/embassy-internal-stack.rs @@ -123,7 +123,7 @@ async fn main(spawner: Spawner) { .set_desired_state(OperationState::DataEstablished) .await; info!("set_desired_state(PowerState::Alive)"); - while control.power_state() != OperationState::DataEstablished { + while control.operation_state() != OperationState::DataEstablished { Timer::after(Duration::from_millis(1000)).await; } Timer::after(Duration::from_millis(10000)).await; @@ -160,7 +160,7 @@ async fn main(spawner: Spawner) { 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 { + while control.operation_state() != OperationState::PowerDown { Timer::after(Duration::from_millis(1000)).await; } diff --git a/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp-mqtt.rs b/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp-mqtt.rs new file mode 100644 index 0000000..02eaffa --- /dev/null +++ b/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp-mqtt.rs @@ -0,0 +1,168 @@ +#![cfg(feature = "ppp")] + +use embassy_net::dns::DnsSocket; +use embassy_net::tcp::client::TcpClient; +use embassy_net::tcp::client::TcpClientState; +use embassy_net::Stack; +use embassy_net::StackResources; + +use embassy_net_ppp::Device; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_time::Duration; +use embedded_mqtt::transport::embedded_tls::TlsNalTransport; +use embedded_mqtt::transport::embedded_tls::TlsState; +use embedded_mqtt::DomainBroker; +use embedded_tls::Aes128GcmSha256; +use embedded_tls::Certificate; +use embedded_tls::TlsConfig; +use embedded_tls::UnsecureProvider; +use log::*; +use rand_chacha::rand_core::SeedableRng; +use rand_chacha::ChaCha8Rng; +use static_cell::StaticCell; +use tokio_serial::SerialPort; +use tokio_serial::SerialPortBuilderExt; +use ublox_cellular::asynch::Resources; + +use ublox_cellular::config::NoPin; +use ublox_cellular::config::{Apn, CellularConfig}; + +const CMD_BUF_SIZE: usize = 128; +const INGRESS_BUF_SIZE: usize = 512; +const URC_CAPACITY: usize = 2; + +struct MyCelullarConfig; + +impl<'a> CellularConfig<'a> for MyCelullarConfig { + type ResetPin = NoPin; + type PowerPin = NoPin; + type VintPin = NoPin; + + const FLOW_CONTROL: bool = true; + + const APN: Apn<'a> = Apn::Given { + name: "em", + username: None, + password: None, + }; + + const PPP_CONFIG: embassy_net_ppp::Config<'a> = embassy_net_ppp::Config { + username: b"", + password: b"", + }; +} + +const TTY: &str = "/dev/ttyUSB0"; +const HOSTNAME: &str = "a2twqv2u8qs5xt-ats.iot.eu-west-1.amazonaws.com"; +const MQTT_MAX_SUBSCRIBERS: usize = 2; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + info!("HELLO"); + + let mut ppp_iface = tokio_serial::new(TTY, 115200).open_native_async()?; + ppp_iface + .set_flow_control(tokio_serial::FlowControl::Hardware) + .unwrap(); + + let (rx, tx) = tokio::io::split(ppp_iface); + let rx = embedded_io_adapters::tokio_1::FromTokio::new(tokio::io::BufReader::new(rx)); + let tx = embedded_io_adapters::tokio_1::FromTokio::new(tx); + + static RESOURCES: StaticCell> = + StaticCell::new(); + let mut runner = ublox_cellular::asynch::Runner::new( + (rx, tx), + RESOURCES.init(Resources::new()), + MyCelullarConfig, + ); + + static PPP_STATE: StaticCell> = StaticCell::new(); + let net_device = runner.ppp_stack(PPP_STATE.init(embassy_net_ppp::State::new())); + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + static STACK: StaticCell>> = StaticCell::new(); + static STACK_RESOURCES: StaticCell> = StaticCell::new(); + + let stack = &*STACK.init(Stack::new( + net_device, + embassy_net::Config::default(), + STACK_RESOURCES.init(StackResources::new()), + seed, + )); + + let mqtt_fut = async { + stack.wait_config_up().await; + + info!("We have network!"); + + static DNS: StaticCell> = StaticCell::new(); + let broker = DomainBroker::<_, 64>::new(HOSTNAME, DNS.init(DnsSocket::new(stack))).unwrap(); + + static MQTT_STATE: StaticCell< + embedded_mqtt::State, + > = StaticCell::new(); + + let (mut mqtt_stack, mqtt_client) = embedded_mqtt::new( + MQTT_STATE.init(embedded_mqtt::State::new()), + embedded_mqtt::Config::new("csr_test", broker) + .keepalive_interval(Duration::from_secs(50)), + ); + + let mqtt_tcp_state = TcpClientState::<1, 4096, 4096>::new(); + let mqtt_tcp_client = TcpClient::new(stack, &mqtt_tcp_state); + + let provider = UnsecureProvider::new::(ChaCha8Rng::seed_from_u64(seed)); + + let tls_config = TlsConfig::new() + .with_server_name(HOSTNAME) + // .with_ca(Certificate::X509(include_bytes!( + // "/home/mathias/Downloads/AmazonRootCA3.cer" + // ))) + .with_cert(Certificate::X509(include_bytes!( + "/home/mathias/Downloads/embedded-tls-test-certs/cert.der" + ))) + .with_priv_key(include_bytes!( + "/home/mathias/Downloads/embedded-tls-test-certs/private.der" + )); + + let tls_state = TlsState::<16640, 16640>::new(); + let mut transport = + TlsNalTransport::new(&mqtt_tcp_client, &tls_state, &tls_config, provider); + + mqtt_stack.run(&mut transport).await; + }; + + embassy_futures::join::join3( + stack.run(), + runner.run(|ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = heapless::Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); + } + let config = embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new( + embassy_net::Ipv4Address::from_bytes(&addr.0), + 0, + ), + gateway: None, + dns_servers, + }); + + stack.set_config_v4(config); + }), + mqtt_fut, + ) + .await; + + unreachable!(); +} diff --git a/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp.rs b/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp.rs index 77e7ba6..87b65a5 100644 --- a/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp.rs +++ b/examples/tokio-std-example/src/bin/tokio-smoltcp-ppp.rs @@ -1,24 +1,11 @@ #![cfg(feature = "ppp")] -use atat::asynch::AtatClient as _; -use atat::asynch::SimpleClient; -use atat::AtatIngress as _; -use embassy_net::dns::DnsSocket; -use embassy_net::tcp::client::TcpClient; -use embassy_net::tcp::client::TcpClientState; use embassy_net::tcp::TcpSocket; use embassy_net::Stack; use embassy_net::StackResources; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::Duration; -use embassy_time::Instant; -use embassy_time::Timer; -use embedded_mqtt::transport::embedded_tls::TlsNalTransport; -use embedded_mqtt::transport::embedded_tls::TlsState; -use embedded_mqtt::DomainBroker; use embedded_tls::Aes128GcmSha256; -use embedded_tls::Certificate; use embedded_tls::TlsConfig; use embedded_tls::TlsConnection; use embedded_tls::TlsContext; @@ -33,17 +20,8 @@ use reqwless::response::Response; use static_cell::StaticCell; use tokio_serial::SerialPort; use tokio_serial::SerialPortBuilderExt; -use ublox_cellular::asynch::state::OperationState; use ublox_cellular::asynch::Resources; -use ublox_cellular::command::control::SetDataRate; -use ublox_cellular::command::control::SetFlowControl; -use ublox_cellular::command::general::GetModelId; -use ublox_cellular::command::ipc::SetMultiplexing; -use ublox_cellular::command::psn::DeactivatePDPContext; -use ublox_cellular::command::psn::EnterPPP; -use ublox_cellular::command::Urc; -use ublox_cellular::command::AT; use ublox_cellular::config::NoPin; use ublox_cellular::config::{Apn, CellularConfig}; @@ -85,11 +63,20 @@ async fn main() -> Result<(), Box> { .set_flow_control(tokio_serial::FlowControl::Hardware) .unwrap(); + let (rx, tx) = tokio::io::split(ppp_iface); + let rx = embedded_io_adapters::tokio_1::FromTokio::new(tokio::io::BufReader::new(rx)); + let tx = embedded_io_adapters::tokio_1::FromTokio::new(tx); + static RESOURCES: StaticCell> = StaticCell::new(); + let mut runner = ublox_cellular::asynch::Runner::new( + (rx, tx), + RESOURCES.init(Resources::new()), + MyCelullarConfig, + ); - let (net_device, mut cell_control, mut runner) = - ublox_cellular::asynch::new_ppp(RESOURCES.init(Resources::new()), MyCelullarConfig); + static PPP_STATE: StaticCell> = StaticCell::new(); + let net_device = runner.ppp_stack(PPP_STATE.init(embassy_net_ppp::State::new())); // Generate random seed let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. @@ -165,12 +152,9 @@ async fn main() -> Result<(), Box> { info!("{:?}", core::str::from_utf8(&buf[..len])); }; - let (rx, tx) = tokio::io::split(ppp_iface); - let rx = embedded_io_adapters::tokio_1::FromTokio::new(tokio::io::BufReader::new(rx)); - let tx = embedded_io_adapters::tokio_1::FromTokio::new(tx); embassy_futures::join::join3( stack.run(), - runner.run(rx, tx, |ipv4| { + runner.run(|ipv4| { let Some(addr) = ipv4.address else { warn!("PPP did not provide an IP address."); return; diff --git a/src/asynch/control.rs b/src/asynch/control.rs index 562b960..9126492 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -6,12 +6,12 @@ use super::state::{LinkState, OperationState}; use super::{state, AtHandle}; pub struct Control<'a, AT: AtatClient> { - state_ch: state::StateRunner<'a>, + state_ch: state::Runner<'a>, at: AtHandle<'a, AT>, } impl<'a, AT: AtatClient> Control<'a, AT> { - pub(crate) fn new(state_ch: state::StateRunner<'a>, at: AtHandle<'a, AT>) -> Self { + pub(crate) fn new(state_ch: state::Runner<'a>, at: AtHandle<'a, AT>) -> Self { Self { state_ch, at } } @@ -25,8 +25,8 @@ impl<'a, AT: AtatClient> Control<'a, AT> { self.state_ch.link_state() } - pub fn power_state(&mut self) -> OperationState { - self.state_ch.power_state() + pub fn operation_state(&mut self) -> OperationState { + self.state_ch.operation_state() } pub fn desired_state(&mut self) -> OperationState { @@ -44,30 +44,30 @@ impl<'a, AT: AtatClient> Control<'a, AT> { self.state_ch.wait_for_desired_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_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)) - } + // 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 - } + // /// 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 + // } } diff --git a/src/asynch/internal_stack.rs b/src/asynch/internal_stack.rs deleted file mode 100644 index 23f8609..0000000 --- a/src/asynch/internal_stack.rs +++ /dev/null @@ -1,122 +0,0 @@ -// pub mod ublox_stack; - -use core::mem::MaybeUninit; - -use atat::{asynch::Client, AtatIngress}; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; -use embedded_io_async::{Read, Write}; - -use crate::{command::Urc, config::CellularConfig}; - -pub use super::resources::UbxResources as Resources; - -use super::{ - control::Control, - runner::{Runner, URC_SUBSCRIBERS}, - state, AtHandle, -}; - -pub fn new_internal< - 'a, - R: Read, - W: Write, - C: CellularConfig<'a>, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, ->( - reader: R, - writer: W, - resources: &'a mut Resources, - config: C, -) -> ( - state::Device<'a, Client<'a, W, INGRESS_BUF_SIZE>, URC_CAPACITY>, - Control<'a, Client<'a, W, INGRESS_BUF_SIZE>>, - InternalRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY>, -) { - // safety: this is a self-referential struct, however: - // - it can't move while the `'a` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let at_client_uninit: *mut MaybeUninit>> = - (&mut resources.at_client - as *mut MaybeUninit>>) - .cast(); - - unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( - writer, - &resources.res_slot, - &mut resources.cmd_buf, - atat::Config::default(), - ))); - - let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; - - let (ch_runner, net_device) = state::new( - &mut resources.ch, - AtHandle(at_client), - resources.urc_channel.subscribe().unwrap(), - ); - - let control = Control::new(ch_runner.state_runner(), AtHandle(at_client)); - - let runner = Runner::new( - ch_runner, - AtHandle(at_client), - config, - resources.urc_channel.subscribe().unwrap(), - ); - - let ingress = atat::Ingress::new( - atat::AtDigester::::new(), - &mut resources.ingress_buf, - &resources.res_slot, - &resources.urc_channel, - ); - - let runner = InternalRunner { - cellular_runner: runner, - ingress, - reader, - }; - - (net_device, control, runner) -} - -pub struct InternalRunner< - 'a, - R: Read, - W: Write, - C: CellularConfig<'a>, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> { - pub cellular_runner: Runner<'a, Client<'a, W, INGRESS_BUF_SIZE>, C, URC_CAPACITY>, - pub ingress: atat::Ingress< - 'a, - atat::AtDigester, - Urc, - INGRESS_BUF_SIZE, - URC_CAPACITY, - URC_SUBSCRIBERS, - >, - pub reader: R, -} - -impl< - 'a, - R: Read, - W: Write, - C: CellularConfig<'a>, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, - > InternalRunner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> -{ - pub async fn run(&mut self) -> ! { - embassy_futures::join::join( - self.ingress.read_from(&mut self.reader), - self.cellular_runner.run(), - ) - .await; - core::unreachable!() - } -} diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index d0cd66a..4921391 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,35 +1,44 @@ -pub mod control; +// pub mod control; +mod network; 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")] -mod internal_stack; -#[cfg(feature = "internal-network-stack")] -pub use internal_stack::{new_internal, InternalRunner, Resources}; - -#[cfg(feature = "ppp")] -mod ppp; -#[cfg(feature = "ppp")] -pub use ppp::{new_ppp, PPPRunner, Resources}; - -#[cfg(feature = "ppp")] -pub type Control<'d, const INGRESS_BUF_SIZE: usize> = control::Control< - 'd, - atat::asynch::Client< - 'd, - embassy_at_cmux::ChannelTx<'d, { ppp::CMUX_CHANNEL_SIZE }>, - INGRESS_BUF_SIZE, - >, ->; - -use atat::asynch::AtatClient; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; - -pub struct AtHandle<'d, AT: AtatClient>(&'d Mutex); - -impl<'d, AT: AtatClient> AtHandle<'d, AT> { - async fn send(&mut self, cmd: &Cmd) -> Result { - self.0.lock().await.send_retry::(cmd).await +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 new file mode 100644 index 0000000..b145364 --- /dev/null +++ b/src/asynch/network.rs @@ -0,0 +1,1045 @@ +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::GetCIMI; +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::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 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]; + +pub struct NetDevice<'a, 'b, C, A> { + ch: &'b state::Runner<'a>, + config: &'b mut C, + at_client: A, +} + +impl<'a, 'b, C, A> NetDevice<'a, 'b, C, A> +where + C: CellularConfig<'a>, + A: AtatClient, +{ + pub fn new(ch: &'b state::Runner<'a>, config: &'b mut C, 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(()) + } + } + + /// Register with the cellular network + /// + /// # Errors + /// + /// Returns an error if any of the internal network operations fail. + /// + pub async fn register_network(&mut self, mcc_mnc: Option<()>) -> Result<(), Error> { + self.prepare_connect().await?; + + if mcc_mnc.is_none() { + // If no MCC/MNC is given, make sure we are in automatic network + // selection mode. + + // Set automatic operator selection, if not already set + let OperatorSelection { mode, .. } = self.at_client.send(&GetOperatorSelection).await?; + + if mode != OperatorSelectionMode::Automatic { + // Don't check error code here as some modules can + // return an error as we still have the radio off (but they still + // obey) + let _ = self + .at_client + .send(&SetOperatorSelection { + mode: OperatorSelectionMode::Automatic, + format: None, + }) + .await; + } + } + + // Reset the current registration status + self.ch.update_registration_with(|f| f.reset()); + + self.at_client + .send(&SetModuleFunctionality { + fun: Functionality::Full, + rst: None, + }) + .await?; + + if let Some(_) = mcc_mnc { + // TODO: If MCC & MNC is set, register with manual operator selection. + // This is currently not supported! + + // let crate::command::network_service::responses::OperatorSelection { mode, .. } = self + // .at_client + // .send(&crate::command::network_service::GetOperatorSelection) + // .await?; + + // // Only run AT+COPS=0 if currently de-registered, to avoid PLMN reselection + // if !matches!( + // mode, + // crate::command::network_service::types::OperatorSelectionMode::Automatic + // | crate::command::network_service::types::OperatorSelectionMode::Manual + // ) { + // self.at_client + // .send(&crate::command::network_service::SetOperatorSelection { + // mode: crate::command::network_service::types::OperatorSelectionMode::Automatic, + // format: Some(C::OPERATOR_FORMAT as u8), + // }) + // .await?; + // } + unimplemented!() + } + + Ok(()) + } + + pub(crate) 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?; + + // CGREG URC + self.at_client + .send(&crate::command::psn::SetGPRSNetworkRegistrationStatus { + n: crate::command::psn::types::GPRSNetworkRegistrationUrcConfig::UrcEnabled, + }) + .await?; + + // CEREG URC + self.at_client + .send(&crate::command::psn::SetEPSNetworkRegistrationStatus { + n: crate::command::psn::types::EPSNetworkRegistrationUrcConfig::UrcEnabled, + }) + .await?; + + for _ in 0..10 { + if self.at_client.send(&GetCIMI).await.is_ok() { + break; + } + + Timer::after(Duration::from_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::AllFiles, + nvm_op: + crate::command::system_features::types::NVMFactoryRestoreType::NVMFlashSectors, + }) + .await?; + + info!("Successfully factory reset modem!"); + + if self.soft_reset(true).await.is_err() { + self.reset().await?; + } + + Ok(()) + } + + /// Reset the module by sending AT CFUN command + pub async fn soft_reset(&mut self, sim_reset: bool) -> Result<(), Error> { + trace!( + "Attempting to soft reset of the modem with sim reset: {}.", + sim_reset + ); + + let fun = if sim_reset { + Functionality::SilentResetWithSimReset + } else { + Functionality::SilentReset + }; + + match self + .at_client + .send(&SetModuleFunctionality { fun, rst: None }) + .await + { + Ok(_) => { + info!("Successfully soft reset modem!"); + Ok(()) + } + Err(err) => { + error!("Failed to soft reset modem: {:?}", err); + Err(Error::Atat(err)) + } + } + } + + /// 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> { + let state_runner = self.ch.clone(); + let update_fut = async { + loop { + self.update_registration().await; + + Timer::after(Duration::from_millis(300)).await; + } + }; + + Ok(embassy_time::with_timeout( + timeout, + select( + update_fut, + poll_fn(|cx| match state_runner.is_registered(Some(cx)) { + true => Poll::Ready(()), + false => Poll::Pending, + }), + ), + ) + .await + .map(drop)?) + } + + async fn update_registration(&mut self) { + if let Ok(reg) = self.at_client.send(&GetNetworkRegistrationStatus).await { + self.ch + .update_registration_with(|state| state.compare_and_set(reg.into())); + } + + if let Ok(reg) = self.at_client.send(&GetGPRSNetworkRegistrationStatus).await { + self.ch + .update_registration_with(|state| state.compare_and_set(reg.into())); + } + + if let Ok(reg) = self.at_client.send(&GetEPSNetworkRegistrationStatus).await { + self.ch + .update_registration_with(|state| state.compare_and_set(reg.into())); + } + } + + 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(&GetModelId).await?; + self.ch.set_module(Module::from_model_id(model_id)); + + // Echo off + self.at_client.send(&SetEcho { enabled: Echo::Off }).await?; + + // Extended errors on + self.at_client + .send(&SetReportMobileTerminationError { + n: TerminationErrorMode::Enabled, + }) + .await?; + + #[cfg(feature = "internal-network-stack")] + if C::HEX_MODE { + self.at_client + .send(&crate::command::ip_transport_layer::SetHexMode { + hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Enabled, + }) + .await?; + } else { + self.at_client + .send(&crate::command::ip_transport_layer::SetHexMode { + hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Disabled, + }) + .await?; + } + + // FIXME: The following three GPIO settings should not be here! + self.at_client + .send(&SetGpioConfiguration { + gpio_id: 23, + gpio_mode: GpioMode::NetworkStatus, + }) + .await; + + // Select SIM + self.at_client + .send(&SetGpioConfiguration { + gpio_id: 25, + gpio_mode: GpioMode::Output(GpioOutValue::Low), + }) + .await?; + + #[cfg(feature = "lara-r6")] + self.at_client + .send(&SetGpioConfiguration { + gpio_id: 42, + gpio_mode: GpioMode::Input(GpioInPull::NoPull), + }) + .await?; + + // self.at_client + // .send(&IdentificationInformation { n: 9 }) + // .await?; + + // DCD circuit (109) changes in accordance with the carrier + self.at_client + .send(&SetCircuit109Behaviour { + value: Circuit109Behaviour::AlwaysPresent, + }) + .await?; + + // Ignore changes to DTR + self.at_client + .send(&SetCircuit108Behaviour { + value: Circuit108Behaviour::Ignore, + }) + .await?; + + self.check_sim_status().await?; + + let ccid = self.at_client.send(&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(&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(&SetFlowControl { + value: FlowControl::RtsCts, + }) + .await?; + } else { + self.at_client + .send(&SetFlowControl { + value: FlowControl::Disabled, + }) + .await?; + } + + // Switch off UART power saving until it is integrated into this API + self.at_client + .send(&SetPowerSavingControl { + mode: PowerSavingMode::Disabled, + timeout: None, + }) + .await?; + + if !self.ch.is_registered(None) { + self.at_client + .send(&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); + + let module_cfun = self + .ch + .module() + .ok_or(Error::Uninitialized)? + .radio_off_cfun(); + + let cfun_power_mode = PowerMode::try_from(module_cfun as u8).ok(); + + let mut last_err = None; + for _ in 0..3 { + match self + .at_client + .send(&SetModuleFunctionality { + fun: module_cfun, + rst: None, + }) + .await + { + Ok(_) => return Ok(()), + Err(e) => { + last_err.replace(e); + + if let Some(expected_mode) = cfun_power_mode { + match self.at_client.send(&GetModuleFunctionality).await { + Ok(ModuleFunctionality { power_mode, .. }) + if power_mode == expected_mode => + { + // If we got no response, abort the command and + // check the status + return Ok(()); + } + _ => {} + } + } + } + } + } + + 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); + } + } + + 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(), + ) + .await + { + Either::First(desired_state) => { + info!("Desired state: {:?}", desired_state); + let _ = self.run_to_state(desired_state).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; + } + Either::Second(true) => { + // This flag will be set if we had been knocked out + // of our PDP context by a network outage and need + // to get it back again; make sure to get this in the + // 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); + } + } + _ => {} + } + } + } + + 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) => { + match self.connect(C::APN, C::PROFILE_ID, C::CONTEXT_ID).await { + Ok(_) => { + #[cfg(not(feature = "use-upsd-context-activation"))] + self.ch + .set_profile_state(crate::registration::ProfileState::ShouldBeUp); + + self.ch.set_operation_state(OperationState::DataEstablished); + Timer::after(Duration::from_secs(5)).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); + } + } + } + Ok(()) + } + + #[allow(unused_variables)] + async fn connect( + &mut self, + apn_info: crate::config::Apn<'_>, + profile_id: ProfileId, + context_id: ContextId, + ) -> Result<(), Error> { + #[cfg(not(feature = "use-upsd-context-activation"))] + self.define_context(context_id, apn_info).await?; + + // 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 { + if let Ok(true) = self.is_network_attached().await { + attached = true; + break; + }; + Timer::after(Duration::from_secs(1)).await; + } + if !attached { + return Err(Error::AttachTimeout); + } + + // Activate the context + #[cfg(feature = "use-upsd-context-activation")] + self.activate_context_upsd(profile_id, apn_info).await?; + #[cfg(not(feature = "use-upsd-context-activation"))] + self.activate_context(context_id, profile_id).await?; + + Ok(()) + } + + /// Define a PDP context + #[cfg(not(feature = "use-upsd-context-activation"))] + async fn define_context( + &mut self, + cid: ContextId, + apn_info: crate::config::Apn<'_>, + ) -> Result<(), Error> { + use crate::command::psn::{ + types::AuthenticationType, SetAuthParameters, SetPDPContextDefinition, + }; + + self.at_client + .send(&SetModuleFunctionality { + fun: self + .ch + .module() + .ok_or(Error::Uninitialized)? + .radio_off_cfun(), + rst: None, + }) + .await?; + + if let crate::config::Apn::Given { + name, + username, + password, + } = apn_info + { + self.at_client + .send(&SetPDPContextDefinition { + cid, + pdp_type: "IP", + apn: name, + }) + .await?; + + if let Some(username) = username { + self.at_client + .send(&SetAuthParameters { + cid, + auth_type: AuthenticationType::Auto, + username, + password: password.unwrap_or_default(), + }) + .await?; + } + } + + self.at_client + .send(&SetModuleFunctionality { + fun: Functionality::Full, + rst: None, + }) + .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_client.send(&GetGPRSAttached).await?; + Ok(state == GPRSAttachedState::Attached) + } + + /// Activate context using AT+UPSD commands. + #[cfg(feature = "use-upsd-context-activation")] + async fn activate_context_upsd( + &mut self, + profile_id: ProfileId, + apn_info: crate::config::Apn<'_>, + ) -> Result<(), Error> { + // SARA-U2 pattern: everything is done through AT+UPSD + // Set up the APN + if let crate::config::Apn::Given { + name, + username, + password, + } = apn_info + { + self.at_client + .send(&psn::SetPacketSwitchedConfig { + profile_id, + param: psn::types::PacketSwitchedParam::APN( + String::<99>::try_from(name).unwrap(), + ), + }) + .await?; + + // Set up the user name + if let Some(user_name) = username { + self.at_client + .send(&psn::SetPacketSwitchedConfig { + profile_id, + param: psn::types::PacketSwitchedParam::Username( + String::<64>::try_from(user_name).unwrap(), + ), + }) + .await?; + } + + // Set up the password + if let Some(password) = password { + self.at_client + .send(&psn::SetPacketSwitchedConfig { + profile_id, + param: psn::types::PacketSwitchedParam::Password( + String::<64>::try_from(password).unwrap(), + ), + }) + .await?; + } + } + // Set up the dynamic IP address assignment. + self.at_client + .send(&psn::SetPacketSwitchedConfig { + profile_id, + param: psn::types::PacketSwitchedParam::IPAddress(Ipv4Addr::unspecified().into()), + }) + .await?; + + // Automatic authentication protocol selection + self.at_client + .send(&psn::SetPacketSwitchedConfig { + profile_id, + param: psn::types::PacketSwitchedParam::Authentication(AuthenticationType::Auto), + }) + .await?; + + self.at_client + .send(&psn::SetPacketSwitchedAction { + profile_id, + action: psn::types::PacketSwitchedAction::Activate, + }) + .await?; + + Ok(()) + } + + /// Activate context using 3GPP commands + #[cfg(not(feature = "use-upsd-context-activation"))] + async fn activate_context( + &mut self, + cid: ContextId, + _profile_id: ProfileId, + ) -> Result<(), Error> { + for _ in 0..5 { + #[cfg(feature = "sara-r422")] + { + // Note: it seems a bit strange to do this first, + // rather than just querying the +CGACT status, + // but a specific case has been found where SARA-R422 + // indicated that it was activated whereas in fact, + // at least for the internal clients (so sockets, HTTP + // and MQTT), it was not. Forcing with AT+CGACT=1,x has + // been shown to fix that. We don't do it in all + // cases as SARA-R41x modules object to that. + self.at_client + .send(&SetPDPContextState { + status: PDPContextStatus::Activated, + cid: Some(cid), + }) + .await?; + } + + let context_states = self.at_client.send(&GetPDPContextState).await?; + + let activated = context_states + .iter() + .find_map(|state| { + if state.cid == cid { + Some(state.status == PDPContextStatus::Activated) + } else { + None + } + }) + .unwrap_or(false); + + if activated { + // [Re]attach a PDP context to an internal module profile + #[cfg(feature = "context-mapping-required")] + { + self.at_client + .send(&psn::SetPacketSwitchedConfig { + profile_id: _profile_id, + param: psn::types::PacketSwitchedParam::ProtocolType( + psn::types::ProtocolType::IPv4, + ), + }) + .await?; + + self.at_client + .send(&psn::SetPacketSwitchedConfig { + profile_id: _profile_id, + param: psn::types::PacketSwitchedParam::MapProfile(cid), + }) + .await?; + + // SARA-R5 pattern: the context also has to be + // activated and we're not actually done + // until the +UUPSDA URC comes back, + #[cfg(feature = "sara-r5")] + self.at_client + .send(&psn::SetPacketSwitchedAction { + profile_id, + action: psn::types::PacketSwitchedAction::Activate, + }) + .await?; + } + + return Ok(()); + } else { + #[cfg(not(feature = "sara-r422"))] + self.at_client + .send(&SetPDPContextState { + status: PDPContextStatus::Activated, + cid: Some(cid), + }) + .await?; + } + } + Err(Error::ContextActivationTimeout) + } +} diff --git a/src/asynch/ppp.rs b/src/asynch/ppp.rs deleted file mode 100644 index d7f423f..0000000 --- a/src/asynch/ppp.rs +++ /dev/null @@ -1,331 +0,0 @@ -use core::mem::MaybeUninit; - -use crate::{ - command::{ - ipc::SetMultiplexing, - psn::{types::ContextId, EnterPPP}, - Urc, - }, - config::CellularConfig, - module_timing::boot_time, -}; -use atat::{ - asynch::{AtatClient, Client, SimpleClient}, - AtatIngress, -}; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; -use embassy_time::{Duration, Instant, Timer}; -use embedded_io_async::{BufRead, Error, ErrorKind, Read, Write}; - -use super::{ - control::Control, - resources::UbxResources, - runner::{Runner, URC_SUBSCRIBERS}, - state, AtHandle, -}; - -pub const CMUX_MAX_FRAME_SIZE: usize = 512; -pub const CMUX_CHANNEL_SIZE: usize = CMUX_MAX_FRAME_SIZE * 2; -pub const CMUX_CHANNELS: usize = 2; // AT Control + PPP data - -pub type Resources< - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> = UbxResources< - embassy_at_cmux::ChannelTx<'static, CMUX_CHANNEL_SIZE>, - CMD_BUF_SIZE, - INGRESS_BUF_SIZE, - URC_CAPACITY, ->; - -pub fn new_ppp< - 'a, - C: CellularConfig<'a>, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, ->( - resources: &'a mut Resources, - config: C, -) -> ( - embassy_net_ppp::Device<'a>, - Control<'a, Client<'a, embassy_at_cmux::ChannelTx<'a, CMUX_CHANNEL_SIZE>, INGRESS_BUF_SIZE>>, - PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY>, -) { - let ch_runner = state::new_ppp(&mut resources.ch); - let state_ch = ch_runner.state_runner(); - - let (mux_runner, [control_channel, ppp_channel]) = resources.mux.start(); - let (control_rx, control_tx, _) = control_channel.split(); - - // safety: this is a self-referential struct, however: - // - it can't move while the `'a` borrow is active. - // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. - let at_client_uninit: *mut MaybeUninit< - Mutex< - NoopRawMutex, - Client<'a, embassy_at_cmux::ChannelTx<'a, CMUX_CHANNEL_SIZE>, INGRESS_BUF_SIZE>, - >, - > = (&mut resources.at_client - as *mut MaybeUninit< - Mutex< - NoopRawMutex, - Client< - 'static, - embassy_at_cmux::ChannelTx<'static, CMUX_CHANNEL_SIZE>, - INGRESS_BUF_SIZE, - >, - >, - >) - .cast(); - - unsafe { &mut *at_client_uninit }.write(Mutex::new(Client::new( - control_tx, - &resources.res_slot, - &mut resources.cmd_buf, - atat::Config::default(), - ))); - - let at_client = unsafe { (&*at_client_uninit).assume_init_ref() }; - - let cellular_runner = Runner::new( - ch_runner, - AtHandle(at_client), - config, - resources.urc_channel.subscribe().unwrap(), - ); - - let ingress = atat::Ingress::new( - atat::AtDigester::::new(), - &mut resources.ingress_buf, - &resources.res_slot, - &resources.urc_channel, - ); - - let control = Control::new(state_ch, AtHandle(at_client)); - - let (net_device, ppp_runner) = embassy_net_ppp::new(&mut resources.ppp_state); - - let runner = PPPRunner { - powered: false, - ppp_runner, - cellular_runner, - ingress, - ppp_channel, - control_rx, - mux_runner, - }; - - (net_device, control, runner) -} - -pub struct PPPRunner< - 'a, - C: CellularConfig<'a>, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> { - pub powered: bool, - pub ppp_runner: embassy_net_ppp::Runner<'a>, - pub cellular_runner: Runner< - 'a, - Client<'a, embassy_at_cmux::ChannelTx<'a, CMUX_CHANNEL_SIZE>, INGRESS_BUF_SIZE>, - C, - URC_CAPACITY, - >, - pub ingress: atat::Ingress< - 'a, - atat::AtDigester, - Urc, - INGRESS_BUF_SIZE, - URC_CAPACITY, - URC_SUBSCRIBERS, - >, - pub ppp_channel: embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, - pub control_rx: embassy_at_cmux::ChannelRx<'a, CMUX_CHANNEL_SIZE>, - pub mux_runner: embassy_at_cmux::Runner<'a, CMUX_CHANNELS, CMUX_CHANNEL_SIZE>, -} - -impl<'a, C: CellularConfig<'a>, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> - PPPRunner<'a, C, INGRESS_BUF_SIZE, URC_CAPACITY> -{ - async fn init_multiplexer( - rx: &mut R, - tx: &mut W, - ) -> Result<(), crate::error::Error> { - let mut buf = [0u8; 64]; - let mut interface = ReadWriteAdapter(rx, tx); - let mut at_client = SimpleClient::new( - &mut interface, - atat::AtDigester::::new(), - &mut buf, - atat::Config::default(), - ); - - super::runner::init_at(&mut at_client, C::FLOW_CONTROL).await?; - - at_client - .send(&SetMultiplexing { - mode: 0, - subset: Some(0), - port_speed: Some(5), - n1: Some(CMUX_MAX_FRAME_SIZE as u16), - t1: None, //Some(10), - n2: None, //Some(3), - t2: None, //Some(30), - t3: None, //Some(10), - k: None, //Some(2), - }) - .await?; - - drop(at_client); - - // 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; - } - }) - .await; - - Ok(()) - } - - pub async fn run( - &mut self, - mut rx: R, - mut tx: W, - on_ipv4_up: impl FnMut(embassy_net_ppp::Ipv4Status) + Copy, - ) -> ! { - loop { - if !self.powered { - // Reset modem - self.cellular_runner - .change_state_to_desired_state(state::OperationState::PowerDown) - .await; - self.cellular_runner - .change_state_to_desired_state(state::OperationState::PowerUp) - .await; - - Timer::after(boot_time()).await; - } - - // Do AT init and enter CMUX mode using interface - if Self::init_multiplexer(&mut rx, &mut tx).await.is_err() { - Timer::after(Duration::from_secs(5)).await; - continue; - }; - - let ppp_fut = async { - let mut fails = 0; - let mut last_start = None; - - Timer::after(Duration::from_secs(10)).await; - - loop { - if let Some(last_start) = last_start { - Timer::at(last_start + Duration::from_secs(10)).await; - // Do not attempt to start too fast. - - // If was up stably for at least 1 min, reset fail counter. - if Instant::now() > last_start + Duration::from_secs(60) { - fails = 0; - } else { - fails += 1; - if fails == 10 { - warn!("modem: PPP failed too much, rebooting modem."); - break; - } - } - } - last_start = Some(Instant::now()); - - { - let mut buf = [0u8; 16]; // Enough room for "ATD*99***1#\r\n" - let mut at_client = SimpleClient::new( - &mut self.ppp_channel, - atat::AtDigester::::new(), - &mut buf, - atat::Config::default(), - ); - - // hangup just in case a call was already in progress. - // Ignore errors because this fails if it wasn't. - let _ = at_client.send(&DeactivatePDPContext).await; - - // Send AT command to enter PPP mode - let res = at_client.send(&EnterPPP { cid: C::CONTEXT_ID }).await; - - if let Err(e) = res { - warn!("ppp dial failed {:?}", e); - continue; - } - - Timer::after(Duration::from_millis(100)).await; - } - - // Check for CTS low (bit 2) - // self.ppp_channel.set_hangup_detection(0x04, 0x00); - - info!("RUNNING PPP"); - let res = self - .ppp_runner - .run(&mut self.ppp_channel, C::PPP_CONFIG, on_ipv4_up) - .await; - - info!("ppp failed: {:?}", res); - - self.ppp_channel.clear_hangup_detection(); - - // escape back to data mode. - self.ppp_channel - .set_lines(embassy_at_cmux::Control::from_bits(0x44 << 1), None); - Timer::after(Duration::from_millis(100)).await; - self.ppp_channel - .set_lines(embassy_at_cmux::Control::from_bits(0x46 << 1), None); - } - }; - - self.ingress.clear(); - - embassy_futures::select::select4( - self.mux_runner.run(&mut rx, &mut tx, CMUX_MAX_FRAME_SIZE), - ppp_fut, - self.ingress.read_from(&mut self.control_rx), - async { - self.cellular_runner - .change_state_to_desired_state(state::OperationState::DataEstablished) - .await; - - self.cellular_runner.run().await - }, - ) - .await; - - self.powered = false; - } - } -} - -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 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/resources.rs b/src/asynch/resources.rs index 3698c74..656b4b0 100644 --- a/src/asynch/resources.rs +++ b/src/asynch/resources.rs @@ -1,15 +1,13 @@ -use core::mem::MaybeUninit; - -use atat::{asynch::Client, ResponseSlot, UrcChannel}; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex}; -use embedded_io_async::Write; +use atat::{ResponseSlot, UrcChannel}; use crate::command::Urc; use super::{runner::URC_SUBSCRIBERS, state}; -pub struct UbxResources< - W: Write, +#[cfg(feature = "cmux")] +use super::runner::{CMUX_CHANNELS, CMUX_CHANNEL_SIZE}; + +pub struct Resources< const CMD_BUF_SIZE: usize, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize, @@ -21,22 +19,20 @@ pub struct UbxResources< pub(crate) cmd_buf: [u8; CMD_BUF_SIZE], pub(crate) ingress_buf: [u8; INGRESS_BUF_SIZE], - pub(crate) at_client: MaybeUninit>>, - - #[cfg(feature = "ppp")] - pub(crate) ppp_state: embassy_net_ppp::State<2, 2>, + #[cfg(feature = "cmux")] + pub(crate) mux: embassy_at_cmux::Mux, +} - #[cfg(feature = "ppp")] - pub(crate) mux: - embassy_at_cmux::Mux<{ super::ppp::CMUX_CHANNELS }, { super::ppp::CMUX_CHANNEL_SIZE }>, +impl Default + for Resources +{ + fn default() -> Self { + Self::new() + } } -impl< - W: Write, - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, - > UbxResources +impl + Resources { pub fn new() -> Self { Self { @@ -47,12 +43,7 @@ impl< cmd_buf: [0; CMD_BUF_SIZE], ingress_buf: [0; INGRESS_BUF_SIZE], - at_client: MaybeUninit::uninit(), - - #[cfg(feature = "ppp")] - ppp_state: embassy_net_ppp::State::new(), - - #[cfg(feature = "ppp")] + #[cfg(feature = "cmux")] mux: embassy_at_cmux::Mux::new(), } } diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 457f83e..88b5427 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -1,951 +1,379 @@ -use crate::command::control::types::Echo; -use crate::command::control::SetEcho; -use crate::command::psn::GetGPRSAttached; -use crate::command::psn::GetPDPContextState; -use crate::command::psn::SetPDPContextState; +use crate::asynch::network::NetDevice; +use crate::command::ipc::SetMultiplexing; +use crate::command::psn::DeactivatePDPContext; +use crate::command::psn::EnterPPP; + +use crate::command::AT; use crate::{command::Urc, config::CellularConfig}; use super::state; +use super::urc_handler::UrcHandler; +use super::Resources; use crate::asynch::state::OperationState; -use crate::command::control::types::{Circuit108Behaviour, Circuit109Behaviour, FlowControl}; -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, GetFirmwareVersion, GetModelId}; -use crate::command::gpio::types::{GpioInPull, GpioMode, GpioOutValue}; -use crate::command::gpio::SetGpioConfiguration; -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::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 crate::module_timing::{boot_time, reset_time}; -use atat::{asynch::AtatClient, UrcSubscription}; -use embassy_futures::select::select; -use embassy_time::{with_timeout, Duration, Timer}; -use embedded_hal::digital::{InputPin, OutputPin}; - -use crate::command::psn::types::{ContextId, ProfileId}; + +use atat::asynch::AtatClient; +use atat::asynch::SimpleClient; +use atat::AtatIngress as _; +use atat::UrcChannel; + use embassy_futures::select::Either; +use embassy_futures::select::Either3; +use embassy_time::with_timeout; +use embassy_time::Instant; +use embassy_time::{Duration, Timer}; -use super::AtHandle; +use embedded_io_async::BufRead; +use embedded_io_async::Read; +use embedded_io_async::Write; -#[cfg(feature = "ppp")] pub(crate) const URC_SUBSCRIBERS: usize = 2; -#[cfg(feature = "internal-network-stack")] -pub(crate) const URC_SUBSCRIBERS: usize = 2; +pub const CMUX_MAX_FRAME_SIZE: usize = 128; +pub const CMUX_CHANNEL_SIZE: usize = CMUX_MAX_FRAME_SIZE * 4; + +#[cfg(any(feature = "internal-network-stack", feature = "ppp"))] +pub const CMUX_CHANNELS: usize = 2; + +#[cfg(not(any(feature = "internal-network-stack", feature = "ppp")))] +pub const CMUX_CHANNELS: usize = 1; /// 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<'d>, const URC_CAPACITY: usize> { - ch: state::Runner<'d>, - at: AtHandle<'d, AT>, - config: C, - urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, URC_SUBSCRIBERS>, +pub struct Runner<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> { + iface: (R, W), + + pub ch: state::Runner<'a>, + pub config: C, + pub urc_channel: &'a UrcChannel, + + pub ingress: atat::Ingress< + 'a, + atat::AtDigester, + Urc, + INGRESS_BUF_SIZE, + URC_CAPACITY, + URC_SUBSCRIBERS, + >, + pub cmd_buf: &'a mut [u8], + pub res_slot: &'a atat::ResponseSlot, + + #[cfg(feature = "cmux")] + pub mux_runner: embassy_at_cmux::Runner<'a, CMUX_CHANNELS, CMUX_CHANNEL_SIZE>, + + #[cfg(feature = "cmux")] + network_channel: embassy_at_cmux::Channel<'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<'d, AT: AtatClient, C: CellularConfig<'d>, const URC_CAPACITY: usize> - Runner<'d, AT, C, URC_CAPACITY> +impl<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> + Runner<'a, R, W, C, INGRESS_BUF_SIZE, URC_CAPACITY> +where + R: BufRead + Read, + W: Write, + C: CellularConfig<'a> + 'a, { - pub(crate) fn new( - ch: state::Runner<'d>, - at: AtHandle<'d, AT>, + pub fn new( + iface: (R, W), + resources: &'a mut Resources, config: C, - urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, URC_SUBSCRIBERS>, ) -> Self { - Self { - ch, - at, - config, - urc_subscription, - } - } - - // TODO: crate visibility only makes sense if reset and co are also crate visibility - // pub(crate) async fn init(&mut self) -> Result<(), Error> { - pub async fn init(&mut self) -> Result<(), Error> { - // Initilize a new ublox device to a known state (set RS232 settings) - debug!("Initializing module"); - // Hard reset module - if Ok(false) == self.has_power().await { - self.power_up().await?; - }; - self.reset().await?; - - Ok(()) - } - - pub async fn is_alive(&mut self) -> Result { - if !self.has_power().await? { - return Err(Error::PoweredDown); - } + let ch_runner = state::Runner::new(&mut resources.ch); - match self.at.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) - } - } + let ingress = atat::Ingress::new( + atat::AtDigester::::new(), + &mut resources.ingress_buf, + &resources.res_slot, + &resources.urc_channel, + ); - pub async fn power_up(&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(crate::module_timing::pwr_on_time()).await; - pin.set_high().map_err(|_| Error::IoPin)?; - Timer::after(boot_time()).await; - self.ch.set_power_state(OperationState::PowerUp); - debug!("Powered up"); - Ok(()) - } else { - warn!("No power pin configured"); - Ok(()) - } - } else { - Ok(()) - } - } + #[cfg(feature = "cmux")] + let (mux_runner, channels) = resources.mux.start(); + #[cfg(feature = "cmux")] + let mut channel_iter = channels.into_iter(); - pub async fn wait_for_desired_state( - &mut self, - ps: OperationState, - ) -> Result { - self.ch.state_runner().wait_for_desired_state(ps).await - } + Self { + iface, - 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(crate::module_timing::pwr_off_time()).await; - pin.set_high().map_err(|_| Error::IoPin)?; - self.ch.set_power_state(OperationState::PowerDown); - debug!("Powered down"); - - // FIXME: Is this needed? - Timer::after(Duration::from_millis(1000)).await; - - Ok(()) - } else { - warn!("No power pin configured"); - Ok(()) - } - } else { - Ok(()) - } - } + ch: ch_runner, + config, + urc_channel: &resources.urc_channel, - /// Initializes the network only valid after `init_at`. - /// - /// # Errors - /// - /// Returns an error if any of the internal network operations fail. - /// - pub async fn init_network(&mut self) -> Result<(), Error> { - // Disable Message Waiting URCs (UMWI) - #[cfg(any(feature = "toby-r2"))] - self.at - .send(&crate::command::sms::SetMessageWaitingIndication { - mode: crate::command::sms::types::MessageWaitingMode::Disabled, - }) - .await?; - - self.at - .send( - &crate::command::mobile_control::SetAutomaticTimezoneUpdate { - on_off: crate::command::mobile_control::types::AutomaticTimezone::EnabledLocal, - }, - ) - .await?; - - self.at - .send(&crate::command::mobile_control::SetModuleFunctionality { - fun: Functionality::Full, - rst: None, - }) - .await?; - - self.enable_registration_urcs().await?; - - // Set automatic operator selection, if not already set - let crate::command::network_service::responses::OperatorSelection { mode, .. } = self - .at - .send(&crate::command::network_service::GetOperatorSelection) - .await?; - - // Only run AT+COPS=0 if currently de-registered, to avoid PLMN reselection - if !matches!( - mode, - crate::command::network_service::types::OperatorSelectionMode::Automatic - | crate::command::network_service::types::OperatorSelectionMode::Manual - ) { - self.at - .send(&crate::command::network_service::SetOperatorSelection { - mode: crate::command::network_service::types::OperatorSelectionMode::Automatic, - format: Some(C::OPERATOR_FORMAT as u8), - }) - .await?; - } + ingress, + cmd_buf: &mut resources.cmd_buf, + res_slot: &resources.res_slot, - Ok(()) - } + #[cfg(feature = "cmux")] + mux_runner, - pub(crate) async fn enable_registration_urcs(&mut self) -> Result<(), Error> { - // if packet domain event reporting is not set it's not a stopper. We - // might lack some events when we are dropped from the network. - // TODO: Re-enable this when it works, and is useful! - if self - .at - .send(&crate::command::psn::SetPacketSwitchedEventReporting { - mode: crate::command::psn::types::PSEventReportingMode::CircularBufferUrcs, - bfr: None, - }) - .await - .is_err() - { - warn!("Packet domain event reporting set failed"); - } + #[cfg(feature = "cmux")] + network_channel: channel_iter.next().unwrap(), - // FIXME: Currently `atat` is unable to distinguish `xREG` family of - // commands from URC's - - // CREG URC - self.at.send( - &crate::command::network_service::SetNetworkRegistrationStatus { - n: crate::command::network_service::types::NetworkRegistrationUrcConfig::UrcDisabled, - }).await?; - - // CGREG URC - self.at - .send(&crate::command::psn::SetGPRSNetworkRegistrationStatus { - n: crate::command::psn::types::GPRSNetworkRegistrationUrcConfig::UrcDisabled, - }) - .await?; - - // CEREG URC - self.at - .send(&crate::command::psn::SetEPSNetworkRegistrationStatus { - n: crate::command::psn::types::EPSNetworkRegistrationUrcConfig::UrcDisabled, - }) - .await?; - - Ok(()) - } + #[cfg(feature = "cmux")] + data_channel: channel_iter.next().unwrap(), - /// 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(reset_time()).await; - pin.set_high().ok(); - Timer::after(boot_time()).await; - // self.is_alive().await?; - } else { - warn!("No reset pin configured"); + #[cfg(feature = "ppp")] + ppp_runner: None, } - 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 - .send(&crate::command::system_features::SetFactoryConfiguration { - fs_op: crate::command::system_features::types::FSFactoryRestoreType::AllFiles, - nvm_op: - crate::command::system_features::types::NVMFactoryRestoreType::NVMFlashSectors, - }) - .await?; - - info!("Successfully factory reset modem!"); - - if self.soft_reset(true).await.is_err() { - self.reset().await?; - } - - Ok(()) + #[cfg(feature = "ppp")] + pub fn ppp_stack<'d: 'a, const N_RX: usize, const N_TX: usize>( + &mut self, + ppp_state: &'d mut embassy_net_ppp::State, + ) -> embassy_net_ppp::Device<'d> { + let (net_device, ppp_runner) = embassy_net_ppp::new(ppp_state); + self.ppp_runner.replace(ppp_runner); + net_device } - /// Reset the module by sending AT CFUN command - pub async fn soft_reset(&mut self, sim_reset: bool) -> Result<(), Error> { - trace!( - "Attempting to soft reset of the modem with sim reset: {}.", - sim_reset - ); - - let fun = if sim_reset { - Functionality::SilentResetWithSimReset - } else { - Functionality::SilentReset - }; - - match self - .at - .send(&SetModuleFunctionality { - fun, - // SARA-R5 This parameter can be used only when is 1, 4 or 19 - #[cfg(feature = "sara-r5")] - rst: None, - #[cfg(not(feature = "sara-r5"))] - rst: Some(ResetMode::DontReset), - }) - .await - { - Ok(_) => { - info!("Successfully soft reset modem!"); - Ok(()) - } - Err(err) => { - error!("Failed to soft reset modem: {:?}", err); - Err(Error::Atat(err)) - } + #[cfg(feature = "internal-network-stack")] + pub fn internal_stack(&mut self) -> state::Device { + state::Device { + shared: &self.ch.shared, + desired_state_pub_sub: &self.ch.desired_state_pub_sub, + urc_subscription: self.urc_channel.subscribe().unwrap(), } } - // checks alive status continuiously until it is alive - async fn check_is_alive_loop(&mut self) -> bool { - loop { - if let Ok(alive) = self.is_alive().await { - return alive; - } - Timer::after(Duration::from_millis(100)).await; - } - } + pub async fn run(mut self, on_ipv4_up: impl FnMut(embassy_net_ppp::Ipv4Status) + Copy) -> ! { + #[cfg(feature = "ppp")] + let mut ppp_runner = self.ppp_runner.take().unwrap(); - 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; - } - } + #[cfg(feature = "cmux")] + let (mut at_rx, mut at_tx, _) = self.network_channel.split(); - pub async fn run(&mut self) -> ! { - match self.has_power().await.ok() { - Some(true) => { - self.ch.set_power_state(OperationState::PowerUp); - } - Some(false) | None => { - self.ch.set_power_state(OperationState::PowerDown); - } - } + let at_config = atat::Config::default(); loop { - match select( - self.ch.state_runner().wait_for_desired_state_change(), - self.urc_subscription.next_message_pure(), - ) - .await + // 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. { - Either::First(desired_state) => { - info!("Desired state: {:?}", desired_state); - if let Err(err) = desired_state { - error!("Error in desired_state retrival: {:?}", err); - continue; - } - let desired_state = desired_state.unwrap(); - let _ = self.change_state_to_desired_state(desired_state).await; - } - Either::Second(event) => { - self.handle_urc(event).await; + 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(); + if cell_device + .run_to_state(OperationState::PowerDown) + .await + .is_err() + { + continue; } - } - } - } - pub 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); + 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!() } - }, - Ok(OperationState::Initialized) => { - #[cfg(not(feature = "ppp"))] - match init_at(&mut self.at, C::FLOW_CONTROL).await { - Ok(_) => { - self.ch.set_power_state(OperationState::Initialized); - } - Err(err) => { - error!("Error in init_at: {:?}", err); - return Err(err); - } + Either3::Third(Err(_)) => { + // Reboot the cellular module and try again! + continue; } - - #[cfg(feature = "ppp")] - { - self.ch.set_power_state(OperationState::Initialized); + Either3::Third(Ok(_)) => { + // All good! We are now in `DataEstablished` and ready + // to start communication services! } } - 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); - return Err(Error::StateTimeout); + } + + #[cfg(feature = "ppp")] + let ppp_fut = async { + #[cfg(not(feature = "cmux"))] + let mut iface = super::ReadWriteAdapter(&mut self.iface.0, &mut self.iface.1); + + let mut fails = 0; + let mut last_start = None; + + loop { + if let Some(last_start) = last_start { + Timer::at(last_start + Duration::from_secs(10)).await; + // Do not attempt to start too fast. + + // If was up stably for at least 1 min, reset fail counter. + if Instant::now() > last_start + Duration::from_secs(60) { + fails = 0; + } else { + fails += 1; + if fails == 10 { + warn!("modem: PPP failed too much, rebooting modem."); + break; } } } - Err(err) => { - error!("Error in init_network: {:?}", err); - return Err(err); - } - }, - Ok(OperationState::DataEstablished) => { - match self.connect(C::APN, C::PROFILE_ID, 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(()) - } + last_start = Some(Instant::now()); - async fn handle_urc(&mut self, event: Urc) -> Result<(), Error> { - match event { - // Handle network URCs - 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"), - #[cfg(feature = "internal-network-stack")] - Urc::SocketDataAvailable(_) => warn!("Socket data available"), - #[cfg(feature = "internal-network-stack")] - Urc::SocketDataAvailableUDP(_) => warn!("Socket data available UDP"), - Urc::DataConnectionActivated(_) => warn!("Data connection activated"), - Urc::DataConnectionDeactivated(_) => warn!("Data connection deactivated"), - #[cfg(feature = "internal-network-stack")] - 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: crate::config::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); - } - - #[cfg(not(feature = "use-upsd-context-activation"))] - self.define_context(context_id, apn_info).await?; - - // Activate the context - #[cfg(feature = "use-upsd-context-activation")] - self.activate_context_upsd(profile_id, apn_info).await?; - #[cfg(not(feature = "use-upsd-context-activation"))] - self.activate_context(context_id, profile_id).await?; - - Ok(()) - } - - /// Define a PDP context - #[cfg(not(feature = "use-upsd-context-activation"))] - async fn define_context(&mut self, cid: ContextId, apn_info: crate::config::Apn<'_>) -> Result<(), Error> { - use crate::command::psn::SetPDPContextDefinition; - - self.at.send( - &SetModuleFunctionality { - fun: Functionality::Minimum, - // SARA-R5: this parameter can be used only when is 1, 4 or 19 - #[cfg(feature = "sara-r5")] - rst: None, - #[cfg(not(feature = "sara-r5"))] - rst: Some(ResetMode::DontReset), - }, - ).await?; - - if let crate::config::Apn::Given { name, .. } = apn_info { - self.at.send( - &SetPDPContextDefinition { - cid, - pdp_type: "IP", - apn: name, - }, - ).await?; - } - - // self.at.send( - // &SetAuthParameters { - // cid, - // auth_type: AuthenticationType::Auto, - // username: &apn_info.clone().user_name.unwrap_or_default(), - // password: &apn_info.clone().password.unwrap_or_default(), - // }, - // true, - // ).await?; - - self.at.send( - &SetModuleFunctionality { - fun: Functionality::Full, - rst: Some(ResetMode::DontReset), - }, - ).await?; - - Ok(()) - } + { + // Must be large enough to hold 'ATD*99***1#\r\n' + let mut buf = [0u8; 16]; + + let mut at_client = SimpleClient::new( + &mut self.data_channel, + atat::AtDigester::::new(), + &mut buf, + 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; + + // Send AT command to enter PPP mode + let res = at_client.send(&EnterPPP { cid: C::CONTEXT_ID }).await; + + if let Err(e) = res { + warn!("ppp dial failed {:?}", e); + continue; + } + Timer::after(Duration::from_millis(100)).await; + } - // 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?; + // Check for CTS low (bit 2) + // #[cfg(feature = "cmux")] + // self.data_channel.set_hangup_detection(0x04, 0x00); - if state == GPRSAttachedState::Attached { - return Ok(true); - } - return Ok(false); + info!("RUNNING PPP"); + let res = ppp_runner + .run(&mut self.data_channel, C::PPP_CONFIG, on_ipv4_up) + .await; - // self.at .send( &SetGPRSAttached { state: - // GPRSAttachedState::Attached, } ).await .map_err(Error::from)?; - } + info!("ppp failed: {:?}", res); - /// Activate context using AT+UPSD commands - /// Required for SARA-G3, SARA-U2 SARA-R5 modules. - #[cfg(feature = "use-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)?; + #[cfg(feature = "cmux")] + { + 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); + } } + }; - // Set up the password - if let Some(password) = password { - self.at - .send(&SetPacketSwitchedConfig { - profile_id, - param: PacketSwitchedParam::Password( - String::<64>::try_from(password).unwrap(), - ), + #[cfg(feature = "cmux")] + let mux_fut = async { + // 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, + atat::AtDigester::::new(), + &mut buf, + at_config, + ); + + at_client + .send(&SetMultiplexing { + mode: 0, + subset: Some(0), + port_speed: Some(5), + n1: Some(CMUX_MAX_FRAME_SIZE as u16), + t1: None, //Some(10), + n2: None, //Some(3), + t2: None, //Some(30), + t3: None, //Some(10), + k: None, //Some(2), }) .await - .map_err(Error::from)?; + .unwrap(); } - } - // 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)?; + // 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; - #[cfg(feature = "sara-r5")] - self.at - .send(&SetPacketSwitchedConfig { - profile_id, - param: PacketSwitchedParam::ProtocolType(ProtocolType::IPv4), + // 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; + } }) - .await - .map_err(Error::from)?; + .await; - #[cfg(feature = "sara-r5")] - self.at - .send(&SetPacketSwitchedConfig { - profile_id, - param: PacketSwitchedParam::MapProfile(ContextId(1)), - }) - .await - .map_err(Error::from)?; + self.mux_runner + .run(&mut self.iface.0, &mut self.iface.1, CMUX_MAX_FRAME_SIZE) + .await + }; - self.at - .send(&SetPacketSwitchedAction { - profile_id, - action: PacketSwitchedAction::Activate, - }) - .await - .map_err(Error::from)?; - } + #[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; - Ok(()) - } + let at_client = + atat::asynch::Client::new(&mut at_tx, self.res_slot, self.cmd_buf, at_config); + let mut cell_device = NetDevice::new(&self.ch, &mut self.config, at_client); - /// Activate context using 3GPP commands - /// Required for SARA-R4 and TOBY modules. - #[cfg(not(feature = "use-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); + let mut urc_handler = UrcHandler::new(&self.ch, self.urc_channel); - 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: _profile_id, - param: psn::types::PacketSwitchedParamReq::MapProfile, - }) - .await - .map_err(Error::from)? - { - if context != cid { - self.at - .send(&psn::SetPacketSwitchedConfig { - profile_id: _profile_id, - param: psn::types::PacketSwitchedParam::MapProfile(cid), - }) - .await - .map_err(Error::from)?; - - self.at - .send( - &psn::GetPacketSwitchedNetworkData { - profile_id: _profile_id, - param: psn::types::PacketSwitchedNetworkDataParam::PsdProfileStatus, - }, - ).await - .map_err(Error::from)?; - } - } + // TODO: Should we set ATE0 and CMEE=1 here, again? - let psn::responses::PacketSwitchedNetworkData { param_tag, .. } = self - .at - .send(&psn::GetPacketSwitchedNetworkData { - profile_id: _profile_id, - param: psn::types::PacketSwitchedNetworkDataParam::PsdProfileStatus, - }) - .await - .map_err(Error::from)?; - - if param_tag == 0 { - self.at - .send(&psn::SetPacketSwitchedAction { - profile_id: _profile_id, - action: psn::types::PacketSwitchedAction::Activate, - }) - .await - .map_err(Error::from)?; - } - } + embassy_futures::join::join3( + self.ingress.read_from(&mut at_rx), + cell_device.run(), + urc_handler.run(), + ) + .await; + }; - 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); - } -} + #[cfg(all(feature = "ppp", not(feature = "cmux")))] + ppp_fut.await; -pub(crate) async fn init_at( - at_client: &mut A, - enable_flow_control: bool, -) -> Result<(), Error> { - // Allow auto bauding to kick in - embassy_time::with_timeout(boot_time() * 2, async { - loop { - if let Ok(alive) = at_client.send(&AT).await { - break alive; + #[cfg(all(feature = "ppp", feature = "cmux"))] + match embassy_futures::select::select3(mux_fut, ppp_fut, network_fut).await { + Either3::First(_) => { + warn!("Breaking to reboot modem from multiplexer"); + } + Either3::Second(_) => { + warn!("Breaking to reboot modem from PPP"); + } + Either3::Third(_) => { + warn!("Breaking to reboot modem from network runner"); + } } - Timer::after(Duration::from_millis(100)).await; - } - }) - .await - .map_err(|_| Error::PoweredDown)?; - - // Extended errors on - at_client - .send(&SetReportMobileTerminationError { - n: TerminationErrorMode::Enabled, - }) - .await?; - - // Echo off - at_client.send(&SetEcho { enabled: Echo::Off }).await?; - - // Select SIM - at_client - .send(&SetGpioConfiguration { - gpio_id: 25, - gpio_mode: GpioMode::Output(GpioOutValue::High), - }) - .await?; - - #[cfg(any(feature = "lara-r6"))] - at_client - .send(&SetGpioConfiguration { - gpio_id: 42, - gpio_mode: GpioMode::Input(GpioInPull::NoPull), - }) - .await?; - - let _model_id = at_client.send(&GetModelId).await?; - - // at_client.send( - // &IdentificationInformation { - // n: 9 - // }, - // ).await?; - - at_client.send(&GetFirmwareVersion).await?; - - select_sim_card(at_client).await?; - - let ccid = at_client.send(&GetCCID).await?; - info!("CCID: {}", ccid.ccid); - - // DCD circuit (109) changes in accordance with the carrier - at_client - .send(&SetCircuit109Behaviour { - value: Circuit109Behaviour::ChangesWithCarrier, - }) - .await?; - - // Ignore changes to DTR - at_client - .send(&SetCircuit108Behaviour { - value: Circuit108Behaviour::Ignore, - }) - .await?; - - // Switch off UART power saving until it is integrated into this API - at_client - .send(&SetPowerSavingControl { - mode: PowerSavingMode::Disabled, - timeout: None, - }) - .await?; - #[cfg(feature = "internal-network-stack")] - if C::HEX_MODE { - at_client - .send(&crate::command::ip_transport_layer::SetHexMode { - hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Enabled, - }) - .await?; - } else { - at_client - .send(&crate::command::ip_transport_layer::SetHexMode { - hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Disabled, - }) - .await?; - } - - // Tell module whether we support flow control - if enable_flow_control { - at_client.send(&SetFlowControl).await?; - } else { - at_client.send(&SetFlowControl).await?; - } - Ok(()) -} - -pub(crate) async fn select_sim_card(at_client: &mut A) -> Result<(), Error> { - for _ in 0..2 { - match at_client.send(&GetPinStatus).await { - Ok(PinStatus { code }) if code == PinStatusCode::Ready => { - debug!("SIM is ready"); - return Ok(()); + #[cfg(all(feature = "cmux", not(feature = "ppp")))] + match embassy_futures::select::select(mux_fut, network_fut).await { + embassy_futures::select::Either::First(_) => { + warn!("Breaking to reboot modem from multiplexer"); + } + embassy_futures::select::Either::Second(_) => { + warn!("Breaking to reboot modem from network runner"); + } } - _ => {} } - - 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. - at_client - .send(&SetModuleFunctionality { - fun: Functionality::Minimum, - // SARA-R5 This parameter can be used only when is 1, 4 or 19 - #[cfg(feature = "sara-r5")] - rst: None, - #[cfg(not(feature = "sara-r5"))] - rst: Some(ResetMode::DontReset), - }) - .await?; - at_client - .send(&SetModuleFunctionality { - fun: Functionality::Full, - rst: Some(ResetMode::DontReset), - }) - .await?; - - Ok(()) } diff --git a/src/asynch/state.rs b/src/asynch/state.rs index e02ecd6..d558e2b 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -1,9 +1,9 @@ #![allow(dead_code)] use core::cell::RefCell; -use core::task::Context; +use core::future::poll_fn; +use core::task::{Context, Poll}; -use atat::asynch::AtatClient; use atat::UrcSubscription; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; @@ -49,12 +49,11 @@ impl TryFrom for OperationState { use crate::command::Urc; use crate::error::Error; - -use super::AtHandle; +use crate::modules::Module; +use crate::registration::{ProfileState, RegistrationState}; pub struct State { shared: Mutex>, - desired_state_pub_sub: PubSubChannel, } impl State { @@ -62,17 +61,13 @@ impl State { Self { shared: Mutex::new(RefCell::new(Shared { link_state: LinkState::Down, - power_state: OperationState::PowerDown, + operation_state: OperationState::PowerDown, + module: None, desired_state: OperationState::PowerDown, - waker: WakerRegistration::new(), + registration_state: RegistrationState::new(), + state_waker: WakerRegistration::new(), + registration_waker: WakerRegistration::new(), })), - desired_state_pub_sub: PubSubChannel::< - NoopRawMutex, - OperationState, - 1, - MAX_STATE_LISTENERS, - 1, - >::new(), } } } @@ -80,207 +75,186 @@ impl State { /// State of the LinkState pub struct Shared { link_state: LinkState, - power_state: OperationState, + operation_state: OperationState, desired_state: OperationState, - waker: WakerRegistration, + module: Option, + registration_state: RegistrationState, + state_waker: WakerRegistration, + registration_waker: WakerRegistration, } +#[derive(Clone)] pub struct Runner<'d> { pub(crate) shared: &'d Mutex>, - pub(crate) desired_state_pub_sub: - &'d PubSubChannel, -} - -#[derive(Clone, Copy)] -pub struct StateRunner<'d> { - shared: &'d Mutex>, - desired_state_pub_sub: - &'d PubSubChannel, } impl<'d> Runner<'d> { - pub fn state_runner(&self) -> StateRunner<'d> { - StateRunner { - shared: self.shared, - desired_state_pub_sub: self.desired_state_pub_sub, + pub fn new(state: &'d mut State) -> Self { + Self { + shared: &state.shared, } } - pub fn set_link_state(&mut self, state: LinkState) { - self.shared.lock(|s| { - let s = &mut *s.borrow_mut(); - s.link_state = state; - s.waker.wake(); - }); + pub fn module(&self) -> Option { + self.shared.lock(|s| s.borrow().module) } - pub fn set_power_state(&mut self, state: OperationState) { + pub fn set_module(&self, module: Module) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.power_state = state; - s.waker.wake(); + s.module.replace(module); }); } - pub fn set_desired_state(&mut self, ps: OperationState) { + pub fn update_registration_with(&self, f: impl FnOnce(&mut RegistrationState)) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.desired_state = ps; - s.waker.wake(); - }); - self.desired_state_pub_sub - .immediate_publisher() - .publish_immediate(ps); + f(&mut s.registration_state); + info!( + "Registration status changed! Registered: {:?}", + s.registration_state.is_registered() + ); + s.registration_waker.wake(); + }) } -} -impl<'d> StateRunner<'d> { - pub fn set_link_state(&self, state: LinkState) { + pub fn is_registered(&self, cx: Option<&mut Context>) -> bool { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.link_state = state; - s.waker.wake(); - }); + if let Some(cx) = cx { + s.registration_waker.register(cx.waker()); + } + s.registration_state.is_registered() + }) } - pub fn link_state_poll_fn(&mut self, cx: &mut Context) -> LinkState { + pub fn set_profile_state(&self, state: ProfileState) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.waker.register(cx.waker()); - s.link_state + s.registration_state.profile_state = state; }) } - pub fn set_power_state(&self, state: OperationState) { - self.shared.lock(|s| { - let s = &mut *s.borrow_mut(); - s.power_state = state; - s.waker.wake(); - }); + pub fn get_profile_state(&self) -> ProfileState { + self.shared + .lock(|s| s.borrow().registration_state.profile_state) } - pub fn power_state_poll_fn(&mut self, cx: &mut Context) -> OperationState { + pub fn set_link_state(&self, state: LinkState) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.waker.register(cx.waker()); - s.power_state - }) + s.link_state = state; + s.state_waker.wake(); + }); } - pub fn link_state(&mut self) -> LinkState { + pub fn link_state(&mut self, cx: Option<&mut Context>) -> LinkState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); + if let Some(cx) = cx { + s.state_waker.register(cx.waker()); + } s.link_state }) } - pub fn power_state(&mut self) -> OperationState { + pub fn set_operation_state(&self, state: OperationState) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.power_state - }) + s.operation_state = state; + s.state_waker.wake(); + }); } - pub fn desired_state(&mut self) -> OperationState { + pub fn operation_state(&mut self, cx: Option<&mut Context>) -> OperationState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.desired_state + if let Some(cx) = cx { + s.state_waker.register(cx.waker()); + } + s.operation_state }) } - pub async fn set_desired_state(&mut self, ps: OperationState) { + pub fn set_desired_state(&mut self, ps: OperationState) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.desired_state = ps; - s.waker.wake(); + s.state_waker.wake(); }); - self.desired_state_pub_sub - .immediate_publisher() - .publish_immediate(ps); } - pub async fn wait_for_desired_state( - &mut self, - ps: OperationState, - ) -> Result { - if self.desired_state() == ps { - info!("Desired state already set to {:?}, returning", ps); - return Ok(ps); - } - let mut sub = self - .desired_state_pub_sub - .subscriber() - .map_err(|x| Error::SubscriberOverflow(x))?; - loop { - let ps_now = sub.next_message_pure().await; - if ps_now == ps { - return Ok(ps_now); + pub fn desired_state(&self, cx: Option<&mut Context>) -> OperationState { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + if let Some(cx) = cx { + s.state_waker.register(cx.waker()); } - } + s.desired_state + }) } - pub async fn wait_for_desired_state_change(&mut self) -> Result { - let mut sub = self - .desired_state_pub_sub - .subscriber() - .map_err(|x| Error::SubscriberOverflow(x))?; - Ok(sub.next_message_pure().await) + pub async fn wait_for_desired_state(&mut self, ps: OperationState) { + poll_fn(|cx| { + if self.desired_state(Some(cx)) == ps { + return Poll::Ready(()); + } + Poll::Pending + }) + .await } -} -pub fn new<'d, AT: AtatClient, const URC_CAPACITY: usize>( - state: &'d mut State, - at: AtHandle<'d, AT>, - urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, 2>, -) -> (Runner<'d>, Device<'d, AT, URC_CAPACITY>) { - let runner = Runner { - shared: &state.shared, - desired_state_pub_sub: &state.desired_state_pub_sub, - }; - - let shared = runner.shared; - let desired_state_pub_sub = runner.desired_state_pub_sub; - - ( - runner, - Device { - shared, - urc_subscription, - at, - desired_state_pub_sub, - }, - ) -} + pub async fn wait_for_desired_state_change(&mut self) -> OperationState { + let old_desired = self.shared.lock(|s| s.borrow().desired_state); -pub fn new_ppp<'d>(state: &'d mut State) -> Runner<'d> { - Runner { - shared: &state.shared, - desired_state_pub_sub: &state.desired_state_pub_sub, + poll_fn(|cx| { + let current_desired = self.desired_state(Some(cx)); + if current_desired != old_desired { + return Poll::Ready(current_desired); + } + Poll::Pending + }) + .await + } + + pub async fn wait_registration_change(&mut self) -> bool { + let old_state = self + .shared + .lock(|s| s.borrow().registration_state.is_registered()); + + poll_fn(|cx| { + let current_state = self.is_registered(Some(cx)); + if current_state != old_state { + return Poll::Ready(current_state); + } + Poll::Pending + }) + .await } } -pub struct Device<'d, AT: AtatClient, const URC_CAPACITY: usize> { +#[cfg(feature = "internal-network-stack")] +pub struct Device<'d, const URC_CAPACITY: usize> { pub(crate) shared: &'d Mutex>, - pub(crate) desired_state_pub_sub: - &'d PubSubChannel, - pub(crate) at: AtHandle<'d, AT>, + // pub(crate) at: AtHandle<'d, AT>, pub(crate) urc_subscription: UrcSubscription<'d, Urc, URC_CAPACITY, 2>, } -impl<'d, AT: AtatClient, const URC_CAPACITY: usize> Device<'d, AT, URC_CAPACITY> { - pub fn link_state_poll_fn(&mut self, cx: &mut Context) -> LinkState { +#[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 { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.waker.register(cx.waker()); + s.state_waker.register(cx.waker()); s.link_state }) } - pub fn power_state_poll_fn(&mut self, cx: &mut Context) -> OperationState { + pub fn operation_state(&mut self, cx: &mut Context) -> OperationState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.waker.register(cx.waker()); - s.power_state + s.state_waker.register(cx.waker()); + s.operation_state }) } @@ -291,16 +265,17 @@ impl<'d, AT: AtatClient, const URC_CAPACITY: usize> Device<'d, AT, URC_CAPACITY> }) } - pub fn power_state(&mut self) -> OperationState { + pub fn operation_state(&mut self) -> OperationState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); - s.power_state + s.operation_state }) } - pub fn desired_state(&mut self) -> OperationState { + pub fn desired_state(&self, cx: &mut Context) -> OperationState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); + s.state_waker.register(cx.waker()); s.desired_state }) } @@ -309,29 +284,29 @@ impl<'d, AT: AtatClient, const URC_CAPACITY: usize> Device<'d, AT, URC_CAPACITY> self.shared.lock(|s| { let s = &mut *s.borrow_mut(); s.desired_state = ps; - s.waker.wake(); + s.state_waker.wake(); }); - self.desired_state_pub_sub - .immediate_publisher() - .publish_immediate(ps); } - pub async fn wait_for_desired_state( - &mut self, - ps: OperationState, - ) -> Result { - if self.desired_state() == ps { - return Ok(ps); - } - let mut sub = self - .desired_state_pub_sub - .subscriber() - .map_err(|x| Error::SubscriberOverflow(x))?; - loop { - let ps_now = sub.next_message_pure().await; - if ps_now == ps { - return Ok(ps_now); + pub async fn wait_for_desired_state(&mut self, ps: OperationState) { + poll_fn(|cx| { + if self.desired_state(cx) == ps { + return Poll::Ready(()); } - } + Poll::Pending + }) + .await + } + + pub async fn wait_for_desired_state_change(&mut self) -> OperationState { + let current_desired = self.shared.lock(|s| s.borrow().desired_state); + + poll_fn(|cx| { + if self.desired_state(cx) != current_desired { + return Poll::Ready(ps); + } + Poll::Pending + }) + .await } } diff --git a/src/asynch/urc_handler.rs b/src/asynch/urc_handler.rs new file mode 100644 index 0000000..3a271d6 --- /dev/null +++ b/src/asynch/urc_handler.rs @@ -0,0 +1,73 @@ +use atat::{UrcChannel, UrcSubscription}; + +use crate::command::Urc; + +use super::{runner::URC_SUBSCRIBERS, state}; + +pub struct UrcHandler<'a, 'b, const URC_CAPACITY: usize> { + ch: &'b state::Runner<'a>, + urc_subscription: UrcSubscription<'a, Urc, URC_CAPACITY, URC_SUBSCRIBERS>, +} + +impl<'a, 'b, const URC_CAPACITY: usize> UrcHandler<'a, 'b, URC_CAPACITY> { + pub fn new( + ch: &'b state::Runner<'a>, + urc_channel: &'a UrcChannel, + ) -> Self { + Self { + ch, + urc_subscription: urc_channel.subscribe().unwrap(), + } + } + + pub async fn run(&mut self) -> ! { + loop { + let event = self.urc_subscription.next_message_pure().await; + self.handle_urc(event).await; + } + } + + async fn handle_urc(&mut self, event: Urc) { + match event { + // Handle network URCs + 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"), + #[cfg(feature = "internal-network-stack")] + Urc::SocketDataAvailable(_) => warn!("Socket data available"), + #[cfg(feature = "internal-network-stack")] + Urc::SocketDataAvailableUDP(_) => warn!("Socket data available UDP"), + Urc::DataConnectionActivated(_) => warn!("Data connection activated"), + Urc::DataConnectionDeactivated(_) => { + warn!("Data connection deactivated"); + #[cfg(not(feature = "use-upsd-context-activation"))] + if self.ch.get_profile_state() == crate::registration::ProfileState::ShouldBeUp { + // Set the state so that, should we re-register with the + // network, we will reactivate the internal profile + self.ch + .set_profile_state(crate::registration::ProfileState::RequiresReactivation); + } + } + #[cfg(feature = "internal-network-stack")] + Urc::SocketClosed(_) => warn!("Socket closed"), + Urc::MessageWaitingIndication(_) => warn!("Message waiting indication"), + Urc::ExtendedPSNetworkRegistration(_) => warn!("Extended PS network registration"), + Urc::HttpResponse(_) => warn!("HTTP response"), + Urc::NetworkRegistration(reg) => { + self.ch + .update_registration_with(|state| state.compare_and_set(reg.into())); + } + Urc::GPRSNetworkRegistration(reg) => { + self.ch + .update_registration_with(|state| state.compare_and_set(reg.into())); + } + Urc::EPSNetworkRegistration(reg) => { + self.ch + .update_registration_with(|state| state.compare_and_set(reg.into())); + } + }; + } +} diff --git a/src/command/control/mod.rs b/src/command/control/mod.rs index 6a3bef9..3fe7188 100644 --- a/src/command/control/mod.rs +++ b/src/command/control/mod.rs @@ -46,8 +46,11 @@ pub struct SetCircuit108Behaviour { /// - HW flow control also referred with RTS / CTS flow control /// - SW flow control also referred with XON / XOFF flow control #[derive(Clone, AtatCmd)] -#[at_cmd("+IFC=2,2", NoResponse, value_sep = false)] -pub struct SetFlowControl; +#[at_cmd("&K", NoResponse, value_sep = false)] +pub struct SetFlowControl { + #[at_arg(position = 0)] + pub value: FlowControl, +} /// 15.8 Set flow control \Q /// diff --git a/src/command/device_lock/impl_.rs b/src/command/device_lock/impl_.rs index 4e405d8..e5e0042 100644 --- a/src/command/device_lock/impl_.rs +++ b/src/command/device_lock/impl_.rs @@ -137,8 +137,7 @@ mod test { use super::*; use crate::command::device_lock::responses::PinStatus; use atat::serde_at::de::from_str; - use atat::serde_at::ser::to_string; - use heapless::String; + use atat::serde_at::ser::to_slice; #[test] fn serialize_pin_status() { @@ -146,9 +145,10 @@ mod test { value_sep: false, ..atat::serde_at::SerializeOptions::default() }; - let s = to_string::<_, 32>(&PinStatusCode::PhNetSubPin, "", options).unwrap(); + let mut buf = [0u8; 32]; + let s = to_slice(&PinStatusCode::PhNetSubPin, "", &mut buf, options).unwrap(); - assert_eq!(s, String::<32>::try_from("PH-NETSUB PIN").unwrap()) + assert_eq!(&buf[..s], b"PH-NETSUB PIN") } #[test] diff --git a/src/command/gpio/types.rs b/src/command/gpio/types.rs index fa6c049..c44f04b 100644 --- a/src/command/gpio/types.rs +++ b/src/command/gpio/types.rs @@ -20,13 +20,6 @@ pub enum GpioInPull { PullDown = 2, } -// #[derive(Clone, PartialEq, AtatEnum)] -// pub enum GpioNumber { -// #[cfg(feature = "toby-r2")] -// Gpio1, - -// } - #[derive(Clone, PartialEq, Eq, AtatEnum)] pub enum GpioMode { /// • 0: output diff --git a/src/command/mobile_control/types.rs b/src/command/mobile_control/types.rs index 134ebde..30b550d 100644 --- a/src/command/mobile_control/types.rs +++ b/src/command/mobile_control/types.rs @@ -1,7 +1,7 @@ //! Argument and parameter types used by Mobile equipment control and status Commands and Responses use atat::atat_derive::AtatEnum; -#[derive(Clone, PartialEq, Eq, AtatEnum)] +#[derive(Clone, Copy, PartialEq, Eq, AtatEnum)] pub enum Functionality { /// 0: Sets the MT to minimum functionality (disable both transmit and receive RF /// circuits by deactivating both CS and PS services) @@ -206,7 +206,7 @@ pub enum TerminationErrorMode { Verbose = 2, } -#[derive(Clone, PartialEq, Eq, AtatEnum)] +#[derive(Clone, Copy, PartialEq, Eq, AtatEnum)] pub enum PowerMode { ///MT is switched on with minimum functionality Minimum = 0, diff --git a/src/command/mod.rs b/src/command/mod.rs index 0db4b01..136ece2 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -19,7 +19,10 @@ pub mod psn; pub mod sms; pub mod system_features; -use atat::atat_derive::{AtatCmd, AtatResp, AtatUrc}; +use atat::{ + atat_derive::{AtatCmd, AtatResp, AtatUrc}, + nom, +}; #[derive(Clone, AtatResp)] pub struct NoResponse; @@ -60,15 +63,87 @@ pub enum Urc { #[at_urc("+UMWI")] MessageWaitingIndication(sms::urc::MessageWaitingIndication), - // #[at_urc("+CREG")] - // NetworkRegistration(network_service::urc::NetworkRegistration), - // #[at_urc("+CGREG")] - // GPRSNetworkRegistration(psn::urc::GPRSNetworkRegistration), - // #[at_urc("+CEREG")] - // EPSNetworkRegistration(psn::urc::EPSNetworkRegistration), + #[at_urc("+CREG", parse = custom_cxreg_parse)] + NetworkRegistration(network_service::urc::NetworkRegistration), + #[at_urc("+CGREG", parse = custom_cxreg_parse)] + GPRSNetworkRegistration(psn::urc::GPRSNetworkRegistration), + #[at_urc("+CEREG", parse = custom_cxreg_parse)] + EPSNetworkRegistration(psn::urc::EPSNetworkRegistration), #[at_urc("+UREG")] ExtendedPSNetworkRegistration(psn::urc::ExtendedPSNetworkRegistration), #[at_urc("+UUHTTPCR")] HttpResponse(http::urc::HttpResponse), } + +fn custom_cxreg_parse<'a, T, Error: nom::error::ParseError<&'a [u8]> + core::fmt::Debug>( + token: T, +) -> impl Fn(&'a [u8]) -> nom::IResult<&'a [u8], (&'a [u8], usize), Error> +where + &'a [u8]: nom::Compare + nom::FindSubstring, + T: nom::InputLength + Clone + nom::InputTake + nom::InputIter + nom::AsBytes, +{ + move |i| { + let (i, (urc, len)) = atat::digest::parser::urc_helper(token.clone())(i)?; + + let index = urc.iter().position(|&x| x == b':').unwrap_or(urc.len()); + let arguments = &urc[index + 1..]; + + // "+CxREG?" response will always have atleast 2 arguments, both being + // integers. + // + // "+CxREG:" URC will always have at least 1 integer argument, and the + // second argument, if present, will be a string. + + // Parse the first + let (rem, _) = nom::sequence::tuple(( + nom::character::complete::space0, + nom::number::complete::u8, + nom::branch::alt((nom::combinator::eof, nom::bytes::complete::tag(","))), + ))(arguments)?; + + if !rem.is_empty() { + // If we have more arguments, we want to make sure this is a quoted string for the URC case. + nom::sequence::tuple(( + nom::character::complete::space0, + nom::sequence::delimited( + nom::bytes::complete::tag("\""), + nom::bytes::complete::escaped( + nom::character::streaming::none_of("\"\\"), + '\\', + nom::character::complete::one_of("\"\\"), + ), + nom::bytes::complete::tag("\""), + ), + nom::branch::alt((nom::combinator::eof, nom::bytes::complete::tag(","))), + ))(rem)?; + } + + Ok((i, (urc, len))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_custom_parse_cxreg() { + let creg_resp = b"\r\n+CREG: 2,5,\"9E9A\",\"019624BD\",2\r\n"; + let creg_urc_min = b"\r\n+CREG: 0\r\n"; + let creg_urc_full = b"\r\n+CREG: 5,\"9E9A\",\"0196BDB0\",2\r\n"; + + assert!( + custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_resp) + .is_err() + ); + assert!( + custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_urc_min) + .is_ok() + ); + assert!( + custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_urc_full) + .is_ok() + ); + } +} diff --git a/src/command/network_service/mod.rs b/src/command/network_service/mod.rs index 94bb918..ed331c5 100644 --- a/src/command/network_service/mod.rs +++ b/src/command/network_service/mod.rs @@ -139,3 +139,20 @@ pub struct SetNetworkRegistrationStatus { #[derive(Clone, AtatCmd)] #[at_cmd("+CREG?", NetworkRegistrationStatus)] pub struct GetNetworkRegistrationStatus; + +/// 7.15 Channel and network environment description +UCGED +/// +/// Enables the protocol stack and network environment information collection. +/// The information text response of the read command reports only the current +/// RAT (if any) parameters, determined by the parameter value. +/// +/// **NOTES** +/// - LARA-R6: The command provides only the information on the serving cell, +/// unless =2 (short form reporting enabled) and =2 (2G). If =2 +/// (short form reporting enabled) and =2 (2G), where supported, the module +/// returns also the information on the neighbor cells. +#[derive(Clone, AtatCmd)] +#[at_cmd("+UCGED", NoResponse)] +pub struct SetChannelAndNetworkEnvDesc { + pub mode: u8, +} diff --git a/src/command/network_service/types.rs b/src/command/network_service/types.rs index f06c262..cdb6e3e 100644 --- a/src/command/network_service/types.rs +++ b/src/command/network_service/types.rs @@ -80,7 +80,7 @@ pub enum NetworkRegistrationStat { /// • 3: registration denied RegistrationDenied = 3, /// • 4: unknown (e.g. out of GERAN/UTRAN/E-UTRAN coverage) - Unknown = 4, + OutOfCoverage = 4, /// • 5: registered, roaming RegisteredRoaming = 5, /// • 6: registered for "SMS only", home network (applicable only when diff --git a/src/command/psn/mod.rs b/src/command/psn/mod.rs index 17180c5..013ab13 100644 --- a/src/command/psn/mod.rs +++ b/src/command/psn/mod.rs @@ -97,7 +97,6 @@ pub struct SetPDPContextDefinition<'a> { #[at_cmd("+CGDCONT?", NoResponse)] pub struct GetPDPContextDefinition; - /// 18.7 Set Packet switched data configuration +UPSD /// /// Sets all the parameters in a specific packet switched data (PSD) profile. @@ -464,6 +463,7 @@ pub struct GetEPSNetworkRegistrationStatus; /// - **TOBY-L4 / TOBY-L2 / MPCI-L2 / LARA-R2 / TOBY-R2 / SARA-U2 / LISA-U2 / /// SARA-G4 / SARA-G3** - The command returns an error result code if the /// input is already active or not yet defined. + #[derive(Clone, AtatCmd)] #[at_cmd("+UAUTHREQ", NoResponse)] pub struct SetAuthParameters<'a> { @@ -471,8 +471,43 @@ pub struct SetAuthParameters<'a> { pub cid: ContextId, #[at_arg(position = 1)] pub auth_type: AuthenticationType, - #[at_arg(position = 2, len = 64)] + // For SARA-R4 and LARA-R6 modules the username and parameters are reversed + #[cfg_attr( + any( + feature = "sara_r410m", + feature = "sara_r412m", + feature = "sara_r422", + feature = "lara_r6" + ), + at_arg(position = 3, len = 64) + )] + #[cfg_attr( + not(any( + feature = "sara_r410m", + feature = "sara_r412m", + feature = "sara_r422", + feature = "lara_r6" + )), + at_arg(position = 2, len = 64) + )] pub username: &'a str, - #[at_arg(position = 3, len = 64)] + #[cfg_attr( + any( + feature = "sara_r410m", + feature = "sara_r412m", + feature = "sara_r422", + feature = "lara_r6" + ), + at_arg(position = 3, len = 64) + )] + #[cfg_attr( + not(any( + feature = "sara_r410m", + feature = "sara_r412m", + feature = "sara_r422", + feature = "lara_r6" + )), + at_arg(position = 2, len = 64) + )] pub password: &'a str, } diff --git a/src/config.rs b/src/config.rs index 2e5f41c..e8f70c3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -91,9 +91,11 @@ pub trait CellularConfig<'a> { fn reset_pin(&mut self) -> Option<&mut Self::ResetPin> { None } + fn power_pin(&mut self) -> Option<&mut Self::PowerPin> { None } + fn vint_pin(&mut self) -> Option<&mut Self::VintPin> { None } diff --git a/src/error.rs b/src/error.rs index 14a440d..3b28f1f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -39,6 +39,12 @@ pub enum Error { SubscriberOverflow(embassy_sync::pubsub::Error), } +impl From for Error { + fn from(_value: embassy_time::TimeoutError) -> Self { + Error::Generic(GenericError::Timeout) + } +} + #[cfg(feature = "defmt")] impl defmt::Format for Error { fn format(&self, f: defmt::Formatter<'_>) { diff --git a/src/lib.rs b/src/lib.rs index ffc82e9..3ba2859 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub(crate) mod fmt; pub mod command; pub mod config; pub mod error; -mod module_timing; +mod modules; +mod registration; pub mod asynch; diff --git a/src/module_timing.rs b/src/module_timing.rs deleted file mode 100644 index cd97881..0000000 --- a/src/module_timing.rs +++ /dev/null @@ -1,62 +0,0 @@ -#![allow(clippy::if_same_then_else)] - -use embassy_time::Duration; - -/// Low time of `PWR_ON` pin to trigger module switch on from power off mode -pub fn pwr_on_time() -> Duration { - if cfg!(feature = "lara-r6") { - Duration::from_millis(150) - } else if cfg!(feature = "sara-r5") { - Duration::from_millis(150) - } else if cfg!(feature = "toby-r2") { - Duration::from_micros(50) - } else { - Duration::from_micros(50) - } -} - -/// Low time of `PWR_ON` pin to trigger module graceful switch off -pub fn pwr_off_time() -> Duration { - if cfg!(feature = "lara-r6") { - Duration::from_millis(1500) - } else if cfg!(feature = "sara-r5") { - Duration::from_millis(5000) - } else if cfg!(feature = "toby-r2") { - Duration::from_secs(1) - } else { - Duration::from_secs(1) - } -} - -/// Low time of `RESET_N` pin to trigger module reset (reboot) -pub fn reset_time() -> Duration { - if cfg!(feature = "lara-r6") { - Duration::from_millis(10) - } else if cfg!(feature = "toby-r2") { - Duration::from_millis(50) - } else if cfg!(feature = "sara-r5") { - Duration::from_millis(100) - } else { - Duration::from_millis(50) - } -} - -/// Time to wait for module to boot -pub fn boot_time() -> Duration { - if cfg!(feature = "sara-r5") { - Duration::from_secs(3) - } else { - Duration::from_secs(3) - } -} - -/// Low time of `RESET_N` pin to trigger module abrupt emergency switch off -/// -/// NOTE: Not all modules support this operation from `RESET_N` -pub fn kill_time() -> Option { - if cfg!(feature = "lara-r6") { - Some(Duration::from_secs(10)) - } else { - None - } -} diff --git a/src/modules/lara_r6.rs b/src/modules/lara_r6.rs new file mode 100644 index 0000000..670a00f --- /dev/null +++ b/src/modules/lara_r6.rs @@ -0,0 +1,30 @@ +use super::ModuleParams; +use crate::command::mobile_control::types::Functionality; +use embassy_time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct LaraR6; + +impl ModuleParams for LaraR6 { + fn power_on_pull_time(&self) -> Option { + Some(Duration::from_millis(300)) + } + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(2000) + } + fn boot_wait(&self) -> Duration { + Duration::from_secs(10) + } + fn reboot_command_wait(&self) -> Duration { + Duration::from_secs(10) + } + fn command_delay_default(&self) -> Duration { + Duration::from_millis(20) + } + fn reset_hold(&self) -> Duration { + Duration::from_millis(150) + } + fn max_num_simultaneous_rats(&self) -> u8 { + 3 + } +} diff --git a/src/modules/lena_r8.rs b/src/modules/lena_r8.rs new file mode 100644 index 0000000..65f119f --- /dev/null +++ b/src/modules/lena_r8.rs @@ -0,0 +1,27 @@ +use super::ModuleParams; +use crate::command::mobile_control::types::Functionality; +use embassy_time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct LenaR8; + +impl ModuleParams for LenaR8 { + fn power_on_pull_time(&self) -> Duration { + Duration::from_millis(2000) + } + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(3100) + } + fn command_delay_default(&self) -> Duration { + Duration::from_millis(20) + } + fn reset_hold(&self) -> Duration { + Duration::from_millis(50) + } + fn max_num_simultaneous_rats(&self) -> u8 { + 2 + } + fn at_c_fun_reboot_command(&self) -> Functionality { + Functionality::SilentResetWithSimReset + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs new file mode 100644 index 0000000..9ced7b1 --- /dev/null +++ b/src/modules/mod.rs @@ -0,0 +1,179 @@ +#[cfg(any(feature = "any-module", feature = "lara-r6"))] +pub(crate) mod lara_r6; +#[cfg(any(feature = "any-module", feature = "lena-r8"))] +pub(crate) mod lena_r8; +#[cfg(any(feature = "any-module", feature = "sara-r410m"))] +pub(crate) mod sara_r410m; +#[cfg(any(feature = "any-module", feature = "sara-r412m"))] +pub(crate) mod sara_r412m; +#[cfg(any(feature = "any-module", feature = "sara-r422"))] +pub(crate) mod sara_r422; +#[cfg(any(feature = "any-module", feature = "sara-r5"))] +pub(crate) mod sara_r5; +#[cfg(any(feature = "any-module", feature = "sara-u201"))] +pub(crate) mod sara_u201; +#[cfg(any(feature = "any-module", feature = "toby-r2"))] +pub(crate) mod toby_r2; + +use crate::command::{general::responses::ModelId, mobile_control::types::Functionality}; +use embassy_time::Duration; + +pub trait ModuleParams: Copy { + /// The time for which PWR_ON must be pulled down to effect power-on + fn power_on_pull_time(&self) -> Option { + None + } + + /// The time for which PWR_ON must be pulled down to effect power-off + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(3100) + } + + /// How long to wait before the module is ready after boot + fn boot_wait(&self) -> Duration { + Duration::from_secs(5) + } + + /// How long to wait for a organised power-down in the ansence of VInt + fn power_down_wait(&self) -> Duration { + Duration::from_secs(35) + } + + /// How long to wait before the module is ready after it has been commanded + /// to reboot + fn reboot_command_wait(&self) -> Duration { + Duration::from_secs(5) + } + + /// How long to wait between the end of one AT command and the start of the + /// next, default value + fn command_delay_default(&self) -> Duration { + Duration::from_millis(100) + } + + /// The type of AT+CFUN state to use to switch the radio off: either 0 for + /// truly off or 4 for "airplane" mode + fn radio_off_cfun(&self) -> Functionality { + Functionality::AirplaneMode + } + + /// How long the reset line has to be held for to reset the cellular module + fn reset_hold(&self) -> Duration { + Duration::from_millis(16500) + } + + /// The maximum number of simultaneous RATs that are supported by the + /// cellular module + fn max_num_simultaneous_rats(&self) -> u8 { + 1 + } + + /// Normally 15, but in some cases 16 + fn at_c_fun_reboot_command(&self) -> Functionality { + Functionality::SilentReset + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum Module { + #[cfg(any(feature = "any-module", feature = "lara-r6"))] + LaraR6(lara_r6::LaraR6), + #[cfg(any(feature = "any-module", feature = "lena-r8"))] + LenaR8(lena_r8::LenaR8), + #[cfg(any(feature = "any-module", feature = "sara-r410m"))] + SaraR410m(sara_r410m::SaraR410m), + #[cfg(any(feature = "any-module", feature = "sara-r412m"))] + SaraR412m(sara_r412m::SaraR412m), + #[cfg(any(feature = "any-module", feature = "sara-r422"))] + SaraR422(sara_r422::SaraR422), + #[cfg(any(feature = "any-module", feature = "sara-r5"))] + SaraR5(sara_r5::SaraR5), + #[cfg(any(feature = "any-module", feature = "sara-u201"))] + SaraU201(sara_u201::SaraU201), + #[cfg(any(feature = "any-module", feature = "toby-r2"))] + TobyR2(toby_r2::TobyR2), + Generic(Generic), +} + +impl Module { + pub fn from_model_id(model_id: ModelId) -> Self { + match model_id.model.as_slice() { + b"LARA-R6001D" => Self::LaraR6(lara_r6::LaraR6), + id => { + warn!("Attempting to run {:?} using generic module parameters! This may or may not work.", id); + Self::Generic(Generic) + } + } + } +} + +macro_rules! inner { + ($self: ident, $fn: ident) => { + match $self { + #[cfg(any(feature = "any-module", feature = "lara-r6"))] + Self::LaraR6(inner) => inner.$fn(), + #[cfg(any(feature = "any-module", feature = "lena-r8"))] + Self::LenaR8(inner) => inner.$fn(), + #[cfg(any(feature = "any-module", feature = "sara-r410m"))] + Self::SaraR410m(inner) => inner.$fn(), + #[cfg(any(feature = "any-module", feature = "sara-r412m"))] + Self::SaraR412m(inner) => inner.$fn(), + #[cfg(any(feature = "any-module", feature = "sara-r422"))] + Self::SaraR422(inner) => inner.$fn(), + #[cfg(any(feature = "any-module", feature = "sara-r5"))] + Self::SaraR5(inner) => inner.$fn(), + #[cfg(any(feature = "any-module", feature = "sara-u201"))] + Self::SaraU201(inner) => inner.$fn(), + #[cfg(any(feature = "any-module", feature = "toby-r2"))] + Self::TobyR2(inner) => inner.$fn(), + Self::Generic(inner) => inner.$fn(), + } + }; +} + +impl ModuleParams for Module { + fn power_on_pull_time(&self) -> Option { + inner!(self, power_on_pull_time) + } + + fn power_off_pull_time(&self) -> Duration { + inner!(self, power_off_pull_time) + } + + fn boot_wait(&self) -> Duration { + inner!(self, boot_wait) + } + + fn power_down_wait(&self) -> Duration { + inner!(self, power_down_wait) + } + + fn reboot_command_wait(&self) -> Duration { + inner!(self, reboot_command_wait) + } + + fn command_delay_default(&self) -> Duration { + inner!(self, command_delay_default) + } + + fn radio_off_cfun(&self) -> Functionality { + inner!(self, radio_off_cfun) + } + + fn reset_hold(&self) -> Duration { + inner!(self, reset_hold) + } + + fn max_num_simultaneous_rats(&self) -> u8 { + inner!(self, max_num_simultaneous_rats) + } + + fn at_c_fun_reboot_command(&self) -> Functionality { + inner!(self, at_c_fun_reboot_command) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Generic; + +impl ModuleParams for Generic {} diff --git a/src/modules/sara_r410m.rs b/src/modules/sara_r410m.rs new file mode 100644 index 0000000..b7317d6 --- /dev/null +++ b/src/modules/sara_r410m.rs @@ -0,0 +1,21 @@ +use super::ModuleParams; +use crate::command::mobile_control::types::Functionality; +use embassy_time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct SaraR410m; + +impl ModuleParams for SaraR410m { + fn power_on_pull_time(&self) -> Option { + Some(Duration::from_millis(300)) + } + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(2000) + } + fn boot_wait(&self) -> Duration { + Duration::from_secs(6) + } + fn max_num_simultaneous_rats(&self) -> u8 { + 2 + } +} diff --git a/src/modules/sara_r412m.rs b/src/modules/sara_r412m.rs new file mode 100644 index 0000000..7a2adc9 --- /dev/null +++ b/src/modules/sara_r412m.rs @@ -0,0 +1,28 @@ +use super::ModuleParams; +use crate::command::mobile_control::types::Functionality; +use embassy_time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct SaraR412m; + +impl ModuleParams for SaraR412m { + fn power_on_pull_time(&self) -> Option { + Some(Duration::from_millis(300)) + } + + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(2000) + } + fn boot_wait(&self) -> Duration { + Duration::from_secs(6) + } + fn reboot_command_wait(&self) -> Duration { + Duration::from_secs(10) + } + fn max_num_simultaneous_rats(&self) -> u8 { + 3 + } + fn at_c_fun_reboot_command(&self) -> Functionality { + Functionality::SilentReset + } +} diff --git a/src/modules/sara_r422.rs b/src/modules/sara_r422.rs new file mode 100644 index 0000000..8847c36 --- /dev/null +++ b/src/modules/sara_r422.rs @@ -0,0 +1,24 @@ +use super::ModuleParams; +use crate::command::mobile_control::types::Functionality; +use embassy_time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct SaraR422; + +impl ModuleParams for SaraR422 { + fn power_on_pull_time(&self) -> Duration { + Duration::from_millis(300) + } + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(2000) + } + fn reboot_command_wait(&self) -> Duration { + Duration::from_secs(10) + } + fn command_delay_default(&self) -> Duration { + Duration::from_millis(20) + } + fn max_num_simultaneous_rats(&self) -> u8 { + 3 + } +} diff --git a/src/modules/sara_r5.rs b/src/modules/sara_r5.rs new file mode 100644 index 0000000..3fc3ef8 --- /dev/null +++ b/src/modules/sara_r5.rs @@ -0,0 +1,33 @@ +use super::ModuleParams; +use crate::command::mobile_control::types::Functionality; +use embassy_time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct SaraR5; + +impl ModuleParams for SaraR5 { + fn power_on_pull_time(&self) -> Option { + Some(Duration::from_millis(1500)) + } + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(2000) + } + fn boot_wait(&self) -> Duration { + Duration::from_secs(6) + } + fn power_down_wait(&self) -> Duration { + Duration::from_secs(20) + } + fn reboot_command_wait(&self) -> Duration { + Duration::from_secs(15) + } + fn command_delay_default(&self) -> Duration { + Duration::from_millis(20) + } + fn reset_hold(&self) -> Duration { + Duration::from_millis(150) + } + fn at_c_fun_reboot_command(&self) -> Functionality { + Functionality::SilentResetWithSimReset + } +} diff --git a/src/modules/sara_u201.rs b/src/modules/sara_u201.rs new file mode 100644 index 0000000..4ba89e1 --- /dev/null +++ b/src/modules/sara_u201.rs @@ -0,0 +1,27 @@ +use super::ModuleParams; +use crate::command::mobile_control::types::Functionality; +use embassy_time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct SaraU201; + +impl ModuleParams for SaraU201 { + fn power_on_pull_time(&self) -> Duration { + Duration::from_millis(1) + } + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(1500) + } + fn power_down_wait(&self) -> Duration { + Duration::from_secs(5) + } + fn command_delay_default(&self) -> Duration { + Duration::from_millis(20) + } + fn reset_hold(&self) -> Duration { + Duration::from_millis(75) + } + fn max_num_simultaneous_rats(&self) -> u8 { + 2 + } +} diff --git a/src/modules/toby_r2.rs b/src/modules/toby_r2.rs new file mode 100644 index 0000000..7170a64 --- /dev/null +++ b/src/modules/toby_r2.rs @@ -0,0 +1,25 @@ +use super::ModuleParams; +use crate::command::mobile_control::types::Functionality; +use embassy_time::Duration; + +#[derive(Debug, Clone, Copy)] +pub struct TobyR2; + +impl ModuleParams for TobyR2 { + fn power_on_pull_time(&self) -> Option { + Some(Duration::from_micros(50)) + } + fn power_off_pull_time(&self) -> Duration { + Duration::from_millis(1000) + } + fn command_delay_default(&self) -> Duration { + Duration::from_millis(20) + } + fn reset_hold(&self) -> Duration { + Duration::from_millis(50) + } + fn max_num_simultaneous_rats(&self) -> u8 { + // TODO: Is this correct? + 3 + } +} diff --git a/src/registration.rs b/src/registration.rs new file mode 100644 index 0000000..db2cf99 --- /dev/null +++ b/src/registration.rs @@ -0,0 +1,354 @@ +use core::default; + +use crate::command::{ + network_service::{ + responses::NetworkRegistrationStatus, + types::{NetworkRegistrationStat, RatAct}, + urc::NetworkRegistration, + }, + psn::{ + responses::{EPSNetworkRegistrationStatus, GPRSNetworkRegistrationStatus}, + types::{EPSNetworkRegistrationStat, GPRSNetworkRegistrationStat}, + urc::{EPSNetworkRegistration, GPRSNetworkRegistration}, + }, +}; +use embassy_time::{Duration, Instant}; +use heapless::String; + +#[derive(Debug, Clone, Default)] +pub struct CellularRegistrationStatus { + status: Status, + updated: Option, + started: Option, +} + +impl CellularRegistrationStatus { + pub const fn new() -> Self { + Self { + status: Status::None, + updated: None, + started: None, + } + } + + pub fn duration(&self, ts: Instant) -> Duration { + self.started + .and_then(|started| ts.checked_duration_since(started)) + .unwrap_or_else(|| Duration::from_millis(0)) + } + + pub fn started(&self) -> Option { + self.started + } + + pub fn updated(&self) -> Option { + self.updated + } + + pub fn reset(&mut self) { + self.status = Status::None; + self.updated = None; + self.started = None; + } + + pub fn get_status(&self) -> Status { + self.status + } + + pub fn set_status(&mut self, stat: Status) { + let ts = Instant::now(); + if self.status != stat { + self.status = stat; + self.started = Some(ts); + } + self.updated = Some(ts); + } + + pub fn registered(&self) -> bool { + matches!(self.status, Status::Home | Status::Roaming) + } + + pub fn sticky(&self) -> bool { + self.updated.is_some() && self.updated != self.started + } +} + +impl From for Status { + fn from(v: u8) -> Self { + match v { + 0 => Self::NotRegistering, + 1 => Self::Home, + 2 => Self::Searching, + 3 => Self::Denied, + 4 => Self::OutOfCoverage, + 5 => Self::Roaming, + _ => Self::None, + } + } +} + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Status { + #[default] + None, + NotRegistering, + Home, + Searching, + Denied, + OutOfCoverage, + Roaming, +} + +/// Convert the 3GPP registration status from a CREG URC to [`RegistrationStatus`]. +impl From for Status { + fn from(v: NetworkRegistrationStat) -> Self { + Self::from(v as u8) + } +} + +/// Convert the 3GPP registration status from a CGREG URC to [`RegistrationStatus`]. +impl From for Status { + fn from(v: GPRSNetworkRegistrationStat) -> Self { + Self::from(v as u8) + } +} + +/// Convert the 3GPP registration status from a CEREG URC to [`RegistrationStatus`]. +impl From for Status { + fn from(v: EPSNetworkRegistrationStat) -> Self { + Self::from(v as u8) + } +} + +#[cfg(not(feature = "use-upsd-context-activation"))] +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ProfileState { + #[default] + Unknown, + ShouldBeUp, + RequiresReactivation, + ShouldBeDown, +} + +#[derive(Debug, Default)] +pub struct RegistrationParams { + reg_type: RegType, + pub(crate) status: Status, + act: RatAct, + + cell_id: Option>, + lac: Option>, +} + +#[derive(Debug, Default, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RegType { + Creg, + Cgreg, + Cereg, + #[default] + Unknown, +} + +impl From for RegType { + fn from(ran: RadioAccessNetwork) -> Self { + match ran { + RadioAccessNetwork::UnknownUnused => Self::Unknown, + RadioAccessNetwork::Geran => Self::Creg, + RadioAccessNetwork::Utran => Self::Cgreg, + RadioAccessNetwork::Eutran => Self::Cereg, + } + } +} + +impl From for RadioAccessNetwork { + fn from(regtype: RegType) -> Self { + match regtype { + RegType::Unknown => Self::UnknownUnused, + RegType::Creg => Self::Geran, + RegType::Cgreg => Self::Utran, + RegType::Cereg => Self::Eutran, + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct CellularGlobalIdentity { + /// Registered network operator cell Id. + cell_id: Option>, + /// Registered network operator Location Area Code. + lac: Option>, + // Registered network operator Routing Area Code. + // rac: u8, + // Registered network operator Tracking Area Code. + // tac: u8, +} + +impl CellularGlobalIdentity { + pub const fn new() -> Self { + Self { + cell_id: None, + lac: None, + } + } +} + +#[derive(Debug, Clone)] +pub struct RegistrationState { + /// CSD (Circuit Switched Data) registration status (registered/searching/roaming etc.). + pub(crate) csd: CellularRegistrationStatus, + /// PSD (Packet Switched Data) registration status (registered/searching/roaming etc.). + pub(crate) psd: CellularRegistrationStatus, + /// EPS (Evolved Packet Switched) registration status (registered/searching/roaming etc.). + pub(crate) eps: CellularRegistrationStatus, + + pub(crate) cgi: CellularGlobalIdentity, + + #[cfg(not(feature = "use-upsd-context-activation"))] + pub(crate) profile_state: ProfileState, +} + +impl RegistrationState { + pub const fn new() -> Self { + Self { + csd: CellularRegistrationStatus::new(), + psd: CellularRegistrationStatus::new(), + eps: CellularRegistrationStatus::new(), + cgi: CellularGlobalIdentity::new(), + + #[cfg(not(feature = "use-upsd-context-activation"))] + profile_state: ProfileState::Unknown, + } + } + + /// Determine if a given cellular network status value means that we're + /// registered with the network. + pub fn is_registered(&self) -> bool { + // If PSD or EPS are registered, we are connected! + self.psd.registered() || self.eps.registered() + } + + pub fn reset(&mut self) { + self.csd.reset(); + self.psd.reset(); + self.eps.reset(); + } + + pub fn compare_and_set(&mut self, new_params: RegistrationParams) { + match new_params.reg_type { + RegType::Creg => { + self.csd.set_status(new_params.status); + } + RegType::Cgreg => { + self.psd.set_status(new_params.status); + } + RegType::Cereg => { + self.eps.set_status(new_params.status); + } + RegType::Unknown => { + error!("unknown reg type"); + return; + } + } + + // Update Cellular Global Identity + if new_params.cell_id.is_some() && self.cgi.cell_id != new_params.cell_id { + self.cgi.cell_id = new_params.cell_id.clone(); + self.cgi.lac = new_params.lac; + } + } +} + +impl From for RegistrationParams { + fn from(v: NetworkRegistration) -> Self { + Self { + act: RatAct::Gsm, + reg_type: RegType::Creg, + status: v.stat.into(), + cell_id: None, + lac: None, + } + } +} + +impl From for RegistrationParams { + fn from(v: NetworkRegistrationStatus) -> Self { + Self { + act: RatAct::Gsm, + reg_type: RegType::Creg, + status: v.stat.into(), + cell_id: None, + lac: None, + } + } +} + +impl From for RegistrationParams { + fn from(v: GPRSNetworkRegistration) -> Self { + Self { + act: v.act.unwrap_or(RatAct::GsmGprsEdge), + reg_type: RegType::Cgreg, + status: v.stat.into(), + cell_id: v.ci, + lac: v.lac, + } + } +} + +impl From for RegistrationParams { + fn from(v: GPRSNetworkRegistrationStatus) -> Self { + Self { + reg_type: RegType::Cgreg, + status: v.stat.into(), + cell_id: v.ci, + lac: v.lac, + act: v.act.unwrap_or(RatAct::GsmGprsEdge), + } + } +} + +impl From for RegistrationParams { + fn from(v: EPSNetworkRegistration) -> Self { + Self { + reg_type: RegType::Cereg, + status: v.stat.into(), + cell_id: v.ci, + lac: v.tac, + act: v.act.unwrap_or(RatAct::Lte), + } + } +} + +impl From for RegistrationParams { + fn from(v: EPSNetworkRegistrationStatus) -> Self { + Self { + reg_type: RegType::Cereg, + status: v.stat.into(), + cell_id: v.ci, + lac: v.tac, + act: v.act.unwrap_or(RatAct::Lte), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RadioAccessNetwork { + UnknownUnused = 0, + Geran = 1, + Utran = 2, + Eutran = 3, +} + +impl From for RadioAccessNetwork { + fn from(v: usize) -> Self { + match v { + 1 => Self::Geran, + 2 => Self::Utran, + 3 => Self::Eutran, + _ => Self::UnknownUnused, + } + } +} From 584bc40b955be90083d002cb849dc8584b549e8c Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 27 May 2024 11:19:55 +0200 Subject: [PATCH 12/22] Change modelId to borrowed --- src/asynch/network.rs | 7 ++++++- src/modules/mod.rs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/asynch/network.rs b/src/asynch/network.rs index b145364..6e432b3 100644 --- a/src/asynch/network.rs +++ b/src/asynch/network.rs @@ -6,7 +6,9 @@ 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; @@ -436,7 +438,10 @@ where .map_err(|_| Error::PoweredDown)?; let model_id = self.at_client.send(&GetModelId).await?; - self.ch.set_module(Module::from_model_id(model_id)); + self.ch.set_module(Module::from_model_id(&model_id)); + + let FirmwareVersion { version } = self.at_client.send(&GetFirmwareVersion).await?; + info!("Found module to be: {=[u8]:a}, {=[u8]:a}", model_id.model.as_slice(), version.as_slice()); // Echo off self.at_client.send(&SetEcho { enabled: Echo::Off }).await?; diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 9ced7b1..1f6aff4 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -96,7 +96,7 @@ pub(crate) enum Module { } impl Module { - pub fn from_model_id(model_id: ModelId) -> Self { + pub fn from_model_id(model_id: &ModelId) -> Self { match model_id.model.as_slice() { b"LARA-R6001D" => Self::LaraR6(lara_r6::LaraR6), id => { From 52f9c70b8478b7a886ab189ed66bee96066bc3fd Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 17 Jun 2024 14:49:47 +0200 Subject: [PATCH 13/22] Bump embassy-sync to 0.6 --- Cargo.toml | 3 +-- examples/embassy-rp2040-example/Cargo.toml | 2 +- examples/tokio-std-example/Cargo.toml | 2 +- src/asynch/network.rs | 10 ++++++--- src/asynch/runner.rs | 25 ++++++++++++++++++++-- src/asynch/state.rs | 2 -- 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fee7a9c..2505611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,11 @@ doctest = false [dependencies] atat = { version = "0.22", features = ["derive", "bytes"] } heapless = { version = "^0.8", features = ["serde"] } -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.3" -embassy-sync = "0.5" +embassy-sync = "0.6" no-std-net = { version = "^0.6", features = ["serde"] } diff --git a/examples/embassy-rp2040-example/Cargo.toml b/examples/embassy-rp2040-example/Cargo.toml index 238845f..12ed225 100644 --- a/examples/embassy-rp2040-example/Cargo.toml +++ b/examples/embassy-rp2040-example/Cargo.toml @@ -23,7 +23,7 @@ embassy-time = { version = "0.3", features = [ "defmt-timestamp-uptime", ] } embassy-futures = { version = "0.1", features = ["defmt"] } -embassy-sync = { version = "0.5", features = ["defmt"] } +embassy-sync = { version = "0.6", features = ["defmt"] } embassy-net = { version = "0.4", features = [ "defmt", diff --git a/examples/tokio-std-example/Cargo.toml b/examples/tokio-std-example/Cargo.toml index fc30306..0966950 100644 --- a/examples/tokio-std-example/Cargo.toml +++ b/examples/tokio-std-example/Cargo.toml @@ -9,7 +9,7 @@ tokio-serial = "5.4.1" embassy-time = { version = "0.3", features = ["std", "generic-queue"] } embassy-futures = { version = "0.1", features = ["log"] } -embassy-sync = { version = "0.5", features = ["log"] } +embassy-sync = { version = "0.6", features = ["log"] } embassy-net = { version = "0.4", features = [ "log", diff --git a/src/asynch/network.rs b/src/asynch/network.rs index 6e432b3..f7c5dee 100644 --- a/src/asynch/network.rs +++ b/src/asynch/network.rs @@ -317,7 +317,7 @@ where 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::AllFiles, + fs_op: crate::command::system_features::types::FSFactoryRestoreType::NoRestore, nvm_op: crate::command::system_features::types::NVMFactoryRestoreType::NVMFlashSectors, }) @@ -439,9 +439,13 @@ where let model_id = self.at_client.send(&GetModelId).await?; self.ch.set_module(Module::from_model_id(&model_id)); - + let FirmwareVersion { version } = self.at_client.send(&GetFirmwareVersion).await?; - info!("Found module to be: {=[u8]:a}, {=[u8]:a}", model_id.model.as_slice(), version.as_slice()); + info!( + "Found module to be: {=[u8]:a}, {=[u8]:a}", + model_id.model.as_slice(), + version.as_slice() + ); // Echo off self.at_client.send(&SetEcho { enabled: Echo::Off }).await?; diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 88b5427..613701a 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -142,7 +142,7 @@ where } } - pub async fn run(mut self, on_ipv4_up: impl FnMut(embassy_net_ppp::Ipv4Status) + Copy) -> ! { + pub async fn run(mut self, stack: &embassy_net::Stack) -> ! { #[cfg(feature = "ppp")] let mut ppp_runner = self.ppp_runner.take().unwrap(); @@ -262,7 +262,28 @@ where info!("RUNNING PPP"); let res = ppp_runner - .run(&mut self.data_channel, C::PPP_CONFIG, on_ipv4_up) + .run(&mut self.data_channel, C::PPP_CONFIG, |ipv4| { + let Some(addr) = ipv4.address else { + warn!("PPP did not provide an IP address."); + return; + }; + let mut dns_servers = heapless::Vec::new(); + for s in ipv4.dns_servers.iter().flatten() { + let _ = + dns_servers.push(embassy_net::Ipv4Address::from_bytes(&s.0)); + } + let config = + embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new( + embassy_net::Ipv4Address::from_bytes(&addr.0), + 0, + ), + gateway: None, + dns_servers, + }); + + stack.set_config_v4(config); + }) .await; info!("ppp failed: {:?}", res); diff --git a/src/asynch/state.rs b/src/asynch/state.rs index d558e2b..bf72562 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -10,8 +10,6 @@ use embassy_sync::blocking_mutex::Mutex; use embassy_sync::pubsub::PubSubChannel; use embassy_sync::waitqueue::WakerRegistration; -const MAX_STATE_LISTENERS: usize = 5; - /// The link state of a network device. #[derive(PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] From dd4328c7000124a9c04939462b327f3a295afdd5 Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 17 Jun 2024 14:58:02 +0200 Subject: [PATCH 14/22] Use git dependencies --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2505611..4a138c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ embedded-hal = "1.0.0" embedded-nal = "0.8" embedded-nal-async = { version = "0.7" } -embassy-at-cmux = { path = "../embassy/embassy-at-cmux", optional = true } +embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a", optional = true } embassy-net-ppp = { version = "0.1", optional = true } embassy-net = { version = "0.4", features = [ "proto-ipv4", @@ -142,14 +142,14 @@ exclude = ["examples"] #ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", branch = "feature/async-borrowed-sockets" } no-std-net = { git = "https://github.com/rushmorem/no-std-net", branch = "issue-15" } #ublox-sockets = { path = "../ublox-sockets" } -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-net-ppp = { git = "https://github.com/embassy-rs/embassy", branch = "main" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", branch = "main" } +embassy-time = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } +embassy-sync = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } +embassy-futures = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } +embassy-net-ppp = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } +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" } \ No newline at end of file +atat = { path = "../atat/atat" } From c7a1ef408feed7bf92a287ece2cf2745724e6a3e Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 21 Jun 2024 14:00:17 +0200 Subject: [PATCH 15/22] Allow setting AT+UEMBPF when using PPP --- Cargo.toml | 4 +- src/asynch/control.rs | 41 +++++-------- src/asynch/mod.rs | 2 +- src/asynch/network.rs | 91 ++++++++++++++++++----------- src/asynch/runner.rs | 64 ++++++++++++++------ src/asynch/state.rs | 26 +++++++-- src/command/gpio/mod.rs | 2 +- src/command/mod.rs | 1 + src/command/networking/mod.rs | 36 ++++++++++++ src/command/networking/responses.rs | 13 +++++ src/command/networking/types.rs | 74 +++++++++++++++++++++++ src/command/psn/mod.rs | 32 +++++----- src/config.rs | 8 ++- src/fmt.rs | 85 ++++++++++++++++++--------- 14 files changed, 347 insertions(+), 132 deletions(-) create mode 100644 src/command/networking/mod.rs create mode 100644 src/command/networking/responses.rs create mode 100644 src/command/networking/types.rs diff --git a/Cargo.toml b/Cargo.toml index 4a138c2..850dc8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,8 @@ embedded-hal = "1.0.0" 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 = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a", optional = true } +embassy-at-cmux = { path = "../embassy/embassy-at-cmux", optional = true } embassy-net-ppp = { version = "0.1", optional = true } embassy-net = { version = "0.4", features = [ "proto-ipv4", @@ -119,6 +120,7 @@ log = ["dep:log", "ublox-sockets?/log", "atat/log"] # # Note: if you add a new module type here, you also need to add it in # `modules.rs` +any-module = [] lara-r6 = ["ucged"] lena-r8 = [] sara-r410m = ["ucged", "ucged5"] diff --git a/src/asynch/control.rs b/src/asynch/control.rs index 9126492..b1c69fc 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -1,49 +1,38 @@ -use atat::asynch::AtatClient; +use super::state::{self, LinkState, OperationState}; -use crate::error::Error; - -use super::state::{LinkState, OperationState}; -use super::{state, AtHandle}; - -pub struct Control<'a, AT: AtatClient> { +pub struct Control<'a> { state_ch: state::Runner<'a>, - at: AtHandle<'a, AT>, } -impl<'a, AT: AtatClient> Control<'a, AT> { - pub(crate) fn new(state_ch: state::Runner<'a>, at: AtHandle<'a, AT>) -> Self { - Self { state_ch, at } - } - - pub(crate) async fn init(&mut self) -> Result<(), Error> { - debug!("Initalizing ublox control"); - - Ok(()) +impl<'a> Control<'a> { + pub(crate) fn new(state_ch: state::Runner<'a>) -> Self { + Self { state_ch } } pub fn link_state(&mut self) -> LinkState { - self.state_ch.link_state() + self.state_ch.link_state(None) } pub fn operation_state(&mut self) -> OperationState { - self.state_ch.operation_state() + self.state_ch.operation_state(None) } pub fn desired_state(&mut self) -> OperationState { - self.state_ch.desired_state() + self.state_ch.desired_state(None) } - pub async fn set_desired_state(&mut self, ps: OperationState) { - self.state_ch.set_desired_state(ps).await; + pub fn set_desired_state(&mut self, ps: OperationState) { + self.state_ch.set_desired_state(ps); } - pub async fn wait_for_desired_state( - &mut self, - ps: OperationState, - ) -> Result { + pub async fn wait_for_desired_state(&mut self, ps: OperationState) { self.state_ch.wait_for_desired_state(ps).await } + pub async fn wait_for_operation_state(&mut self, ps: OperationState) { + self.state_ch.wait_for_operation_state(ps).await + } + // pub async fn get_signal_quality( // &mut self, // ) -> Result { diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 4921391..a51d4f9 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,4 +1,4 @@ -// pub mod control; +pub mod control; mod network; mod resources; pub mod runner; diff --git a/src/asynch/network.rs b/src/asynch/network.rs index f7c5dee..5e11dae 100644 --- a/src/asynch/network.rs +++ b/src/asynch/network.rs @@ -19,6 +19,8 @@ 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; @@ -437,44 +439,25 @@ where .await .map_err(|_| Error::PoweredDown)?; - let model_id = self.at_client.send(&GetModelId).await?; + 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(&GetFirmwareVersion).await?; + 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() ); - // Echo off - self.at_client.send(&SetEcho { enabled: Echo::Off }).await?; - - // Extended errors on self.at_client - .send(&SetReportMobileTerminationError { - n: TerminationErrorMode::Enabled, + .send_retry(&SetEmbeddedPortFiltering { + mode: C::EMBEDDED_PORT_FILTERING, }) .await?; - #[cfg(feature = "internal-network-stack")] - if C::HEX_MODE { - self.at_client - .send(&crate::command::ip_transport_layer::SetHexMode { - hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Enabled, - }) - .await?; - } else { - self.at_client - .send(&crate::command::ip_transport_layer::SetHexMode { - hex_mode_disable: crate::command::ip_transport_layer::types::HexMode::Disabled, - }) - .await?; - } - // FIXME: The following three GPIO settings should not be here! self.at_client - .send(&SetGpioConfiguration { + .send_retry(&SetGpioConfiguration { gpio_id: 23, gpio_mode: GpioMode::NetworkStatus, }) @@ -482,7 +465,7 @@ where // Select SIM self.at_client - .send(&SetGpioConfiguration { + .send_retry(&SetGpioConfiguration { gpio_id: 25, gpio_mode: GpioMode::Output(GpioOutValue::Low), }) @@ -490,33 +473,71 @@ where #[cfg(feature = "lara-r6")] self.at_client - .send(&SetGpioConfiguration { + .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(&IdentificationInformation { n: 9 }) + // .send_retry(&IdentificationInformation { n: 9 }) // .await?; // DCD circuit (109) changes in accordance with the carrier self.at_client - .send(&SetCircuit109Behaviour { + .send_retry(&SetCircuit109Behaviour { value: Circuit109Behaviour::AlwaysPresent, }) .await?; // Ignore changes to DTR self.at_client - .send(&SetCircuit108Behaviour { + .send_retry(&SetCircuit108Behaviour { value: Circuit108Behaviour::Ignore, }) .await?; self.check_sim_status().await?; - let ccid = self.at_client.send(&GetCCID).await?; + let ccid = self.at_client.send_retry(&GetCCID).await?; info!("CCID: {}", ccid.ccid); #[cfg(all( @@ -529,7 +550,7 @@ where ) ))] self.at_client - .send(&SetChannelAndNetworkEnvDesc { + .send_retry(&SetChannelAndNetworkEnvDesc { mode: if cfg!(feature = "ucged5") { 5 } else { 2 }, }) .await?; @@ -537,13 +558,13 @@ where // Tell module whether we support flow control if C::FLOW_CONTROL { self.at_client - .send(&SetFlowControl { + .send_retry(&SetFlowControl { value: FlowControl::RtsCts, }) .await?; } else { self.at_client - .send(&SetFlowControl { + .send_retry(&SetFlowControl { value: FlowControl::Disabled, }) .await?; @@ -551,7 +572,7 @@ where // Switch off UART power saving until it is integrated into this API self.at_client - .send(&SetPowerSavingControl { + .send_retry(&SetPowerSavingControl { mode: PowerSavingMode::Disabled, timeout: None, }) @@ -559,7 +580,7 @@ where if !self.ch.is_registered(None) { self.at_client - .send(&SetModuleFunctionality { + .send_retry(&SetModuleFunctionality { fun: self .ch .module() diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 613701a..3093cdb 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -7,6 +7,7 @@ use crate::command::psn::EnterPPP; use crate::command::AT; use crate::{command::Urc, config::CellularConfig}; +use super::control::Control; use super::state; use super::urc_handler::UrcHandler; use super::Resources; @@ -63,7 +64,11 @@ pub struct Runner<'a, R, W, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY pub mux_runner: embassy_at_cmux::Runner<'a, CMUX_CHANNELS, CMUX_CHANNEL_SIZE>, #[cfg(feature = "cmux")] - network_channel: embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, + 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>, @@ -113,7 +118,7 @@ where mux_runner, #[cfg(feature = "cmux")] - network_channel: channel_iter.next().unwrap(), + network_channel: channel_iter.next().unwrap().split(), #[cfg(feature = "cmux")] data_channel: channel_iter.next().unwrap(), @@ -142,13 +147,14 @@ where } } - pub async fn run(mut self, stack: &embassy_net::Stack) -> ! { - #[cfg(feature = "ppp")] - let mut ppp_runner = self.ppp_runner.take().unwrap(); - - #[cfg(feature = "cmux")] - let (mut at_rx, mut at_tx, _) = self.network_channel.split(); + pub fn control(&self) -> Control<'a> { + Control::new(self.ch.clone()) + } + 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 @@ -167,15 +173,20 @@ where ); 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(); - if cell_device - .run_to_state(OperationState::PowerDown) - .await - .is_err() - { - continue; + + 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; + } } match embassy_futures::select::select3( @@ -209,6 +220,13 @@ where 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. @@ -261,7 +279,10 @@ where // self.data_channel.set_hangup_detection(0x04, 0x00); info!("RUNNING PPP"); - let res = ppp_runner + let res = self + .ppp_runner + .as_mut() + .unwrap() .run(&mut self.data_channel, C::PPP_CONFIG, |ipv4| { let Some(addr) = ipv4.address else { warn!("PPP did not provide an IP address."); @@ -304,6 +325,10 @@ where #[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); @@ -354,8 +379,11 @@ where #[cfg(not(feature = "cmux"))] let (mut at_rx, mut at_tx) = self.iface; + #[cfg(feature = "cmux")] + let (at_rx, at_tx, _) = &mut self.network_channel; + let at_client = - atat::asynch::Client::new(&mut at_tx, self.res_slot, self.cmd_buf, at_config); + 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 urc_handler = UrcHandler::new(&self.ch, self.urc_channel); @@ -363,7 +391,7 @@ where // TODO: Should we set ATE0 and CMEE=1 here, again? embassy_futures::join::join3( - self.ingress.read_from(&mut at_rx), + self.ingress.read_from(at_rx), cell_device.run(), urc_handler.run(), ) diff --git a/src/asynch/state.rs b/src/asynch/state.rs index bf72562..42ee2c3 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -61,7 +61,7 @@ impl State { link_state: LinkState::Down, operation_state: OperationState::PowerDown, module: None, - desired_state: OperationState::PowerDown, + desired_state: OperationState::DataEstablished, registration_state: RegistrationState::new(), state_waker: WakerRegistration::new(), registration_waker: WakerRegistration::new(), @@ -164,7 +164,7 @@ impl<'d> Runner<'d> { }); } - pub fn operation_state(&mut self, cx: Option<&mut Context>) -> OperationState { + pub fn operation_state(&self, cx: Option<&mut Context>) -> OperationState { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); if let Some(cx) = cx { @@ -193,6 +193,10 @@ impl<'d> Runner<'d> { } pub async fn wait_for_desired_state(&mut self, ps: OperationState) { + if self.desired_state(None) == ps { + return; + } + poll_fn(|cx| { if self.desired_state(Some(cx)) == ps { return Poll::Ready(()); @@ -202,7 +206,21 @@ impl<'d> Runner<'d> { .await } - pub async fn wait_for_desired_state_change(&mut self) -> OperationState { + pub async fn wait_for_operation_state(&self, ps: OperationState) { + if self.operation_state(None) == ps { + return; + } + + poll_fn(|cx| { + if self.operation_state(Some(cx)) == ps { + return Poll::Ready(()); + } + Poll::Pending + }) + .await + } + + pub async fn wait_for_desired_state_change(&self) -> OperationState { let old_desired = self.shared.lock(|s| s.borrow().desired_state); poll_fn(|cx| { @@ -215,7 +233,7 @@ impl<'d> Runner<'d> { .await } - pub async fn wait_registration_change(&mut self) -> bool { + pub async fn wait_registration_change(&self) -> bool { let old_state = self .shared .lock(|s| s.borrow().registration_state.is_registered()); diff --git a/src/command/gpio/mod.rs b/src/command/gpio/mod.rs index 28e9d89..bf47f51 100644 --- a/src/command/gpio/mod.rs +++ b/src/command/gpio/mod.rs @@ -23,7 +23,7 @@ use super::NoResponse; /// The test command provides the list of the supported GPIOs, the supported functions and the status of all the /// GPIOs. #[derive(Clone, AtatCmd)] -#[at_cmd("+UGPIOC", NoResponse)] +#[at_cmd("+UGPIOC", NoResponse, timeout_ms = 3000)] pub struct SetGpioConfiguration { /// GPIO pin identifier: pin number /// See the GPIO mapping for the available GPIO pins, their mapping and factoryprogrammed values on different u-blox cellular modules series and product version. diff --git a/src/command/mod.rs b/src/command/mod.rs index 136ece2..2cb2c39 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -15,6 +15,7 @@ pub mod ip_transport_layer; pub mod ipc; pub mod mobile_control; pub mod network_service; +pub mod networking; pub mod psn; pub mod sms; pub mod system_features; diff --git a/src/command/networking/mod.rs b/src/command/networking/mod.rs new file mode 100644 index 0000000..0ec06f4 --- /dev/null +++ b/src/command/networking/mod.rs @@ -0,0 +1,36 @@ +//! ### 34 - Networking +pub mod responses; +pub mod types; + +use super::NoResponse; +use atat::atat_derive::AtatCmd; + +use responses::EmbeddedPortFiltering; +use types::EmbeddedPortFilteringMode; + +/// 34.4 Configure port filtering for embedded applications +UEMBPF +/// +/// Enables/disables port filtering to allow IP data traffic from embedded +/// applications when a dial-up connection is active. When enabled, the +/// application will pick source port inside the configured range and the +/// incoming traffic to those ports will be directed to embedded application +/// instead of PPP DTE. +/// +/// **NOTE:** +/// - Each set command overwrites the previous configuration. Only one port +/// range can be configured. +/// - When set command with =0 is issued, the parameter shall +/// not be inserted otherwise the command will return an error result code. +/// - If =0 is configured, the read command will not return any range, but +/// only +UEMBPF: 0 as information text response. +#[derive(Clone, AtatCmd)] +#[at_cmd("+UEMBPF?", EmbeddedPortFiltering)] +pub struct GetEmbeddedPortFiltering; + +/// 34.4 Configure port filtering for embedded applications +UEMBPF +#[derive(Clone, AtatCmd)] +#[at_cmd("+UEMBPF", NoResponse)] +pub struct SetEmbeddedPortFiltering { + #[at_arg(position = 0)] + pub mode: EmbeddedPortFilteringMode, +} diff --git a/src/command/networking/responses.rs b/src/command/networking/responses.rs new file mode 100644 index 0000000..b56b514 --- /dev/null +++ b/src/command/networking/responses.rs @@ -0,0 +1,13 @@ +//! 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)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EmbeddedPortFiltering { + #[at_arg(position = 0)] + pub mode: u8, + #[at_arg(position = 1)] + pub port_range: heapless::String<16>, +} diff --git a/src/command/networking/types.rs b/src/command/networking/types.rs new file mode 100644 index 0000000..44d73cd --- /dev/null +++ b/src/command/networking/types.rs @@ -0,0 +1,74 @@ +//! Argument and parameter types used by Networking Commands and Responses +use core::fmt::Write; + +use atat::AtatLen; +use serde::{Serialize, Serializer}; + +/// Port filtering enable/disable +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EmbeddedPortFilteringMode { + /// 0: disable. The configured range is removed. + Disable, + /// 1: enable. The parameter is mandatory + Enable(u16, u16), +} + +impl AtatLen for EmbeddedPortFilteringMode { + const LEN: usize = 20; +} + +impl Serialize for EmbeddedPortFilteringMode { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: Serializer, + { + match *self { + EmbeddedPortFilteringMode::Disable => Serializer::serialize_u8(serializer, 0), + EmbeddedPortFilteringMode::Enable(port_start, port_end) => { + let mut serde_state = + atat::serde_at::serde::ser::Serializer::serialize_tuple_variant( + serializer, + "EmbeddedPortFilteringMode", + 1 as u32, + "Enable", + 0, + )?; + atat::serde_at::serde::ser::SerializeTupleVariant::serialize_field( + &mut serde_state, + &{ + let mut str = heapless::String::<16>::new(); + str.write_fmt(format_args!("{}-{}", port_start, port_end)) + .ok(); + str + }, + )?; + atat::serde_at::serde::ser::SerializeTupleVariant::end(serde_state) + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use atat::serde_at::ser::to_slice; + + #[test] + fn serialize_embedded_port_filtering_mode() { + let options = atat::serde_at::SerializeOptions { + value_sep: false, + ..atat::serde_at::SerializeOptions::default() + }; + let mut buf = [0u8; 32]; + let s = to_slice( + &EmbeddedPortFilteringMode::Enable(6000, 6200), + "", + &mut buf, + options, + ) + .unwrap(); + + assert_eq!(&buf[..s], b"1,\"6000-6200\"") + } +} diff --git a/src/command/psn/mod.rs b/src/command/psn/mod.rs index 013ab13..c8aaf4b 100644 --- a/src/command/psn/mod.rs +++ b/src/command/psn/mod.rs @@ -474,38 +474,38 @@ pub struct SetAuthParameters<'a> { // For SARA-R4 and LARA-R6 modules the username and parameters are reversed #[cfg_attr( any( - feature = "sara_r410m", - feature = "sara_r412m", - feature = "sara_r422", - feature = "lara_r6" + feature = "sara-r410m", + feature = "sara-r412m", + feature = "sara-r422", + feature = "lara-r6" ), at_arg(position = 3, len = 64) )] #[cfg_attr( not(any( - feature = "sara_r410m", - feature = "sara_r412m", - feature = "sara_r422", - feature = "lara_r6" + feature = "sara-r410m", + feature = "sara-r412m", + feature = "sara-r422", + feature = "lara-r6" )), at_arg(position = 2, len = 64) )] pub username: &'a str, #[cfg_attr( any( - feature = "sara_r410m", - feature = "sara_r412m", - feature = "sara_r422", - feature = "lara_r6" + feature = "sara-r410m", + feature = "sara-r412m", + feature = "sara-r422", + feature = "lara-r6" ), at_arg(position = 3, len = 64) )] #[cfg_attr( not(any( - feature = "sara_r410m", - feature = "sara_r412m", - feature = "sara_r422", - feature = "lara_r6" + feature = "sara-r410m", + feature = "sara-r412m", + feature = "sara-r422", + feature = "lara-r6" )), at_arg(position = 2, len = 64) )] diff --git a/src/config.rs b/src/config.rs index e8f70c3..a7f6122 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,10 @@ use core::convert::Infallible; use embedded_hal::digital::{ErrorType, InputPin, OutputPin, PinState}; -use crate::command::psn::types::{ContextId, ProfileId}; +use crate::command::{ + networking::types::EmbeddedPortFilteringMode, + psn::types::{ContextId, ProfileId}, +}; pub struct NoPin; @@ -78,6 +81,9 @@ pub trait CellularConfig<'a> { #[cfg(feature = "internal-network-stack")] const HEX_MODE: bool = true; + const EMBEDDED_PORT_FILTERING: EmbeddedPortFilteringMode = + EmbeddedPortFilteringMode::Enable(6000, 6200); + const OPERATOR_FORMAT: OperatorFormat = OperatorFormat::Long; const PROFILE_ID: ProfileId = ProfileId(1); diff --git a/src/fmt.rs b/src/fmt.rs index c06793e..35b929f 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -1,31 +1,12 @@ -// MIT License - -// Copyright (c) 2020 Dario Nieuwenhuis - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - #![macro_use] -#![allow(unused_macros)] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); +#[collapse_debuginfo(yes)] macro_rules! assert { ($($x:tt)*) => { { @@ -37,6 +18,7 @@ macro_rules! assert { }; } +#[collapse_debuginfo(yes)] macro_rules! assert_eq { ($($x:tt)*) => { { @@ -48,6 +30,7 @@ macro_rules! assert_eq { }; } +#[collapse_debuginfo(yes)] macro_rules! assert_ne { ($($x:tt)*) => { { @@ -59,6 +42,7 @@ macro_rules! assert_ne { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert { ($($x:tt)*) => { { @@ -70,6 +54,7 @@ macro_rules! debug_assert { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert_eq { ($($x:tt)*) => { { @@ -81,6 +66,7 @@ macro_rules! debug_assert_eq { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert_ne { ($($x:tt)*) => { { @@ -92,6 +78,7 @@ macro_rules! debug_assert_ne { }; } +#[collapse_debuginfo(yes)] macro_rules! todo { ($($x:tt)*) => { { @@ -103,17 +90,23 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) }; } +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +#[collapse_debuginfo(yes)] macro_rules! panic { ($($x:tt)*) => { { @@ -125,6 +118,7 @@ macro_rules! panic { }; } +#[collapse_debuginfo(yes)] macro_rules! trace { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -138,6 +132,7 @@ macro_rules! trace { }; } +#[collapse_debuginfo(yes)] macro_rules! debug { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -151,6 +146,7 @@ macro_rules! debug { }; } +#[collapse_debuginfo(yes)] macro_rules! info { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -164,6 +160,7 @@ macro_rules! info { }; } +#[collapse_debuginfo(yes)] macro_rules! warn { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -177,6 +174,7 @@ macro_rules! warn { }; } +#[collapse_debuginfo(yes)] macro_rules! error { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -191,6 +189,7 @@ macro_rules! error { } #[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] macro_rules! unwrap { ($($x:tt)*) => { ::defmt::unwrap!($($x)*) @@ -198,6 +197,7 @@ macro_rules! unwrap { } #[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] macro_rules! unwrap { ($arg:expr) => { match $crate::fmt::Try::into_result($arg) { @@ -245,3 +245,30 @@ impl Try for Result { self } } + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} From 5a4572a9425639b4cf47866b6b61e32123779260 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 17 Jul 2024 11:19:54 +0200 Subject: [PATCH 16/22] Rework pwr control and operation state logic. Also add AT command support to control handle --- Cargo.toml | 13 +- examples/embassy-rp2040-example/Cargo.toml | 2 +- examples/embassy-stm32-example/Cargo.toml | 2 +- examples/tokio-std-example/Cargo.toml | 2 +- src/asynch/control.rs | 123 ++-- src/asynch/mod.rs | 34 +- src/asynch/network.rs | 660 ++++----------------- src/asynch/pwr.rs | 138 +++++ src/asynch/resources.rs | 13 +- src/asynch/runner.rs | 625 +++++++++++++------ src/asynch/state.rs | 67 +-- src/command/control/mod.rs | 14 +- src/command/control/types.rs | 10 +- src/command/general/responses.rs | 3 +- src/command/general/types.rs | 76 +++ src/command/mod.rs | 2 +- src/command/networking/responses.rs | 1 - src/command/networking/types.rs | 2 +- src/command/psn/mod.rs | 1 - src/config.rs | 20 +- src/error.rs | 1 + src/lib.rs | 3 + src/modules/lara_r6.rs | 1 - src/registration.rs | 8 +- 24 files changed, 960 insertions(+), 861 deletions(-) create mode 100644 src/asynch/pwr.rs 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 { From dc3ca793a8b4f455447d4e217a0e42e0a336aab3 Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 18 Jul 2024 09:57:28 +0200 Subject: [PATCH 17/22] Allow control handle to query firmware version of device --- src/asynch/control.rs | 51 ++++++++++++++++++++++++------------ src/asynch/runner.rs | 46 +++----------------------------- src/command/general/types.rs | 12 ++++++--- 3 files changed, 47 insertions(+), 62 deletions(-) diff --git a/src/asynch/control.rs b/src/asynch/control.rs index 5104d2f..65829ba 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -1,12 +1,10 @@ -use atat::{ - asynch::{AtatClient, SimpleClient}, - AtDigester, -}; +use atat::asynch::{AtatClient, SimpleClient}; +use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; use crate::{ command::{ general::{types::FirmwareVersion, GetFirmwareVersion}, - gpio::{types::GpioMode, GetGpioConfiguration, SetGpioConfiguration}, + gpio::{types::GpioMode, SetGpioConfiguration}, network_service::{ responses::{OperatorSelection, SignalQuality}, GetOperatorSelection, GetSignalQuality, @@ -21,13 +19,15 @@ use super::{ state::{self, LinkState, OperationState}, }; -pub struct Control<'a> { +pub struct Control<'a, M: RawMutex> { state_ch: state::Runner<'a>, - at_client: + at_client: Mutex< + M, SimpleClient<'a, embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, atat::AtDigester>, + >, } -impl<'a> Control<'a> { +impl<'a, M: RawMutex> Control<'a, M> { pub(crate) fn new( state_ch: state::Runner<'a>, at_client: SimpleClient< @@ -38,7 +38,7 @@ impl<'a> Control<'a> { ) -> Self { Self { state_ch, - at_client, + at_client: Mutex::new(at_client), } } @@ -66,28 +66,43 @@ impl<'a> Control<'a> { self.state_ch.wait_for_operation_state(ps).await } - pub async fn get_signal_quality(&mut self) -> Result { + pub async fn get_signal_quality(&self) -> Result { if self.operation_state() == OperationState::PowerDown { return Err(Error::Uninitialized); } - Ok(self.at_client.send_retry(&GetSignalQuality).await?) + Ok(self + .at_client + .lock() + .await + .send_retry(&GetSignalQuality) + .await?) } - pub async fn get_operator(&mut self) -> Result { + pub async fn get_operator(&self) -> Result { if self.operation_state() == OperationState::PowerDown { return Err(Error::Uninitialized); } - Ok(self.at_client.send_retry(&GetOperatorSelection).await?) + Ok(self + .at_client + .lock() + .await + .send_retry(&GetOperatorSelection) + .await?) } - pub async fn get_version(&mut self) -> Result { + pub async fn get_version(&self) -> Result { if self.operation_state() == OperationState::PowerDown { return Err(Error::Uninitialized); } - let res = self.at_client.send_retry(&GetFirmwareVersion).await?; + let res = self + .at_client + .lock() + .await + .send_retry(&GetFirmwareVersion) + .await?; Ok(res.version) } @@ -101,6 +116,8 @@ impl<'a> Control<'a> { } self.at_client + .lock() + .await .send_retry(&SetGpioConfiguration { gpio_id, gpio_mode }) .await?; Ok(()) @@ -109,7 +126,7 @@ impl<'a> Control<'a> { /// 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?) + pub async fn send(&self, cmd: &Cmd) -> Result { + Ok(self.at_client.lock().await.send_retry::(cmd).await?) } } diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 0c2ebc0..e9b491b 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -12,10 +12,6 @@ use crate::{ }, device_lock::{responses::PinStatus, types::PinStatusCode, GetPinStatus}, general::{responses::FirmwareVersion, GetCCID, GetFirmwareVersion, GetModelId}, - gpio::{ - types::{GpioInPull, GpioMode, GpioOutValue}, - SetGpioConfiguration, - }, ipc::SetMultiplexing, mobile_control::{ types::{Functionality, TerminationErrorMode}, @@ -41,6 +37,7 @@ use atat::{ }; use embassy_futures::select::{select3, Either3}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::{with_timeout, Duration, Instant, Timer}; pub(crate) const URC_SUBSCRIBERS: usize = 2; @@ -98,7 +95,7 @@ where transport: T, resources: &'a mut Resources, config: C, - ) -> (Self, Control<'a>) { + ) -> (Self, Control<'a, NoopRawMutex>) { let ch_runner = state::Runner::new(&mut resources.ch); let ingress = atat::Ingress::new( @@ -311,9 +308,9 @@ where let FirmwareVersion { version } = at_client.send_retry(&GetFirmwareVersion).await?; info!( - "Found module to be: {=[u8]:a}, {=[u8]:a}", + "Found module to be: {=[u8]:a}, {}", model_id.model.as_slice(), - version.as_slice() + version ); at_client @@ -322,41 +319,6 @@ where }) .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 }) diff --git a/src/command/general/types.rs b/src/command/general/types.rs index aea1eeb..15b01b4 100644 --- a/src/command/general/types.rs +++ b/src/command/general/types.rs @@ -1,6 +1,9 @@ //! Argument and parameter types used by General Commands and Responses +use core::fmt::Write as _; + use atat::atat_derive::AtatEnum; +use serde::{Deserialize, Deserializer, Serialize}; #[derive(Clone, PartialEq, Eq, AtatEnum)] pub enum Snt { /// (default value): International Mobile station Equipment Identity (IMEI) @@ -75,7 +78,7 @@ impl Serialize for FirmwareVersion { 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) + serializer.serialize_bytes(str.as_bytes()) } } @@ -84,7 +87,10 @@ impl<'de> Deserialize<'de> for FirmwareVersion { where D: Deserializer<'de>, { - let s = heapless::String::<7>::deserialize(deserializer)?; - core::str::FromStr::from_str(&s).map_err(serde::de::Error::custom) + let s = atat::heapless_bytes::Bytes::<7>::deserialize(deserializer)?; + core::str::FromStr::from_str( + &core::str::from_utf8(s.as_slice()).map_err(serde::de::Error::custom)?, + ) + .map_err(serde::de::Error::custom) } } From ad09beab4ede390dc6a50ebf0d3d34338524322b Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 18 Jul 2024 10:33:13 +0200 Subject: [PATCH 18/22] Use online dependency for embassy-at-cmux --- Cargo.toml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 41734df..14af442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,7 @@ embedded-hal = "1.0.0" 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" } +embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2", optional = true } embassy-net-ppp = { version = "0.1", optional = true } embassy-net = { version = "0.4", features = [ "proto-ipv4", @@ -143,14 +142,18 @@ exclude = ["examples"] #ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", branch = "feature/async-borrowed-sockets" } no-std-net = { git = "https://github.com/rushmorem/no-std-net", branch = "issue-15" } #ublox-sockets = { path = "../ublox-sockets" } -embassy-time = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } -embassy-sync = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } -embassy-futures = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } -embassy-net-ppp = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } -embassy-net = { git = "https://github.com/MathiasKoch/embassy", rev = "5fa7b8f4a" } + +embassy-time = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } +embassy-sync = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } +embassy-futures = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } +embassy-net-ppp = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } +embassy-net = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } #embassy-time = { path = "../embassy/embassy-time" } #embassy-sync = { path = "../embassy/embassy-sync" } #embassy-futures = { path = "../embassy/embassy-futures" } atat = { git = "https://github.com/BlackbirdHQ/atat", rev = "a466836" } # atat = { path = "../atat/atat" } + +# [patch."https://github.com/MathiasKoch/embassy"] +# embassy-at-cmux = { path = "../embassy/embassy-at-cmux" } From 944d0894938594fa6324c813d3fcbe72226c6ed1 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 9 Aug 2024 15:21:11 +0200 Subject: [PATCH 19/22] Fix logging when using log --- Cargo.toml | 4 +++- src/asynch/network.rs | 5 ++++- src/asynch/runner.rs | 6 +----- src/asynch/state.rs | 12 ++++++++---- src/modules/lara_r6.rs | 1 + src/modules/lena_r8.rs | 1 + src/modules/mod.rs | 5 +++++ src/modules/sara_r410m.rs | 1 + src/modules/sara_r412m.rs | 1 + src/modules/sara_r422.rs | 1 + src/modules/sara_r5.rs | 1 + src/modules/sara_u201.rs | 1 + src/modules/toby_r2.rs | 1 + 13 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14af442..b60654f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,9 @@ embedded-hal = "1.0.0" embedded-nal = "0.8" embedded-nal-async = { version = "0.7" } -embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2", optional = true } +embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "e2f4365", 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", diff --git a/src/asynch/network.rs b/src/asynch/network.rs index 6d51ced..4029077 100644 --- a/src/asynch/network.rs +++ b/src/asynch/network.rs @@ -334,7 +334,10 @@ where let current_state = self.ch.operation_state(None); let desired_state = self.ch.desired_state(None); - info!("State transition: {} -> {}", current_state, desired_state); + info!( + "State transition: {:?} -> {:?}", + current_state, desired_state + ); match (current_state, desired_state.cmp(¤t_state)) { (_, Ordering::Equal) => break, diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index e9b491b..2263bc5 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -307,11 +307,7 @@ where 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}, {}", - model_id.model.as_slice(), - version - ); + info!("Found module to be: {:?}, {:?}", self.ch.module(), version); at_client .send_retry(&SetEmbeddedPortFiltering { diff --git a/src/asynch/state.rs b/src/asynch/state.rs index fbdae44..20a82c9 100644 --- a/src/asynch/state.rs +++ b/src/asynch/state.rs @@ -94,11 +94,15 @@ impl<'d> Runner<'d> { pub fn update_registration_with(&self, f: impl FnOnce(&mut RegistrationState)) { self.shared.lock(|s| { let s = &mut *s.borrow_mut(); + let prev = s.registration_state.is_registered(); f(&mut s.registration_state); - info!( - "Registration status changed! Registered: {:?}", - s.registration_state.is_registered() - ); + if prev != s.registration_state.is_registered() { + info!( + "Cellular registration status changed! Registered: {:?} -> {:?}", + prev, + s.registration_state.is_registered() + ); + } s.registration_waker.wake(); }) } diff --git a/src/modules/lara_r6.rs b/src/modules/lara_r6.rs index 7b0199b..fa92d81 100644 --- a/src/modules/lara_r6.rs +++ b/src/modules/lara_r6.rs @@ -2,6 +2,7 @@ use super::ModuleParams; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct LaraR6; impl ModuleParams for LaraR6 { diff --git a/src/modules/lena_r8.rs b/src/modules/lena_r8.rs index 65f119f..48e2cbe 100644 --- a/src/modules/lena_r8.rs +++ b/src/modules/lena_r8.rs @@ -3,6 +3,7 @@ use crate::command::mobile_control::types::Functionality; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct LenaR8; impl ModuleParams for LenaR8 { diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 1f6aff4..ded0ea0 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -75,6 +75,7 @@ pub trait ModuleParams: Copy { } #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub(crate) enum Module { #[cfg(any(feature = "any-module", feature = "lara-r6"))] LaraR6(lara_r6::LaraR6), @@ -100,6 +101,9 @@ impl Module { match model_id.model.as_slice() { b"LARA-R6001D" => Self::LaraR6(lara_r6::LaraR6), id => { + #[cfg(feature = "defmt")] + warn!("Attempting to run {=[u8]:a} using generic module parameters! This may or may not work.", id); + #[cfg(feature = "log")] warn!("Attempting to run {:?} using generic module parameters! This may or may not work.", id); Self::Generic(Generic) } @@ -174,6 +178,7 @@ impl ModuleParams for Module { } #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Generic; impl ModuleParams for Generic {} diff --git a/src/modules/sara_r410m.rs b/src/modules/sara_r410m.rs index b7317d6..58b3c91 100644 --- a/src/modules/sara_r410m.rs +++ b/src/modules/sara_r410m.rs @@ -3,6 +3,7 @@ use crate::command::mobile_control::types::Functionality; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SaraR410m; impl ModuleParams for SaraR410m { diff --git a/src/modules/sara_r412m.rs b/src/modules/sara_r412m.rs index 7a2adc9..1b107a9 100644 --- a/src/modules/sara_r412m.rs +++ b/src/modules/sara_r412m.rs @@ -3,6 +3,7 @@ use crate::command::mobile_control::types::Functionality; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SaraR412m; impl ModuleParams for SaraR412m { diff --git a/src/modules/sara_r422.rs b/src/modules/sara_r422.rs index 8847c36..7c8ec57 100644 --- a/src/modules/sara_r422.rs +++ b/src/modules/sara_r422.rs @@ -3,6 +3,7 @@ use crate::command::mobile_control::types::Functionality; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SaraR422; impl ModuleParams for SaraR422 { diff --git a/src/modules/sara_r5.rs b/src/modules/sara_r5.rs index 3fc3ef8..ee9a4d9 100644 --- a/src/modules/sara_r5.rs +++ b/src/modules/sara_r5.rs @@ -3,6 +3,7 @@ use crate::command::mobile_control::types::Functionality; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SaraR5; impl ModuleParams for SaraR5 { diff --git a/src/modules/sara_u201.rs b/src/modules/sara_u201.rs index 4ba89e1..4a14bd9 100644 --- a/src/modules/sara_u201.rs +++ b/src/modules/sara_u201.rs @@ -3,6 +3,7 @@ use crate::command::mobile_control::types::Functionality; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SaraU201; impl ModuleParams for SaraU201 { diff --git a/src/modules/toby_r2.rs b/src/modules/toby_r2.rs index 7170a64..a2e2e89 100644 --- a/src/modules/toby_r2.rs +++ b/src/modules/toby_r2.rs @@ -3,6 +3,7 @@ use crate::command::mobile_control::types::Functionality; use embassy_time::Duration; #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TobyR2; impl ModuleParams for TobyR2 { From 10532948b2836d70340b0d900159d7f7c1f6e223 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 4 Sep 2024 13:49:11 +0200 Subject: [PATCH 20/22] Make embassy-at-cmux non-optional --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b60654f..010a286 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ embedded-hal = "1.0.0" embedded-nal = "0.8" embedded-nal-async = { version = "0.7" } -embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "e2f4365", optional = true } +embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "e2f4365" } # embassy-at-cmux = { path = "../embassy/embassy-at-cmux" } embassy-net-ppp = { version = "0.1", optional = true } From a3890fd1891d2f63dff48ab9063bcc57ec80a821 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 13 Sep 2024 10:03:41 +0200 Subject: [PATCH 21/22] Fix Control by adding ProxyClient, because only two AT/data channels are supported in CMUX --- src/asynch/control.rs | 149 ++++++++++++++++++----------- src/asynch/network.rs | 10 +- src/asynch/resources.rs | 25 +++-- src/asynch/runner.rs | 116 +++++++++++++++------- src/command/system_features/mod.rs | 31 ++++++ 5 files changed, 222 insertions(+), 109 deletions(-) diff --git a/src/asynch/control.rs b/src/asynch/control.rs index 65829ba..9d4a298 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -1,5 +1,8 @@ -use atat::asynch::{AtatClient, SimpleClient}; -use embassy_sync::{blocking_mutex::raw::RawMutex, mutex::Mutex}; +use core::cell::Cell; + +use atat::{asynch::AtatClient, response_slot::ResponseSlotGuard}; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Sender}; +use embassy_time::{with_timeout, Duration, Timer}; use crate::{ command::{ @@ -9,36 +12,100 @@ use crate::{ responses::{OperatorSelection, SignalQuality}, GetOperatorSelection, GetSignalQuality, }, - Urc, }, error::Error, }; use super::{ - runner::CMUX_CHANNEL_SIZE, + runner::MAX_CMD_LEN, state::{self, LinkState, OperationState}, }; -pub struct Control<'a, M: RawMutex> { +pub(crate) struct ProxyClient<'a, const INGRESS_BUF_SIZE: usize> { + pub(crate) req_sender: Sender<'a, NoopRawMutex, heapless::Vec, 1>, + pub(crate) res_slot: &'a atat::ResponseSlot, + cooldown_timer: Cell>, +} + +impl<'a, const INGRESS_BUF_SIZE: usize> ProxyClient<'a, INGRESS_BUF_SIZE> { + pub fn new( + req_sender: Sender<'a, NoopRawMutex, heapless::Vec, 1>, + res_slot: &'a atat::ResponseSlot, + ) -> Self { + Self { + req_sender, + res_slot, + cooldown_timer: Cell::new(None), + } + } + + async fn wait_response( + &self, + timeout: Duration, + ) -> Result, atat::Error> { + with_timeout(timeout, self.res_slot.get()) + .await + .map_err(|_| atat::Error::Timeout) + } +} + +impl<'a, const INGRESS_BUF_SIZE: usize> atat::asynch::AtatClient + for &ProxyClient<'a, INGRESS_BUF_SIZE> +{ + async fn send(&mut self, cmd: &Cmd) -> Result { + let mut buf = [0u8; MAX_CMD_LEN]; + let len = cmd.write(&mut buf); + + if len < 50 { + trace!( + "Sending command: {:?}", + atat::helpers::LossyStr(&buf[..len]) + ); + } else { + trace!("Sending command with long payload ({} bytes)", len); + } + + if let Some(cooldown) = self.cooldown_timer.take() { + cooldown.await + } + + // TODO: Guard against race condition! + with_timeout( + Duration::from_secs(1), + self.req_sender + .send(heapless::Vec::try_from(&buf[..len]).unwrap()), + ) + .await + .map_err(|_| atat::Error::Timeout)?; + + self.cooldown_timer.set(Some(Timer::after_millis(20))); + + if !Cmd::EXPECTS_RESPONSE_CODE { + cmd.parse(Ok(&[])) + } else { + let response = self + .wait_response(Duration::from_millis(Cmd::MAX_TIMEOUT_MS.into())) + .await?; + let response: &atat::Response = &response.borrow(); + cmd.parse(response.into()) + } + } +} + +pub struct Control<'a, const INGRESS_BUF_SIZE: usize> { state_ch: state::Runner<'a>, - at_client: Mutex< - M, - SimpleClient<'a, embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, atat::AtDigester>, - >, + at_client: ProxyClient<'a, INGRESS_BUF_SIZE>, } -impl<'a, M: RawMutex> Control<'a, M> { +impl<'a, const INGRESS_BUF_SIZE: usize> Control<'a, INGRESS_BUF_SIZE> { pub(crate) fn new( state_ch: state::Runner<'a>, - at_client: SimpleClient< - 'a, - embassy_at_cmux::Channel<'a, CMUX_CHANNEL_SIZE>, - atat::AtDigester, - >, + req_sender: Sender<'a, NoopRawMutex, heapless::Vec, 1>, + res_slot: &'a atat::ResponseSlot, ) -> Self { Self { state_ch, - at_client: Mutex::new(at_client), + at_client: ProxyClient::new(req_sender, res_slot), } } @@ -67,42 +134,15 @@ impl<'a, M: RawMutex> Control<'a, M> { } pub async fn get_signal_quality(&self) -> Result { - if self.operation_state() == OperationState::PowerDown { - return Err(Error::Uninitialized); - } - - Ok(self - .at_client - .lock() - .await - .send_retry(&GetSignalQuality) - .await?) + Ok(self.send(&GetSignalQuality).await?) } pub async fn get_operator(&self) -> Result { - if self.operation_state() == OperationState::PowerDown { - return Err(Error::Uninitialized); - } - - Ok(self - .at_client - .lock() - .await - .send_retry(&GetOperatorSelection) - .await?) + Ok(self.send(&GetOperatorSelection).await?) } pub async fn get_version(&self) -> Result { - if self.operation_state() == OperationState::PowerDown { - return Err(Error::Uninitialized); - } - - let res = self - .at_client - .lock() - .await - .send_retry(&GetFirmwareVersion) - .await?; + let res = self.send(&GetFirmwareVersion).await?; Ok(res.version) } @@ -111,22 +151,19 @@ impl<'a, M: RawMutex> Control<'a, M> { gpio_id: u8, gpio_mode: GpioMode, ) -> Result<(), Error> { - if self.operation_state() == OperationState::PowerDown { - return Err(Error::Uninitialized); - } - - self.at_client - .lock() - .await - .send_retry(&SetGpioConfiguration { gpio_id, gpio_mode }) + self.send(&SetGpioConfiguration { gpio_id, gpio_mode }) .await?; Ok(()) } - /// Send an AT command to the modem This is usefull if you have special + /// Send an AT command to the modem This is useful if you have special /// configuration but might break the drivers functionality if your settings /// interfere with the drivers settings pub async fn send(&self, cmd: &Cmd) -> Result { - Ok(self.at_client.lock().await.send_retry::(cmd).await?) + if self.operation_state() == OperationState::PowerDown { + return Err(Error::Uninitialized); + } + + Ok((&self.at_client).send_retry::(cmd).await?) } } diff --git a/src/asynch/network.rs b/src/asynch/network.rs index 4029077..7bce97b 100644 --- a/src/asynch/network.rs +++ b/src/asynch/network.rs @@ -178,7 +178,7 @@ where // Ok(()) // } - // /// Reset the module by sending AT CFUN command + /// 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: {}.", @@ -334,7 +334,7 @@ where let current_state = self.ch.operation_state(None); let desired_state = self.ch.desired_state(None); - info!( + debug!( "State transition: {:?} -> {:?}", current_state, desired_state ); @@ -342,6 +342,11 @@ where match (current_state, desired_state.cmp(¤t_state)) { (_, Ordering::Equal) => break, + (OperationState::PowerDown, Ordering::Greater) => { + self.ch + .wait_for_operation_state(OperationState::Initialized) + .await + } (OperationState::Initialized, Ordering::Greater) => { self.register_network(None).await?; self.wait_network_registered(Duration::from_secs(180)) @@ -356,7 +361,6 @@ where .set_profile_state(crate::registration::ProfileState::ShouldBeUp); self.ch.set_operation_state(OperationState::DataEstablished); - Timer::after_secs(1).await; } Err(err) => { // Switch radio off after failure diff --git a/src/asynch/resources.rs b/src/asynch/resources.rs index 29da8db..0bdc7c0 100644 --- a/src/asynch/resources.rs +++ b/src/asynch/resources.rs @@ -1,46 +1,43 @@ use atat::{ResponseSlot, UrcChannel}; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel}; use super::{ - runner::{CMUX_CHANNELS, CMUX_CHANNEL_SIZE, URC_SUBSCRIBERS}, + runner::{CMUX_CHANNELS, CMUX_CHANNEL_SIZE, MAX_CMD_LEN, URC_SUBSCRIBERS}, state, }; use crate::command::Urc; -pub struct Resources< - const CMD_BUF_SIZE: usize, - const INGRESS_BUF_SIZE: usize, - const URC_CAPACITY: usize, -> { +pub struct Resources { pub(crate) ch: state::State, pub(crate) res_slot: ResponseSlot, + pub(crate) req_slot: Channel, 1>, + 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], pub(crate) mux: embassy_at_cmux::Mux, } -impl Default - for Resources +impl Default + for Resources { fn default() -> Self { Self::new() } } -impl - Resources +impl + Resources { pub fn new() -> Self { Self { ch: state::State::new(), res_slot: ResponseSlot::new(), + req_slot: Channel::new(), + urc_channel: UrcChannel::new(), - cmd_buf: [0; CMD_BUF_SIZE], - control_cmd_buf: [0; CMD_BUF_SIZE], ingress_buf: [0; INGRESS_BUF_SIZE], mux: embassy_at_cmux::Mux::new(), diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index 2263bc5..a4bc211 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -29,28 +29,65 @@ use crate::{ DEFAULT_BAUD_RATE, }; -use super::{control::Control, pwr::PwrCtrl, state, urc_handler::UrcHandler, Resources}; +use super::{ + control::{Control, ProxyClient}, + pwr::PwrCtrl, + state, + urc_handler::UrcHandler, + Resources, +}; use atat::{ asynch::{AtatClient, SimpleClient}, AtatIngress as _, UrcChannel, }; -use embassy_futures::select::{select3, Either3}; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_futures::{ + join::join, + select::{select3, Either3}, +}; +use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel}; use embassy_time::{with_timeout, Duration, Instant, Timer}; +use embedded_io_async::Write as _; pub(crate) const URC_SUBSCRIBERS: usize = 2; +pub(crate) const MAX_CMD_LEN: usize = 128; + pub const CMUX_MAX_FRAME_SIZE: usize = 128; pub const CMUX_CHANNEL_SIZE: usize = CMUX_MAX_FRAME_SIZE * 4; -#[cfg(any(feature = "internal-network-stack", feature = "ppp"))] -pub const CMUX_CHANNELS: usize = 3; - -#[cfg(not(any(feature = "internal-network-stack", feature = "ppp")))] pub const CMUX_CHANNELS: usize = 2; +async fn at_bridge<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize>( + (rx, tx): ( + &mut embassy_at_cmux::ChannelRx<'a, CMUX_CHANNEL_SIZE>, + &mut embassy_at_cmux::ChannelTx<'a, CMUX_CHANNEL_SIZE>, + ), + req_slot: &Channel, 1>, + ingress: &mut atat::Ingress< + 'a, + atat::AtDigester, + Urc, + INGRESS_BUF_SIZE, + URC_CAPACITY, + URC_SUBSCRIBERS, + >, +) -> ! { + ingress.clear(); + + let tx_fut = async { + loop { + let msg = req_slot.receive().await; + let _ = tx.write_all(&msg).await; + } + }; + + embassy_futures::join::join(tx_fut, ingress.read_from(rx)).await; + + unreachable!() +} + /// Background runner for the Ublox Module. /// /// You must call `.run()` in a background task for the Ublox Module to operate. @@ -69,12 +106,12 @@ pub struct Runner<'a, T, C, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: u URC_CAPACITY, URC_SUBSCRIBERS, >, - pub cmd_buf: &'a mut [u8], pub res_slot: &'a atat::ResponseSlot, + pub req_slot: &'a Channel, 1>, pub mux_runner: embassy_at_cmux::Runner<'a, CMUX_CHANNELS, CMUX_CHANNEL_SIZE>, - network_channel: ( + at_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>, @@ -91,11 +128,11 @@ where T: Transport, C: CellularConfig<'a> + 'a, { - pub fn new( + pub fn new( transport: T, - resources: &'a mut Resources, + resources: &'a mut Resources, config: C, - ) -> (Self, Control<'a, NoopRawMutex>) { + ) -> (Self, Control<'a, INGRESS_BUF_SIZE>) { let ch_runner = state::Runner::new(&mut resources.ch); let ingress = atat::Ingress::new( @@ -108,17 +145,14 @@ where let (mux_runner, channels) = resources.mux.start(); let mut channel_iter = channels.into_iter(); - let network_channel = channel_iter.next().unwrap().split(); + let at_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(), + resources.req_slot.sender(), + &resources.res_slot, ); - let control = Control::new(ch_runner.clone(), control_client); ( Self { @@ -129,12 +163,12 @@ where urc_channel: &resources.urc_channel, ingress, - cmd_buf: &mut resources.cmd_buf, res_slot: &resources.res_slot, + req_slot: &resources.req_slot, mux_runner, - network_channel, + at_channel, data_channel, #[cfg(feature = "ppp")] @@ -173,12 +207,13 @@ where baudrate as u32 ); self.transport.set_baudrate(baudrate as u32); + let mut cmd_buf = [0u8; 16]; { let mut at_client = SimpleClient::new( &mut self.transport, atat::AtDigester::::new(), - self.cmd_buf, + &mut cmd_buf, C::AT_CONFIG, ); @@ -216,7 +251,7 @@ where SimpleClient::new( &mut self.transport, atat::AtDigester::::new(), - self.cmd_buf, + &mut cmd_buf, C::AT_CONFIG, ) .send_retry(&AT) @@ -282,10 +317,11 @@ where return Err(Error::BaudDetection); } + let mut cmd_buf = [0u8; 64]; let mut at_client = SimpleClient::new( &mut self.transport, atat::AtDigester::::new(), - self.cmd_buf, + &mut cmd_buf, C::AT_CONFIG, ); @@ -440,8 +476,6 @@ where .await?; } - self.ch.set_operation_state(OperationState::Initialized); - Ok(()) } @@ -608,22 +642,32 @@ where .await; let (mut tx, mut rx) = self.transport.split_ref(); - self.mux_runner - .run(&mut rx, &mut tx, CMUX_MAX_FRAME_SIZE) - .await + + // Signal to the rest of the driver, that the MUX is operational and running + let fut = async { + // TODO: It should be possible to check on ChannelLines, + // when the MUX is opened successfully, instead of waiting a fixed time + Timer::after_secs(3).await; + self.ch.set_operation_state(OperationState::Initialized); + }; + + join( + self.mux_runner.run(&mut rx, &mut tx, CMUX_MAX_FRAME_SIZE), + fut, + ) + .await }; let device_fut = async { - let (at_rx, at_tx, _) = &mut self.network_channel; - let mut cell_device = NetDevice::::new( - &self.ch, - atat::asynch::Client::new(at_tx, self.res_slot, self.cmd_buf, C::AT_CONFIG), - ); + let (at_rx, at_tx, _) = &mut self.at_channel; + + let at_client = ProxyClient::new(self.req_slot.sender(), self.res_slot); + let mut cell_device = NetDevice::::new(&self.ch, &at_client); let mut urc_handler = UrcHandler::new(&self.ch, self.urc_channel); select3( - self.ingress.read_from(at_rx), + at_bridge((at_rx, at_tx), self.req_slot, &mut self.ingress), urc_handler.run(), cell_device.run(), ) diff --git a/src/command/system_features/mod.rs b/src/command/system_features/mod.rs index b336945..1160e55 100644 --- a/src/command/system_features/mod.rs +++ b/src/command/system_features/mod.rs @@ -12,6 +12,37 @@ use types::{FSFactoryRestoreType, NVMFactoryRestoreType, PowerSavingMode, Second use super::NoResponse; +/// Serial interfaces configuration selection +USIO +/// +/// Selects the serial interfaces' configuration. The configuration affects how +/// an available (either physical or logical) serial interface is used, i.e. the +/// meaning of the data flowing over it. +/// +/// Possible usages are: +/// - Modem interface (AT command) +/// - Trace interface (diagnostic log) +/// - Raw interface (e.g. GPS/GNSS tunneling or SAP) +/// - Digital audio interface +/// - None +/// +/// A set of configurations, that considers all the available serial interfaces' +/// and their associated usage, is called +USIO's configuration variant. +/// +/// **NOTE** +/// - The serial interfaces' configuration switch is not performed run-time. The +/// settings are saved in NVM; the new configuration will be effective at the +/// subsequent module reboot. +/// - A serial interface might not support all the usages. For instance, UART +/// cannot be used as digital audio interface. +/// - For the complete list of allowed USIO variants supported by each series +/// modules, see Notes. +#[derive(Clone, AtatCmd)] +#[at_cmd("+USIO", NoResponse)] +pub struct SetSerialInterfaceConfig { + #[at_arg(position = 0)] + pub variant: u8, +} + /// 19.8 Power saving control (Power Saving) +UPSV /// /// Sets the UART power saving configuration, but it has a global effect on the From 2a1eb07a24a87405ff67889871c3097885bb6696 Mon Sep 17 00:00:00 2001 From: Mathias Date: Tue, 24 Sep 2024 14:59:03 +0200 Subject: [PATCH 22/22] Bump embassy --- Cargo.toml | 12 ++++++------ src/asynch/runner.rs | 5 +---- src/command/general/types.rs | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 010a286..cccffdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ embedded-hal = "1.0.0" embedded-nal = "0.8" embedded-nal-async = { version = "0.7" } -embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "e2f4365" } +embassy-at-cmux = { git = "https://github.com/MathiasKoch/embassy", rev = "c83b7a052" } # embassy-at-cmux = { path = "../embassy/embassy-at-cmux" } embassy-net-ppp = { version = "0.1", optional = true } @@ -145,11 +145,11 @@ exclude = ["examples"] no-std-net = { git = "https://github.com/rushmorem/no-std-net", branch = "issue-15" } #ublox-sockets = { path = "../ublox-sockets" } -embassy-time = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } -embassy-sync = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } -embassy-futures = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } -embassy-net-ppp = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } -embassy-net = { git = "https://github.com/MathiasKoch/embassy", rev = "e9ce1e2" } +embassy-time = { git = "https://github.com/MathiasKoch/embassy", rev = "84dba2941" } +embassy-sync = { git = "https://github.com/MathiasKoch/embassy", rev = "84dba2941" } +embassy-futures = { git = "https://github.com/MathiasKoch/embassy", rev = "84dba2941" } +embassy-net-ppp = { git = "https://github.com/MathiasKoch/embassy", rev = "84dba2941" } +embassy-net = { git = "https://github.com/MathiasKoch/embassy", rev = "84dba2941" } #embassy-time = { path = "../embassy/embassy-time" } #embassy-sync = { path = "../embassy/embassy-sync" } diff --git a/src/asynch/runner.rs b/src/asynch/runner.rs index a4bc211..f5344e9 100644 --- a/src/asynch/runner.rs +++ b/src/asynch/runner.rs @@ -479,10 +479,7 @@ where Ok(()) } - pub async fn run( - &mut self, - stack: &embassy_net::Stack, - ) -> ! { + pub async fn run(&mut self, stack: embassy_net::Stack<'_>) -> ! { loop { let _ = PwrCtrl::new(&self.ch, &mut self.config).power_down().await; diff --git a/src/command/general/types.rs b/src/command/general/types.rs index 15b01b4..86a6d0c 100644 --- a/src/command/general/types.rs +++ b/src/command/general/types.rs @@ -78,7 +78,7 @@ impl Serialize for FirmwareVersion { let mut str = heapless::String::<7>::new(); str.write_fmt(format_args!("{}.{}", self.major, self.minor)) .map_err(serde::ser::Error::custom)?; - serializer.serialize_bytes(str.as_bytes()) + serializer.serialize_str(&str) } }