aboutsummaryrefslogtreecommitdiff
path: root/embassy-net-nrf91/src
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2024-09-04 11:04:36 +0200
committerUlf Lilleengen <[email protected]>2024-09-04 11:04:36 +0200
commita6db8678eb5a7c9ebcf6449799b4ff525fe52d5f (patch)
tree8a044238a1c0ac0c0606aa5acd7f0af7068399e4 /embassy-net-nrf91/src
parent86a45b47e529864ac5a203b4ad26b9ee62127a1e (diff)
Add utility for setting configuration for a context
Diffstat (limited to 'embassy-net-nrf91/src')
-rw-r--r--embassy-net-nrf91/src/at.rs45
-rw-r--r--embassy-net-nrf91/src/context.rs196
-rw-r--r--embassy-net-nrf91/src/lib.rs2
3 files changed, 198 insertions, 45 deletions
diff --git a/embassy-net-nrf91/src/at.rs b/embassy-net-nrf91/src/at.rs
deleted file mode 100644
index 04cbf3876..000000000
--- a/embassy-net-nrf91/src/at.rs
+++ /dev/null
@@ -1,45 +0,0 @@
1use crate::{Error, Control};
2
3// Drives the control loop of the modem based on declarative configuration.
4pub struct AtDriver<'a> {
5 control: Control<'a>,
6 config: Config,
7}
8
9pub struct Config {
10 pub network: NetworkConfig,
11}
12
13pub struct NetworkConfig {
14 pub apn: &'static str,
15 pub prot: AuthProtection,
16 pub userid: &'static str,
17 pub password: &'static str,
18}
19
20#[repr(u8)]
21pub enum AuthProtection {
22 None = 0,
23 Pap = 1,
24 Chap = 2,
25}
26
27impl<'a> AtDriver<'a> {
28 pub async fn new(control: Control<'a>, config: Config) -> Result<Self, Error> {
29 control.wait_init().await;
30 Ok(Self {
31 control,
32 config,
33 })
34 }
35
36 async fn setup(&self) -> Result<(), Error> {
37
38 }
39
40 pub fn run(&self, stack: Stack<crate::NetDriver<'static>>) -> ! {
41 loop {
42
43 }
44 }
45}
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}
diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs
index 70ad176da..beed7c65a 100644
--- a/embassy-net-nrf91/src/lib.rs
+++ b/embassy-net-nrf91/src/lib.rs
@@ -6,6 +6,8 @@
6// must be first 6// must be first
7mod fmt; 7mod fmt;
8 8
9pub mod context;
10
9use core::cell::RefCell; 11use core::cell::RefCell;
10use core::future::poll_fn; 12use core::future::poll_fn;
11use core::marker::PhantomData; 13use core::marker::PhantomData;