diff --git a/src/command.rs b/src/command.rs index bd97e9d..73470d9 100644 --- a/src/command.rs +++ b/src/command.rs @@ -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()), } } diff --git a/src/modem.rs b/src/modem.rs index 357deca..dd23052 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -11,7 +11,7 @@ use esp_idf_hal::serial::{self, Rx, Tx}; pub type Result = std::result::Result; pub struct Modem { - rx: Rx, + rx: IterableRx, tx: Tx, } @@ -31,10 +31,51 @@ impl std::fmt::Display for ModemError { } } +pub struct IterableRx { + inner: Rx, + timeout: Option, +} + +impl IterableRx { + fn reset(&mut self, timeout: Duration) -> &mut Self { + self.timeout = Some(timeout); + self + } +} + +impl Iterator for IterableRx { + 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 { + 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 Modem { pub fn new(tx: Tx, rx: Rx) -> Self { Self { - rx, + rx: IterableRx { inner: rx, timeout: None }, tx, } } @@ -70,32 +111,23 @@ impl Modem { } /// 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 { - 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 Modem { fn read_response(&mut self, contains: Option, timeout: Duration) -> Result { 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 Modem { 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 { + 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 { @@ -214,8 +262,8 @@ impl Modem { } 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(()) } }