diff options
| author | Ulf Lilleengen <[email protected]> | 2024-09-04 11:04:36 +0200 |
|---|---|---|
| committer | Ulf Lilleengen <[email protected]> | 2024-09-04 11:04:36 +0200 |
| commit | a6db8678eb5a7c9ebcf6449799b4ff525fe52d5f (patch) | |
| tree | 8a044238a1c0ac0c0606aa5acd7f0af7068399e4 /embassy-net-nrf91/src | |
| parent | 86a45b47e529864ac5a203b4ad26b9ee62127a1e (diff) | |
Add utility for setting configuration for a context
Diffstat (limited to 'embassy-net-nrf91/src')
| -rw-r--r-- | embassy-net-nrf91/src/at.rs | 45 | ||||
| -rw-r--r-- | embassy-net-nrf91/src/context.rs | 196 | ||||
| -rw-r--r-- | embassy-net-nrf91/src/lib.rs | 2 |
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 @@ | |||
| 1 | use crate::{Error, Control}; | ||
| 2 | |||
| 3 | // Drives the control loop of the modem based on declarative configuration. | ||
| 4 | pub struct AtDriver<'a> { | ||
| 5 | control: Control<'a>, | ||
| 6 | config: Config, | ||
| 7 | } | ||
| 8 | |||
| 9 | pub struct Config { | ||
| 10 | pub network: NetworkConfig, | ||
| 11 | } | ||
| 12 | |||
| 13 | pub 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)] | ||
| 21 | pub enum AuthProtection { | ||
| 22 | None = 0, | ||
| 23 | Pap = 1, | ||
| 24 | Chap = 2, | ||
| 25 | } | ||
| 26 | |||
| 27 | impl<'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 @@ | |||
| 1 | use core::net::IpAddr; | ||
| 2 | use heapless::String; | ||
| 3 | use core::str::FromStr; | ||
| 4 | use core::fmt::Write; | ||
| 5 | |||
| 6 | /// Provides a higher level API for configuring and reading information for a given | ||
| 7 | /// context id. | ||
| 8 | pub struct Control<'a> { | ||
| 9 | control: crate::Control<'a>, | ||
| 10 | cid: u8, | ||
| 11 | } | ||
| 12 | |||
| 13 | pub 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)] | ||
| 20 | pub 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))] | ||
| 28 | pub enum Error { | ||
| 29 | BufferTooSmall, | ||
| 30 | AtCommand, | ||
| 31 | AddrParseError, | ||
| 32 | Format, | ||
| 33 | } | ||
| 34 | |||
| 35 | impl From<core::fmt::Error> for Error { | ||
| 36 | fn from(_: core::fmt::Error) -> Self { | ||
| 37 | Self::Format | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | #[derive(PartialEq, Debug)] | ||
| 42 | pub struct Status { | ||
| 43 | pub attached: bool, | ||
| 44 | pub ip: Option<IpAddr>, | ||
| 45 | } | ||
| 46 | |||
| 47 | #[cfg(feature = "defmt")] | ||
| 48 | impl 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 | |||
| 57 | impl<'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 | |||
| 147 | pub(crate) fn is_whitespace(char: u8) -> bool { | ||
| 148 | match char { | ||
| 149 | b'\r' | b'\n' | b' ' => true, | ||
| 150 | _ => false, | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | pub(crate) fn is_separator(char: u8) -> bool { | ||
| 155 | match char { | ||
| 156 | b',' | b'\r' | b'\n' | b' ' => true, | ||
| 157 | _ => false, | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | pub(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 | |||
| 192 | pub(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 |
| 7 | mod fmt; | 7 | mod fmt; |
| 8 | 8 | ||
| 9 | pub mod context; | ||
| 10 | |||
| 9 | use core::cell::RefCell; | 11 | use core::cell::RefCell; |
| 10 | use core::future::poll_fn; | 12 | use core::future::poll_fn; |
| 11 | use core::marker::PhantomData; | 13 | use core::marker::PhantomData; |
