Add additional ways to manage AT Commands

This commit is contained in:
Sofia 2026-06-01 19:45:36 +03:00
parent c1241798fe
commit dde828d63d
4 changed files with 240 additions and 91 deletions

View File

@ -135,7 +135,7 @@ impl Default for AsyncIO {
unsafe impl Send for AsyncIO {} unsafe impl Send for AsyncIO {}
unsafe impl Sync for AsyncIO {} unsafe impl Sync for AsyncIO {}
#[derive(Default, Clone)] #[derive(Default, Clone, Debug)]
pub struct ATPromise<T: ATCommand> { pub struct ATPromise<T: ATCommand> {
_data: PhantomData<T>, _data: PhantomData<T>,
} }

View File

@ -264,7 +264,7 @@ pub trait ATResponse {
fn is_error(&self) -> bool; fn is_error(&self) -> bool;
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum SimpleATResponse { pub enum SimpleATResponse {
Ok, Ok,
Error, Error,
@ -326,7 +326,7 @@ impl ATCommand for ATInformationCommand {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum EnterPinResult { pub enum EnterPinResult {
Ok, Ok,
Error, Error,
@ -364,7 +364,8 @@ impl ATCommand for EnterPinCommand {
Err(ATParseError::EOF) Err(ATParseError::EOF)
} }
} }
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum CheckPinResult { pub enum CheckPinResult {
Status(String), Status(String),
Error, Error,
@ -437,7 +438,7 @@ impl ATCommand for SelectSMSFormatCommand {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum CheckSMSFormatResult { pub enum CheckSMSFormatResult {
Mode(String), Mode(String),
Error, Error,
@ -480,7 +481,7 @@ impl ATCommand for CheckSMSFormatCommand {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum TECharacterSets { pub enum TECharacterSets {
Charsets(Vec<String>), Charsets(Vec<String>),
} }
@ -557,7 +558,7 @@ impl ATCommand for SetTECharsetCommand {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum SendSMSResponse { pub enum SendSMSResponse {
MessageRefrence(String), MessageRefrence(String),
Error, Error,

View File

@ -2,7 +2,8 @@ use alloc::{boxed::Box, string::String, vec::Vec};
use esp_hal::{Blocking, delay::Delay}; use esp_hal::{Blocking, delay::Delay};
use crate::{ use crate::{
async_io::AsyncIO, async_io::{ATPromise, AsyncIO},
at_commands::{ATCommand, ATParseError},
display::{Color, Display, Position, Rgb565}, display::{Color, Display, Position, Rgb565},
font::{FontRenderer, HorizontalAlignment, VerticalAlignment}, font::{FontRenderer, HorizontalAlignment, VerticalAlignment},
}; };
@ -112,3 +113,47 @@ impl<'a> StateManager<'a> {
} }
} }
} }
#[derive(Debug, Clone)]
pub struct ATCommandHelper<T: ATCommand> {
command: Option<T>,
promise: Option<ATPromise<T>>,
response: Option<Result<T::Response, ATParseError>>,
}
impl<T: ATCommand + 'static> ATCommandHelper<T>
where
T::Response: Clone,
{
pub fn new(cmd: T) -> ATCommandHelper<T> {
ATCommandHelper {
command: Some(cmd),
promise: None,
response: None,
}
}
pub fn poll(&mut self, io: &mut AsyncIO) -> Option<Result<T::Response, ATParseError>> {
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!();
}
}
}
}

View File

@ -11,46 +11,76 @@ use esp_hal::time::{Duration, Instant};
use crate::{ use crate::{
async_io::{ATPromise, KeypadButton}, async_io::{ATPromise, KeypadButton},
at_commands::{ at_commands::{
ATCommand, ATInformationCommand, ATResponse, CheckPinCommand, EnterPinCommand, SMSFormat, ATCommand, ATInformationCommand, ATParseError, ATResponse, CheckPinCommand, CheckPinResult,
SelectSMSFormatCommand, SendSMSCommand, SetTECharsetCommand, TECharset, EnterPinCommand, EnterPinResult, SMSFormat, SelectSMSFormatCommand, SendSMSCommand,
SendSMSResponse, SetTECharsetCommand, SimpleATResponse, TECharset,
}, },
display::{Position, Rgb565}, display::{Position, Rgb565},
font::{HorizontalAlignment, VerticalAlignment}, font::{HorizontalAlignment, VerticalAlignment},
state::{State, StateData, TextSettings}, state::{ATCommandHelper, State, StateData, TextSettings},
}; };
#[derive(Clone)] #[derive(Debug)]
pub struct ATCommandState<T: State, TErr: State, Cmd: ATCommand> { pub struct DotsMessage {
message: String, message: String,
command: Cmd,
dots: u8, dots: u8,
prev_dots: Instant, prev_dots: Instant,
promise: Option<ATPromise<Cmd>>,
after_state: T,
err_state: TErr,
} }
impl<T: State + Clone, TErr: State + Clone, Cmd: ATCommand> ATCommandState<T, TErr, Cmd> { impl Default for DotsMessage {
pub fn with( fn default() -> Self {
message: String, Self {
command: Cmd, message: Default::default(),
after: T, dots: Default::default(),
err: TErr,
) -> ATCommandState<T, TErr, Cmd> {
ATCommandState {
message,
command,
dots: 0,
prev_dots: Instant::now(), prev_dots: Instant::now(),
promise: None,
after_state: after,
err_state: err,
} }
} }
} }
impl<T: State + Clone + 'static, TErr: State + Clone + 'static, Cmd: ATCommand + Clone + 'static> impl DotsMessage {
State for ATCommandState<T, TErr, Cmd> 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::<String>();
format!("{}{}", self.message, dots)
}
}
pub struct ATCommandState<Cmd: ATCommand, F>
where
F: FnOnce(Result<Cmd::Response, ATParseError>) -> Box<dyn State>,
{
message: DotsMessage,
command: Cmd,
promise: Option<ATPromise<Cmd>>,
fun: F,
}
impl<Cmd: ATCommand, F> ATCommandState<Cmd, F>
where
F: FnOnce(Result<Cmd::Response, ATParseError>) -> Box<dyn State>,
{
pub fn with(message: String, command: Cmd, after: F) -> ATCommandState<Cmd, F> {
ATCommandState {
message: DotsMessage {
message,
..DotsMessage::default()
},
command,
promise: None,
fun: after,
}
}
}
impl<Cmd: ATCommand + Clone + 'static, F: Clone> State for ATCommandState<Cmd, F>
where
F: FnOnce(Result<Cmd::Response, ATParseError>) -> Box<dyn State>,
{ {
fn init(&mut self, data: &mut StateData) { fn init(&mut self, data: &mut StateData) {
self.promise = Some(data.io.send_at_command(self.command.clone()).unwrap()); self.promise = Some(data.io.send_at_command(self.command.clone()).unwrap());
@ -58,23 +88,13 @@ impl<T: State + Clone + 'static, TErr: State + Clone + 'static, Cmd: ATCommand +
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> { fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
// Update dots // Update dots
if self.prev_dots.elapsed() > Duration::from_millis(200) { self.message.poll();
self.dots = (self.dots + 1) % 3;
self.prev_dots = Instant::now();
}
if let Some(promise) = &self.promise { if let Some(promise) = &self.promise {
match promise.poll(&mut data.io) { match promise.poll(&mut data.io) {
Some(response) => match response { Some(response) => match response {
Some(response) => { Some(response) => {
match response { return Some((self.fun.clone())(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;
} }
None => {} None => {}
}, },
@ -83,14 +103,13 @@ impl<T: State + Clone + 'static, TErr: State + Clone + 'static, Cmd: ATCommand +
return None; return None;
} }
return Some(Box::new(self.after_state.clone())); None
} }
fn draw(&self, data: &mut StateData) { fn draw(&self, data: &mut StateData) {
data.clear_screen(Rgb565::black().as_color()); data.clear_screen(Rgb565::black().as_color());
let dots = repeat(".").take(self.dots as usize).collect::<String>();
data.draw_text( data.draw_text(
format!("{}{}", self.message, dots), self.message.render(),
Position::new(0, 0), Position::new(0, 0),
TextSettings::default(), TextSettings::default(),
); );
@ -98,62 +117,137 @@ impl<T: State + Clone + 'static, TErr: State + Clone + 'static, Cmd: ATCommand +
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct InitATState; pub struct InitATState {
ati: ATCommandHelper<ATInformationCommand>,
enter_pin: ATCommandHelper<EnterPinCommand>,
check_pin: ATCommandHelper<CheckPinCommand>,
sms_charset: ATCommandHelper<SelectSMSFormatCommand>,
te_charset: ATCommandHelper<SetTECharsetCommand>,
message: String,
}
impl Default for InitATState { impl Default for InitATState {
fn default() -> Self { 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 { impl State for InitATState {
fn update(&mut self, _: &mut StateData) -> Option<Box<dyn State>> { fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
let state = ATCommandState::with( if let Some(resp) = self.ati.poll(&mut data.io) {
"Checking info".to_owned(), resp.unwrap()
ATInformationCommand, } else {
ATCommandState::with( self.message = "Checking ATI".to_owned();
"Entering PIN".to_owned(), return None;
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(),
);
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)] #[derive(Clone)]
pub struct TextState { pub struct TextState<T: State + Clone + 'static> {
text: String, text: String,
after: T,
} }
impl State for TextState { impl<T: State + Clone> State for TextState<T> {
fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> { fn update(&mut self, data: &mut StateData) -> Option<Box<dyn State>> {
if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 { if data.io.keypad.get_presses(KeypadButton::KeypadA) > 0 {
Some(Box::new(PhoneNumberState { Some(Box::new(self.after.clone()))
written: String::new(),
}))
} else { } else {
None None
} }
@ -174,6 +268,7 @@ impl State for TextState {
} }
} }
#[derive(Clone, Default)]
pub struct PhoneNumberState { pub struct PhoneNumberState {
written: String, written: String,
} }
@ -256,11 +351,19 @@ impl State for MessageState {
destination: self.number.clone(), destination: self.number.clone(),
message: self.written.clone(), message: self.written.clone(),
}, },
TextState { |resp| match resp.unwrap() {
text: "SMS Sent!".to_owned(), SendSMSResponse::MessageRefrence(refrence) => Box::new(TextState {
}, text: format!("SMS sent\nRef: {}", refrence),
TextState { after: PhoneNumberState::default(),
text: "Error!".to_owned(), }),
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 { } else {