nrf_modem/embassy_net_modem/
context.rs1use core::net::IpAddr;
8use core::str::FromStr;
9
10use at_commands::builder::CommandBuilder;
11use at_commands::parser::CommandParser;
12use embassy_time::{Duration, Timer};
13use heapless::Vec;
14
15use crate::embassy_net_modem::CAP_SIZE;
16
17pub struct Control<'a> {
19 control: super::Control<'a>,
20 cid: u8,
21}
22
23pub struct Config<'a> {
25 pub apn: &'a [u8],
27 pub auth_prot: AuthProt,
29 pub auth: Option<(&'a [u8], &'a [u8])>,
31 pub pin: Option<&'a [u8]>,
33}
34
35#[derive(Clone, Copy, PartialEq, Debug)]
37#[cfg_attr(feature = "defmt", derive(defmt::Format))]
38#[repr(u8)]
39pub enum AuthProt {
40 None = 0,
42 Pap = 1,
44 Chap = 2,
46}
47
48#[derive(Clone, Copy, PartialEq, Debug)]
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
51pub enum Error {
52 BufferTooSmall,
54 AtParseError,
56 AddrParseError,
58}
59
60impl From<at_commands::parser::ParseError> for Error {
61 fn from(_: at_commands::parser::ParseError) -> Self {
62 Self::AtParseError
63 }
64}
65
66#[derive(PartialEq, Debug)]
68pub struct Status {
69 pub attached: bool,
71 pub ip: Option<IpAddr>,
73 pub gateway: Option<IpAddr>,
75 pub dns: Vec<IpAddr, 2>,
77}
78
79#[cfg(feature = "defmt")]
80impl defmt::Format for Status {
81 fn format(&self, f: defmt::Formatter<'_>) {
82 defmt::write!(f, "attached: {}", self.attached);
83 if let Some(ip) = &self.ip {
84 defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip));
85 }
86 }
87}
88
89impl<'a> Control<'a> {
90 pub async fn new(control: super::Control<'a>, cid: u8) -> Self {
94 Self { control, cid }
95 }
96
97 pub async fn at_command(self, req: &[u8]) -> arrayvec::ArrayString<CAP_SIZE> {
99 self.control.at_command(req).await
100 }
101
102 pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> {
109 let mut cmd: [u8; 256] = [0; 256];
110
111 let op = CommandBuilder::create_set(&mut cmd, true)
112 .named("+CFUN")
113 .with_int_parameter(0)
114 .finish()
115 .map_err(|_| Error::BufferTooSmall)?;
116 let n = self.control.at_command(op).await;
117 CommandParser::parse(n.as_bytes())
118 .expect_identifier(b"OK")
119 .finish()?;
120
121 let op = CommandBuilder::create_set(&mut cmd, true)
122 .named("+CGDCONT")
123 .with_int_parameter(self.cid)
124 .with_string_parameter("IP")
125 .with_string_parameter(config.apn)
126 .finish()
127 .map_err(|_| Error::BufferTooSmall)?;
128 let n = self.control.at_command(op).await;
129 CommandParser::parse(n.as_bytes())
131 .expect_identifier(b"OK")
132 .finish()?;
133
134 let mut op = CommandBuilder::create_set(&mut cmd, true)
135 .named("+CGAUTH")
136 .with_int_parameter(self.cid)
137 .with_int_parameter(config.auth_prot as u8);
138 if let Some((username, password)) = config.auth {
139 op = op
140 .with_string_parameter(username)
141 .with_string_parameter(password);
142 }
143 let op = op.finish().map_err(|_| Error::BufferTooSmall)?;
144
145 let n = self.control.at_command(op).await;
146 CommandParser::parse(n.as_bytes())
148 .expect_identifier(b"OK")
149 .finish()?;
150
151 if let Some(pin) = config.pin {
152 let op = CommandBuilder::create_set(&mut cmd, true)
153 .named("+CPIN")
154 .with_string_parameter(pin)
155 .finish()
156 .map_err(|_| Error::BufferTooSmall)?;
157 let _ = self.control.at_command(op).await;
158 }
160
161 Ok(())
162 }
163
164 pub async fn attach(&self) -> Result<(), Error> {
166 let mut cmd: [u8; 256] = [0; 256];
167 let op = CommandBuilder::create_set(&mut cmd, true)
168 .named("+CGATT")
169 .with_int_parameter(1)
170 .finish()
171 .map_err(|_| Error::BufferTooSmall)?;
172 let n = self.control.at_command(op).await;
173 CommandParser::parse(n.as_bytes())
174 .expect_identifier(b"OK")
175 .finish()?;
176 Ok(())
177 }
178
179 pub async fn detach(&self) -> Result<(), Error> {
181 let mut cmd: [u8; 256] = [0; 256];
182 let op = CommandBuilder::create_set(&mut cmd, true)
183 .named("+CGATT")
184 .with_int_parameter(0)
185 .finish()
186 .map_err(|_| Error::BufferTooSmall)?;
187 let n = self.control.at_command(op).await;
188 CommandParser::parse(n.as_bytes())
189 .expect_identifier(b"OK")
190 .finish()?;
191 Ok(())
192 }
193
194 async fn attached(&self) -> Result<bool, Error> {
195 let mut cmd: [u8; 256] = [0; 256];
196
197 let op = CommandBuilder::create_query(&mut cmd, true)
198 .named("+CGATT")
199 .finish()
200 .map_err(|_| Error::BufferTooSmall)?;
201 let n = self.control.at_command(op).await;
202 let (res,) = CommandParser::parse(n.as_bytes())
203 .expect_identifier(b"+CGATT: ")
204 .expect_int_parameter()
205 .expect_identifier(b"\r\nOK")
206 .finish()?;
207 Ok(res == 1)
208 }
209
210 pub async fn status(&self) -> Result<Status, Error> {
212 let mut cmd: [u8; 256] = [0; 256];
213
214 let op = CommandBuilder::create_query(&mut cmd, true)
215 .named("+CGATT")
216 .finish()
217 .map_err(|_| Error::BufferTooSmall)?;
218 let n = self.control.at_command(op).await;
219 let (res,) = CommandParser::parse(n.as_bytes())
220 .expect_identifier(b"+CGATT: ")
221 .expect_int_parameter()
222 .expect_identifier(b"\r\nOK")
223 .finish()?;
224 let attached = res == 1;
225 if !attached {
226 return Ok(Status {
227 attached,
228 ip: None,
229 gateway: None,
230 dns: Vec::new(),
231 });
232 }
233
234 let op = CommandBuilder::create_set(&mut cmd, true)
235 .named("+CGPADDR")
236 .with_int_parameter(self.cid)
237 .finish()
238 .map_err(|_| Error::BufferTooSmall)?;
239 let n = self.control.at_command(op).await;
240 let (_, ip1, _ip2) = CommandParser::parse(n.as_bytes())
241 .expect_identifier(b"+CGPADDR: ")
242 .expect_int_parameter()
243 .expect_optional_string_parameter()
244 .expect_optional_string_parameter()
245 .expect_identifier(b"\r\nOK")
246 .finish()?;
247
248 let ip = if let Some(ip) = ip1 {
249 let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?;
250 Some(ip)
251 } else {
252 None
253 };
254
255 let op = CommandBuilder::create_set(&mut cmd, true)
256 .named("+CGCONTRDP")
257 .with_int_parameter(self.cid)
258 .finish()
259 .map_err(|_| Error::BufferTooSmall)?;
260 let n = self.control.at_command(op).await;
261 let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) =
262 CommandParser::parse(n.as_bytes())
263 .expect_identifier(b"+CGCONTRDP: ")
264 .expect_int_parameter()
265 .expect_optional_int_parameter()
266 .expect_optional_string_parameter()
267 .expect_optional_string_parameter()
268 .expect_optional_string_parameter()
269 .expect_optional_string_parameter()
270 .expect_optional_string_parameter()
271 .expect_optional_int_parameter()
272 .expect_optional_int_parameter()
273 .expect_optional_int_parameter()
274 .expect_optional_int_parameter()
275 .expect_optional_int_parameter()
276 .expect_identifier(b"\r\nOK")
277 .finish()?;
278
279 let gateway = if let Some(ip) = gateway {
280 if ip.is_empty() {
281 None
282 } else {
283 Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
284 }
285 } else {
286 None
287 };
288
289 let mut dns = Vec::new();
290 if let Some(ip) = dns1 {
291 dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
292 .unwrap();
293 }
294
295 if let Some(ip) = dns2 {
296 dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
297 .unwrap();
298 }
299
300 Ok(Status {
301 attached,
302 ip,
303 gateway,
304 dns,
305 })
306 }
307
308 async fn wait_attached(&self) -> Result<Status, Error> {
309 while !self.attached().await? {
310 Timer::after(Duration::from_secs(1)).await;
311 }
312 let status = self.status().await?;
313 Ok(status)
314 }
315
316 pub async fn disable(&self) -> Result<(), Error> {
318 let mut cmd: [u8; 256] = [0; 256];
319
320 let op = CommandBuilder::create_set(&mut cmd, true)
321 .named("+CFUN")
322 .with_int_parameter(0)
323 .finish()
324 .map_err(|_| Error::BufferTooSmall)?;
325 let n = self.control.at_command(op).await;
326 CommandParser::parse(n.as_bytes())
327 .expect_identifier(b"OK")
328 .finish()?;
329
330 Ok(())
331 }
332
333 pub async fn enable(&self) -> Result<(), Error> {
335 let mut cmd: [u8; 256] = [0; 256];
336
337 let op = CommandBuilder::create_set(&mut cmd, true)
338 .named("+CFUN")
339 .with_int_parameter(1)
340 .finish()
341 .map_err(|_| Error::BufferTooSmall)?;
342 let n = self.control.at_command(op).await;
343 CommandParser::parse(n.as_bytes())
344 .expect_identifier(b"OK")
345 .finish()?;
346
347 let op = CommandBuilder::create_set(&mut cmd, true)
349 .named("%XPDNCFG")
350 .with_int_parameter(1)
351 .finish()
352 .map_err(|_| Error::BufferTooSmall)?;
353 let n = self.control.at_command(op).await;
354 CommandParser::parse(n.as_bytes())
355 .expect_identifier(b"OK")
356 .finish()?;
357 Ok(())
358 }
359
360 pub async fn run<F: Fn(&Status)>(&self, reattach: F) -> Result<(), Error> {
362 self.enable().await?;
363 let status = self.wait_attached().await?;
364 self.control.open_raw_socket().await;
365 reattach(&status);
366
367 loop {
368 if !self.attached().await? {
369 self.control.close_raw_socket().await;
370 let status = self.wait_attached().await?;
371 self.control.open_raw_socket().await;
372 reattach(&status);
373 }
374 Timer::after(Duration::from_secs(10)).await;
375 }
376 }
377}