From dde828d63d42d3f43bfcc56d03a9cb491cd40f7b Mon Sep 17 00:00:00 2001 From: Sofia Date: Mon, 1 Jun 2026 19:45:36 +0300 Subject: [PATCH] Add additional ways to manage AT Commands --- src/async_io.rs | 2 +- src/at_commands.rs | 13 ++- src/state.rs | 47 +++++++- src/states.rs | 269 +++++++++++++++++++++++++++++++-------------- 4 files changed, 240 insertions(+), 91 deletions(-) diff --git a/src/async_io.rs b/src/async_io.rs index bc6eef8..5194be7 100644 --- a/src/async_io.rs +++ b/src/async_io.rs @@ -135,7 +135,7 @@ impl Default for AsyncIO { unsafe impl Send for AsyncIO {} unsafe impl Sync for AsyncIO {} -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub struct ATPromise { _data: PhantomData, } diff --git a/src/at_commands.rs b/src/at_commands.rs index 8d0f094..f353d34 100644 --- a/src/at_commands.rs +++ b/src/at_commands.rs @@ -264,7 +264,7 @@ pub trait ATResponse { fn is_error(&self) -> bool; } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum SimpleATResponse { Ok, Error, @@ -326,7 +326,7 @@ impl ATCommand for ATInformationCommand { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum EnterPinResult { Ok, Error, @@ -364,7 +364,8 @@ impl ATCommand for EnterPinCommand { Err(ATParseError::EOF) } } -#[derive(Debug)] + +#[derive(Debug, Clone)] pub enum CheckPinResult { Status(String), Error, @@ -437,7 +438,7 @@ impl ATCommand for SelectSMSFormatCommand { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum CheckSMSFormatResult { Mode(String), Error, @@ -480,7 +481,7 @@ impl ATCommand for CheckSMSFormatCommand { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TECharacterSets { Charsets(Vec), } @@ -557,7 +558,7 @@ impl ATCommand for SetTECharsetCommand { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum SendSMSResponse { MessageRefrence(String), Error, diff --git a/src/state.rs b/src/state.rs index c01f674..a91a3c2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,8 @@ use alloc::{boxed::Box, string::String, vec::Vec}; use esp_hal::{Blocking, delay::Delay}; use crate::{ - async_io::AsyncIO, + async_io::{ATPromise, AsyncIO}, + at_commands::{ATCommand, ATParseError}, display::{Color, Display, Position, Rgb565}, font::{FontRenderer, HorizontalAlignment, VerticalAlignment}, }; @@ -112,3 +113,47 @@ impl<'a> StateManager<'a> { } } } + +#[derive(Debug, Clone)] +pub struct ATCommandHelper { + command: Option, + promise: Option>, + response: Option>, +} + +impl ATCommandHelper +where + T::Response: Clone, +{ + pub fn new(cmd: T) -> ATCommandHelper { + ATCommandHelper { + command: Some(cmd), + promise: None, + response: None, + } + } + + pub fn poll(&mut self, io: &mut AsyncIO) -> Option> { + if let Some(resp) = self.response.clone() { + return Some(resp); + } else { + if let Some(promise) = &mut self.promise { + match promise.poll(io) { + Some(response) => match response { + Some(response) => { + self.response = Some(response); + self.response.clone() + } + None => None, + }, + None => None, + } + } else if let Some(cmd) = self.command.take() { + self.promise = Some(io.send_at_command(cmd).unwrap()); + None + } else { + panic!(); + } + } + } +} diff --git a/src/states.rs b/src/states.rs index 808cb34..6815595 100644 --- a/src/states.rs +++ b/src/states.rs @@ -11,46 +11,76 @@ use esp_hal::time::{Duration, Instant}; use crate::{ async_io::{ATPromise, KeypadButton}, at_commands::{ - ATCommand, ATInformationCommand, ATResponse, CheckPinCommand, EnterPinCommand, SMSFormat, - SelectSMSFormatCommand, SendSMSCommand, SetTECharsetCommand, TECharset, + ATCommand, ATInformationCommand, ATParseError, ATResponse, CheckPinCommand, CheckPinResult, + EnterPinCommand, EnterPinResult, SMSFormat, SelectSMSFormatCommand, SendSMSCommand, + SendSMSResponse, SetTECharsetCommand, SimpleATResponse, TECharset, }, display::{Position, Rgb565}, font::{HorizontalAlignment, VerticalAlignment}, - state::{State, StateData, TextSettings}, + state::{ATCommandHelper, State, StateData, TextSettings}, }; -#[derive(Clone)] -pub struct ATCommandState { +#[derive(Debug)] +pub struct DotsMessage { message: String, - command: Cmd, dots: u8, prev_dots: Instant, - promise: Option>, - after_state: T, - err_state: TErr, } -impl ATCommandState { - pub fn with( - message: String, - command: Cmd, - after: T, - err: TErr, - ) -> ATCommandState { - ATCommandState { - message, - command, - dots: 0, +impl Default for DotsMessage { + fn default() -> Self { + Self { + message: Default::default(), + dots: Default::default(), prev_dots: Instant::now(), - promise: None, - after_state: after, - err_state: err, } } } -impl - State for ATCommandState +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()); @@ -58,23 +88,13 @@ impl Option> { // Update dots - if self.prev_dots.elapsed() > Duration::from_millis(200) { - self.dots = (self.dots + 1) % 3; - self.prev_dots = Instant::now(); - } + self.message.poll(); if let Some(promise) = &self.promise { match promise.poll(&mut data.io) { Some(response) => match response { Some(response) => { - match response { - Ok(response) => match response.is_error() { - true => return Some(Box::new(self.err_state.clone())), - false => log::info!("Response: {:?}", response), - }, - Err(_) => return Some(Box::new(self.err_state.clone())), - } - self.promise = None; + return Some((self.fun.clone())(response)); } None => {} }, @@ -83,14 +103,13 @@ impl(); data.draw_text( - format!("{}{}", self.message, dots), + self.message.render(), Position::new(0, 0), TextSettings::default(), ); @@ -98,62 +117,137 @@ impl, + enter_pin: ATCommandHelper, + check_pin: ATCommandHelper, + sms_charset: ATCommandHelper, + te_charset: ATCommandHelper, + message: String, +} impl Default for InitATState { fn default() -> Self { - Self {} + Self { + ati: ATCommandHelper::new(ATInformationCommand), + enter_pin: ATCommandHelper::new(EnterPinCommand("1234".to_owned())), + check_pin: ATCommandHelper::new(CheckPinCommand), + sms_charset: ATCommandHelper::new(SelectSMSFormatCommand(SMSFormat::TextMode)), + te_charset: ATCommandHelper::new(SetTECharsetCommand(TECharset::IRA)), + message: "Initializing".to_owned(), + } } } impl State for InitATState { - fn update(&mut self, _: &mut StateData) -> Option> { - let state = ATCommandState::with( - "Checking info".to_owned(), - ATInformationCommand, - ATCommandState::with( - "Entering PIN".to_owned(), - EnterPinCommand("1234".to_owned()), - ATCommandState::with( - "Checking PIN".to_owned(), - CheckPinCommand, - ATCommandState::with( - "Selecting SMS\ncharset".to_owned(), - SelectSMSFormatCommand(SMSFormat::TextMode), - ATCommandState::with( - "Setting\nTE charset".to_owned(), - SetTECharsetCommand(TECharset::IRA), - TextState { - text: "All done!".to_owned(), - }, - InitATState::default(), - ), - InitATState::default(), - ), - InitATState::default(), - ), - InitATState::default(), - ), - InitATState::default(), - ); + fn update(&mut self, data: &mut StateData) -> Option> { + if let Some(resp) = self.ati.poll(&mut data.io) { + resp.unwrap() + } else { + self.message = "Checking ATI".to_owned(); + return None; + }; - Some(Box::new(state)) + if let Some(resp) = self.enter_pin.poll(&mut data.io) { + match resp.unwrap() { + EnterPinResult::Ok => {} + EnterPinResult::Error => { + return Some(Box::new(TextState { + text: "ERROR!".to_owned(), + after: InitATState::default(), + })); + } + EnterPinResult::ErrorMessage(msg) => { + return Some(Box::new(TextState { + text: format!("Error:\n{}", msg), + after: InitATState::default(), + })); + } + } + } else { + self.message = "Entering PIN".to_owned(); + return None; + }; + + if let Some(resp) = self.check_pin.poll(&mut data.io) { + match resp.unwrap() { + CheckPinResult::Status(status) => { + log::info!("Status: {}", status) + } + CheckPinResult::Error => { + return Some(Box::new(TextState { + text: "ERROR!".to_owned(), + after: InitATState::default(), + })); + } + CheckPinResult::ErrorMessage(msg) => { + return Some(Box::new(TextState { + text: format!("Error:\n{}", msg), + after: InitATState::default(), + })); + } + } + } else { + self.message = "Checking PIN".to_owned(); + return None; + }; + + if let Some(resp) = self.sms_charset.poll(&mut data.io) { + match resp.unwrap() { + SimpleATResponse::Ok => {} + SimpleATResponse::Error => { + return Some(Box::new(TextState { + text: "ERROR!".to_owned(), + after: InitATState::default(), + })); + } + } + } else { + self.message = "Selecting SMS\ncharset".to_owned(); + return None; + }; + + if let Some(resp) = self.te_charset.poll(&mut data.io) { + match resp.unwrap() { + SimpleATResponse::Ok => {} + SimpleATResponse::Error => { + return Some(Box::new(TextState { + text: "ERROR!".to_owned(), + after: InitATState::default(), + })); + } + } + } else { + self.message = "Selecting TE\ncharset".to_owned(); + return None; + }; + + Some(Box::new(TextState { + text: "All done!".to_owned(), + after: PhoneNumberState::default(), + })) } - fn draw(&self, _: &mut StateData) {} + fn draw(&self, data: &mut StateData) { + data.clear_screen(Rgb565::black().as_color()); + data.draw_text( + format!("{}", self.message), + Position::new(0, 0), + TextSettings::default(), + ); + } } #[derive(Clone)] -pub struct TextState { +pub struct TextState { text: String, + after: T, } -impl State for TextState { +impl State for TextState { fn update(&mut self, data: &mut StateData) -> Option> { if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 { - Some(Box::new(PhoneNumberState { - written: String::new(), - })) + Some(Box::new(self.after.clone())) } else { None } @@ -174,6 +268,7 @@ impl State for TextState { } } +#[derive(Clone, Default)] pub struct PhoneNumberState { written: String, } @@ -256,11 +351,19 @@ impl State for MessageState { destination: self.number.clone(), message: self.written.clone(), }, - TextState { - text: "SMS Sent!".to_owned(), - }, - TextState { - text: "Error!".to_owned(), + |resp| match resp.unwrap() { + SendSMSResponse::MessageRefrence(refrence) => Box::new(TextState { + text: format!("SMS sent\nRef: {}", refrence), + after: PhoneNumberState::default(), + }), + SendSMSResponse::Error => Box::new(TextState { + text: "ERROR!".to_owned(), + after: PhoneNumberState::default(), + }), + SendSMSResponse::ErrorMessage(msg) => Box::new(TextState { + text: format!("Error:\n{}", msg), + after: PhoneNumberState::default(), + }), }, ))) } else {