rust: Add esp8266at library

Add library for communicating with WiFi using the ESP8266 using AT
commands.

This really should have its own repo at some point.
This commit is contained in:
Wladimir J. van der Laan 2019-05-27 07:34:01 +00:00
parent adc1e73ad1
commit bfe58fe72b
12 changed files with 1057 additions and 0 deletions

3
rust/esp8266at/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

25
rust/esp8266at/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "esp8266at"
version = "0.1.0"
authors = ["Wladimir J. van der Laan <laanwj@gmail.com>"]
edition = "2018"
[features]
std = []
default = ["std"]
[dependencies]
nom = { version = "4", default-features = false }
[dev-dependencies]
clap = "2"
serialport = { version = "3", default-features = false }
arrayvec = "0.4"
[[example]]
name = "parsetest"
required-features = ["std"]
[[example]]
name = "serial"
required-features = ["std"]

4
rust/esp8266at/README.md Normal file
View File

@ -0,0 +1,4 @@
# `esp8266at`
A crate for communicating with WiFi using the ESP8266 using AT commands.

View File

@ -0,0 +1,53 @@
> AT
< OK
> AT+CWJAP_CUR?
< No AP
< OK
> AT+CWQAP
< OK
> AT+CWMODE?
< +CWMODE:1
< OK
> AT+CWJAP_CUR="x","x"
< WIFI DISCONNECT
> AT+CWJAP_CUR="x","x"
< busy p...
> AT+CWJAP_CUR="x","x"
< busy p...
< +CWJAP:3
< FAIL
> AT
< OK
> AT+CWJAP_CUR?
< No AP
< OK
> AT+CWQAP
< OK
> AT+CWMODE?
< +CWMODE:1
< OK
> AT+CWJAP_CUR="x","x"
< WIFI CONNECTED
< WIFI GOT IP
< OK
> AT+CIFSR
< +CIFSR:STAIP,"192.168.1.1"
< +CIFSR:STAMAC,"12:34:56:78:9a:bc"
< OK
> AT+CIPSTATUS
< STATUS:2
< OK
> AT+CIPMUX=0
< OK
> AT+CIPSTART="TCP","wttr.in",80
< CONNECT
< OK
> AT+CIPSEND=81
< OK
< >
< Recv 81 bytes
< SEND OK
> AT+CWJAP_CUR?
< +CWJAP_CUR:"xxxxxxxx","12:34:56:78:9a:bc",10,-58
< +IPD:8:012345
<

View File

@ -0,0 +1,33 @@
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use esp8266at::response::parse_response;
fn main() {
let f = File::open("data/parses.txt").unwrap();
let file = BufReader::new(&f);
for line in file.lines() {
let l = line.unwrap();
if l.len() >= 2 {
let mut lb = l[2..].as_bytes().to_vec();
lb.push(13);
lb.push(10);
let res = parse_response(&lb);
match res {
Err(x) => {
println!("failed command was: {}", l);
println!("{:?}", x);
}
Ok((res, x)) => {
if res.is_empty() {
println!("{:?}", x);
} else {
println!("non-empty residue command was: {}", l);
println!("{:?} {:?}", res, x);
}
}
}
}
}
}

View File

