use crate::{
SimX00X,
at_commands::{
BEGIN_SEND_MESSAGE, BEGIN_SEND_USSD, CHECK_REGISTERED, CTRL_Z, DISABLE_ECHO, ENABLE_CLI,
ENABLE_CME_ERROR_VERBOSE, ENABLE_CREG, ENTER_PIN, GET_SIGNAL_STRENGTH, LIST_CURRENT_CALLS,
SET_TEXT_MODE, SETUP_OUTGOING_CALL,
},
call_state::CallState,
errors::SimError,
log::{debug, info, warning},
};
#[derive(PartialEq, Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
StateIdle,
StatePoweringDown,
StatePoweredDown,
StateInit,
StateCheckPin,
StateSetupCmgf,
StateSetupClip,
StateCreg,
StateCregWait,
StateCsq,
StateCsqResponse,
StateSendingSms1,
StateSendingSms2,
StateSendingSms3,
StateCheckSms,
StateParseSmsResponse,
StateReceiveSms,
StateSmsReceived,
StateDisableEcho,
StateDialing1,
StateDialing2,
StateParseClip,
StateAtaSent,
StateCheckCall,
StateSendUssd1,
StateSendUssd2,
StateCheckUssd,
StateReceivedUssd,
}
pub(crate) fn parse_cmd<
'a,
U: embedded_io::Write + embedded_io::WriteReady + embedded_io::Read + embedded_io::ReadReady,
const QUEUE_LEN: usize,
>(
module: &mut SimX00X<'a, U, QUEUE_LEN>,
) -> Result<(), SimError<U>> {
let msg = unsafe { str::from_utf8_unchecked(&module.uart_buffer[..module.uart_buffer_idx]) };
let uart = unsafe { &mut *module.uart.get() };
if msg.is_empty() {
return Ok(());
}
if module.state == State::StatePoweringDown {
if msg.contains("NORMAL POWER DOWN") {
info!("[Sim800L] Powered down.");
module.state = State::StatePoweredDown;
if let Some(f) = module.config.module_powered_down_callback.as_mut() {
f();
}
}
return Ok(());
}
debug!("[Sim800L] PARSING MSG: {} - {:?}", msg, module.state);
if module.state != State::StateReceiveSms {
if msg == "RING" {
module.state = State::StateParseClip;
module.expect_ack = false;
} else if msg == "NO CARRIER" {
if module.call_state != CallState::Disconnect {
module.call_state = CallState::Disconnect;
if let Some(f) = module.config.call_disconnected_callback.as_mut() {
f();
}
}
}
}
if msg == "ERROR" {
let err = SimError::AtError(module.state);
module.state = State::StateIdle;
return Err(err);
}
let ok = msg == "OK";
if module.expect_ack {
module.expect_ack = false;
if !ok {
if module.state == State::StateSetupCmgf && msg == "AT" {
module.state = State::StateDisableEcho;
module.expect_ack = true;
} else {
module.state = State::StateIdle; return Ok(());
}
}
} else if ok
&& (module.state != State::StateParseSmsResponse
&& module.state != State::StateCheckCall
&& module.state != State::StateReceiveSms
&& module.state != State::StateDialing2)
{
warning!("Received unexpected OK. Ignoring");
module.expect_ack = false;
return Ok(());
}
match module.state {
State::StateInit => {
let message_available = msg.contains("+CMTI:");
if !message_available {
if msg == "RING" {
module.state = State::StateParseClip;
} else if msg == "NO CARRIER" {
if module.call_state != CallState::Disconnect {
module.call_state = CallState::Disconnect;
if let Some(f) = module.config.call_disconnected_callback.as_mut() {
f();
}
}
} else if msg.contains("+CUSD:") {
module.state = State::StateCheckUssd;
} else {
warning!("[Sim800L] Unhandled: {} - {:?}", msg, module.state);
}
} else {
module.check_sms()?;
}
}
State::StateCheckPin => {
if msg.contains("+CPIN:") {
if msg.contains("READY") {
module.pin_locked = false;
module.state = State::StateInit;
debug!("[Sim800L] SIM unlocked.");
} else if msg.contains("SIM PIN") {
if let Some(pin) = module.config.sim_card_pin {
module.push_cmd(ENTER_PIN)?;
module.push_cmd(pin)?;
module.push_cmd("\"")?;
module.confirm_cmd()?;
} else {
warning!("[Sim800L] PIN needed!");
return Err(SimError::PinNeeded);
}
}
return Ok(());
}
}
State::StateSetupCmgf => {
module.send_cmd(SET_TEXT_MODE)?;
module.state = State::StateSetupClip;
module.expect_ack = true;
}
State::StateSetupClip => {
module.send_cmd(ENABLE_CLI)?;
module.state = State::StateCreg;
module.expect_ack = true;
}
State::StateCreg => {
module.send_cmd(CHECK_REGISTERED)?;
module.state = State::StateCregWait;
}
State::StateCregWait => {
if let Some(m) = msg.strip_prefix("+CREG: ") {
let mut split = m.split(',');
let (en_status, conn_status) = (split.next(), split.next());
let registered = conn_status.is_some_and(|n| n == "1" || n == "5");
if registered {
if !module.registered {
debug!("[Sim800L] Registered OK");
}
module.state = State::StateCsq;
module.expect_ack = true;
} else {
warning!("[Sim800L] Registration Fail");
if en_status.is_some_and(|n| n == "0") {
module.send_cmd(ENABLE_CREG)?;
module.expect_ack = true;
module.state = State::StateSetupCmgf;
} else {
debug!("Keep waiting registration...");
module.state = State::StateInit;
}
}
module.registered = registered;
} else {
debug!("Incorrect +CREG response. Keep waiting registration...");
module.state = State::StateInit;
}
}
State::StateCsq => {
module.send_cmd(GET_SIGNAL_STRENGTH)?;
module.state = State::StateCsqResponse;
}
State::StateCsqResponse => {
if let Some(m) = msg.strip_prefix("+CSQ: ") {
let val = m.split(',').next();
if let Some(_val) = val {
debug!("[Sim800L] RSSI: {}", _val);
}
}
module.expect_ack = true;
module.state = State::StateCheckSms;
}
State::StateSendingSms1 => {
module.push_cmd(BEGIN_SEND_MESSAGE)?;
let (rec, _) = module.outgoing_smses.peek().unwrap();
uart.write_all(rec.as_bytes()).map_err(SimError::IOError)?;
module.push_cmd("\"")?;
module.confirm_cmd()?;
module.state = State::StateSendingSms2;
}
State::StateSendingSms2 => {
if msg == ">" {
let (_rec, message) = module.outgoing_smses.peek().unwrap();
info!(
"[Sim800L] Sending to {} message: '{}'",
_rec.as_str(),
message.as_str()
);
uart.write_all(message.as_bytes())
.map_err(SimError::IOError)?;
uart.write_all(&[CTRL_Z]).map_err(SimError::IOError)?;
module.state = State::StateSendingSms3;
} else {
module.registered = false;
module.state = State::StateInit;
module.send_cmd(ENABLE_CME_ERROR_VERBOSE)?;
uart.write_all(&[CTRL_Z]).map_err(SimError::IOError)?;
}
}
State::StateSendingSms3 => {
if msg.contains("+CMGS:") {
debug!("[Sim800L] SMS Sent OK: {}", msg);
module.state = State::StateCheckSms;
module.expect_ack = true;
module.outgoing_smses.advance_cons();
module.send_pending = module.outgoing_smses.cons_avail();
}
}
State::StateCheckSms => {
module.check_sms()?;
}
State::StateParseSmsResponse => {
if msg.contains("+CMGL:") && module.parse_index == 0 {
if let Some(m) = msg.strip_prefix("+CMGL: ") {
let mut split = m.split(',');
module.parse_index =
u8::from_str_radix(split.next().unwrap_or_default(), 10).unwrap_or(0);
split.next();
module.sender.clear();
module.sender.push_str(split.next().unwrap_or("?"))?;
module.message.clear();
} else {
debug!("[Sim800L] Invalid message {:?} {}", module.state, msg);
return Ok(());
}
module.state = State::StateReceiveSms;
}
if ok {
module.send_cmd(LIST_CURRENT_CALLS)?;
module.state = State::StateCheckCall;
}
}
State::StateReceiveSms => {
if ok || msg.contains("+CMGL:") {
debug!(
"[Sim800L] Received SMS from: {}\n{}",
module.sender, module.message
);
if let Some(f) = module.config.received_sms_callback.as_mut() {
f(module.message.as_str(), module.sender.as_str());
}
module.state = State::StateSmsReceived;
} else {
if !module.message.is_empty() {
module.message.push_str("\n")?;
}
module.message.push_str(msg)?;
}
}
State::StateSmsReceived => {}
State::StateDisableEcho => {
module.send_cmd(DISABLE_ECHO)?;
module.state = State::StateSetupCmgf;
module.expect_ack = true;
}
State::StateDialing1 => {
module.push_cmd(SETUP_OUTGOING_CALL)?;
uart.write_all(module.recipient.as_bytes())
.map_err(SimError::IOError)?;
module.push_cmd(";")?;
module.confirm_cmd()?;
module.state = State::StateDialing2;
}
State::StateDialing2 => {
if ok {
info!("[Sim800L] Dialing: '{}'", module.recipient.as_str());
module.dial_pending = false;
} else {
module.registered = false;
module.send_cmd(ENABLE_CME_ERROR_VERBOSE)?;
uart.write_all(&[CTRL_Z]).map_err(SimError::IOError)?;
}
module.state = State::StateInit;
}
State::StateParseClip => {
if msg.contains("+CLIP:") {
let mut caller_id = "NULL";
if let Some(m) = msg.strip_prefix("+CLIP: ") {
let mut split = m.split(',');
caller_id = split.next().unwrap_or("?");
}
if module.call_state != CallState::Incoming {
module.call_state = CallState::Incoming;
info!("[Sim800L] Incoming call from {}", caller_id);
if let Some(f) = module.config.received_call_callback.as_mut() {
if f(caller_id) {
module.answer_call();
}
}
}
module.state = State::StateInit;
}
}
State::StateAtaSent => {
info!("[Sim800L] Call connected");
if module.call_state != CallState::Active {
module.call_state = CallState::Active;
if let Some(f) = module.config.call_connected_callback.as_mut() {
f();
}
}
module.state = State::StateInit;
}
State::StateCheckCall => {
if msg.contains("+CLCC:") && module.parse_index == 0 {
module.expect_ack = true;
if let Some(m) = msg.strip_prefix("+CLCC: ") {
let mut v = m.split(',').skip(2);
let current_call_state = CallState::from(
u8::from_str_radix(v.next().unwrap_or_default(), 10).unwrap_or(6),
);
if current_call_state != module.call_state {
if current_call_state == CallState::Incoming {
debug!(
"[Sim800L] Premature call state '4'. Ignoring, waiting for RING"
);
} else {
debug!("[Sim800L] Call state is now: {:?}", current_call_state);
if current_call_state == CallState::Active {
if let Some(f) = module.config.call_connected_callback.as_mut() {
f();
}
}
module.call_state = current_call_state;
}
}
} else {
debug!("[Sim800L] Invalid message {:?} {}", module.state, msg);
return Ok(());
}
} else if ok {
if module.call_state != CallState::Disconnect {
module.call_state = CallState::Disconnect; if let Some(f) = module.config.call_disconnected_callback.as_mut() {
f();
}
}
}
module.state = State::StateInit;
}
State::StateSendUssd1 => {
module.push_cmd(BEGIN_SEND_USSD)?;
uart.write_all(module.ussd.as_bytes())
.map_err(SimError::IOError)?;
module.push_cmd("\"")?;
module.confirm_cmd()?;
module.state = State::StateSendUssd2;
module.expect_ack = true;
}
State::StateSendUssd2 => {
debug!("[Sim800L] SendUssd2: '{}'", msg);
if msg == "OK" {
debug!("[Sim800L] Dialing ussd code: '{}' done.", module.ussd);
module.state = State::StateCheckUssd;
module.send_ussd_pending = false;
} else {
module.registered = false;
module.state = State::StateInit;
module.send_cmd(ENABLE_CME_ERROR_VERBOSE)?;
uart.write_all(&[CTRL_Z]).map_err(SimError::IOError)?;
}
}
State::StateCheckUssd => {
debug!("[Sim800L] Check ussd code: '{}'", msg);
if msg.contains("+CUSD:") {
module.state = State::StateReceivedUssd;
module.ussd.clear();
if let Some(m) = msg.strip_prefix("+CUSD: ") {
let mut split = m.split(',').skip(1);
if let Some(ussd) = split.next() {
module.ussd.push_str(ussd.trim_matches('"'))?;
if let Some(f) = module.config.received_ussd_callback.as_mut() {
f(module.ussd.as_str());
}
}
}
}
if msg == "OK" {
module.state = State::StateInit;
}
}
State::StateReceivedUssd => {
}
_ => {
warning!("[Sim800L] Unhandled: {} - {:?}", msg, module.state);
}
}
Ok(())
}