Use structs instead of enums for AT commands

This commit is contained in:
Sofia 2026-05-28 22:58:17 +03:00
parent ea70f50e4a
commit 7e4b2e3162
4 changed files with 195 additions and 76 deletions

View File

@ -1,50 +1,9 @@
use core::cell::RefCell; use core::{cell::RefCell, marker::PhantomData};
use alloc::{borrow::ToOwned, rc::Rc, string::String}; use alloc::{rc::Rc, string::String};
use critical_section::Mutex; use critical_section::Mutex;
#[derive(Clone)] use crate::at_commands::ATCommand;
pub enum ATCommand {
/// ATI
ATInformation,
/// AT+CPIN=num
EnterPin(String),
/// AT+CPIN?
CheckPin,
/// AT+CMGF=num
SelectSMSFormat(SMSFormat),
/// AT+CMGF?
CheckSMSFormat,
/// AT+CSCS=?
ListTECharacterSets,
SetTECharSet(Charset),
SendSMS(String, String),
}
#[derive(Clone)]
pub enum SMSFormat {
PDUMode = 0,
TextMode = 1,
}
#[derive(Clone)]
pub enum Charset {
IRA,
UCS2,
HEX,
GSM,
}
impl Charset {
pub fn into_str(&self) -> String {
match self {
Charset::IRA => "IRA".to_owned(),
Charset::UCS2 => "UCS2".to_owned(),
Charset::HEX => "HEX".to_owned(),
Charset::GSM => "GSM".to_owned(),
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct Button { pub struct Button {
@ -92,9 +51,20 @@ impl Button {
} }
} }
#[derive(Debug, Clone)]
pub enum ConstructedATCommand {
/// Single-part command
Single(String),
/// Two-part AT-command
AddInfo(String, String),
}
unsafe impl Send for ConstructedATCommand {}
unsafe impl Sync for ConstructedATCommand {}
#[derive(Clone)] #[derive(Clone)]
pub struct AsyncIO { pub struct AsyncIO {
at_command: Rc<Mutex<RefCell<Option<ATCommand>>>>, at_command: Rc<Mutex<RefCell<Option<ConstructedATCommand>>>>,
at_response: Rc<Mutex<RefCell<Option<String>>>>, at_response: Rc<Mutex<RefCell<Option<String>>>>,
pub button: Button, pub button: Button,
} }
@ -112,21 +82,41 @@ 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)]
pub struct ATPromise<T: ATCommand> {
_data: PhantomData<T>,
}
impl<T: ATCommand> ATPromise<T> {
pub fn poll(&self, io: &mut AsyncIO) -> Option<Option<T::Response>> {
match io.poll_at_response() {
Some(response) => match response {
Some(response) => Some(Some(T::parse_response(response))),
None => Some(None),
},
None => Some(None),
}
}
}
impl AsyncIO { impl AsyncIO {
pub fn send_at_command(&self, command: ATCommand) -> Result<(), ()> { pub fn send_at_command<Resp, T: ATCommand<Response = Resp>>(
&self,
command: T,
) -> Result<(), ()> {
critical_section::with(|cs| { critical_section::with(|cs| {
let mut borrow = self.at_command.borrow_ref_mut(cs); let mut borrow = self.at_command.borrow_ref_mut(cs);
if borrow.is_some() { if borrow.is_some() {
return Err(()); return Err(());
} }
*borrow = Some(command); *borrow = Some(command.execute());
Ok(()) Ok(())
}) })
} }
pub unsafe fn check_at_command(&self) -> Option<ATCommand> { pub unsafe fn check_at_command(&self) -> Option<ConstructedATCommand> {
critical_section::with(|cs| { critical_section::with(|cs| {
let borrow = self.at_command.borrow_ref(cs); let borrow = self.at_command.borrow_ref(cs);
borrow.clone() borrow.clone()

View File

@ -4,6 +4,8 @@ use alloc::{borrow::ToOwned, format};
use core::fmt::Write; use core::fmt::Write;
use esp_hal::{Blocking, delay::Delay, gpio::Output, uart::Uart}; use esp_hal::{Blocking, delay::Delay, gpio::Output, uart::Uart};
use crate::async_io::ConstructedATCommand;
static RESPONSES: [&'static str; 3] = ["OK", "ERROR", "DOWNLOAD"]; static RESPONSES: [&'static str; 3] = ["OK", "ERROR", "DOWNLOAD"];
pub struct ATCommands<'a, 'd> { pub struct ATCommands<'a, 'd> {
@ -155,3 +157,145 @@ impl<'a, 'd> ATCommands<'a, 'd> {
} }
} }
} }
pub trait ATCommand: Send + Sync {
type Response;
fn execute(&self) -> ConstructedATCommand;
fn parse_response(text: String) -> Self::Response;
}
pub struct ATInformationCommand;
impl ATCommand for ATInformationCommand {
type Response = String;
fn execute(&self) -> ConstructedATCommand {
ConstructedATCommand::Single("ATI".to_string())
}
fn parse_response(text: String) -> Self::Response {
text
}
}
pub struct EnterPinCommand(pub String);
impl ATCommand for EnterPinCommand {
type Response = String;
fn execute(&self) -> ConstructedATCommand {
ConstructedATCommand::Single(format!("AT+CPIN={}", self.0))
}
fn parse_response(text: String) -> Self::Response {
text
}
}
pub struct CheckPinCommand;
impl ATCommand for CheckPinCommand {
type Response = String;
fn execute(&self) -> ConstructedATCommand {
ConstructedATCommand::Single("AT+CPIN?".to_string())
}
fn parse_response(text: String) -> Self::Response {
text
}
}
#[derive(Clone, Copy)]
pub enum SMSFormat {
PDUMode = 0,
TextMode = 1,
}
pub struct SelectSMSFormatCommand(pub SMSFormat);
impl ATCommand for SelectSMSFormatCommand {
type Response = String;
fn execute(&self) -> ConstructedATCommand {
ConstructedATCommand::Single(format!("AT+CMGF={}", self.0 as u8))
}
fn parse_response(text: String) -> Self::Response {
text
}
}
pub struct CheckSMSFormatCommand;
impl ATCommand for CheckSMSFormatCommand {
type Response = String;
fn execute(&self) -> ConstructedATCommand {
ConstructedATCommand::Single("AT+CMGF?".to_string())
}
fn parse_response(text: String) -> Self::Response {
text
}
}
pub struct ListTECharacterSetsCommand;
impl ATCommand for ListTECharacterSetsCommand {
type Response = String;
fn execute(&self) -> ConstructedATCommand {
ConstructedATCommand::Single("AT+CSCS=?".to_string())
}
fn parse_response(text: String) -> Self::Response {
text
}
}
#[derive(Clone)]
pub enum TECharset {
IRA,
UCS2,
HEX,
GSM,
}
impl TECharset {
pub fn into_str(&self) -> String {
match self {
TECharset::IRA => "IRA".to_owned(),
TECharset::UCS2 => "UCS2".to_owned(),
TECharset::HEX => "HEX".to_owned(),
TECharset::GSM => "GSM".to_owned(),
}
}
}
pub struct SetTECharsetCommand(pub TECharset);
impl ATCommand for SetTECharsetCommand {
type Response = String;
fn execute(&self) -> ConstructedATCommand {
ConstructedATCommand::Single(format!("AT+CSCS=\"{}\"", self.0.into_str()))
}
fn parse_response(text: String) -> Self::Response {
text
}
}
pub struct SendSMSCommand {
pub destination: String,
pub message: String,
}
impl ATCommand for SendSMSCommand {
type Response = String;
fn execute(&self) -> ConstructedATCommand {
ConstructedATCommand::AddInfo(
format!("AT+CMGS={}", self.destination),
self.message.clone(),
)
}
fn parse_response(text: String) -> Self::Response {
text
}
}

View File

@ -181,25 +181,9 @@ fn thread_2_main(async_io: AsyncIO, mut at_commands: ATCommands<'static, 'static
loop { loop {
if let Some(command) = unsafe { async_io.check_at_command() } { if let Some(command) = unsafe { async_io.check_at_command() } {
let response = match command { let response = match command {
async_io::ATCommand::ATInformation => at_commands.raw_command("ATI".to_string()), async_io::ConstructedATCommand::Single(cmd) => at_commands.raw_command(cmd),
async_io::ATCommand::EnterPin(pin) => { async_io::ConstructedATCommand::AddInfo(cmd, add) => {
at_commands.raw_command(format!("AT+CPIN={}", pin)) at_commands.raw_two_part_command(cmd, add)
}
async_io::ATCommand::CheckPin => at_commands.raw_command("AT+CPIN?".to_string()),
async_io::ATCommand::SelectSMSFormat(smsformat) => {
at_commands.raw_command(format!("AT+CMGF={}", smsformat as u8))
}
async_io::ATCommand::CheckSMSFormat => {
at_commands.raw_command("AT+CMGF?".to_string())
}
async_io::ATCommand::ListTECharacterSets => {
at_commands.raw_command("AT+CSCS=?".to_string())
}
async_io::ATCommand::SetTECharSet(charset) => {
at_commands.raw_command(format!("AT+CSCS=\"{}\"", charset.into_str()))
}
async_io::ATCommand::SendSMS(da, text) => {
at_commands.raw_two_part_command(format!("AT+CMGS={}", da), text)
} }
}; };
unsafe { async_io.set_at_response(response) }; unsafe { async_io.set_at_response(response) };

View File

@ -9,7 +9,10 @@ use alloc::{
use esp_hal::time::{Duration, Instant}; use esp_hal::time::{Duration, Instant};
use crate::{ use crate::{
async_io::{ATCommand, Charset, SMSFormat}, at_commands::{
ATInformationCommand, CheckPinCommand, EnterPinCommand, ListTECharacterSetsCommand,
SMSFormat, SelectSMSFormatCommand, SendSMSCommand, SetTECharsetCommand, TECharset,
},
display::{Position, Rgb565}, display::{Position, Rgb565},
font::{HorizontalAlignment, VerticalAlignment}, font::{HorizontalAlignment, VerticalAlignment},
state::{State, StateData, TextSettings}, state::{State, StateData, TextSettings},
@ -55,38 +58,36 @@ impl State for InitATState {
// Send next AT command // Send next AT command
let res: Option<Box<dyn State>> = match self.inner_state { let res: Option<Box<dyn State>> = match self.inner_state {
0 => { 0 => {
data.io.send_at_command(ATCommand::ATInformation).unwrap(); data.io.send_at_command(ATInformationCommand).unwrap();
self.message = "Checking info".to_owned(); self.message = "Checking info".to_owned();
None None
} }
1 => { 1 => {
data.io data.io
.send_at_command(ATCommand::EnterPin("1234".to_owned())) .send_at_command(EnterPinCommand("1234".to_owned()))
.unwrap(); .unwrap();
self.message = "Entering PIN".to_owned(); self.message = "Entering PIN".to_owned();
None None
} }
2 => { 2 => {
data.io.send_at_command(ATCommand::CheckPin).unwrap(); data.io.send_at_command(CheckPinCommand).unwrap();
None None
} }
3 => { 3 => {
data.io data.io.send_at_command(ListTECharacterSetsCommand).unwrap();
.send_at_command(ATCommand::ListTECharacterSets)
.unwrap();
self.message = "Checking\ncharsets".to_owned(); self.message = "Checking\ncharsets".to_owned();
None None
} }
4 => { 4 => {
data.io data.io
.send_at_command(ATCommand::SelectSMSFormat(SMSFormat::TextMode)) .send_at_command(SelectSMSFormatCommand(SMSFormat::TextMode))
.unwrap(); .unwrap();
self.message = "Selecting SMS\nformat".to_owned(); self.message = "Selecting SMS\nformat".to_owned();
None None
} }
5 => { 5 => {
data.io data.io
.send_at_command(ATCommand::SetTECharSet(Charset::IRA)) .send_at_command(SetTECharsetCommand(TECharset::IRA))
.unwrap(); .unwrap();
self.message = "Selecting SMS\nformat".to_owned(); self.message = "Selecting SMS\nformat".to_owned();
None None