@ -0,0 +1,106 @@
use std::fmt;
use std::str;
use std::time::Duration;
use clap::{App, AppSettings, Arg};
use serialport::prelude::*;
use esp8266at::handler::{NetworkEvent, SerialNetworkHandler};
use esp8266at::mainloop::mainloop;
use esp8266at::response::ConnectionType;
use esp8266at::traits::Write;
struct StdoutDebug {}
impl fmt::Write for StdoutDebug {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
print!("{}", s);
Ok(())
}
}
fn main() {
let matches = App::new("Serialport Example - Receive Data")
.about("Reads data from a serial port and echoes it to stdout")
.setting(AppSettings::DisableVersion)
.arg(
Arg::with_name("port")
.help("The device path to a serial port")
.use_delimiter(false)
.required(true),
)
.arg(
Arg::with_name("baud")
.help("The baud rate to connect at")
.use_delimiter(false)
.required(true),
)
.arg(
Arg::with_name("apname")
.help("Access point name")
.use_delimiter(false)
.required(true),
)
.arg(
Arg::with_name("appass")
.help("Access point password")
.use_delimiter(false)
.required(true),
)
.get_matches();
let port_name = matches.value_of("port").unwrap();
let baud_rate = matches.value_of("baud").unwrap();
let apname = matches.value_of("apname").unwrap();
let appass = matches.value_of("appass").unwrap();
let mut settings: SerialPortSettings = Default::default();
settings.timeout = Duration::from_millis(10);
if let Ok(rate) = baud_rate.parse::<u32>() {
settings.baud_rate = rate.into();
} else {
eprintln!("Error: Invalid baud rate '{}' specified", baud_rate);
::std::process::exit(1);
}
match serialport::open_with_settings(&port_name, &settings) {
Ok(mut tx) => {
// Split into TX and RX halves
let mut rx = tx.try_clone().unwrap();
println!("Receiving data on {} at {} baud:", &port_name, &baud_rate);
let mut sh = SerialNetworkHandler::new(&mut tx, apname.as_bytes(), appass.as_bytes());
sh.start(true).unwrap();
mainloop(&mut sh, &mut rx, |port, ev, debug| {
match ev {
NetworkEvent::Ready => {
writeln!(debug, "--Ready--").unwrap();
port.connect(ConnectionType::TCP, b"wttr.in", 80).unwrap();
//port.connect(0, ConnectionType::SSL, b"wttr.in", 443);
true
}
NetworkEvent::Error => {
writeln!(debug, "--Could not connect to AP--").unwrap();
false
}
NetworkEvent::ConnectionEstablished(_) => {
port.write_all(b"GET /?0qA HTTP/1.1\r\nHost: wttr.in\r\nConnection: close\r\nUser-Agent: Weather-Spy\r\n\r\n").unwrap();
port.send(0).unwrap();
true
}
NetworkEvent::Data(_, data) => {
write!(debug, "{}", str::from_utf8(data).unwrap()).unwrap();
true
}
NetworkEvent::ConnectionClosed(_) => {
false
}
_ => { true }
}
}, &mut StdoutDebug {}).unwrap();
}
Err(e) => {
eprintln!("Failed to open \"{}\". Error: {}", port_name, e);
::std::process::exit(1);
}
}
}

View File

