Add utility for setting configuration for a context
This commit is contained in:
parent
86a45b47e5
commit
a6db8678eb
@ -1,45 +0,0 @@
|
|||||||
use crate::{Error, Control};
|
|
||||||
|
|
||||||
// Drives the control loop of the modem based on declarative configuration.
|
|
||||||
pub struct AtDriver<'a> {
|
|
||||||
control: Control<'a>,
|
|
||||||
config: Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Config {
|
|
||||||
pub network: NetworkConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NetworkConfig {
|
|
||||||
pub apn: &'static str,
|
|
||||||
pub prot: AuthProtection,
|
|
||||||
pub userid: &'static str,
|
|
||||||
pub password: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum AuthProtection {
|
|
||||||
None = 0,
|
|
||||||
Pap = 1,
|
|
||||||
Chap = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> AtDriver<'a> {
|
|
||||||
pub async fn new(control: Control<'a>, config: Config) -> Result<Self, Error> {
|
|
||||||
control.wait_init().await;
|
|
||||||
Ok(Self {
|
|
||||||
control,
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn setup(&self) -> Result<(), Error> {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&self, stack: Stack<crate::NetDriver<'static>>) -> ! {
|
|
||||||
loop {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
196
embassy-net-nrf91/src/context.rs
Normal file
196
embassy-net-nrf91/src/context.rs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
use core::net::IpAddr;
|
||||||
|
use heapless::String;
|
||||||
|
use core::str::FromStr;
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
|
/// Provides a higher level API for configuring and reading information for a given
|
||||||
|
/// context id.
|
||||||
|
pub struct Control<'a> {
|
||||||
|
control: crate::Control<'a>,
|
||||||
|
cid: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Config<'a> {
|
||||||
|
pub gateway: &'a str,
|
||||||
|
pub auth_prot: AuthProt,
|
||||||
|
pub auth: Option<(&'a str, &'a str)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum AuthProt {
|
||||||
|
None = 0,
|
||||||
|
Pap = 1,
|
||||||
|
Chap = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Error {
|
||||||
|
BufferTooSmall,
|
||||||
|
AtCommand,
|
||||||
|
AddrParseError,
|
||||||
|
Format,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<core::fmt::Error> for Error {
|
||||||
|
fn from(_: core::fmt::Error) -> Self {
|
||||||
|
Self::Format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct Status {
|
||||||
|
pub attached: bool,
|
||||||
|
pub ip: Option<IpAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
impl defmt::Format for Status {
|
||||||
|
fn format(&self, f: defmt::Formatter<'_>) {
|
||||||
|
defmt::write!(f, "attached: {}", self.attached);
|
||||||
|
if let Some(ip) = &self.ip {
|
||||||
|
defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Control<'a> {
|
||||||
|
pub async fn new(control: crate::Control<'a>, cid: u8) -> Self {
|
||||||
|
control.wait_init().await;
|
||||||
|
Self { control, cid }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bypass modem configurator
|
||||||
|
pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize {
|
||||||
|
self.control.at_command(req, resp).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 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)?;
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
cmd.clear();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn status(&self) -> Result<Status, Error> {
|
||||||
|
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";
|
||||||
|
|
||||||
|
if !attached {
|
||||||
|
return Ok(Status { attached, ip: None })
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
info!("RES: {:?}", unsafe {core::str::from_utf8_unchecked(res)});
|
||||||
|
if s.len() > res.len() {
|
||||||
|
let res = split_field(&mut res);
|
||||||
|
if res == b"OK" {
|
||||||
|
Ok(Status { attached, ip: None })
|
||||||
|
} else {
|
||||||
|
Err(Error::AtCommand)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pop_prefix(&mut res, s.as_bytes());
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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..];
|
||||||
|
}
|
||||||
|
*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()..];
|
||||||
|
}
|
||||||
@ -6,6 +6,8 @@
|
|||||||
// must be first
|
// must be first
|
||||||
mod fmt;
|
mod fmt;
|
||||||
|
|
||||||
|
pub mod context;
|
||||||
|
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::future::poll_fn;
|
use core::future::poll_fn;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|||||||
@ -2,14 +2,15 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
|
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, str};
|
||||||
|
|
||||||
use defmt::{assert, *};
|
use defmt::{assert, info, warn, unwrap};
|
||||||
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};
|
use embassy_net_nrf91::{Runner, State, context};
|
||||||
use embassy_nrf::buffered_uarte::{self, BufferedUarteTx};
|
use embassy_nrf::buffered_uarte::{self, BufferedUarteTx};
|
||||||
use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin};
|
use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin};
|
||||||
use embassy_nrf::uarte::Baudrate;
|
use embassy_nrf::uarte::Baudrate;
|
||||||
@ -63,9 +64,9 @@ async fn blink_task(pin: AnyPin) {
|
|||||||
let mut led = Output::new(pin, Level::Low, OutputDrive::Standard);
|
let mut led = Output::new(pin, Level::Low, OutputDrive::Standard);
|
||||||
loop {
|
loop {
|
||||||
led.set_high();
|
led.set_high();
|
||||||
Timer::after_millis(100).await;
|
Timer::after_millis(1000).await;
|
||||||
led.set_low();
|
led.set_low();
|
||||||
Timer::after_millis(100).await;
|
Timer::after_millis(1000).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,51 +124,30 @@ async fn main(spawner: Spawner) {
|
|||||||
|
|
||||||
unwrap!(spawner.spawn(net_task(stack)));
|
unwrap!(spawner.spawn(net_task(stack)));
|
||||||
|
|
||||||
control.wait_init().await;
|
let control = context::Control::new(control, 0).await;
|
||||||
info!("INIT OK");
|
|
||||||
|
|
||||||
let mut buf = [0u8; 256];
|
unwrap!(control.configure(context::Config {
|
||||||
|
gateway: "iot.nat.es",
|
||||||
let n = control.at_command(b"AT+CFUN?", &mut buf).await;
|
auth_prot: context::AuthProt::Pap,
|
||||||
info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) });
|
auth: Some(("orange", "orange")),
|
||||||
|
}).await);
|
||||||
let n = control
|
|
||||||
.at_command(b"AT+CGDCONT=0,\"IP\",\"iot.nat.es\"", &mut buf)
|
|
||||||
.await;
|
|
||||||
info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) });
|
|
||||||
let n = control
|
|
||||||
.at_command(b"AT+CGAUTH=0,1,\"orange\",\"orange\"", &mut buf)
|
|
||||||
.await;
|
|
||||||
info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) });
|
|
||||||
|
|
||||||
let n = control.at_command(b"AT+CFUN=1", &mut buf).await;
|
|
||||||
info!("AT resp: '{}'", unsafe { str::from_utf8_unchecked(&buf[..n]) });
|
|
||||||
|
|
||||||
info!("waiting for attach...");
|
info!("waiting for attach...");
|
||||||
loop {
|
|
||||||
Timer::after_millis(500).await;
|
let mut status = unwrap!(control.status().await);
|
||||||
let n = control.at_command(b"AT+CGATT?", &mut buf).await;
|
while !status.attached && status.ip.is_none() {
|
||||||
let mut res = &buf[..n];
|
Timer::after_millis(1000).await;
|
||||||
pop_prefix(&mut res, b"+CGATT: ");
|
status = unwrap!(control.status().await);
|
||||||
let res = split_field(&mut res);
|
info!("STATUS: {:?}", status);
|
||||||
info!("AT resp field: '{}'", unsafe { str::from_utf8_unchecked(res) });
|
|
||||||
if res == b"1" {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = control.at_command(b"AT+CGPADDR=0", &mut buf).await;
|
let Some(IpAddr::V4(addr)) = status.ip else {
|
||||||
let mut res = &buf[..n];
|
panic!("Unexpected IP address");
|
||||||
pop_prefix(&mut res, b"+CGPADDR: 0,");
|
};
|
||||||
let ip = split_field(&mut res);
|
let addr = Ipv4Address(addr.octets());
|
||||||
let ip = Ipv4Address::from_str(unsafe { str::from_utf8_unchecked(ip) }).unwrap();
|
|
||||||
info!("IP: '{}'", ip);
|
|
||||||
|
|
||||||
info!("============== OPENING SOCKET");
|
|
||||||
control.open_raw_socket().await;
|
|
||||||
|
|
||||||
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(ip, 32),
|
address: Ipv4Cidr::new(addr, 32),
|
||||||
gateway: None,
|
gateway: None,
|
||||||
dns_servers: Default::default(),
|
dns_servers: Default::default(),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user