nrf_modem/
lte_link.rs

1//! Implementation of [LteLink]
2
3use crate::{at, at_notifications::AtNotificationStream, error::Error, CancellationToken};
4use core::{mem, ops::ControlFlow, task::Poll};
5
6/// An object that keeps the modem connected.
7/// As long as there is an instance, the modem will be kept on.
8/// The drop function disables the modem if there is no link left.
9///
10/// Everything will work even if the user doesn't use this directly.
11/// Every API that requires a network connection uses this internally.
12///
13/// However, this can lead to inefficiencies.
14/// For example, if you do a dns request and then make a socket to connect to the IP,
15/// the link will fall away in between.
16///
17/// The user can prevent this by creating his own instance that the user only drops when all network tasks are done.
18#[derive(Debug, PartialEq, Eq)]
19#[cfg_attr(feature = "defmt", derive(defmt::Format))]
20pub struct LteLink(());
21
22impl LteLink {
23    /// Create a new instance
24    pub async fn new() -> Result<Self, Error> {
25        if unsafe { !nrfxlib_sys::nrf_modem_is_initialized() } {
26            return Err(Error::ModemNotInitialized);
27        }
28
29        crate::MODEM_RUNTIME_STATE.activate_lte().await?;
30
31        Ok(LteLink(()))
32    }
33
34    /// While there is an instance of the LteLink, the modem is active.
35    /// But that does not mean that there is access to the network.
36    ///
37    /// Call this function to wait until there is a connection.
38    pub async fn wait_for_link(&self) -> Result<(), Error> {
39        self.wait_for_link_with_cancellation(&Default::default())
40            .await
41    }
42
43    /// While there is an instance of the LteLink, the modem is active.
44    /// But that does not mean that there is access to the network.
45    ///
46    /// Call this function to wait until there is a connection.
47    pub async fn wait_for_link_with_cancellation(
48        &self,
49        token: &CancellationToken,
50    ) -> Result<(), Error> {
51        use futures::StreamExt;
52
53        token.bind_to_current_task().await;
54
55        // We're gonna be looking for notifications. And to make sure we don't miss one,
56        // we already create the stream and register it.
57        let notification_stream = AtNotificationStream::<64, 4>::new().await;
58        futures::pin_mut!(notification_stream);
59        notification_stream.as_mut().register().await;
60
61        // Enable the notifications
62        at::send_at::<0>("AT+CEREG=1").await?;
63
64        token.as_result()?;
65
66        // We won't get a notification if we're already connected.
67        // So query the current status
68        match Self::get_cereg_stat_control_flow(Self::parse_cereg(
69            at::send_at::<64>("AT+CEREG?").await?.as_str(),
70        )) {
71            ControlFlow::Continue(_) => {}
72            ControlFlow::Break(result) => return result,
73        }
74
75        token.as_result()?;
76
77        // We are currently not connected, so lets wait for what the stream turns up
78        let mut stream = notification_stream
79            .map(|notif| Self::get_cereg_stat_control_flow(Self::parse_cereg(notif.as_str())));
80
81        while let Some(cereg) = core::future::poll_fn(|cx| {
82            if token.is_cancelled() {
83                Poll::Ready(None)
84            } else {
85                stream.poll_next_unpin(cx)
86            }
87        })
88        .await
89        {
90            match cereg {
91                ControlFlow::Continue(_) => {
92                    token.as_result()?;
93                }
94                ControlFlow::Break(result) => return result,
95            }
96        }
97
98        token.as_result()?;
99
100        unreachable!()
101    }
102
103    fn parse_cereg(string: &str) -> Result<i32, Error> {
104        // We can expect two kinds of strings here.
105        // The first is the response to our query that ends with 'OK'.
106        // The second is the notification string.
107
108        let cereg = at_commands::parser::CommandParser::parse(string.as_bytes())
109            .expect_identifier(b"+CEREG:")
110            .expect_int_parameter()
111            .expect_int_parameter()
112            .expect_identifier(b"\r\nOK\r\n")
113            .finish()
114            .map(|(_, stat)| stat);
115
116        cereg
117            .or_else(|_| {
118                at_commands::parser::CommandParser::parse(string.as_bytes())
119                    .expect_identifier(b"+CEREG:")
120                    .expect_int_parameter()
121                    .expect_identifier(b"\r\n")
122                    .finish()
123                    .map(|(stat,)| stat)
124            })
125            .map_err(|e| e.into())
126    }
127
128    fn get_cereg_stat_control_flow(stat: Result<i32, Error>) -> ControlFlow<Result<(), Error>, ()> {
129        // Based on the stat number, we know that state of the connection
130        match stat {
131            Err(_) => ControlFlow::Continue(()),
132            Ok(1) | Ok(5) => ControlFlow::Break(Ok(())),
133            Ok(0) | Ok(2) | Ok(4) => ControlFlow::Continue(()),
134            Ok(3) => ControlFlow::Break(Err(Error::LteRegistrationDenied)),
135            Ok(90) => ControlFlow::Break(Err(Error::SimFailure)),
136            _ => ControlFlow::Break(Err(Error::UnexpectedAtResponse)),
137        }
138    }
139
140    /// Deactivates Lte. This does the same as dropping the instance, but in an async manner.
141    pub async fn deactivate(self) -> Result<(), Error> {
142        mem::forget(self);
143        let result = crate::MODEM_RUNTIME_STATE.deactivate_lte().await;
144
145        if result.is_err() {
146            crate::MODEM_RUNTIME_STATE.set_error_active();
147        }
148
149        result
150    }
151}
152
153impl Drop for LteLink {
154    fn drop(&mut self) {
155        #[cfg(feature = "defmt")]
156        defmt::warn!(
157            "Turning off LTE synchronously. Use async function `deactivate` to avoid blocking and to get more guarantees that the modem is actually shut off."
158        );
159
160        if let Err(_e) = crate::MODEM_RUNTIME_STATE.deactivate_lte_blocking() {
161            #[cfg(feature = "defmt")]
162            defmt::error!("Could not turn off the lte: {}", _e);
163            crate::MODEM_RUNTIME_STATE.set_error_active();
164        }
165    }
166}