@ -0,0 +1,325 @@
/** ESP8285 serial WiFi network handler, for connecting to AP and making connections */
use core::{fmt, str};
use crate::response::{
CmdResponse, ConnectionType, GenResponse, IPAddress, MACAddress, Response, Status,
};
use crate::traits::Write;
use crate::util::{write_num_u32, write_qstr};
/** Handler state */
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum State {
Initial,
SetStationMode,
// QueryCurrentAP,
ConnectingToAP,
QueryIP,
SetMux,
MakeConnection(u32),
Error,
Idle,
Sending(u32),
}
/** Wifi network state */
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum WifiState {
Unknown,
Disconnected,
Connected,
GotIP,
}
/** Event type for callback */
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NetworkEvent<'a> {
/** Network handler became idle */
Ready,
/** Error connecting to AP */
Error,
ConnectionEstablished(u32),
ConnectionFailed(u32),
Data(u32, &'a [u8]),
ConnectionClosed(u32),
SendComplete(u32),
}
/** Max CIPSEND buffer size */
const TX_BUFFER_SIZE: usize = 2048;
/** Max link_id */
const MAX_NUM_LINKS: usize = 5;
/** ESP8285 serial WiFi network handler */
pub struct SerialNetworkHandler<'a, S>
where
S: Write,
{
/** Serial port */
port: &'a mut S,
/** Handler state */
state: State,
/** Current AP connction state */
wifistate: WifiState,
/** Current IP */
ip: Option<IPAddress>,
/** Current MAC */
mac: Option<MACAddress>,
/** Access point name to connect to */
apname: &'a [u8],
/** Access point password */
appass: &'a [u8],
/** Send buffer */
txbuf: [u8; TX_BUFFER_SIZE],
/** Send buffer size */
txn: usize,
/** Connection slots (in use) */
links: [bool; MAX_NUM_LINKS],
}
impl<'a, S> SerialNetworkHandler<'a, S>
where
S: Write,
{
pub fn new(port: &'a mut S, apname: &'a [u8], appass: &'a [u8]) -> Self {
Self {
port,
state: State::Initial,
wifistate: WifiState::Unknown,
ip: None,
mac: None,
apname,
appass,
txbuf: [0; TX_BUFFER_SIZE],
txn: 0,
links: [false; MAX_NUM_LINKS],
}
}
/** Start off network handling by checking liveness of the link and ESP device. */
pub fn start(&mut self, echo: bool) -> Result<(), S::Error> {
assert!(self.state == State::Initial || self.state == State::Idle);
if echo {
self.port.write_all(b"AT\r\n")?;
} else {
// Disable echo as very first thing to avoid excess serial traffic
self.port.write_all(b"ATE0\r\n")?;
}
Ok(())
}
/** Handle an incoming message */
pub fn message<F>(
&mut self,
resp: &Response,
on_event: &mut F,
debug: &mut fmt::Write,
) -> Result<(), S::Error>
where
F: FnMut(&mut Self, NetworkEvent, &mut fmt::Write),
{
match resp {
Response::Echo(data) => {
writeln!(debug, "→ {}", str::from_utf8(data).unwrap_or("???")).unwrap();
}
Response::Data(link, _) => {
writeln!(debug, "← Data({}, [...])", link).unwrap();
}
_ => {
writeln!(debug, "← {:?}", resp).unwrap();
}
}
match self.state {
State::Initial => {
if let Response::Gen(GenResponse::OK) = resp {
writeln!(debug, "Initial AT confirmed - configuring station mode").unwrap();
// Set station mode so that we're sure we can connect to an AP
self.port.write_all(b"AT+CWMODE_CUR=1\r\n").unwrap();
self.state = State::SetStationMode;
}
// TODO: retry if ERROR
}
State::SetStationMode => {
if let Response::Gen(GenResponse::OK) = resp {
writeln!(debug, "Station mode set - connecting to AP").unwrap();
self.port.write_all(b"AT+CWJAP_CUR=").unwrap();
write_qstr(self.port, self.apname)?;
self.port.write_all(b",")?;
write_qstr(self.port, self.appass)?;
self.port.write_all(b"\r\n")?;
self.state = State::ConnectingToAP;
}
}
State::ConnectingToAP => match resp {
Response::Gen(GenResponse::FAIL) | Response::Gen(GenResponse::ERROR) => {
writeln!(debug, "Fatal: failed to connect to AP").unwrap();
self.state = State::Error;
on_event(self, NetworkEvent::Error, debug);
}
Response::Gen(GenResponse::OK) => {
if self.wifistate != WifiState::GotIP {
writeln!(debug, "Warning: succesful but did not get IP yet").unwrap();
}
writeln!(debug, "Succesfully connected to AP").unwrap();
self.port.write_all(b"AT+CIFSR\r\n")?;
self.state = State::QueryIP;
}
_ => {}
},
State::QueryIP => {
match resp {
Response::Gen(GenResponse::OK) => {
writeln!(debug, "Succesfully queried IP").unwrap();
// Multi-connection mode
self.port.write_all(b"AT+CIPMUX=1\r\n")?;
self.state = State::SetMux;
}
_ => {}
}
}
State::SetMux => match resp {
Response::Gen(GenResponse::OK) => {
writeln!(debug, "Succesfully set multi-connection mode").unwrap();
self.state = State::Idle;
on_event(self, NetworkEvent::Ready, debug);
}
_ => {}
},
State::MakeConnection(link) => match resp {
Response::Gen(GenResponse::OK) => {
self.state = State::Idle;
on_event(self, NetworkEvent::ConnectionEstablished(link), debug);
}
Response::Gen(GenResponse::ERROR) => {
self.state = State::Idle;
on_event(self, NetworkEvent::ConnectionFailed(link), debug);
}
_ => {}
},
State::Sending(link) => {
match resp {
Response::Gen(GenResponse::OK) => {}
Response::Gen(GenResponse::ERROR) => {}
Response::RecvPrompt => {
// Send queued data
self.port.write_all(&self.txbuf[0..self.txn])?;
self.txn = 0;
}
Response::Status(Status::SEND_OK) => {
self.state = State::Idle;
on_event(self, NetworkEvent::SendComplete(link), debug);
}
_ => {}
}
}
_ => {}
}
match resp {
Response::Status(Status::WIFI_DISCONNECT) => {
writeln!(debug, "Disconnected from AP").unwrap();
self.wifistate = WifiState::Disconnected;
self.ip = None;
self.mac = None;
}
Response::Status(Status::WIFI_CONNECTED) => {
writeln!(debug, "Connected to AP").unwrap();
self.wifistate = WifiState::Connected;
}
Response::Status(Status::WIFI_GOT_IP) => {
writeln!(debug, "Have IP").unwrap();
self.wifistate = WifiState::GotIP;
}
Response::Status(Status::CONNECT(link)) => {
// Mark connection slot id as connected
self.links[*link as usize] = true;
}
Response::Status(Status::CLOSED(link)) => {
// Mark connection slot id as closed
self.links[*link as usize] = false;
on_event(self, NetworkEvent::ConnectionClosed(*link), debug);
}
Response::Cmd(CmdResponse::CIFSR_STAIP(ip)) => {
self.ip = Some(*ip);
writeln!(debug, "Queried IP: {}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]).unwrap();
}
Response::Cmd(CmdResponse::CIFSR_STAMAC(mac)) => {
self.mac = Some(*mac);
writeln!(
debug,
"Queried MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
)
.unwrap();
}
Response::Data(link, data) => {
on_event(self, NetworkEvent::Data(*link, data), debug);
}
_ => {}
}
Ok(())
}
/** Initiate a connection */
pub fn connect(
&mut self,
ctype: ConnectionType,
addr: &[u8],
port: u32,
) -> Result<u32, S::Error> {
assert!(self.state == State::Idle);
// pick out a free link slot automatically
let link = self.links.iter().position(|used| !used).unwrap() as u32;
assert!(!self.links[link as usize]);
self.port.write_all(b"AT+CIPSTART=")?;
write_num_u32(self.port, link)?;
self.port.write_all(b",")?;
write_qstr(
self.port,
match ctype {
ConnectionType::TCP => b"TCP",
ConnectionType::UDP => b"UDP",
ConnectionType::SSL => b"SSL",
},
)?;
self.port.write_all(b",")?;
write_qstr(self.port, addr)?;
self.port.write_all(b",")?;
write_num_u32(self.port, port)?;
self.port.write_all(b"\r\n")?;
self.state = State::MakeConnection(link);
Ok(link)
}
/** Send contents of send buffer to a connection */
pub fn send(&mut self, link: u32) -> Result<(), S::Error> {
assert!(self.state == State::Idle);
self.port.write_all(b"AT+CIPSEND=")?;
write_num_u32(self.port, link)?;
self.port.write_all(b",")?;
write_num_u32(self.port, self.txn as u32)?;
self.port.write_all(b"\r\n")?;
self.state = State::Sending(link);
Ok(())
}
}
/** Write trait for writing to send buffer */
impl<'a, S> Write for SerialNetworkHandler<'a, S>
where
S: Write,
{
type Error = ();
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
assert!(self.state == State::Idle);
if (self.txn + buf.len()) <= TX_BUFFER_SIZE {
self.txbuf[self.txn..self.txn + buf.len()].copy_from_slice(buf);
self.txn += buf.len();
Ok(())
} else {
Err(())
}
}
}

