implement iterator on reader (serial rx)

This commit is contained in:
Vladan Popovic 2022-06-19 03:07:07 +02:00
parent 792eef13ba
commit 08c5cabe6d
2 changed files with 91 additions and 43 deletions

View file

@ -271,7 +271,7 @@ impl Command {
}
}
pub fn tcp_send(size: usize) -> Command {
pub fn tcp_send_size(size: usize) -> Command {
Command {
text: format!("AT+CIPSEND={}", size),
timeout: Duration::from_millis(3000),
@ -279,11 +279,11 @@ impl Command {
}
}
pub fn tcp_write(payload: &str) -> Command {
pub fn tcp_send() -> Command {
Command {
text: payload.to_string(),
timeout: Duration::from_millis(2000),
contains: Some("> ".to_string()),
text: "AT+CIPSEND".to_string(),
timeout: Duration::from_millis(3000),
contains: Some(">".to_string()),
}
}

View file

@ -11,7 +11,7 @@ use esp_idf_hal::serial::{self, Rx, Tx};
pub type Result<T> = std::result::Result<T, ModemError>;
pub struct Modem<UART: serial::Uart> {
rx: Rx<UART>,
rx: IterableRx<UART>,
tx: Tx<UART>,
}
@ -31,10 +31,51 @@ impl std::fmt::Display for ModemError {
}
}
pub struct IterableRx<UART: serial::Uart> {
inner: Rx<UART>,
timeout: Option<Duration>,
}
impl<UART: serial::Uart> IterableRx<UART> {
fn reset(&mut self, timeout: Duration) -> &mut Self {
self.timeout = Some(timeout);
self
}
}
impl<UART: serial::Uart> Iterator for IterableRx<UART> {
type Item = u8;
/// `nb` returns Ok(byte), or one of Err(WouldBlock) and Err(Other) which isn't of anyone's
/// interest, so the retry mechanism is triggered on _any_ error every 200ms until a byte is
/// received, or the timeout is reached.
fn next(&mut self) -> Option<Self::Item> {
let now = Instant::now();
loop {
let timeout = self.timeout.unwrap_or(Duration::from_millis(0));
match self.inner.read() {
Ok(b) => {
break Some(b)
},
_ => {
print!("got into retry loop in read iterator, ");
if now.elapsed() > timeout {
println!("exiting because timeout expired :(");
break None
}
println!("waiting 200ms ...");
thread::sleep(Duration::from_millis(200));
self.timeout = Some(timeout - now.elapsed());
}
}
}
}
}
impl<UART: serial::Uart> Modem<UART> {
pub fn new(tx: Tx<UART>, rx: Rx<UART>) -> Self {
Self {
rx,
rx: IterableRx { inner: rx, timeout: None },
tx,
}
}
@ -70,32 +111,23 @@ impl<UART: serial::Uart> Modem<UART> {
}
/// Reads a whole line (that ends with \\n) within the given `timeout` passed on input.
/// `nb` returns Ok(byte), or one of Err(WouldBlock) and Err(Other) which isn't of anyone's
/// interest, so the retry mechanism is triggered on _any_ error every 200ms until a byte is
/// received, or the timeout is reached.
///
fn read_line(&mut self, timeout: Duration) -> Result<String> {
let mut buf = String::new();
let start = Instant::now();
let line: String = self.rx.reset(timeout)
.map(|b| char::from(b))
.take_while(|c| *c != '\n')
.collect();
loop {
match self.rx.read() {
Ok(b) => {
print!("{}, ", b);
buf.push(b as char);
if b == '\n' as u8 {
return Ok(buf);
}
},
_ => {
if Instant::now() > start + timeout {
return Err(ModemError::ReadError);
}
else {
thread::sleep(Duration::from_millis(200));
}
},
}
// A necessary check because the actual timeout is in the Iterator implementation. The
// iterator exits when the returned Item is None, which happens when there's no data in
// the serial port and the timeout is breached.
//
// This check here is tested on sim800l and works only because the modem has \r\n as CRLF.
if line.ends_with("\r") {
Ok(line)
}
else {
Err(ModemError::ReadError)
}
}
@ -108,25 +140,24 @@ impl<UART: serial::Uart> Modem<UART> {
fn read_response(&mut self, contains: Option<String>, timeout: Duration) -> Result<String> {
let mut response = String::new();
let start = Instant::now();
let match_text: String = contains.unwrap_or("".to_string());
let match_text: String = contains.unwrap_or("\n".to_string());
loop {
if let Ok(line) = self.read_line(start + timeout - Instant::now()) {
let rdln = self.read_line(start + timeout - Instant::now());
if let Ok(line) = rdln {
println!("Read {} bytes from serial ({})", line.len(), line);
response.push_str(&line);
if line.contains("ERROR") || line.contains(&match_text) {
println!("Found match {} for line {} ... exiting response reader now!", match_text, line);
println!("Found match {} for line {} ; exiting response reader now ...", match_text, line);
println!("-----------------------------------------------------------");
break;
break Ok(response.to_string())
}
} else {
if Instant::now() > start + timeout {
return Err(ModemError::TimeoutError);
}
println!("got into retry loop in read_response");
thread::sleep(Duration::from_millis(200));
println!("-----------------------------------------------------------");
println!("Read line {:?}", rdln);
break Err(ModemError::TimeoutError)
}
}
Ok(response.to_string())
}
fn send(&mut self, b: u8) -> Result<()> {
@ -147,7 +178,24 @@ impl<UART: serial::Uart> Modem<UART> {
println!("-----------------------------------------------------------");
println!("Sending {} ...", cmd.text);
let _ = self.send_bytes(cmd.text.as_bytes())?;
self.read_response(cmd.contains.clone(), cmd.timeout.clone())
self.read_response(cmd.contains, cmd.timeout)
}
fn send_command_data(&mut self, cmd: Command, payload: &str) -> Result<String> {
println!("-----------------------------------------------------------");
println!("Sending {} ...", cmd.text);
let _ = self.send_bytes(cmd.text.as_bytes())?;
let prompt: String = self.rx.reset(cmd.timeout)
.map(|b| char::from(b))
.take_while(|c| *c != ' ')
.collect();
if prompt != ">".to_string() {
println!("invalid prompt: {}", prompt);
}
let _ = self.send_bytes(payload.as_bytes())?;
self.read_response(cmd.contains, cmd.timeout)
}
pub fn get_ip_addr(&mut self) -> Result<String> {
@ -214,8 +262,8 @@ impl<UART: serial::Uart> Modem<UART> {
}
pub fn tcp_send(&mut self, payload: &str) -> Result<()> {
self.send_command(Command::tcp_send(payload.len()))?;
self.send_command(Command::tcp_write(payload))?;
self.send_command(Command::tcp_send_size(payload.len()))?;
self.send_command_data(Command::tcp_send(), payload)?;
Ok(())
}
}