atat 0.7.0

AT Parser for serial based device crates
Documentation

A helper crate to abstract away the state management and string parsing of AT command communication.

It works by creating structs for each AT command, that each implements [AtatCmd]. With corresponding response structs that each implements [AtatResp].

This can be simplified alot using the [atat_derive] crate!

[AtatCmd]: trait.AtatCmd.html [AtatResp]: trait.AtatResp.html [atat_derive]: https://crates.io/crates/atat_derive

Examples

Command and response example without atat_derive:

use atat::{AtatCmd, AtatResp, Error};
use core::fmt::Write;
use heapless::{consts, String, Vec};

pub struct SetGreetingText<'a> {
pub text: &'a str,
}

pub struct GetGreetingText;

pub struct NoResponse;

impl AtatResp for NoResponse {};

pub struct GreetingText {
pub text: String<consts::U64>,
};

impl AtatResp for GreetingText {};

impl<'a> AtatCmd for SetGreetingText<'a> {
type CommandLen = consts::U64;
type Response = NoResponse;

fn as_bytes(&self) -> Vec<u8, Self::CommandLen> {
let mut buf: Vec<u8, Self::CommandLen> = Vec::new();
write!(buf, "AT+CSGT={}", self.text);
buf
}

fn parse(&self, resp: &[u8]) -> Result<Self::Response, Error> {
Ok(NoResponse)
}
}

impl AtatCmd for GetGreetingText {
type CommandLen = consts::U8;
type Response = GreetingText;

fn as_bytes(&self) -> Vec<u8, Self::CommandLen> {
Vec::from_slice(b"AT+CSGT?").unwrap()
}

fn parse(&self, resp: &[u8]) -> Result<Self::Response, Error> {
// Parse resp into `GreetingText`
Ok(GreetingText {
text: String::from(core::str::from_utf8(resp).unwrap()),
})
}
}

Same example with atat_derive:

use atat::atat_derive::{AtatCmd, AtatResp};
use heapless::{consts, String};

#[derive(Clone, AtatCmd)]
#[at_cmd("+CSGT", NoResponse)]
pub struct SetGreetingText<'a> {
#[at_arg(position = 0, len = 32)]
pub text: &'a str,
}

#[derive(Clone, AtatCmd)]
#[at_cmd("+CSGT?", GreetingText)]
pub struct GetGreetingText;

#[derive(Clone, AtatResp)]
pub struct NoResponse;

#[derive(Clone, AtatResp)]
pub struct GreetingText {
#[at_arg(position = 0)]
pub text: String<consts::U64>,
};

Basic usage example (More available in examples folder):


use cortex_m::asm;
use hal::{
gpio::{
gpioa::{PA2, PA3},
Alternate, Floating, Input, AF7,
},
pac::{interrupt, Peripherals, USART2},
prelude::*,
serial::{Config, Event::Rxne, Rx, Serial},
timer::{Event, Timer},
};

use atat::{atat_derive::{AtatResp, AtatCmd}};

use heapless::{consts, spsc::Queue, String};

use crate::rt::entry;
static mut INGRESS: Option<atat::IngressManager> = None;
static mut RX: Option<Rx<USART2>> = None;


#[derive(Clone, AtatResp)]
pub struct NoResponse;

#[derive(Clone, AtatCmd)]
#[at_cmd("", NoResponse, timeout_ms = 1000)]
pub struct AT;

#[entry]
fn main() -> ! {
let p = Peripherals::take().unwrap();

let mut flash = p.FLASH.constrain();
let mut rcc = p.RCC.constrain();
let mut pwr = p.PWR.constrain(&mut rcc.apb1r1);

let mut gpioa = p.GPIOA.split(&mut rcc.ahb2);

let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr);

let tx = gpioa.pa2.into_af7(&mut gpioa.moder, &mut gpioa.afrl);
let rx = gpioa.pa3.into_af7(&mut gpioa.moder, &mut gpioa.afrl);

let mut timer = Timer::tim7(p.TIM7, 1.hz(), clocks, &mut rcc.apb1r1);
let at_timer = Timer::tim6(p.TIM6, 100.hz(), clocks, &mut rcc.apb1r1);

let mut serial = Serial::usart2(
p.USART2,
(tx, rx),
Config::default().baudrate(115_200.bps()),
clocks,
&mut rcc.apb1r1,
);

serial.listen(Rxne);

static mut RES_QUEUE: ResQueue<consts::U256, consts::U5> = Queue(heapless::i::Queue::u8());
static mut URC_QUEUE: UrcQueue<consts::U256, consts::U10> = Queue(heapless::i::Queue::u8());
static mut COM_QUEUE: ComQueue<consts::U3> = Queue(heapless::i::Queue::u8());

let queues = Queues {
res_queue: unsafe { RES_QUEUE.split() },
urc_queue: unsafe { URC_QUEUE.split() },
com_queue: unsafe { COM_QUEUE.split() },
};

let (tx, rx) = serial.split();
let (mut client, ingress) =
ClientBuilder::new(tx, timer, atat::Config::new(atat::Mode::Timeout)).build(queues);

unsafe { INGRESS = Some(ingress) };
unsafe { RX = Some(rx) };

// configure NVIC interrupts
unsafe { cortex_m::peripheral::NVIC::unmask(hal::stm32::Interrupt::TIM7) };
timer.listen(Event::TimeOut);

// if all goes well you should reach this breakpoint
asm::bkpt();

loop {
asm::wfi();

match client.send(&AT) {
Ok(response) => {
// Do something with response here
}
Err(e) => {}
}
}
}

#[interrupt]
fn TIM7() {
let ingress = unsafe { INGRESS.as_mut().unwrap() };
ingress.parse_at();
}

#[interrupt]
fn USART2() {
let ingress = unsafe { INGRESS.as_mut().unwrap() };
let rx = unsafe { RX.as_mut().unwrap() };
if let Ok(d) = nb::block!(rx.read()) {
ingress.write(&[d]);
}
}

Optional Cargo Features

  • derive (enabled by default) - Re-exports [atat_derive] to allow deriving Atat__ traits.
  • log-logging (disabled by default) - Enable log statements on various log levels to aid debugging. Powered by log.
  • defmt-default (disabled by default) - Enable log statements at INFO, or TRACE, level and up, to aid debugging. Powered by defmt.
  • defmt-trace (disabled by default) - Enable log statements at TRACE level and up, to aid debugging. Powered by defmt.
  • defmt-debug (disabled by default) - Enable log statements at DEBUG level and up, to aid debugging. Powered by defmt.
  • defmt-info (disabled by default) - Enable log statements at INFO level and up, to aid debugging. Powered by defmt.
  • defmt-warn (disabled by default) - Enable log statements at WARN level and up, to aid debugging. Powered by defmt.
  • defmt-error (disabled by default) - Enable log statements at ERROR level and up, to aid debugging. Powered by defmt.