diff --git a/Cargo.toml b/Cargo.toml index 4a2ca6b..2c2e535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ readme = "README.md" edition = "2018" [dependencies] -atat = { git = "https://github.com/BlackbirdHQ/atat", branch = "master" } +atat = { git = "https://github.com/dbrgn/atat", branch = "custom-urc-handler" } embedded-hal = "0.2" heapless = "0.5" nb = "0.1" diff --git a/README.md b/README.md index 0cab3d8..034a9b7 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ AT based driver crate for ESP8266 WiFi modules. This is still very much work in progress. +Current state: Alpha quality. Connecting to APs in station mode works (although +it's not yet very robust). Sending and receiving data over the network should +work partially. ## Resources diff --git a/examples/linux.rs b/examples/linux.rs index ef8318a..88671e4 100644 --- a/examples/linux.rs +++ b/examples/linux.rs @@ -4,7 +4,7 @@ use std::io; use std::thread; use std::time::Duration; -use espresso::commands::requests; +use espresso::commands::{requests, urcs}; use espresso::types::{ConnectionStatus, MultiplexingType, WifiMode}; use no_std_net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use serialport::{DataBits, FlowControl, Parity, SerialPortSettings, StopBits}; @@ -77,10 +77,20 @@ fn main() { }) .unwrap(); + macro_rules! check_urc { + () => { + if let Some(urc) = client.check_urc::() { + println!("Received URC: {:?}", urc); + } + } + } + print!("Testing whether device is onlineā€¦ "); client.selftest().expect("Self test failed"); println!("OK"); + check_urc!(); + // Get firmware information let version = client .get_firmware_version() @@ -157,9 +167,14 @@ fn main() { data.len().try_into().unwrap(), )) .expect("Could not prepare sending data"); - client - .send_command(&requests::SendData::::new(&data)) - .expect("Could not send data"); + let _ = client + .send_command(&requests::SendData::::new(&data)); + //.expect("Could not send data"); // TODO: Commented out for now because the + // non-standard response code always results in a timeout + + check_urc!(); + check_urc!(); + client .send_command(&requests::CloseConnection::new(MultiplexingType::NonMultiplexed)) .expect("Could not close connection"); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index dace435..3fa92cf 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -3,3 +3,4 @@ pub mod requests; pub mod responses; +pub mod urcs; diff --git a/src/commands/urcs.rs b/src/commands/urcs.rs new file mode 100644 index 0000000..052ddce --- /dev/null +++ b/src/commands/urcs.rs @@ -0,0 +1,71 @@ +//! Raw URCs (Unsolicied Result Codes) that can be received from the ESP8266 device. + +use core::convert::TryFrom; +use core::iter::FromIterator; + +use atat::{AtatUrc, Error}; +use heapless::{consts, Vec}; + +use crate::types::ConnectionId; + +#[derive(Debug)] +pub enum EspUrc { + /// Incoming data from the network + NetworkData(NetworkData), + Other(Vec), +} + +/// Incoming data from the network (+IPD). +#[derive(Debug)] +pub struct NetworkData { + /// The connection ID. Only set in multiplexed mode. + pub connection_id: Option, + + /// The incoming bytes. + pub data: Vec, +} + +impl NetworkData { + const PREFIX: &'static str = "+IPD,"; + + fn from_urc(urc: &str) -> Result { + if !urc.starts_with(Self::PREFIX) { + return Err(Error::ParseString); + } + let urc = urc.trim_start_matches(Self::PREFIX); + let (params, data) = match urc.find(':') { + Some(index) => urc.split_at(index), + None => return Err(Error::ParseString), + }; + let connection_id = match params.bytes().filter(|b| *b == b',').count() { + 0 | 2 => { + // Single connection, non multiplexed + // TODO: Parse IP / Port (and test parsing) + None + } + 1 | 3 => { + // Multiplexed connection + // TODO: Parse IP / Port (and test parsing) + let connection_id_raw = params.split(',').next().unwrap(); + Some(ConnectionId::try_from(connection_id_raw)?) + } + _ => return Err(Error::ParseString), + }; + Ok(Self { + connection_id, + data: Vec::from_iter(data.bytes()), + }) + } +} + +impl AtatUrc for EspUrc { + type Response = Self; + + fn parse(urc: &str) -> Result { + if urc.starts_with(NetworkData::PREFIX) { + Ok(Self::NetworkData(NetworkData::from_urc(urc)?)) + } else { + Ok(Self::Other(Vec::from_iter(urc.bytes()))) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index fd370c0..4dee93a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use atat::AtatClient; use embedded_hal::serial; use embedded_hal::timer; -use heapless::String; +use heapless::{consts, String}; pub mod commands; pub mod types; @@ -14,6 +14,49 @@ use types::ConfigWithDefault; /// Type alias for a result that may return an ATAT error. pub type EspResult = Result>; +pub struct EspUrcDetector {} + +impl atat::UrcMatcher for EspUrcDetector { + type MaxLen = consts::U256; // TODO: Max data bytes: 1460 (TCP MTU) + fn process(&mut self, buf: &mut String) -> atat::UrcMatcherResult { + if buf.starts_with("+IPD,") { + // The IPD URC tells us how many bytes will follow + let start = 5; + let mut end = 5; + for i in 5..buf.len() - 1 { + if &buf[i..i + 1] == ":" { + end = i; + break; + } + } + + // If we didn't find the length bytes, we haven't yet received the entire URC. + if end == start { + return atat::UrcMatcherResult::Incomplete; + } + + // Convert the length to a number + let length = match buf[start..end].parse::() { + Ok(length) => length, + Err(_) => return atat::UrcMatcherResult::Incomplete, + }; + + // Check whether we've got the entire response + let total_length = end + 1 + length; + if buf.len() < total_length { + atat::UrcMatcherResult::Incomplete + } else { + // TODO: Optimize this, so there's less copying + let data = String::from(&buf[0..total_length]); + *buf = String::from(&buf[total_length..]); + atat::UrcMatcherResult::Complete(data) + } + } else { + atat::UrcMatcherResult::NotHandled + } + } +} + /// An ESP8266 client. pub struct EspClient where @@ -36,9 +79,9 @@ where /// returned. That needs to be hooked up with the incoming serial bytes. /// /// [IngressManager]: ../atat/istruct.IngressManager.html - pub fn new(serial_tx: TX, timer: TIMER) -> (Self, atat::IngressManager) { + pub fn new(serial_tx: TX, timer: TIMER) -> (Self, atat::IngressManager) { let config = atat::Config::new(atat::Mode::Timeout); - let (client, ingress) = atat::new(serial_tx, timer, config); + let (client, ingress) = atat::new(serial_tx, timer, config, Some(EspUrcDetector {})); (Self { client }, ingress) } @@ -50,6 +93,10 @@ where self.client.send(command) } + pub fn check_urc(&mut self) -> Option { + self.client.check_urc::() + } + /// Test whether the device is connected and able to communicate. pub fn selftest(&mut self) -> EspResult<()> { self.client diff --git a/src/types.rs b/src/types.rs index 090c494..8bd9032 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,7 @@ //! Shared types. +use core::convert::TryFrom; + /// The WiFi mode. #[derive(Debug)] pub enum WifiMode { @@ -66,6 +68,21 @@ impl ConnectionId { } } +impl TryFrom<&str> for ConnectionId { + type Error = atat::Error; + + fn try_from(val: &str) -> Result { + match val { + "0" => Ok(ConnectionId::Zero), + "1" => Ok(ConnectionId::One), + "2" => Ok(ConnectionId::Two), + "3" => Ok(ConnectionId::Three), + "4" => Ok(ConnectionId::Four), + _ => Err(atat::Error::ParseString), + } + } +} + /// The ESP8266 can either run in single-connection mode (`NonMultiplexed`) or /// in multi-connection mode (`Multiplexed`). #[derive(Debug)]