From b76b7ca9f5d64e83f7f69a6f7d97ba605ab2af7f Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Wed, 4 Sep 2024 12:58:33 +0200 Subject: Use at-commands crate and support DNS * Use at-commands for building and parsing AT commands which has better error handling. * Retrieve DNS servers * Retrieve gateway * Update example to configure embassy-net with retrieved parameters. --- embassy-net-nrf91/Cargo.toml | 1 + embassy-net-nrf91/src/context.rs | 208 ++++++++++++++++++++------------------- 2 files changed, 107 insertions(+), 102 deletions(-) (limited to 'embassy-net-nrf91') diff --git a/embassy-net-nrf91/Cargo.toml b/embassy-net-nrf91/Cargo.toml index e2d596d59..07a0c8886 100644 --- a/embassy-net-nrf91/Cargo.toml +++ b/embassy-net-nrf91/Cargo.toml @@ -26,6 +26,7 @@ embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver- heapless = "0.8" embedded-io = "0.6.1" +at-commands = "0.5.4" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/" diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs index c47b1ade6..b532dca14 100644 --- a/embassy-net-nrf91/src/context.rs +++ b/embassy-net-nrf91/src/context.rs @@ -2,6 +2,8 @@ use core::net::IpAddr; use heapless::String; use core::str::FromStr; use core::fmt::Write; +use heapless::Vec; +use at_commands::{builder::CommandBuilder, parser::CommandParser}; /// Provides a higher level API for configuring and reading information for a given /// context id. @@ -28,10 +30,17 @@ pub enum AuthProt { pub enum Error { BufferTooSmall, AtCommand, + AtParseError, AddrParseError, Format, } +impl From for Error { + fn from(_: at_commands::parser::ParseError) -> Self { + Self::AtParseError + } +} + impl From for Error { fn from(_: core::fmt::Error) -> Self { Self::Format @@ -42,6 +51,8 @@ impl From for Error { pub struct Status { pub attached: bool, pub ip: Option, + pub gateway: Option, + pub dns: Vec, } #[cfg(feature = "defmt")] @@ -67,129 +78,122 @@ impl<'a> Control<'a> { /// Configures the modem with the provided config. pub async fn configure(&self, config: Config<'_>) -> Result<(), Error> { - let mut cmd: String<128> = String::new(); + let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; - write!(cmd, "AT+CGDCONT={},\"IP\",\"{}\"", self.cid, config.gateway).map_err(|_| Error::BufferTooSmall)?; - let n = self.control.at_command(cmd.as_bytes(), &mut buf).await; - let mut res = &buf[..n]; - let res = split_field(&mut res); - if res != b"OK" { - return Err(Error::AtCommand) - } - cmd.clear(); - - write!(cmd, "AT+CGAUTH={},{}", self.cid, config.auth_prot as u8)?; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGDCONT") + .with_int_parameter(self.cid) + .with_string_parameter("IP") + .with_string_parameter(config.gateway) + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let mut op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGAUTH") + .with_int_parameter(self.cid) + .with_int_parameter(config.auth_prot as u8); if let Some((username, password)) = config.auth { - write!(cmd, ",\"{}\",\"{}\"", username, password).map_err(|_| Error::BufferTooSmall)?; - } - let n = self.control.at_command(cmd.as_bytes(), &mut buf).await; - let mut res = &buf[..n]; - let res = split_field(&mut res); - if res != b"OK" { - return Err(Error::AtCommand) + op = op.with_string_parameter(username).with_string_parameter(password); } - cmd.clear(); + let op = op.finish().map_err(|_| Error::BufferTooSmall)?; - let n = self.control.at_command(b"AT+CFUN=1", &mut buf).await; - let mut res = &buf[..n]; - let res = split_field(&mut res); - if res != b"OK" { - return Err(Error::AtCommand); - } + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(1) + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; Ok(()) } pub async fn status(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; let mut buf: [u8; 256] = [0; 256]; - let n = self.control.at_command(b"AT+CGATT?", &mut buf).await; - let mut res = &buf[..n]; - pop_prefix(&mut res, b"+CGATT: "); - let res = split_field(&mut res); - let attached = res == b"1"; + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res, ) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK").finish()?; + let attached = res == 1; if !attached { - return Ok(Status { attached, ip: None }) + return Ok(Status { attached, ip: None, gateway: None, dns: Vec::new() }) } - let mut s: String<128> = String::new(); - write!(s, "AT+CGPADDR={}", self.cid)?; - let n = self.control.at_command(s.as_bytes(), &mut buf).await; - let mut res = &buf[..n]; - s.clear(); - - write!(s, "+CGPADDR: {},", self.cid)?; - - if s.len() > res.len() { - let res = split_field(&mut res); - if res == b"OK" { - Ok(Status { attached, ip: None }) + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGPADDR") + .with_int_parameter(self.cid) + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_, ip1, ip2, ) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGPADDR: ") + .expect_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_identifier(b"\r\nOK").finish()?; + + let ip = if let Some(ip) = ip1 { + let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?; + self.control.open_raw_socket().await; + Some(ip) + } else { + None + }; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGCONTRDP") + .with_int_parameter(self.cid) + .finish().map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, mtu) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGCONTRDP: ") + .expect_int_parameter() + .expect_optional_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_identifier(b"\r\nOK").finish()?; + + let gateway = if let Some(ip) = gateway { + if ip.is_empty() { + None } else { - Err(Error::AtCommand) + Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) } } else { - pop_prefix(&mut res, s.as_bytes()); + None + }; - let ip = split_field(&mut res); - if !ip.is_empty() { - let ip = IpAddr::from_str(unsafe { core::str::from_utf8_unchecked(ip) }).map_err(|_| Error::AddrParseError)?; - self.control.open_raw_socket().await; - Ok(Status { attached, ip: Some(ip) }) - } else { - Ok(Status { attached, ip: None }) - } + let mut dns = Vec::new(); + if let Some(ip) = dns1 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap(); } - } -} -pub(crate) fn is_whitespace(char: u8) -> bool { - match char { - b'\r' | b'\n' | b' ' => true, - _ => false, - } -} - -pub(crate) fn is_separator(char: u8) -> bool { - match char { - b',' | b'\r' | b'\n' | b' ' => true, - _ => false, - } -} - -pub(crate) fn split_field<'a>(data: &mut &'a [u8]) -> &'a [u8] { - while !data.is_empty() && is_whitespace(data[0]) { - *data = &data[1..]; - } - - if data.is_empty() { - return &[]; - } - - if data[0] == b'"' { - let data2 = &data[1..]; - let end = data2.iter().position(|&x| x == b'"').unwrap_or(data2.len()); - let field = &data2[..end]; - let mut rest = &data2[data2.len().min(end + 1)..]; - if rest.first() == Some(&b'\"') { - rest = &rest[1..]; - } - while !rest.is_empty() && is_separator(rest[0]) { - rest = &rest[1..]; + if let Some(ip) = dns2 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap(); } - *data = rest; - field - } else { - let end = data.iter().position(|&x| is_separator(x)).unwrap_or(data.len()); - let field = &data[0..end]; - let rest = &data[data.len().min(end + 1)..]; - *data = rest; - field - } -} -pub(crate) fn pop_prefix(data: &mut &[u8], prefix: &[u8]) { - assert!(data.len() >= prefix.len()); - assert!(&data[..prefix.len()] == prefix); - *data = &data[prefix.len()..]; + Ok(Status { + attached, + ip, + gateway, + dns, + }) + } } -- cgit