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, + } + } +}