aboutsummaryrefslogtreecommitdiff
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
parent86a45b47e529864ac5a203b4ad26b9ee62127a1e (diff)
Add utility for setting configuration for a context
-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
-rw-r--r--examples/nrf9160/src/bin/modem_tcp_client.rs64
4 files changed, 220 insertions, 87 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;
diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs
index b1dac18a1..817ad17c7 100644
--- a/examples/nrf9160/src/bin/modem_tcp_client.rs
+++ b/examples/nrf9160/src/bin/modem_tcp_client.rs
@@ -2,14 +2,15 @@
2#![no_main] 2#![no_main]
3 3
4use core::mem::MaybeUninit; 4use core::mem::MaybeUninit;
5use core::net::IpAddr;
5use core::ptr::addr_of_mut; 6use core::ptr::addr_of_mut;
6use core::str::FromStr; 7use core::str::FromStr;
7use core::{slice, str}; 8use core::{slice, str};
8 9
9use defmt::{assert, *}; 10use defmt::{assert, info, warn, unwrap};
10use embassy_executor::Spawner; 11use embassy_executor::Spawner;
11use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; 12use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources};
12use embassy_net_nrf91::{Runner, State}; 13use embassy_net_nrf91::{Runner, State, context};
13use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; 14use embassy_nrf::buffered_uarte::{self, BufferedUarteTx};
14use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; 15use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin};
15use embassy_nrf::uarte::Baudrate; 16use embassy_nrf::uarte::Baudrate;
@@ -63,9 +64,9 @@ async fn blink_task(pin: AnyPin) {
63 let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); 64 let mut led = Output::new(pin, Level::Low, OutputDrive::Standard);
64 loop { 65 loop {
65 led.set_high(); 66 led.set_high();
66 Timer::after_millis(100).await; 67 Timer::after_millis(1000).await;
67 led.set_low(); 68 led.set_low();
68 Timer::after_millis(100).await; 69 Timer::after_millis(1000).await;
69 } 70 }
70} 71}
71 72
@@ -123,51 +124,30 @@ async fn main(spawner: Spawner) {
123 124
124 unwrap!(spawner.spawn(net_task(stack))); 125 unwrap!(spawner.spawn(net_task(stack)));
125 126
126 control.wait_init().await; 127 let control = context::Control::new(control, 0).await;
127 info!("INIT OK");
128 128
129 let mut buf = [0u8; 256]; 129 unwrap!(control.configure(context::Config {
130 130 gateway: "iot.nat.es",
131 let n = control.at_command(b"AT+CFUN?", &mut buf).await; 131 auth_prot: context::AuthProt::Pap,
132 info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) }); 132 auth: Some(("orange", "orange")),
133 133 }).await);
134 let n = control
135 .at_command(b"AT+CGDCONT=0,\"IP\",\"iot.nat.es\"", &mut buf)
136 .await;
137 info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) });
138 let n = control
139 .at_command(b"AT+CGAUTH=0,1,\"orange\",\"orange\"", &mut buf)
140 .await;
141 info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) });
142
143 let n = control.at_command(b"AT+CFUN=1", &mut buf).await;
144 info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) });
145 134
146 info!("waiting for attach..."); 135 info!("waiting for attach...");
147 loop {
148 Timer::after_millis(500).await;
149 let n = control.at_command(b"AT+CGATT?", &mut buf).await;
150 let mut res = &buf[..n];
151 pop_prefix(&mut res, b"+CGATT: ");
152 let res = split_field(&mut res);
153 info!("AT resp field: '{}'", unsafe { str::from_utf8_unchecked(res) });
154 if res == b"1" {
155 break;
156 }
157 }
158 136
159 let n = control.at_command(b"AT+CGPADDR=0", &mut buf).await; 137 let mut status = unwrap!(control.status().await);
160 let mut res = &buf[..n]; 138 while !status.attached && status.ip.is_none() {
161 pop_prefix(&mut res, b"+CGPADDR: 0,"); 139 Timer::after_millis(1000).await;
162 let ip = split_field(&mut res); 140 status = unwrap!(control.status().await);
163 let ip = Ipv4Address::from_str(unsafe { str::from_utf8_unchecked(ip) }).unwrap(); 141 info!("STATUS: {:?}", status);
164 info!("IP: '{}'", ip); 142 }
165 143
166 info!("============== OPENING SOCKET"); 144 let Some(IpAddr::V4(addr)) = status.ip else {
167 control.open_raw_socket().await; 145 panic!("Unexpected IP address");
146 };
147 let addr = Ipv4Address(addr.octets());
168 148
169 stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { 149 stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 {
170 address: Ipv4Cidr::new(ip, 32), 150 address: Ipv4Cidr::new(addr, 32),
171 gateway: None, 151 gateway: None,
172 dns_servers: Default::default(), 152 dns_servers: Default::default(),
173 })); 153 }));