aboutsummaryrefslogtreecommitdiff
path: root/embassy-net-nrf91/src/context.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-net-nrf91/src/context.rs')
-rw-r--r--embassy-net-nrf91/src/context.rs196
1 files changed, 196 insertions, 0 deletions
diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs
new file mode 100644
index 000000000..6dda51793
--- /dev/null
+++ b/embassy-net-nrf91/src/context.rs
@@ -0,0 +1,196 @@
1use core::net::IpAddr;
2use heapless::String;
3use core::str::FromStr;
4use core::fmt::Write;
5
6/// Provides a higher level API for configuring and reading information for a given
7/// context id.
8pub struct Control<'a> {
9 control: crate::Control<'a>,
10 cid: u8,
11}
12
13pub struct Config<'a> {
14 pub gateway: &'a str,
15 pub auth_prot: AuthProt,
16 pub auth: Option<(&'a str, &'a str)>,
17}
18
19#[repr(u8)]
20pub enum AuthProt {
21 None = 0,
22 Pap = 1,
23 Chap = 2,
24}
25
26#[derive(Clone, Copy, PartialEq, Debug)]
27#[cfg_attr(feature = "defmt", derive(defmt::Format))]
28pub enum Error {
29 BufferTooSmall,
30 AtCommand,
31 AddrParseError,
32 Format,
33}
34
35impl From<core::fmt::Error> for Error {
36 fn from(_: core::fmt::Error) -> Self {
37 Self::Format
38 }
39}
40
41#[derive(PartialEq, Debug)]
42pub struct Status {
43 pub attached: bool,
44 pub ip: Option<IpAddr>,
45}
46
47#[cfg(feature = "defmt")]
48impl defmt::Format for Status {
49 fn format(&self, f: defmt::Formatter<'_>) {
50 defmt::write!(f, "attached: {}", self.attached);
51 if let Some(ip) = &self.ip {
52 defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip));
53 }
54 }
55}
56
57impl<'a> Control<'a> {
58 pub async fn new(control: crate::Control<'a>, cid: u8) -> Self {
59 control.wait_init().await;
60 Self { control, cid }
61 }
62
63 /// Bypass modem configurator
64 pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize {
65 self.control.at_command(req, resp).await
66 }
67
68 /// Configures the modem with the provided config.
69 pub async fn configure(&self, config: Config<'_>) -> Result<(), Error> {
70 let mut cmd: String<128> = String::new();
71 let mut buf: [u8; 256] = [0; 256];
72
73 write!(cmd, "AT+CGDCONT={},\"IP\",\"{}\"", self.cid, config.gateway).map_err(|_| Error::BufferTooSmall)?;
74 let n = self.control.at_command(cmd.as_bytes(), &mut buf).await;
75 let mut res = &buf[..n];
76 let res = split_field(&mut res);
77 if res != b"OK" {
78 return Err(Error::AtCommand)
79 }
80 cmd.clear();
81
82 write!(cmd, "AT+CGAUTH={},{}", self.cid, config.auth_prot as u8)?;
83 if let Some((username, password)) = config.auth {
84 write!(cmd, ",\"{}\",\"{}\"", username, password).map_err(|_| Error::BufferTooSmall)?;
85 }
86 let n = self.control.at_command(cmd.as_bytes(), &mut buf).await;
87 let mut res = &buf[..n];
88 let res = split_field(&mut res);
89 if res != b"OK" {
90 return Err(Error::AtCommand)
91 }
92 cmd.clear();
93
94 let n = self.control.at_command(b"AT+CFUN=1", &mut buf).await;
95 let mut res = &buf[..n];
96 let res = split_field(&mut res);
97 if res != b"OK" {
98 return Err(Error::AtCommand);
99 }
100
101 Ok(())
102 }
103
104 pub async fn status(&self) -> Result<Status, Error> {
105 let mut buf: [u8; 256] = [0; 256];
106 let n = self.control.at_command(b"AT+CGATT?", &mut buf).await;
107 let mut res = &buf[..n];
108 pop_prefix(&mut res, b"+CGATT: ");
109 let res = split_field(&mut res);
110 let attached = res == b"1";
111
112 if !attached {
113 return Ok(Status { attached, ip: None })
114 }
115
116 let mut s: String<128> = String::new();
117 write!(s, "AT+CGPADDR={}", self.cid)?;
118 let n = self.control.at_command(s.as_bytes(), &mut buf).await;
119 let mut res = &buf[..n];
120 s.clear();
121
122 write!(s, "+CGPADDR: {},", self.cid)?;
123
124 info!("RES: {:?}", unsafe {core::str::from_utf8_unchecked(res)});
125 if s.len() > res.len() {
126 let res = split_field(&mut res);
127 if res == b"OK" {
128 Ok(Status { attached, ip: None })
129 } else {
130 Err(Error::AtCommand)
131 }
132 } else {
133 pop_prefix(&mut res, s.as_bytes());
134
135 let ip = split_field(&mut res);
136 if !ip.is_empty() {
137 let ip = IpAddr::from_str(unsafe { core::str::from_utf8_unchecked(ip) }).map_err(|_| Error::AddrParseError)?;
138 self.control.open_raw_socket().await;
139 Ok(Status { attached, ip: Some(ip) })
140 } else {
141 Ok(Status { attached, ip: None })
142 }
143 }
144 }
145}
146
147pub(crate) fn is_whitespace(char: u8) -> bool {
148 match char {
149 b'\r' | b'\n' | b' ' => true,
150 _ => false,
151 }
152}
153
154pub(crate) fn is_separator(char: u8) -> bool {
155 match char {
156 b',' | b'\r' | b'\n' | b' ' => true,
157 _ => false,
158 }
159}
160
161pub(crate) fn split_field<'a>(data: &mut &'a [u8]) -> &'a [u8] {
162 while !data.is_empty() && is_whitespace(data[0]) {
163 *data = &data[1..];
164 }
165
166 if data.is_empty() {
167 return &[];
168 }
169
170 if data[0] == b'"' {
171 let data2 = &data[1..];
172 let end = data2.iter().position(|&x| x == b'"').unwrap_or(data2.len());
173 let field = &data2[..end];
174 let mut rest = &data2[data2.len().min(end + 1)..];
175 if rest.first() == Some(&b'\"') {
176 rest = &rest[1..];
177 }
178 while !rest.is_empty() && is_separator(rest[0]) {
179 rest = &rest[1..];
180 }
181 *data = rest;
182 field
183 } else {
184 let end = data.iter().position(|&x| is_separator(x)).unwrap_or(data.len());
185 let field = &data[0..end];
186 let rest = &data[data.len().min(end + 1)..];
187 *data = rest;
188 field
189 }
190}
191
192pub(crate) fn pop_prefix(data: &mut &[u8], prefix: &[u8]) {
193 assert!(data.len() >= prefix.len());
194 assert!(&data[..prefix.len()] == prefix);
195 *data = &data[prefix.len()..];
196}