Skip to content
This repository has been archived by the owner on Oct 15, 2022. It is now read-only.

Commit

Permalink
Allow receiving responses through URCs
Browse files Browse the repository at this point in the history
  • Loading branch information
dbrgn committed Mar 24, 2020
1 parent fce9478 commit 7e5480b
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 19 additions & 4 deletions examples/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -77,10 +77,20 @@ fn main() {
})
.unwrap();

macro_rules! check_urc {
() => {
if let Some(urc) = client.check_urc::<urcs::EspUrc>() {
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()
Expand Down Expand Up @@ -157,9 +167,14 @@ fn main() {
data.len().try_into().unwrap(),
))
.expect("Could not prepare sending data");
client
.send_command(&requests::SendData::<heapless::consts::U72>::new(&data))
.expect("Could not send data");
let _ = client
.send_command(&requests::SendData::<heapless::consts::U72>::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");
Expand Down
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
pub mod requests;
pub mod responses;
pub mod urcs;
71 changes: 71 additions & 0 deletions src/commands/urcs.rs
Original file line number Diff line number Diff line change
@@ -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<u8, consts::U2048>),
}

/// Incoming data from the network (+IPD).
#[derive(Debug)]
pub struct NetworkData {
/// The connection ID. Only set in multiplexed mode.
pub connection_id: Option<ConnectionId>,

/// The incoming bytes.
pub data: Vec<u8, consts::U2048>,
}

impl NetworkData {
const PREFIX: &'static str = "+IPD,";

fn from_urc(urc: &str) -> Result<Self, Error> {
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<Self::Response, Error> {
if urc.starts_with(NetworkData::PREFIX) {
Ok(Self::NetworkData(NetworkData::from_urc(urc)?))
} else {
Ok(Self::Other(Vec::from_iter(urc.bytes())))
}
}
}
53 changes: 50 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,6 +14,49 @@ use types::ConfigWithDefault;
/// Type alias for a result that may return an ATAT error.
pub type EspResult<T> = Result<T, nb::Error<atat::Error>>;

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<consts::U256>) -> atat::UrcMatcherResult<Self::MaxLen> {
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::<usize>() {
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<TX, TIMER>
where
Expand All @@ -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<EspUrcDetector>) {
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)
}

Expand All @@ -50,6 +93,10 @@ where
self.client.send(command)
}

pub fn check_urc<T: atat::AtatUrc>(&mut self) -> Option<T::Response> {
self.client.check_urc::<T>()
}

/// Test whether the device is connected and able to communicate.
pub fn selftest(&mut self) -> EspResult<()> {
self.client
Expand Down
17 changes: 17 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Shared types.
use core::convert::TryFrom;

/// The WiFi mode.
#[derive(Debug)]
pub enum WifiMode {
Expand Down Expand Up @@ -66,6 +68,21 @@ impl ConnectionId {
}
}

impl TryFrom<&str> for ConnectionId {
type Error = atat::Error;

fn try_from(val: &str) -> Result<Self, atat::Error> {
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)]
Expand Down

0 comments on commit 7e5480b

Please sign in to comment.