nrf_modem/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3// #![warn(missing_docs)]
4
5use crate::error::ErrorSource;
6use core::{
7    cell::RefCell,
8    ops::Range,
9    sync::atomic::{AtomicBool, Ordering},
10};
11use critical_section::Mutex;
12use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
13use linked_list_allocator::Heap;
14
15mod at;
16mod at_notifications;
17mod cancellation;
18mod dns;
19mod dtls_socket;
20#[cfg(feature = "embassy-net")]
21pub mod embassy_net_modem;
22pub(crate) mod embedded_io_macros;
23#[cfg(feature = "embedded-nal-async")]
24mod embedded_nal_async;
25mod error;
26pub mod ffi;
27mod gnss;
28pub(crate) mod ip;
29mod lte_link;
30mod sms;
31/// Contains the core socket types and related functionality.
32pub mod socket;
33mod tcp_stream;
34mod tls_stream;
35mod udp_socket;
36mod uicc_link;
37pub(crate) mod waker_node_list;
38
39pub use nrfxlib_sys;
40
41pub use at::*;
42pub use at_notifications::AtNotificationStream;
43pub use cancellation::CancellationToken;
44pub use dns::*;
45pub use dtls_socket::*;
46#[cfg(feature = "embedded-nal-async")]
47pub use embedded_nal_async::*;
48pub use error::Error;
49pub use gnss::*;
50pub use lte_link::LteLink;
51pub use sms::*;
52pub use socket::CipherSuite;
53pub use socket::PeerVerification;
54pub use tcp_stream::*;
55pub use tls_stream::*;
56pub use udp_socket::*;
57pub use uicc_link::UiccLink;
58
59#[cfg(feature = "nrf9160")]
60use nrf9160_pac as pac;
61
62#[cfg(feature = "nrf9120")]
63use nrf9120_pac as pac;
64
65/// We need to wrap our heap so it's creatable at run-time and accessible from an ISR.
66///
67/// * The Mutex allows us to safely share the heap between interrupt routines
68///   and the main thread - and nrfxlib will definitely use the heap in an
69///   interrupt.
70/// * The RefCell lets us share and object and mutate it (but not at the same
71///   time)
72/// * The Option is because the `linked_list_allocator::empty()` function is not
73///   `const` yet and cannot be called here
74///
75type WrappedHeap = Mutex<RefCell<Option<Heap>>>;
76
77/// Our general heap.
78///
79/// We initialise it later with a static variable as the backing store.
80static LIBRARY_ALLOCATOR: WrappedHeap = Mutex::new(RefCell::new(None));
81
82/// Our transmit heap.
83///
84/// We initalise this later using a special region of shared memory that can be
85/// seen by the Cortex-M33 and the modem CPU.
86static TX_ALLOCATOR: WrappedHeap = Mutex::new(RefCell::new(None));
87
88pub(crate) static MODEM_RUNTIME_STATE: RuntimeState = RuntimeState::new();
89static INITIALIZED: AtomicBool = AtomicBool::new(false);
90
91/// Start the NRF Modem library
92///
93/// With the os_irq feature enabled, you need to specify the OS scheduled IRQ number.
94/// The modem's IPC interrupt should be higher than the os irq. (IPC should pre-empt the executor)
95pub async fn init(mode: SystemMode, #[cfg(feature = "os-irq")] os_irq: u8) -> Result<(), Error> {
96    init_with_custom_layout(
97        mode,
98        Default::default(),
99        #[cfg(feature = "os-irq")]
100        os_irq,
101    )
102    .await
103}
104
105/// Start the NRF Modem library with a manually specified memory layout
106///
107/// With the os_irq feature enabled, you need to specify the OS scheduled IRQ number.
108/// The modem's IPC interrupt should be higher than the os irq. (IPC should pre-empt the executor)
109pub async fn init_with_custom_layout(
110    mode: SystemMode,
111    memory_layout: MemoryLayout,
112    #[cfg(feature = "os-irq")] os_irq: u8,
113) -> Result<(), Error> {
114    if INITIALIZED.fetch_or(true, Ordering::SeqCst) {
115        return Err(Error::ModemAlreadyInitialized);
116    }
117
118    #[cfg(feature = "os-irq")]
119    ffi::OS_IRQ.store(os_irq, Ordering::Relaxed);
120
121    const SHARED_MEMORY_RANGE: Range<u32> = 0x2000_0000..0x2002_0000;
122
123    if !SHARED_MEMORY_RANGE.contains(&memory_layout.base_address) {
124        return Err(Error::BadMemoryLayout);
125    }
126
127    if !SHARED_MEMORY_RANGE.contains(
128        &(memory_layout.base_address
129                + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
130                + memory_layout.tx_area_size
131                + memory_layout.rx_area_size
132                + memory_layout.trace_area_size
133                // Minus one, because this check should be inclusive
134                - 1),
135    ) {
136        return Err(Error::BadMemoryLayout);
137    }
138
139    #[cfg(feature = "modem-trace")]
140    if memory_layout.trace_area_size == 0 {
141        return Err(Error::BadMemoryLayout);
142    }
143
144    // The modem is only certified when the DC/DC converter is enabled and it isn't by default
145    unsafe {
146        (*pac::REGULATORS_NS::PTR)
147            .dcdcen
148            .modify(|_, w| w.dcdcen().enabled());
149    }
150
151    unsafe {
152        const HEAP_SIZE: usize = 1024;
153        /// Allocate some space in global data to use as a heap.
154        static mut HEAP_MEMORY: [u32; HEAP_SIZE] = [0u32; HEAP_SIZE];
155        let heap_start = &raw mut HEAP_MEMORY;
156        let heap_size = HEAP_SIZE * core::mem::size_of::<u32>();
157        critical_section::with(|cs| {
158            *LIBRARY_ALLOCATOR.borrow(cs).borrow_mut() =
159                Some(Heap::new(heap_start.cast::<u8>(), heap_size))
160        });
161    }
162
163    // Tell nrf_modem what memory it can use.
164    static PARAMS: grounded::uninit::GroundedCell<nrfxlib_sys::nrf_modem_init_params> =
165        grounded::uninit::GroundedCell::uninit();
166
167    let params = nrfxlib_sys::nrf_modem_init_params {
168        shmem: nrfxlib_sys::nrf_modem_shmem_cfg {
169            ctrl: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_1 {
170                base: memory_layout.base_address,
171                size: nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE,
172            },
173            tx: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_2 {
174                base: memory_layout.base_address + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE,
175                size: memory_layout.tx_area_size,
176            },
177            rx: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_3 {
178                base: memory_layout.base_address
179                    + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
180                    + memory_layout.tx_area_size,
181                size: memory_layout.rx_area_size,
182            },
183            trace: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_4 {
184                base: memory_layout.base_address
185                    + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
186                    + memory_layout.tx_area_size
187                    + memory_layout.rx_area_size,
188                size: memory_layout.trace_area_size,
189            },
190        },
191        ipc_irq_prio: 0,
192        fault_handler: Some(modem_fault_handler),
193        dfu_handler: Some(modem_dfu_handler),
194    };
195
196    critical_section::with(|_| unsafe { PARAMS.get().write(params) });
197
198    unsafe {
199        // Use the same TX memory region as above
200        critical_section::with(|cs| {
201            *TX_ALLOCATOR.borrow(cs).borrow_mut() = Some(Heap::new(
202                params.shmem.tx.base as usize as *mut u8,
203                params.shmem.tx.size as usize,
204            ))
205        });
206    }
207
208    // OK, let's start the library
209    unsafe { nrfxlib_sys::nrf_modem_init(PARAMS.get()) }.into_result()?;
210
211    // Start tracing
212    #[cfg(feature = "modem-trace")]
213    at::send_at::<0>("AT%XMODEMTRACE=1,2").await?;
214
215    // Initialize AT notifications
216    at_notifications::initialize()?;
217
218    // Turn off the modem
219    let (modem_state,) =
220        at_commands::parser::CommandParser::parse(at::send_at::<32>("AT+CFUN?").await?.as_bytes())
221            .expect_identifier(b"+CFUN: ")
222            .expect_int_parameter()
223            .expect_identifier(b"\r\nOK\r\n")
224            .finish()?;
225
226    if modem_state != 0 {
227        // The modem is still turned on (probably from a previous run). Let's turn it off
228        at::send_at::<0>("AT+CFUN=0").await?;
229    }
230
231    if !mode.is_valid_config() {
232        return Err(Error::InvalidSystemModeConfig);
233    }
234
235    let mut buffer = [0; 64];
236    let command = mode.create_at_command(&mut buffer)?;
237    at::send_at_bytes::<0>(command).await?;
238
239    mode.setup_psm().await?;
240
241    Ok(())
242}
243
244/// Fetch traces from the modem
245///
246/// Make sure to enable the `modem-trace` feature. Call this function regularly
247/// to ensure the trace buffer doesn't overflow.
248///
249/// `cb` will be called for every chunk of tracing data.
250#[cfg(feature = "modem-trace")]
251pub async fn fetch_traces(cb: impl AsyncFn(&[u8])) -> Result<(), Error> {
252    let mut frags: *mut nrfxlib_sys::nrf_modem_trace_data = core::ptr::null_mut();
253    let mut nfrags = 0;
254
255    let res = unsafe {
256        nrfxlib_sys::nrf_modem_trace_get(
257            &mut frags,
258            &mut nfrags,
259            nrfxlib_sys::NRF_MODEM_OS_NO_WAIT as i32,
260        )
261    };
262
263    if res != 0 {
264        return Err(Error::NrfError(res as isize));
265    }
266
267    // SAFETY: if nrf_modem_trace_get returns 0, frags is a valid pointer to the start of an array of size nfrags.
268    let frags = unsafe { core::slice::from_raw_parts(frags, nfrags) };
269    for nrfxlib_sys::nrf_modem_trace_data { data, len } in frags {
270        let data = unsafe { core::slice::from_raw_parts(*data as *mut u8, *len) };
271        cb(data).await;
272        unsafe {
273            nrfxlib_sys::nrf_modem_trace_processed(*len);
274        }
275    }
276
277    Ok(())
278}
279
280/// The memory layout used by the modem library.
281///
282/// The full range needs to be in the lower 128k of ram.
283/// This also contains the fixed [nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE].
284///
285/// Nordic guide: <https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.4.1/nrfxlib/nrf_modem/doc/architecture.html#shared-memory-configuration>
286pub struct MemoryLayout {
287    /// The start of the memory area
288    pub base_address: u32,
289    /// The buffer size of the socket send operations, as well as sent AT commands and TLS certs
290    pub tx_area_size: u32,
291    /// The buffer size of the socket receive operations, as well as received AT commands, gnss messages and TLS certs
292    pub rx_area_size: u32,
293    /// The buffer size of the trace logs
294    pub trace_area_size: u32,
295}
296
297impl Default for MemoryLayout {
298    fn default() -> Self {
299        Self {
300            base_address: 0x2001_0000,
301            tx_area_size: 0x2000,
302            rx_area_size: 0x2000,
303            trace_area_size: if cfg!(feature = "modem-trace") {
304                0x2000
305            } else {
306                0
307            },
308        }
309    }
310}
311
312unsafe extern "C" fn modem_fault_handler(info: *mut nrfxlib_sys::nrf_modem_fault_info) {
313    #[cfg(feature = "defmt")]
314    defmt::panic!(
315        "Modem fault - reason: {}, pc: {}",
316        (*info).reason,
317        (*info).program_counter
318    );
319    #[cfg(not(feature = "defmt"))]
320    panic!(
321        "Modem fault - reason: {}, pc: {}",
322        (*info).reason,
323        (*info).program_counter
324    );
325}
326
327unsafe extern "C" fn modem_dfu_handler(_val: u32) {
328    #[cfg(feature = "defmt")]
329    defmt::trace!("Modem DFU handler");
330}
331
332/// IPC code now lives outside `lib_modem`, so call our IPC handler function.
333pub fn ipc_irq_handler() {
334    unsafe {
335        crate::ffi::nrf_ipc_irq_handler();
336    }
337    cortex_m::asm::sev();
338}
339
340/// Identifies which radios in the nRF91* SiP should be active
341///
342/// Based on: <https://infocenter.nordicsemi.com/index.jsp?topic=%2Fref_at_commands%2FREF%2Fat_commands%2Fmob_termination_ctrl_status%2Fcfun.html>
343#[derive(Debug, Copy, Clone)]
344pub struct SystemMode {
345    /// Enables the modem to connect to the LTE network
346    pub lte_support: bool,
347    /// Enables the PowerSavingMode. You want this enabled unless your sim/network doesn't support it
348    pub lte_psm_support: bool,
349    /// Enables the modem to connect to the NBiot network
350    pub nbiot_support: bool,
351    /// Enables the modem to receive gnss signals
352    pub gnss_support: bool,
353    /// Sets up the preference the modem will have for connecting to the mobile network
354    pub preference: ConnectionPreference,
355}
356
357/// The preference the modem will have for connecting to the mobile network
358#[derive(Debug, Copy, Clone)]
359pub enum ConnectionPreference {
360    /// No preference. Initial system selection is based on history data and Universal Subscriber Identity Module (USIM)
361    None = 0,
362    /// LTE-M preferred
363    Lte = 1,
364    /// NB-IoT preferred
365    Nbiot = 2,
366    /// Network selection priorities override system priority, but if the same network or equal priority networks are found, LTE-M is preferred
367    NetworkPreferenceWithLteFallback = 3,
368    /// Network selection priorities override system priority, but if the same network or equal priority networks are found, NB-IoT is preferred
369    NetworkPreferenceWithNbiotFallback = 4,
370}
371
372impl SystemMode {
373    fn is_valid_config(&self) -> bool {
374        if self.lte_psm_support && !self.lte_support {
375            return false;
376        }
377        match self.preference {
378            ConnectionPreference::None => true,
379            ConnectionPreference::Lte => self.lte_support,
380            ConnectionPreference::Nbiot => self.nbiot_support,
381            ConnectionPreference::NetworkPreferenceWithLteFallback => {
382                self.lte_support && self.nbiot_support
383            }
384            ConnectionPreference::NetworkPreferenceWithNbiotFallback => {
385                self.lte_support && self.nbiot_support
386            }
387        }
388    }
389
390    fn create_at_command<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], Error> {
391        at_commands::builder::CommandBuilder::create_set(buffer, true)
392            .named("%XSYSTEMMODE")
393            .with_int_parameter(self.lte_support as u8)
394            .with_int_parameter(self.nbiot_support as u8)
395            .with_int_parameter(self.gnss_support as u8)
396            .with_int_parameter(self.preference as u8)
397            .finish()
398            .map_err(|e| Error::BufferTooSmall(Some(e)))
399    }
400
401    async fn setup_psm(&self) -> Result<(), Error> {
402        if self.lte_support {
403            if self.lte_psm_support {
404                // Set Power Saving Mode (PSM)
405                at::send_at::<0>("AT+CPSMS=1").await?;
406            } else {
407                // Turn off PSM
408                at::send_at::<0>("AT+CPSMS=0").await?;
409            }
410        }
411        Ok(())
412    }
413}
414
415/// Enable GNSS on the nRF9160-DK (PCA10090NS)
416///
417/// Sends a AT%XMAGPIO command which activates the off-chip GNSS RF routing
418/// switch when receiving signals between 1574 MHz and 1577 MHz.
419///
420/// Works on the nRF9160-DK (PCA10090NS) and Actinius Icarus. Other PCBs may
421/// use different MAGPIO pins to control the GNSS switch.
422#[cfg(feature = "nrf9160")]
423pub async fn configure_gnss_on_pca10090ns() -> Result<(), Error> {
424    #[cfg(feature = "defmt")]
425    defmt::debug!("Configuring XMAGPIO pins for 1574-1577 MHz");
426
427    // Configure the GNSS antenna. See `nrf/samples/nrf9160/gps/src/main.c`.
428    crate::at::send_at::<0>("AT%XMAGPIO=1,0,0,1,1,1574,1577").await?;
429    Ok(())
430}
431
432struct RuntimeState {
433    state: embassy_sync::mutex::Mutex<CriticalSectionRawMutex, RuntimeStateInner>,
434    error: AtomicBool,
435}
436
437struct RuntimeStateInner {
438    gps_active: bool,
439    lte_link_count: u16,
440    uicc_link_count: u16,
441}
442
443impl RuntimeState {
444    const fn new() -> Self {
445        Self {
446            state: embassy_sync::mutex::Mutex::new(RuntimeStateInner {
447                gps_active: false,
448                lte_link_count: 0,
449                uicc_link_count: 0,
450            }),
451            error: AtomicBool::new(false),
452        }
453    }
454
455    pub(crate) async fn activate_gps(&self) -> Result<(), Error> {
456        let mut state = self.state.lock().await;
457
458        if state.gps_active {
459            return Err(Error::GnssAlreadyTaken);
460        }
461
462        ModemActivation::Gnss.act_on_modem().await?;
463
464        state.gps_active = true;
465
466        Ok(())
467    }
468
469    pub(crate) async fn deactivate_gps(&self) -> Result<(), Error> {
470        let mut state = self.state.lock().await;
471
472        if !state.gps_active {
473            panic!("Can't deactivate an inactive gps");
474        }
475
476        if state.lte_link_count == 0 && state.uicc_link_count == 0 {
477            ModemDeactivation::Everything.act_on_modem().await?;
478        } else {
479            ModemDeactivation::OnlyGnss.act_on_modem().await?;
480        }
481
482        state.gps_active = false;
483
484        Ok(())
485    }
486
487    pub(crate) fn deactivate_gps_blocking(&self) -> Result<(), Error> {
488        let mut state = self
489            .state
490            .try_lock()
491            .map_err(|_| Error::InternalRuntimeMutexLocked)?;
492
493        if !state.gps_active {
494            panic!("Can't deactivate an inactive gps");
495        }
496
497        if state.lte_link_count == 0 && state.uicc_link_count == 0 {
498            ModemDeactivation::Everything.act_on_modem_blocking()?;
499        } else {
500            ModemDeactivation::OnlyGnss.act_on_modem_blocking()?;
501        }
502
503        state.gps_active = false;
504
505        Ok(())
506    }
507
508    pub(crate) async fn activate_lte(&self) -> Result<(), Error> {
509        let mut state = self.state.lock().await;
510
511        if state.lte_link_count == u16::MAX {
512            return Err(Error::TooManyLteLinks);
513        }
514
515        if state.lte_link_count == 0 {
516            ModemActivation::Lte.act_on_modem().await?;
517        }
518
519        state.lte_link_count += 1;
520
521        Ok(())
522    }
523
524    pub(crate) async fn deactivate_lte(&self) -> Result<(), Error> {
525        let mut state = self.state.lock().await;
526
527        if state.lte_link_count == 0 {
528            panic!("Can't deactivate an inactive lte");
529        }
530
531        if state.lte_link_count == 1 {
532            if !state.gps_active && state.uicc_link_count == 0 {
533                ModemDeactivation::Everything.act_on_modem().await?;
534            } else {
535                ModemDeactivation::OnlyLte.act_on_modem().await?;
536                if state.uicc_link_count == 0 {
537                    ModemDeactivation::OnlyUicc.act_on_modem().await?;
538                }
539            }
540        }
541
542        state.lte_link_count -= 1;
543
544        Ok(())
545    }
546
547    pub(crate) fn deactivate_lte_blocking(&self) -> Result<(), Error> {
548        let mut state = self
549            .state
550            .try_lock()
551            .map_err(|_| Error::InternalRuntimeMutexLocked)?;
552
553        if state.lte_link_count == 0 {
554            panic!("Can't deactivate an inactive lte");
555        }
556
557        if state.lte_link_count == 1 {
558            if !state.gps_active && state.uicc_link_count == 0 {
559                ModemDeactivation::Everything.act_on_modem_blocking()?;
560            } else {
561                ModemDeactivation::OnlyLte.act_on_modem_blocking()?;
562                if state.uicc_link_count == 0 {
563                    ModemDeactivation::OnlyUicc.act_on_modem_blocking()?;
564                }
565            }
566        }
567
568        state.lte_link_count -= 1;
569
570        Ok(())
571    }
572
573    pub(crate) async fn activate_uicc(&self) -> Result<(), Error> {
574        let mut state = self.state.lock().await;
575
576        if state.uicc_link_count == u16::MAX {
577            return Err(Error::TooManyUiccLinks);
578        }
579
580        if state.uicc_link_count == 0 {
581            ModemActivation::Uicc.act_on_modem().await?;
582        }
583
584        state.uicc_link_count += 1;
585
586        Ok(())
587    }
588
589    pub(crate) async fn deactivate_uicc(&self) -> Result<(), Error> {
590        let mut state = self.state.lock().await;
591
592        if state.uicc_link_count == 0 {
593            panic!("Can't deactivate an inactive UICC");
594        }
595
596        if state.uicc_link_count == 1 {
597            if state.gps_active || state.lte_link_count > 0 {
598                ModemDeactivation::OnlyUicc
599            } else {
600                ModemDeactivation::Everything
601            }
602        } else {
603            ModemDeactivation::Nothing
604        }
605        .act_on_modem()
606        .await?;
607
608        state.uicc_link_count -= 1;
609
610        Ok(())
611    }
612
613    pub(crate) fn deactivate_uicc_blocking(&self) -> Result<(), Error> {
614        let mut state = self
615            .state
616            .try_lock()
617            .map_err(|_| Error::InternalRuntimeMutexLocked)?;
618
619        if state.uicc_link_count == 0 {
620            panic!("Can't deactivate an inactive UICC");
621        }
622
623        if state.uicc_link_count == 1 {
624            if state.gps_active || state.lte_link_count > 0 {
625                ModemDeactivation::OnlyUicc
626            } else {
627                ModemDeactivation::Everything
628            }
629        } else {
630            ModemDeactivation::Nothing
631        }
632        .act_on_modem_blocking()?;
633
634        state.uicc_link_count -= 1;
635
636        Ok(())
637    }
638
639    pub(crate) fn set_error_active(&self) {
640        self.error.store(true, Ordering::SeqCst);
641    }
642
643    pub(crate) fn get_error_active(&self) -> bool {
644        self.error.load(Ordering::SeqCst)
645    }
646
647    pub(crate) async unsafe fn reset_runtime_state(&self) -> Result<(), Error> {
648        let mut state = self.state.lock().await;
649
650        if self
651            .error
652            .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
653            .is_ok()
654        {
655            ModemDeactivation::Everything.act_on_modem().await?;
656            state.gps_active = false;
657            state.lte_link_count = 0;
658            state.uicc_link_count = 0;
659        }
660
661        self.error.store(false, Ordering::SeqCst);
662
663        Ok(())
664    }
665}
666
667/// Returns true when the runtime has detected that its state may not represent the actual modem state.
668/// This means that the modem may remain active while the runtime thinks it has turned it off.
669///
670/// This can be fixed using [reset_runtime_state]
671pub fn has_runtime_state_error() -> bool {
672    MODEM_RUNTIME_STATE.get_error_active()
673}
674
675/// Resets the runtime state by forcing the modem to be turned off and resetting the state back to 0.
676///
677/// ## Safety
678///
679/// This function may only be used when you've made sure that **no** active LteLinks instances and Gnss instances exist
680pub async unsafe fn reset_runtime_state() -> Result<(), Error> {
681    MODEM_RUNTIME_STATE.reset_runtime_state().await
682}
683
684enum ModemDeactivation {
685    OnlyGnss,
686    OnlyLte,
687    OnlyUicc,
688    Nothing,
689    Everything,
690}
691
692impl ModemDeactivation {
693    async fn act_on_modem(&self) -> Result<(), Error> {
694        match self {
695            ModemDeactivation::OnlyGnss => {
696                #[cfg(feature = "defmt")]
697                defmt::debug!("Disabling modem GNSS");
698
699                at::send_at::<0>("AT+CFUN=30").await?;
700            }
701            ModemDeactivation::OnlyLte => {
702                #[cfg(feature = "defmt")]
703                defmt::debug!("Disabling modem LTE");
704
705                // Turn off the network side of the modem
706                at::send_at::<0>("AT+CFUN=20").await?;
707                // Do not turn of UICC, let the caller do that.
708            }
709            ModemDeactivation::OnlyUicc => {
710                #[cfg(feature = "defmt")]
711                defmt::debug!("Disabling UICC");
712                // Turn off the UICC
713                at::send_at::<0>("AT+CFUN=40").await?;
714            }
715            ModemDeactivation::Nothing => {}
716            ModemDeactivation::Everything => {
717                #[cfg(feature = "defmt")]
718                defmt::debug!("Disabling full modem");
719
720                at::send_at::<0>("AT+CFUN=0").await?;
721            }
722        }
723
724        Ok(())
725    }
726
727    fn act_on_modem_blocking(&self) -> Result<(), Error> {
728        match self {
729            ModemDeactivation::OnlyGnss => {
730                #[cfg(feature = "defmt")]
731                defmt::debug!("Disabling modem GNSS");
732
733                at::send_at_blocking::<0>("AT+CFUN=30")?;
734            }
735            ModemDeactivation::OnlyLte => {
736                #[cfg(feature = "defmt")]
737                defmt::debug!("Disabling modem LTE");
738
739                // Turn off the network side of the modem
740                at::send_at_blocking::<0>("AT+CFUN=20")?;
741                // Do not turn of UICC, let the caller do that.
742            }
743            ModemDeactivation::OnlyUicc => {
744                #[cfg(feature = "defmt")]
745                defmt::debug!("Disabling UICC");
746                // Turn off the UICC
747                at::send_at_blocking::<0>("AT+CFUN=40")?;
748            }
749            ModemDeactivation::Nothing => {}
750            ModemDeactivation::Everything => {
751                #[cfg(feature = "defmt")]
752                defmt::debug!("Disabling full modem");
753
754                at::send_at_blocking::<0>("AT+CFUN=0")?;
755            }
756        }
757
758        Ok(())
759    }
760}
761
762enum ModemActivation {
763    Lte,
764    Gnss,
765    Uicc,
766}
767
768impl ModemActivation {
769    async fn act_on_modem(&self) -> Result<(), Error> {
770        match self {
771            ModemActivation::Gnss => {
772                #[cfg(feature = "defmt")]
773                defmt::debug!("Enabling modem GNSS");
774
775                at::send_at::<0>("AT+CFUN=31").await?;
776            }
777            ModemActivation::Lte => {
778                #[cfg(feature = "defmt")]
779                defmt::debug!("Enabling modem LTE");
780
781                // Set Ultra low power mode
782                at::send_at::<0>("AT%XDATAPRFL=0").await?;
783                // Set UICC low power mode
784                at::send_at::<0>("AT+CEPPI=1").await?;
785                // Activate LTE without changing GNSS
786                at::send_at::<0>("AT+CFUN=21").await?;
787            }
788            ModemActivation::Uicc => {
789                #[cfg(feature = "defmt")]
790                defmt::debug!("Enabling UICC");
791
792                // Set UICC low power mode
793                at::send_at::<0>("AT+CEPPI=1").await?;
794                // Activate LTE without changing GNSS
795                at::send_at::<0>("AT+CFUN=41").await?;
796            }
797        }
798
799        Ok(())
800    }
801}