10
rust/esp8266at/src/lib.rs Normal file
View File

@ -0,0 +1,10 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[macro_use]
extern crate nom;
pub mod handler;
#[cfg(feature = "std")]
pub mod mainloop;
pub mod response;
pub mod traits;
mod util;

View File

@ -0,0 +1,81 @@
/** Example synchronous serial receive event loop (for std) */
use nom::Offset;
use std::fmt;
use std::io;
use crate::handler::{NetworkEvent, SerialNetworkHandler};
use crate::response::parse_response;
/** Mainloop handling serial input and dispatching network events */
pub fn mainloop<P, F, X>(
h: &mut SerialNetworkHandler<X>,
port: &mut P,
mut f: F,
debug: &mut fmt::Write,
) -> io::Result<()>
where
P: io::Read,
F: FnMut(&mut SerialNetworkHandler<X>, NetworkEvent, &mut fmt::Write) -> bool,
X: io::Write,
{
let mut serial_buf: Vec<u8> = vec![0; 2560]; // 2048 + some
let mut ofs: usize = 0;
let mut running: bool = true;
while running {
// Receive bytes into buffer
match port.read(&mut serial_buf[ofs..]) {
Ok(t) => {
// io::stdout().write_all(&serial_buf[ofs..ofs+t]).unwrap();
ofs += t;
// Loop as long as there's something in the buffer to parse, starting at the
// beginning
let mut start = 0;
while start < ofs {
// try parsing
let tail = &serial_buf[start..ofs];
let erase = match parse_response(tail) {
Ok((residue, resp)) => {
h.message(
&resp,
&mut |a, b, debug| {
running = f(a, b, debug);
},
debug,
)?;
tail.offset(residue)
}
Err(nom::Err::Incomplete(_)) => {
// Incomplete, ignored, just retry after a new receive
0
}
Err(err) => {
writeln!(debug, "err: {:?}", err).unwrap();
// Erase unparseable data to next line, if line is complete
if let Some(ofs) = tail.iter().position(|&x| x == b'\n') {
ofs + 1
} else {
// If not, retry next time
0
}
}
};
if erase == 0 {
// End of input or remainder unparseable
break;
}
start += erase;
}
// Erase everything before new starting offset
for i in start..ofs {
serial_buf[i - start] = serial_buf[i];
}
ofs -= start;
}
Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (),
Err(e) => return Err(e),
}
}
Ok(())
}

