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.
This commit is contained in:
Ulf Lilleengen
2024-09-04 12:58:33 +02:00
parent aabdd45424
commit b76b7ca9f5
4 changed files with 126 additions and 156 deletions

View File

@@ -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/"

View File

@@ -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<at_commands::parser::ParseError> for Error {
fn from(_: at_commands::parser::ParseError) -> Self {
Self::AtParseError
}
}
impl From<core::fmt::Error> for Error {
fn from(_: core::fmt::Error) -> Self {
Self::Format
@@ -42,6 +51,8 @@ impl From<core::fmt::Error> for Error {
pub struct Status {
pub attached: bool,
pub ip: Option<IpAddr>,
pub gateway: Option<IpAddr>,
pub dns: Vec<IpAddr, 2>,
}
#[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();
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()?;
write!(cmd, "AT+CGAUTH={},{}", self.cid, config.auth_prot as u8)?;
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)?;
op = op.with_string_parameter(username).with_string_parameter(password);
}
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();
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<Status, Error> {
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();
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()?;
write!(s, "+CGPADDR: {},", self.cid)?;
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
};
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("+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..];
if let Some(ip) = dns2 {
dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap();
}
while !rest.is_empty() && is_separator(rest[0]) {
rest = &rest[1..];
}
*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
Ok(Status {
attached,
ip,
gateway,
dns,
})
}
}
pub(crate) fn pop_prefix(data: &mut &[u8], prefix: &[u8]) {
assert!(data.len() >= prefix.len());
assert!(&data[..prefix.len()] == prefix);
*data = &data[prefix.len()..];
}