From 38397898dd72f2f9a836090ac5aa2aad1df8e103 Mon Sep 17 00:00:00 2001 From: Nils Ponsard Date: Mon, 23 Mar 2026 14:25:22 +0100 Subject: [PATCH] feat(embassy-net): ability to turn off the modem The previous logic kept the socket running when `context::Control::disable()` was called, keeping one link active and preventing the automatic deactivation of the LTE-M modem. Signed-off-by: Nils Ponsard --- src/embassy_net_modem/context.rs | 34 ++++++++++++++---- src/embassy_net_modem/mod.rs | 59 +++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/src/embassy_net_modem/context.rs b/src/embassy_net_modem/context.rs index 2026beb..2270928 100644 --- a/src/embassy_net_modem/context.rs +++ b/src/embassy_net_modem/context.rs @@ -4,12 +4,12 @@ // Licence: https://github.com/embassy-rs/embassy/blob/main/LICENSE-APACHE // Source file: https://github.com/embassy-rs/embassy/blob/a8cb8a7fe1f594b765dee4cfc6ff3065842c7c6e/embassy-net-nrf91/src/context.rs +use core::cell::RefCell; use core::net::IpAddr; use core::str::FromStr; use at_commands::builder::CommandBuilder; use at_commands::parser::CommandParser; -use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; use embassy_time::{Duration, Timer}; use heapless::Vec; @@ -19,7 +19,8 @@ use crate::{embassy_net_modem::CAP_SIZE, Error, LteLink}; pub struct Control<'a> { control: super::Control<'a>, cid: u8, - lte_link: Mutex>, + lte_link: RefCell>, + needs_reconnect: RefCell, } /// Authentication parameters for the Packet Data Network (PDN). @@ -118,7 +119,8 @@ impl<'a> Control<'a> { Self { control, cid, - lte_link: Mutex::new(None), + lte_link: RefCell::new(None), + needs_reconnect: RefCell::new(false), } } @@ -136,7 +138,9 @@ impl<'a> Control<'a> { pub async fn configure(&self, config: &PdConfig<'_>, pin: Option<&[u8]>) -> Result<(), Error> { let mut cmd: [u8; 256] = [0; 256]; - if let Some(link) = self.lte_link.lock().await.take() { + let link = self.lte_link.borrow_mut().take(); + + if let Some(link) = link { link.deactivate().await?; } @@ -218,6 +222,11 @@ impl<'a> Control<'a> { } async fn attached(&self) -> Result { + // We don't have a link, meaning the modem is disabled. + if self.lte_link.borrow().is_none() { + return Ok(false); + } + let mut cmd: [u8; 256] = [0; 256]; let op = CommandBuilder::create_query(&mut cmd, true) @@ -350,9 +359,15 @@ impl<'a> Control<'a> { /// Disable modem pub async fn disable(&self) -> Result<(), Error> { - if let Some(link) = self.lte_link.lock().await.take() { + let link = self.lte_link.borrow_mut().take(); + if let Some(link) = link { link.deactivate().await?; }; + + // Also close the current socket + self.control.close_raw_socket().await; + + *self.needs_reconnect.borrow_mut() = true; Ok(()) } @@ -360,7 +375,10 @@ impl<'a> Control<'a> { pub async fn enable(&self) -> Result<(), Error> { let mut cmd: [u8; 256] = [0; 256]; - self.lte_link.lock().await.replace(LteLink::new().await?); + if self.lte_link.borrow().is_none() { + let link = LteLink::new().await?; + self.lte_link.borrow_mut().replace(link); + } // Make modem survive PDN detaches let op = CommandBuilder::create_set(&mut cmd, true) @@ -376,6 +394,7 @@ impl<'a> Control<'a> { } /// Run a control loop for this context, ensuring that reaattach is handled. + /// This also allows to reconnect after calling `Control::disable()` then `Control::enable()`. pub async fn run(&self, reattach: F) -> Result<(), Error> { self.enable().await?; let status = self.wait_attached().await?; @@ -383,10 +402,11 @@ impl<'a> Control<'a> { reattach(&status); loop { - if !self.attached().await? { + if !self.attached().await? || *self.needs_reconnect.borrow() { self.control.close_raw_socket().await; let status = self.wait_attached().await?; self.control.open_raw_socket().await; + *self.needs_reconnect.borrow_mut() = false; reattach(&status); } Timer::after(Duration::from_secs(10)).await; diff --git a/src/embassy_net_modem/mod.rs b/src/embassy_net_modem/mod.rs index 2820c6c..4e3b905 100644 --- a/src/embassy_net_modem/mod.rs +++ b/src/embassy_net_modem/mod.rs @@ -5,8 +5,9 @@ use core::cell::RefCell; use core::mem::MaybeUninit; -use embassy_futures::select::{select, Either}; -use embassy_net_driver_channel as ch; +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel::{self as ch, driver::LinkState}; +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; use embassy_time::Timer; pub mod context; @@ -25,15 +26,17 @@ pub async fn new<'a>(state: &'a mut State) -> (NetDriver<'a>, Control<'a>, Runne .inner .write(RefCell::new(StateInner { net_socket: None })); - let control = Control { state: state_inner }; + let control = Control { + state: state_inner, + close_signal: &state.close_signal, + }; let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ip); - let state_ch = ch_runner.state_runner(); - state_ch.set_link_state(ch::driver::LinkState::Up); let runner = Runner { ch: ch_runner, state: state_inner, + close_signal: &state.close_signal, }; (device, control, runner) @@ -43,6 +46,7 @@ pub async fn new<'a>(state: &'a mut State) -> (NetDriver<'a>, Control<'a>, Runne pub struct State { ch: ch::State, inner: MaybeUninit>, + close_signal: Signal, } impl State { @@ -51,6 +55,7 @@ impl State { Self { ch: ch::State::new(), inner: MaybeUninit::uninit(), + close_signal: Signal::new(), } } } @@ -70,6 +75,7 @@ struct StateInner { /// You can use this object to control the modem at runtime, such as running AT commands. pub struct Control<'a> { state: &'a RefCell, + close_signal: &'a Signal, } pub(crate) const CAP_SIZE: usize = 256; @@ -84,13 +90,20 @@ impl<'a> Control<'a> { ) .await .unwrap(); + + // Avoid closing the freshly created socket + self.close_signal.reset(); self.state.borrow_mut().net_socket = Some(socket); } async fn close_raw_socket(&self) { - let sock = self.state.borrow_mut().net_socket.take(); - if let Some(s) = sock { + let socket = self.state.borrow_mut().net_socket.take(); + // If the runner doesn't have the socket we deactivate it + if let Some(s) = socket { s.deactivate().await.unwrap(); + } else { + // If the runner has the socket we send it a signal to deactivate it + self.close_signal.signal(true); } } /// Run an AT command. @@ -107,6 +120,7 @@ impl<'a> Control<'a> { pub struct Runner<'a> { ch: ch::Runner<'a, MTU>, state: &'a RefCell, + close_signal: &'a Signal, } impl<'a> Runner<'a> { @@ -114,8 +128,9 @@ impl<'a> Runner<'a> { /// /// You must run this in a background task, concurrently with all network operations. pub async fn run(mut self) -> ! { + let mut previous_state = LinkState::Down; loop { - let (_, mut rx_chan, mut tx_chan) = self.ch.borrow_split(); + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.borrow_split(); let net_socket = self.state.borrow_mut().net_socket.take(); let mut rx_buf = [0; 2048]; @@ -123,6 +138,13 @@ impl<'a> Runner<'a> { let token: crate::CancellationToken = Default::default(); if let Some(socket) = net_socket { + // Avoid acquiring the lock for every iteration + if previous_state == LinkState::Down { + // We have a socket, this means the link is up + state_chan.set_link_state(LinkState::Up); + previous_state = LinkState::Up; + } + let rx_fut = async { let size = socket .receive_with_cancellation(&mut rx_buf, &token) @@ -132,15 +154,17 @@ impl<'a> Runner<'a> { (size, buf) }; let tx_fut = tx_chan.tx_buf(); - match select(rx_fut, tx_fut).await { - Either::First((size, buf)) => { + match select3(rx_fut, tx_fut, self.close_signal.wait()).await { + Either3::First((size, buf)) => { if size > 0 { // Process received data buf[..size].copy_from_slice(&rx_buf[..size]); rx_chan.rx_done(size); } + // Put the socket back + self.state.borrow_mut().net_socket.replace(socket); } - Either::Second(buf) => { + Either3::Second(buf) => { let size = buf.len(); let mut remaining = size; while remaining > 0 { @@ -151,11 +175,20 @@ impl<'a> Runner<'a> { remaining -= size; } tx_chan.tx_done(); + // Put the socket back + self.state.borrow_mut().net_socket.replace(socket); + } + Either3::Third(_) => { + // We need to close the socket + let _ = socket.deactivate().await; + // The socket has been consumed, self.state.net_socket is now None } } - - self.state.borrow_mut().net_socket.replace(socket); } else { + // We don't have a socket, link is down. + state_chan.set_link_state(LinkState::Down); + previous_state = LinkState::Down; + Timer::after_millis(100).await } }