View File

@ -0,0 +1,305 @@
use core::str;
/** Parser for ESP8266 AT responses */
use nom::{digit, hex_digit};
/** Connection type for CIPSTATUS etc */
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ConnectionType {
TCP,
UDP,
SSL,
}
/** General command responses/statuses */
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GenResponse {
/** Command finished with OK response */
OK,
/** Command finished with ERROR response */
ERROR,
/** Command finished with FAIL response */
FAIL,
/** Command could not be executed because device is busy sending */
BUSY_S,
/** Command could not be executed because device is busy handling previous command */
BUSY_P,
}
/** Async status messages */
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Status {
READY,
WIFI_DISCONNECT,
WIFI_CONNECTED,
WIFI_GOT_IP,
RECV_BYTES(u32),
SEND_OK,
/** TCP/UDP connection connected */
CONNECT(u32),
/** TCP/UDP connection closed */
CLOSED(u32),
}
pub type IPAddress = [u8; 4];
pub type MACAddress = [u8; 6];
/** Specific command responses */
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CmdResponse<'a> {
NO_AP,
CWMODE(u32),
CWJAP(u32),
CWJAP_CUR(&'a [u8], &'a [u8], i32, i32),
CIFSR_STAIP(IPAddress),
CIFSR_STAMAC(MACAddress),
STATUS(u32),
ALREADY_CONNECTED,
}
/** Parsed response */
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Response<'a> {
Empty,
Gen(GenResponse),
Status(Status),
Cmd(CmdResponse<'a>),
Data(u32, &'a [u8]),
Echo(&'a [u8]),
RecvPrompt,
}
/* Decimal unsigned integer */
named!(num_u32<&[u8], u32>,
// The unwrap() here is safe because digit will never return non-UTF8
map_res!(digit, |s| { str::from_utf8(s).unwrap().parse::<u32>() })
);
/* Decimal signed integer.
* May start with unary minus.
*/
named!(num_i32<&[u8], i32>,
// The unwrap() here is safe because digit will never return non-UTF8
map_res!(
recognize!(
tuple!(
opt!(one_of!(b"-")),
digit
)
),
|s| { str::from_utf8(s).unwrap().parse::<i32>() })
);
/* Decimal byte */
named!(num_u8<&[u8], u8>,
// The unwrap() here is safe because digit will never return non-UTF8
map_res!(digit, |s| { str::from_utf8(s).unwrap().parse::<u8>() })
);
/* Hex byte */
named!(hex_u8<&[u8], u8>,
// The unwrap() here is safe because digit will never return non-UTF8
map_res!(hex_digit, |s| { u8::from_str_radix(str::from_utf8(s).unwrap(), 16) })
);
/* Quoted string */
named!(qstr<&[u8], &[u8]>,
do_parse!(
tag!(b"\"") >>
a: escaped!(is_not!("\\\""), b'\\', one_of!(b"\"\\")) >>
//a: is_not!("\"") >>
tag!(b"\"") >>
( a )
)
);
/* Quoted IP */
named!(qip<&[u8], IPAddress>,
do_parse!(
tag!(b"\"") >>
a: num_u8 >>
tag!(b".") >>
b: num_u8 >>
tag!(b".") >>
c: num_u8 >>
tag!(b".") >>
d: num_u8 >>
tag!(b"\"") >>
( [a, b, c, d] )
)
);
/* Quoted MAC address */
named!(qmac<&[u8], MACAddress>,
do_parse!(
tag!(b"\"") >>
a: hex_u8 >>
tag!(b":") >>
b: hex_u8 >>
tag!(b":") >>
c: hex_u8 >>
tag!(b":") >>
d: hex_u8 >>
tag!(b":") >>
e: hex_u8 >>
tag!(b":") >>
f: hex_u8 >>
tag!(b"\"") >>
( [a, b, c, d, e, f] )
)
);
/* Parse general responses */
named!(genresponse<&[u8],GenResponse>,
alt!(
tag!(b"OK") => { |_| GenResponse::OK }
| tag!(b"ERROR") => { |_| GenResponse::ERROR }
| tag!(b"FAIL") => { |_| GenResponse::FAIL }
| tag!(b"busy s...") => { |_| GenResponse::BUSY_S }
| tag!(b"busy p...") => { |_| GenResponse::BUSY_P }
)
);
/* Parse status messages */
named!(status<&[u8],Status>,
alt!(
//tag!(b"") => { |_| Status::EMPTY }
tag!(b"ready") => { |_| Status::READY }
| tag!(b"WIFI DISCONNECT") => { |_| Status::WIFI_DISCONNECT }
| tag!(b"WIFI CONNECTED") => { |_| Status::WIFI_CONNECTED }
| tag!(b"WIFI GOT IP") => { |_| Status::WIFI_GOT_IP }
| tag!(b"SEND OK") => { |_| Status::SEND_OK }
| do_parse!(
tag!(b"Recv ") >>
a: num_u32 >>
tag!(b" bytes") >>
( Status::RECV_BYTES(a) )
)
| do_parse!(
id: num_u32 >>
tag!(b",") >>
r: alt!(
tag!(b"CONNECT") => { |_| Status::CONNECT(id) }
| tag!(b"CLOSED") => { |_| Status::CLOSED(id) }
) >>
( r )
)
)
);
/* Parse command-response messages */
named!(cmdresponse<&[u8],CmdResponse>,
alt!(
/* AT+CWJAP_CUR? */
tag!(b"No AP") => { |_| CmdResponse::NO_AP }
| do_parse!(
tag!(b"+CWJAP_CUR:") >>
a: qstr >>
tag!(",") >>
b: qstr >>
tag!(",") >>
c: num_i32 >>
tag!(",") >>
d: num_i32 >>
(CmdResponse::CWJAP_CUR(a,b,c,d))
)
/* AT+CWMODE? */
| do_parse!(
tag!(b"+CWMODE:") >>
a: num_u32 >>
(CmdResponse::CWMODE(a))
)
| do_parse!(
tag!(b"+CWJAP:") >>
a: num_u32 >>
(CmdResponse::CWJAP(a))
)
| do_parse!(
tag!(b"+CIFSR:STAIP,") >>
a: qip >>
(CmdResponse::CIFSR_STAIP(a))
)
| do_parse!(
tag!(b"+CIFSR:STAMAC,") >>
a: qmac >>
(CmdResponse::CIFSR_STAMAC(a))
)
/* AT+CIPSTATUS */
| do_parse!(
tag!(b"STATUS:") >>
a: num_u32 >>
(CmdResponse::STATUS(a))
)
/* AT+CIPSTART */
| tag!(b"ALREADY CONNECTED") => { |_| CmdResponse::ALREADY_CONNECTED }
// DNS Fail
)
);
/* Parse command-echo messages */
named!(cmdecho<&[u8],&[u8]>,
recognize!(tuple!(
tag!(b"AT"),
take_until!("\r")
))
);
/* Newline-terminated response */
named!(nl_terminated<&[u8],Response>,
do_parse!(
x: alt!(
genresponse => { |x| Response::Gen(x) }
| status => { |x| Response::Status(x) }
| cmdresponse => { |x| Response::Cmd(x) }
| cmdecho => { |x| Response::Echo(x) }
) >>
tag!(b"\r\n") >>
(x)
)
);
/* Data response */
named!(ipd_data<&[u8],Response>,
do_parse!(
tag!(b"+IPD") >>
tag!(b",") >>
id: num_u32 >>
tag!(b",") >>
a: num_u32 >>
tag!(b":") >>
b: take!(a) >>
( Response::Data(id, b) )
)
);
/* Parse response from line */
named!(pub parse_response<&[u8],Response>,
alt!(
nl_terminated
| ipd_data
| tag!(b"> ") => { |_| Response::RecvPrompt }
| tag!(b"\r\n") => { |_| Response::Empty }
)
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
assert_eq!(
parse_response(b"AT\r\n"),
Ok((&b""[..], Response::Echo(b"AT")))
);
assert_eq!(parse_response(b"\r\n"), Ok((&b""[..], Response::Empty)));
assert_eq!(parse_response(b"> "), Ok((&b""[..], Response::RecvPrompt)));
assert_eq!(
parse_response(b"OK\r\n"),
Ok((&b""[..], Response::Gen(GenResponse::OK)))
);
}
}

View File

@ -0,0 +1,27 @@
use core::fmt;
#[cfg(feature = "std")]
use std::io;
/** The trait that's required of anything acting as serial port writer.
* It is much simpler than io::Write. The reason for implementing our own trait here
* is that this is compatible with no_std.
*/
pub trait Write {
type Error: fmt::Debug;
/** Write all bytes from the buffer, or fail */
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error>;
}
/** Implement our write trait for everything that implements io::Write */
#[cfg(feature = "std")]
impl<X> Write for X
where
X: io::Write,
{
type Error = io::Error;
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
(self as &mut io::Write).write_all(buf)
}
}

