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 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>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
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 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!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
269
src/states.rs
269
src/states.rs
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user