diff --git a/src/at_commands.rs b/src/at_commands.rs index e556a45..7295c23 100644 --- a/src/at_commands.rs +++ b/src/at_commands.rs @@ -66,7 +66,6 @@ impl<'a, 'd> ATCommands<'a, 'd> { let command = command.clone() + "\r"; self.uart.write_str(&command).unwrap(); self.uart.flush().unwrap(); - self.delay.delay_millis(500); log::info!("Wrote command {}", command); self.read_response(cmd, command, timeout) } @@ -82,7 +81,6 @@ impl<'a, 'd> ATCommands<'a, 'd> { self.flush_rx(); self.uart.write_str(&(command.clone() + "\r")).unwrap(); self.uart.flush().unwrap(); - self.delay.delay_millis(500); log::info!("Wrote command {}", command); self.flush_rx(); @@ -185,7 +183,9 @@ impl<'a, 'd> ATCommands<'a, 'd> { self.uart.read_ready() } { let length = self.uart.read(&mut buffer).unwrap(); - self.buffer += str::from_utf8(&buffer[..length]).unwrap(); + for character in &buffer[..length] { + self.buffer += str::from_utf8(&[character.clone()]).unwrap_or("?") + } } let lines = self @@ -231,6 +231,13 @@ impl ATResponseParser { result.cloned() } + pub fn params(&mut self) -> Option { + self.readline().map(|l| Params { + raw_params: l.to_owned(), + idx: 0, + }) + } + pub fn expect(&mut self, text: String) -> Result<(), ATError> { let line = if let Some(curr_line) = self.curr_line.take() { curr_line @@ -246,6 +253,59 @@ impl ATResponseParser { Err(ATError::InvalidResponse) } } + + pub fn peek(&self) -> Option { + if let Some(curr_line) = &self.curr_line { + return Some(curr_line.clone()); + } + if let Some(text) = self.text.get(self.index) { + Some(text.clone()) + } else { + None + } + } +} + +pub struct Params { + raw_params: String, + idx: u32, +} + +impl Params { + pub fn read_raw(&mut self) -> Option { + if self.idx > 0 { + self.raw_params = self.raw_params.strip_prefix(",").unwrap().to_owned() + } + + let returned; + + if let Some(rest) = self.raw_params.strip_prefix("\"") { + let mut parts = rest.split("\""); + returned = parts.next().unwrap().to_owned(); + self.raw_params = parts.collect::>().join("\"") + } else { + let mut parts = self.raw_params.split(","); + returned = parts.next().unwrap().to_owned(); + self.raw_params = parts.collect::>().join(",") + } + Some(returned) + } + + pub fn read_u32(&mut self) -> Option { + if let Some(part) = self.read_raw() { + u32::from_str_radix(&part, 10).ok() + } else { + None + } + } + + pub fn read_string(&mut self) -> Option { + if let Some(part) = self.read_raw() { + Some(part) + } else { + None + } + } } #[derive(Clone, Copy, Debug)] @@ -651,3 +711,122 @@ impl ATCommand for SendSMSCommand { Err(ATError::EOF) } } + +#[derive(Debug, Clone)] +pub enum ListSMSMessagesResponse { + Ok(Vec), + Error(String), +} + +#[derive(Debug, Clone)] +pub struct SMSMessageListing { + index: u32, + stat: SMSMessageStat, + from_addr: String, + to_addr: String, + timestamp: String, + text: String, +} + +impl ATResponse for ListSMSMessagesResponse { + fn is_error(&self) -> bool { + match self { + ListSMSMessagesResponse::Error(_) => true, + _ => false, + } + } +} + +#[derive(Debug, Clone)] +pub enum SMSMessageStat { + RecUnread, + RecRead, + StoUnsent, + StoSent, + All, +} + +impl SMSMessageStat { + pub fn into_str(&self) -> String { + match self { + SMSMessageStat::RecUnread => "REC UNREAD", + SMSMessageStat::RecRead => "REC READ", + SMSMessageStat::StoUnsent => "STO UNSENT", + SMSMessageStat::StoSent => "STO SENT", + SMSMessageStat::All => "ALL", + } + .to_string() + } + + pub fn from(text: &str) -> SMSMessageStat { + match text { + "REC UNREAD" => Self::RecUnread, + "REC READ" => Self::RecRead, + "STO UNSENT" => Self::StoUnsent, + "STO SENT" => Self::StoSent, + _ => Self::All, + } + } +} + +#[derive(Debug, Clone)] +pub struct ListSMSMessages { + pub stat: SMSMessageStat, +} + +impl ATCommand for ListSMSMessages { + type Response = ListSMSMessagesResponse; + + fn execute(&self, at_commands: &mut ATCommands) -> Result, ATError> { + at_commands.raw_command( + self, + format!("AT+CMGL=\"{}\"", self.stat.into_str()), + Duration::from_millis(5000), + ) + } + + fn parse_response(parser: &mut ATResponseParser) -> Result { + let mut messages = Vec::new(); + while let Ok(_) = parser.expect("+CMGL: ".to_owned()) { + let mut params = parser.params().unwrap(); + let idx = params.read_u32().unwrap(); + let stat = SMSMessageStat::from(¶ms.read_string().unwrap()); + let from_addr = params.read_string().unwrap(); + let to_addr = params.read_string().unwrap(); + let timestamp = params.read_string().unwrap(); + + let mut message = String::new(); + + while let Some(line) = parser.peek() { + if line.starts_with("OK") || line.starts_with("+CMGL: ") { + break; + } + message += &parser.readline().unwrap(); + } + + messages.push(SMSMessageListing { + index: idx, + stat, + from_addr, + to_addr, + timestamp, + text: message, + }); + } + + log::info!("Messages so far: {:?}", messages); + + while let Some(line) = parser.readline() { + if line.starts_with("OK") { + return Ok(ListSMSMessagesResponse::Ok(messages)); + } + if line.starts_with("+CMS ERROR: ") { + return Ok(ListSMSMessagesResponse::Error( + line.strip_prefix("+CMS ERROR: ").unwrap().to_owned(), + )); + } + } + + Err(ATError::EOF) + } +} diff --git a/src/states.rs b/src/states.rs index 630e69e..0a413f4 100644 --- a/src/states.rs +++ b/src/states.rs @@ -12,8 +12,8 @@ use crate::{ async_io::{ATPromise, KeypadButton, NumberInput, TextInput}, at_commands::{ ATCommand, ATError, ATInformationCommand, CheckPinCommand, CheckPinResult, EnterPinCommand, - EnterPinResult, SMSFormat, SelectSMSFormatCommand, SendSMSCommand, SendSMSResponse, - SetTECharsetCommand, SimpleATResponse, TECharset, + EnterPinResult, ListSMSMessages, SMSFormat, SMSMessageStat, SelectSMSFormatCommand, + SendSMSCommand, SendSMSResponse, SetTECharsetCommand, SimpleATResponse, TECharset, }, display::{Position, Rgb565}, font::{HorizontalAlignment, VerticalAlignment}, @@ -337,7 +337,27 @@ impl State for MainMenuState { if let Some(option) = self.menu.poll(&mut data.io) { match option { MainMenuItem::SendSMS => return Some(Box::new(PhoneNumberState::default())), - MainMenuItem::ReadSMS => {} + MainMenuItem::ReadSMS => { + return Some(Box::new(ATCommandState::with( + "Loading messages".to_owned(), + ListSMSMessages { + stat: SMSMessageStat::All, + }, + |resp| match resp { + Ok(messages) => { + log::info!("{:?}", messages); + Box::new(TextState { + text: format!("Messages read"), + after: MainMenuState::default(), + }) + } + Err(err) => Box::new(TextState { + text: format!("Error:\n{:?}", err), + after: MainMenuState::default(), + }), + }, + ))); + } } } None