View File

@ -0,0 +1,85 @@
use core::slice;
use crate::traits::Write;
/** Write quoted string. `\` and `"` are escaped, and the string
* is automatically surrounded with double-quotes.
*/
pub fn write_qstr<W>(w: &mut W, s: &[u8]) -> Result<(), W::Error>
where
W: Write,
{
w.write_all(b"\"")?;
for ch in s {
w.write_all(match ch {
b'\"' => &[b'\\', b'"'],
b'\\' => &[b'\\', b'\\'],
_ => slice::from_ref(ch),
})?;
}
w.write_all(b"\"")?;
Ok(())
}
/** Write decimal unsigned number */
pub fn write_num_u32<W>(w: &mut W, mut val: u32) -> Result<(), W::Error>
where
W: Write,
{
let mut buf = [0u8; 10];
let mut curr = buf.len();
for byte in buf.iter_mut().rev() {
*byte = b'0' + (val % 10) as u8;
val = val / 10;
curr -= 1;
if val == 0 {
break;
}
}
w.write_all(&buf[curr..])
}
#[cfg(test)]
mod tests {
use super::*;
use arrayvec::ArrayVec;
#[cfg(not(feature = "std"))]
use arrayvec::Array;
#[cfg(not(feature = "std"))]
impl<A: Array<Item = u8>> Write for ArrayVec<A> {
type Error = ();
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
for byte in buf {
self.push(*byte); // Panics if the vector is already full.
}
Ok(())
}
}
#[test]
fn test_qstr() {
let mut o = ArrayVec::<[_; 16]>::new();
write_qstr(&mut o, b"123").unwrap();
assert_eq!(o.as_slice(), b"\"123\"");
o.clear();
write_qstr(&mut o, b"\"\\").unwrap();
assert_eq!(o.as_slice(), b"\"\\\"\\\\\"");
}
#[test]
fn test_num() {
let mut o = ArrayVec::<[_; 16]>::new();
write_num_u32(&mut o, 123).unwrap();
assert_eq!(o.as_slice(), b"123");
o.clear();
write_num_u32(&mut o, 0).unwrap();
assert_eq!(o.as_slice(), b"0");
o.clear();
write_num_u32(&mut o, 4294967295).unwrap();
assert_eq!(o.as_slice(), b"4294967295");
}
}