From 7e4b2e31621f73b6e5328f0a5eeb5e1d71e25473 Mon Sep 17 00:00:00 2001 From: Sofia Date: Thu, 28 May 2026 22:58:17 +0300 Subject: [PATCH] Use structs instead of enums for AT commands --- src/async_io.rs | 86 ++++++++++++--------------- src/at_commands.rs | 144 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 22 +------ src/states.rs | 19 +++--- 4 files changed, 195 insertions(+), 76 deletions(-) diff --git a/src/async_io.rs b/src/async_io.rs index 8a7007e..3f3c8ec 100644 --- a/src/async_io.rs +++ b/src/async_io.rs @@ -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; -#[derive(Clone)] -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(), - } - } -} +use crate::at_commands::ATCommand; #[derive(Clone)] 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)] pub struct AsyncIO { - at_command: Rc>>>, + at_command: Rc>>>, at_response: Rc>>>, pub button: Button, } @@ -112,21 +82,41 @@ impl Default for AsyncIO { unsafe impl Send for AsyncIO {} unsafe impl Sync for AsyncIO {} +#[derive(Default)] +pub struct ATPromise { + _data: PhantomData, +} + +impl ATPromise { + pub fn poll(&self, io: &mut AsyncIO) -> Option> { + 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 { - pub fn send_at_command(&self, command: ATCommand) -> Result<(), ()> { + pub fn send_at_command>( + &self, + command: T, + ) -> Result<(), ()> { critical_section::with(|cs| { let mut borrow = self.at_command.borrow_ref_mut(cs); if borrow.is_some() { return Err(()); } - *borrow = Some(command); + *borrow = Some(command.execute()); Ok(()) }) } - pub unsafe fn check_at_command(&self) -> Option { + pub unsafe fn check_at_command(&self) -> Option { critical_section::with(|cs| { let borrow = self.at_command.borrow_ref(cs); borrow.clone() diff --git a/src/at_commands.rs b/src/at_commands.rs index 32c2f08..e12416b 100644 --- a/src/at_commands.rs +++ b/src/at_commands.rs @@ -4,6 +4,8 @@ use alloc::{borrow::ToOwned, format}; use core::fmt::Write; use esp_hal::{Blocking, delay::Delay, gpio::Output, uart::Uart}; +use crate::async_io::ConstructedATCommand; + static RESPONSES: [&'static str; 3] = ["OK", "ERROR", "DOWNLOAD"]; 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 + } +} diff --git a/src/main.rs b/src/main.rs index fd71fcf..b29ec66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -181,25 +181,9 @@ fn thread_2_main(async_io: AsyncIO, mut at_commands: ATCommands<'static, 'static loop { if let Some(command) = unsafe { async_io.check_at_command() } { let response = match command { - async_io::ATCommand::ATInformation => at_commands.raw_command("ATI".to_string()), - async_io::ATCommand::EnterPin(pin) => { - at_commands.raw_command(format!("AT+CPIN={}", pin)) - } - 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) + async_io::ConstructedATCommand::Single(cmd) => at_commands.raw_command(cmd), + async_io::ConstructedATCommand::AddInfo(cmd, add) => { + at_commands.raw_two_part_command(cmd, add) } }; unsafe { async_io.set_at_response(response) }; diff --git a/src/states.rs b/src/states.rs index eb72b24..55a1b88 100644 --- a/src/states.rs +++ b/src/states.rs @@ -9,7 +9,10 @@ use alloc::{ use esp_hal::time::{Duration, Instant}; use crate::{ - async_io::{ATCommand, Charset, SMSFormat}, + at_commands::{ + ATInformationCommand, CheckPinCommand, EnterPinCommand, ListTECharacterSetsCommand, + SMSFormat, SelectSMSFormatCommand, SendSMSCommand, SetTECharsetCommand, TECharset, + }, display::{Position, Rgb565}, font::{HorizontalAlignment, VerticalAlignment}, state::{State, StateData, TextSettings}, @@ -55,38 +58,36 @@ impl State for InitATState { // Send next AT command let res: Option> = match self.inner_state { 0 => { - data.io.send_at_command(ATCommand::ATInformation).unwrap(); + data.io.send_at_command(ATInformationCommand).unwrap(); self.message = "Checking info".to_owned(); None } 1 => { data.io - .send_at_command(ATCommand::EnterPin("1234".to_owned())) + .send_at_command(EnterPinCommand("1234".to_owned())) .unwrap(); self.message = "Entering PIN".to_owned(); None } 2 => { - data.io.send_at_command(ATCommand::CheckPin).unwrap(); + data.io.send_at_command(CheckPinCommand).unwrap(); None } 3 => { - data.io - .send_at_command(ATCommand::ListTECharacterSets) - .unwrap(); + data.io.send_at_command(ListTECharacterSetsCommand).unwrap(); self.message = "Checking\ncharsets".to_owned(); None } 4 => { data.io - .send_at_command(ATCommand::SelectSMSFormat(SMSFormat::TextMode)) + .send_at_command(SelectSMSFormatCommand(SMSFormat::TextMode)) .unwrap(); self.message = "Selecting SMS\nformat".to_owned(); None } 5 => { data.io - .send_at_command(ATCommand::SetTECharSet(Charset::IRA)) + .send_at_command(SetTECharsetCommand(TECharset::IRA)) .unwrap(); self.message = "Selecting SMS\nformat".to_owned(); None