use core::iter::repeat; use alloc::{ borrow::ToOwned, boxed::Box, format, string::{String, ToString}, }; use esp_hal::time::{Duration, Instant}; use crate::{ async_io::{ATPromise, KeypadButton, NumberInput, TextInput}, at_commands::{ ATCommand, ATError, ATInformationCommand, CheckPinCommand, CheckPinResult, EnterPinCommand, EnterPinResult, ListSMSMessages, SMSFormat, SMSMessageStat, SelectSMSFormatCommand, SendSMSCommand, SendSMSResponse, SetTECharsetCommand, SimpleATResponse, TECharset, }, display::{Position, Rgb565}, font::{HorizontalAlignment, VerticalAlignment}, state::{ATCommandHelper, Menu, MenuItem, State, StateData, TextSettings}, }; #[derive(Debug, Clone)] pub struct DotsMessage { message: String, dots: u8, prev_dots: Instant, } impl Default for DotsMessage { fn default() -> Self { Self { message: Default::default(), dots: Default::default(), prev_dots: Instant::now(), } } } impl DotsMessage { pub fn poll(&mut self) { if self.prev_dots.elapsed() > Duration::from_millis(200) { self.dots = (self.dots + 1) % 3; self.prev_dots = Instant::now(); } } pub fn render(&self) -> String { let dots = repeat(".").take(self.dots as usize).collect::(); format!("{}{}", self.message, dots) } } pub struct ATCommandState where F: FnOnce(Result) -> Box, { message: DotsMessage, command: Cmd, promise: Option>, fun: F, } impl ATCommandState where F: FnOnce(Result) -> Box, { pub fn with(message: String, command: Cmd, after: F) -> ATCommandState { ATCommandState { message: DotsMessage { message, ..DotsMessage::default() }, command, promise: None, fun: after, } } } impl State for ATCommandState where F: FnOnce(Result) -> Box, { fn init(&mut self, data: &mut StateData) { self.promise = Some(data.io.send_at_command(self.command.clone()).unwrap()); } fn update(&mut self, data: &mut StateData) -> Option> { // Update dots self.message.poll(); if let Some(promise) = &self.promise { match promise.poll(&mut data.io) { Some(response) => { return Some((self.fun.clone())(response)); } None => {} } return None; } None } fn draw(&self, data: &mut StateData) { data.clear_screen(Rgb565::black().as_color()); data.draw_text( self.message.render(), Position::new(0, 0), TextSettings::default(), ); } } #[derive(Debug, Clone)] pub struct InitATState { ati: ATCommandHelper, sms_charset: ATCommandHelper, te_charset: ATCommandHelper, message: DotsMessage, } impl Default for InitATState { fn default() -> Self { Self { ati: ATCommandHelper::new(ATInformationCommand), sms_charset: ATCommandHelper::new(SelectSMSFormatCommand(SMSFormat::TextMode)), te_charset: ATCommandHelper::new(SetTECharsetCommand(TECharset::IRA)), message: DotsMessage::default(), } } } impl State for InitATState { fn update(&mut self, data: &mut StateData) -> Option> { // Update dots self.message.poll(); if let Some(resp) = self.ati.poll(&mut data.io) { match resp { Ok(_) => {} Err(err) => { return Some(Box::new(TextState { text: format!("Err: {:?}", err), after: InitATState::default(), })); } } } else { self.message.message = "Checking ATI".to_owned(); return None; }; if let Some(resp) = self.sms_charset.poll(&mut data.io) { match resp { Ok(resp) => match resp { SimpleATResponse::Ok => {} SimpleATResponse::Error => { return Some(Box::new(TextState { text: "ERROR!".to_owned(), after: InitATState::default(), })); } }, Err(err) => { return Some(Box::new(TextState { text: format!("Err: {:?}", err), after: InitATState::default(), })); } } } else { self.message.message = "Selecting SMS\ncharset".to_owned(); return None; }; if let Some(resp) = self.te_charset.poll(&mut data.io) { match resp { Ok(resp) => match resp { SimpleATResponse::Ok => {} SimpleATResponse::Error => { return Some(Box::new(TextState { text: "ERROR!".to_owned(), after: InitATState::default(), })); } }, Err(err) => { return Some(Box::new(TextState { text: format!("Err: {:?}", err), after: InitATState::default(), })); } } } else { self.message.message = "Selecting TE\ncharset".to_owned(); return None; }; Some(Box::new(EnterPinState::default())) } fn draw(&self, data: &mut StateData) { data.clear_screen(Rgb565::black().as_color()); data.draw_text( self.message.render(), Position::new(0, 0), TextSettings::default(), ); } } #[derive(Clone)] pub struct TextState { text: String, after: T, } impl State for TextState { fn update(&mut self, data: &mut StateData) -> Option> { if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 { Some(Box::new(self.after.clone())) } else { None } } fn draw(&self, data: &mut StateData) { data.clear_screen(Rgb565::black().as_color()); data.draw_text(&self.text, Position::new(0, 0), TextSettings::default()); data.draw_text( "OK", Position::new(120, 240), TextSettings { h_align: HorizontalAlignment::Center, v_align: VerticalAlignment::BottomToTop, ..Default::default() }, ); } } #[derive(Clone, Default)] pub struct EnterPinState { input: NumberInput, helper: Option>, } impl State for EnterPinState { fn update(&mut self, data: &mut StateData) -> Option> { if let Some(helper) = &mut self.helper { match helper.poll(&mut data.io) { Some(response) => match response { Ok(response) => match response { EnterPinResult::Ok => { return Some(Box::new(MainMenuState::default())); } EnterPinResult::Error => { return Some(Box::new(TextState { text: "ERROR!".to_owned(), after: EnterPinState::default(), })); } EnterPinResult::ErrorMessage(msg) => { return Some(Box::new(TextState { text: format!("Error:\n{}", msg), after: EnterPinState::default(), })); } }, Err(err) => { return Some(Box::new(TextState { text: format!("Error:\n{:?}", err), after: EnterPinState::default(), })); } }, None => return None, } } self.input.poll(&mut data.io); if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 { self.helper = Some(ATCommandHelper::new(EnterPinCommand( self.input.read().clone(), ))); } None } fn draw(&self, data: &mut StateData) { data.clear_screen(Rgb565::black().as_color()); data.draw_text("PIN:", Position::new(0, 0), TextSettings::default()); data.draw_text( format!("{}", self.input.read()), Position::new(0, 30), TextSettings::default(), ); } } #[derive(Debug, Default, Clone)] enum MainMenuItem { #[default] SendSMS, ReadSMS, } impl MenuItem for MainMenuItem { fn as_text(&self) -> String { match self { MainMenuItem::SendSMS => "Send SMS", MainMenuItem::ReadSMS => "Read SMS", } .to_string() } } #[derive(Debug, Clone)] pub struct MainMenuState { menu: Menu, } impl Default for MainMenuState { fn default() -> Self { Self { menu: Menu::default() .with_item(MainMenuItem::SendSMS) .with_item(MainMenuItem::ReadSMS), } } } impl State for MainMenuState { fn update(&mut self, data: &mut StateData) -> Option> { if let Some(option) = self.menu.poll(&mut data.io) { match option { MainMenuItem::SendSMS => return Some(Box::new(PhoneNumberState::default())), 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 } fn draw(&self, data: &mut StateData) { data.clear_screen(Rgb565::black().as_color()); self.menu.draw(data); } } #[derive(Clone, Default)] pub struct PhoneNumberState { input: NumberInput, } impl State for PhoneNumberState { fn update(&mut self, data: &mut StateData) -> Option> { self.input.poll(&mut data.io); if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 { Some(Box::new(MessageState { number: self.input.read().clone(), input: Default::default(), })) } else { None } } fn draw(&self, data: &mut StateData) { data.clear_screen(Rgb565::black().as_color()); data.draw_text("Phone num:", Position::new(0, 0), TextSettings::default()); data.draw_text( format!("{}", self.input.read()), Position::new(0, 30), TextSettings::default(), ); } } pub struct MessageState { number: String, input: TextInput, } impl State for MessageState { fn update(&mut self, data: &mut StateData) -> Option> { if !self.input.poll(&mut data.io) { if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 { Some(Box::new(ATCommandState::with( "Sending SMS..".to_owned(), SendSMSCommand { destination: self.number.clone(), message: self.input.read().clone(), }, |resp| match resp { Ok(resp) => match resp { SendSMSResponse::MessageRefrence(refrence) => Box::new(TextState { text: format!("SMS sent\nRef: {}", refrence), after: MainMenuState::default(), }), SendSMSResponse::Error => Box::new(TextState { text: "ERROR!".to_owned(), after: MainMenuState::default(), }), SendSMSResponse::ErrorMessage(msg) => Box::new(TextState { text: format!("Error:\n{}", msg), after: MainMenuState::default(), }), }, Err(err) => Box::new(TextState { text: format!("Error:\n{:?}", err), after: MainMenuState::default(), }), }, ))) } else { None } } else { None } } fn draw(&self, data: &mut StateData) { data.clear_screen(Rgb565::black().as_color()); data.draw_text("Message:", Position::new(0, 0), TextSettings::default()); data.draw_text( format!("{}", self.input.read()), Position::new(0, 30), TextSettings::default(), ); } }