mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-25 08:12:30 +00:00
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:
parent
aabdd45424
commit
b76b7ca9f5
@ -26,6 +26,7 @@ embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-
|
|||||||
|
|
||||||
heapless = "0.8"
|
heapless = "0.8"
|
||||||
embedded-io = "0.6.1"
|
embedded-io = "0.6.1"
|
||||||
|
at-commands = "0.5.4"
|
||||||
|
|
||||||
[package.metadata.embassy_docs]
|
[package.metadata.embassy_docs]
|
||||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/"
|
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/"
|
||||||
|
@ -2,6 +2,8 @@ use core::net::IpAddr;
|
|||||||
use heapless::String;
|
use heapless::String;
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use core::fmt::Write;
|
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
|
/// Provides a higher level API for configuring and reading information for a given
|
||||||
/// context id.
|
/// context id.
|
||||||
@ -28,10 +30,17 @@ pub enum AuthProt {
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
BufferTooSmall,
|
BufferTooSmall,
|
||||||
AtCommand,
|
AtCommand,
|
||||||
|
AtParseError,
|
||||||
AddrParseError,
|
AddrParseError,
|
||||||
Format,
|
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 {
|
impl From<core::fmt::Error> for Error {
|
||||||
fn from(_: core::fmt::Error) -> Self {
|
fn from(_: core::fmt::Error) -> Self {
|
||||||
Self::Format
|
Self::Format
|
||||||
@ -42,6 +51,8 @@ impl From<core::fmt::Error> for Error {
|
|||||||
pub struct Status {
|
pub struct Status {
|
||||||
pub attached: bool,
|
pub attached: bool,
|
||||||
pub ip: Option<IpAddr>,
|
pub ip: Option<IpAddr>,
|
||||||
|
pub gateway: Option<IpAddr>,
|
||||||
|
pub dns: Vec<IpAddr, 2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "defmt")]
|
#[cfg(feature = "defmt")]
|
||||||
@ -67,129 +78,122 @@ impl<'a> Control<'a> {
|
|||||||
|
|
||||||
/// Configures the modem with the provided config.
|
/// Configures the modem with the provided config.
|
||||||
pub async fn configure(&self, config: Config<'_>) -> Result<(), Error> {
|
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];
|
let mut buf: [u8; 256] = [0; 256];
|
||||||
|
|
||||||
write!(cmd, "AT+CGDCONT={},\"IP\",\"{}\"", self.cid, config.gateway).map_err(|_| Error::BufferTooSmall)?;
|
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||||
let n = self.control.at_command(cmd.as_bytes(), &mut buf).await;
|
.named("+CGDCONT")
|
||||||
let mut res = &buf[..n];
|
.with_int_parameter(self.cid)
|
||||||
let res = split_field(&mut res);
|
.with_string_parameter("IP")
|
||||||
if res != b"OK" {
|
.with_string_parameter(config.gateway)
|
||||||
return Err(Error::AtCommand)
|
.finish().map_err(|_| Error::BufferTooSmall)?;
|
||||||
}
|
let n = self.control.at_command(op, &mut buf).await;
|
||||||
cmd.clear();
|
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 {
|
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 op = op.finish().map_err(|_| Error::BufferTooSmall)?;
|
||||||
let mut res = &buf[..n];
|
|
||||||
let res = split_field(&mut res);
|
|
||||||
if res != b"OK" {
|
|
||||||
return Err(Error::AtCommand)
|
|
||||||
}
|
|
||||||
cmd.clear();
|
|
||||||
|
|
||||||
let n = self.control.at_command(b"AT+CFUN=1", &mut buf).await;
|
let n = self.control.at_command(op, &mut buf).await;
|
||||||
let mut res = &buf[..n];
|
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
|
||||||
let res = split_field(&mut res);
|
|
||||||
if res != b"OK" {
|
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||||
return Err(Error::AtCommand);
|
.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn status(&self) -> Result<Status, Error> {
|
pub async fn status(&self) -> Result<Status, Error> {
|
||||||
|
let mut cmd: [u8; 256] = [0; 256];
|
||||||
let mut buf: [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 {
|
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();
|
let op = CommandBuilder::create_set(&mut cmd, true)
|
||||||
write!(s, "AT+CGPADDR={}", self.cid)?;
|
.named("+CGPADDR")
|
||||||
let n = self.control.at_command(s.as_bytes(), &mut buf).await;
|
.with_int_parameter(self.cid)
|
||||||
let mut res = &buf[..n];
|
.finish().map_err(|_| Error::BufferTooSmall)?;
|
||||||
s.clear();
|
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 op = CommandBuilder::create_set(&mut cmd, true)
|
||||||
let res = split_field(&mut res);
|
.named("+CGCONTRDP")
|
||||||
if res == b"OK" {
|
.with_int_parameter(self.cid)
|
||||||
Ok(Status { attached, ip: None })
|
.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 {
|
} else {
|
||||||
Err(Error::AtCommand)
|
Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pop_prefix(&mut res, s.as_bytes());
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let ip = split_field(&mut res);
|
let mut dns = Vec::new();
|
||||||
if !ip.is_empty() {
|
if let Some(ip) = dns1 {
|
||||||
let ip = IpAddr::from_str(unsafe { core::str::from_utf8_unchecked(ip) }).map_err(|_| Error::AddrParseError)?;
|
dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap();
|
||||||
self.control.open_raw_socket().await;
|
|
||||||
Ok(Status { attached, ip: Some(ip) })
|
|
||||||
} else {
|
|
||||||
Ok(Status { attached, ip: None })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_whitespace(char: u8) -> bool {
|
if let Some(ip) = dns2 {
|
||||||
match char {
|
dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?).unwrap();
|
||||||
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..];
|
Ok(Status {
|
||||||
}
|
attached,
|
||||||
*data = rest;
|
ip,
|
||||||
field
|
gateway,
|
||||||
} else {
|
dns,
|
||||||
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()..];
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,7 @@ embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defm
|
|||||||
defmt = "0.3"
|
defmt = "0.3"
|
||||||
defmt-rtt = "0.4"
|
defmt-rtt = "0.4"
|
||||||
|
|
||||||
|
heapless = "0.8"
|
||||||
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
|
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
|
||||||
cortex-m-rt = "0.7.0"
|
cortex-m-rt = "0.7.0"
|
||||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||||
|
@ -5,9 +5,10 @@ use core::mem::MaybeUninit;
|
|||||||
use core::net::IpAddr;
|
use core::net::IpAddr;
|
||||||
use core::ptr::addr_of_mut;
|
use core::ptr::addr_of_mut;
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use core::{slice, str};
|
use core::slice;
|
||||||
|
|
||||||
use defmt::{assert, info, warn, unwrap};
|
use defmt::{assert, info, warn, unwrap};
|
||||||
|
use heapless::Vec;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources};
|
use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources};
|
||||||
use embassy_net_nrf91::{Runner, State, context};
|
use embassy_net_nrf91::{Runner, State, context};
|
||||||
@ -146,10 +147,23 @@ async fn main(spawner: Spawner) {
|
|||||||
};
|
};
|
||||||
let addr = Ipv4Address(addr.octets());
|
let addr = Ipv4Address(addr.octets());
|
||||||
|
|
||||||
|
let gateway = if let Some(IpAddr::V4(addr)) = status.gateway {
|
||||||
|
Some(Ipv4Address(addr.octets()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dns_servers = Vec::new();
|
||||||
|
for dns in status.dns {
|
||||||
|
if let IpAddr::V4(ip) = dns {
|
||||||
|
unwrap!(dns_servers.push(Ipv4Address(ip.octets())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 {
|
stack.set_config_v4(embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 {
|
||||||
address: Ipv4Cidr::new(addr, 32),
|
address: Ipv4Cidr::new(addr, 32),
|
||||||
gateway: None,
|
gateway,
|
||||||
dns_servers: Default::default(),
|
dns_servers,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut rx_buffer = [0; 4096];
|
let mut rx_buffer = [0; 4096];
|
||||||
@ -159,15 +173,16 @@ async fn main(spawner: Spawner) {
|
|||||||
socket.set_timeout(Some(Duration::from_secs(10)));
|
socket.set_timeout(Some(Duration::from_secs(10)));
|
||||||
|
|
||||||
info!("Connecting...");
|
info!("Connecting...");
|
||||||
let host_addr = embassy_net::Ipv4Address::from_str("83.51.182.206").unwrap();
|
let host_addr = embassy_net::Ipv4Address::from_str("45.79.112.203").unwrap();
|
||||||
if let Err(e) = socket.connect((host_addr, 8000)).await {
|
if let Err(e) = socket.connect((host_addr, 4242)).await {
|
||||||
warn!("connect error: {:?}", e);
|
warn!("connect error: {:?}", e);
|
||||||
|
Timer::after_secs(1).await;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
info!("Connected to {:?}", socket.remote_endpoint());
|
info!("Connected to {:?}", socket.remote_endpoint());
|
||||||
|
|
||||||
let msg = b"Hello world!\n";
|
let msg = b"Hello world!\n";
|
||||||
loop {
|
for _ in 0..10 {
|
||||||
if let Err(e) = socket.write_all(msg).await {
|
if let Err(e) = socket.write_all(msg).await {
|
||||||
warn!("write error: {:?}", e);
|
warn!("write error: {:?}", e);
|
||||||
break;
|
break;
|
||||||
@ -177,54 +192,3 @@ async fn main(spawner: Spawner) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_whitespace(char: u8) -> bool {
|
|
||||||
match char {
|
|
||||||
b'\r' | b'\n' | b' ' => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_separator(char: u8) -> bool {
|
|
||||||
match char {
|
|
||||||
b',' | b'\r' | b'\n' | b' ' => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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..];
|
|
||||||
}
|
|
||||||
*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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop_prefix(data: &mut &[u8], prefix: &[u8]) {
|
|
||||||
assert!(data.len() >= prefix.len());
|
|
||||||
assert!(&data[..prefix.len()] == prefix);
|
|
||||||
*data = &data[prefix.len()..];
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user