Add additional ways to manage AT Commands
This commit is contained in:
parent
c1241798fe
commit
dde828d63d
@ -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<T: ATCommand> {
|
||||
_data: PhantomData<T>,
|
||||
}
|
||||
|
||||
@ -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<String>),
|
||||
}
|
||||
@ -557,7 +558,7 @@ impl ATCommand for SetTECharsetCommand {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SendSMSResponse {
|
||||
MessageRefrence(String),
|
||||
Error,
|
||||
|
||||
47
src/state.rs
47
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<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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
269
src/states.rs
269
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<T: State, TErr: State, Cmd: ATCommand> {
|
||||
#[derive(Debug)]
|
||||
pub struct DotsMessage {
|
||||
message: String,
|
||||
command: Cmd,
|
||||
dots: u8,
|
||||
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> {
|
||||
pub fn with(
|
||||
message: String,
|
||||
command: Cmd,
|
||||
after: T,
|
||||
err: TErr,
|
||||
) -> ATCommandState<T, TErr, Cmd> {
|
||||
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<T: State + Clone + 'static, TErr: State + Clone + 'static, Cmd: ATCommand + Clone + 'static>
|
||||
State for ATCommandState<T, TErr, Cmd>
|
||||
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::<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) {
|
||||
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>> {
|
||||
// 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<T: State + Clone + 'static, TErr: State + Clone + 'static, Cmd: ATCommand +
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(Box::new(self.after_state.clone()));
|
||||
None
|
||||
}
|
||||
|
||||
fn draw(&self, data: &mut StateData) {
|
||||
data.clear_screen(Rgb565::black().as_color());
|
||||
let dots = repeat(".").take(self.dots as usize).collect::<String>();
|
||||
data.draw_text(
|
||||
format!("{}{}", self.message, dots),
|
||||
self.message.render(),
|
||||
Position::new(0, 0),
|
||||
TextSettings::default(),
|
||||
);
|
||||
@ -98,62 +117,137 @@ impl<T: State + Clone + 'static, TErr: State + Clone + 'static, Cmd: ATCommand +
|
||||
}
|
||||
|
||||
#[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 {
|
||||
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<Box<dyn State>> {
|
||||
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<Box<dyn State>> {
|
||||
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<T: State + Clone + 'static> {
|
||||
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>> {
|
||||
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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user