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