use crate::command::Command; use std::iter::FromIterator; use std::thread; use std::error::Error; use std::time::{Duration, Instant}; use embedded_hal::serial::{Read, Write}; use embedded_hal::digital::v2::OutputPin; use esp_idf_hal::serial::{self, Rx, Tx}; pub struct Modem { is_connected: bool, rx: Rx, tx: Tx, } #[derive(Debug)] pub enum ModemError { CommandError(String), SetupError(String), } impl Error for ModemError {} impl std::fmt::Display for ModemError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:?}", self) } } pub type Result = std::result::Result; impl Modem { pub fn new(tx: Tx, rx: Rx) -> Self { Self { is_connected: false, rx, tx, } } /// Reads the serial RX until it has bytes, or until a timeout is reached. The timeout is /// provided on input via the `timeout` argument. The first argument `expected` is the expected /// end of the buffer. If it's `None`, the whole response is returned as is. If it's /// `Some(expected_end)`, then the end of the response is matched against `expected_end`. If /// they match, great! The response is returned from the function, but if not, then a /// [ModemError::CommandError](crate::modem::ModemError::CommandError) is returned. /// /// It's ok to use this function like this for now because the write/read cycle is blocking, /// hence the case where multiple writes happen asynchronously isn't handled. See /// [send_command](crate::modem::Modem::send_command) for more info on the blocking part. fn read_response(&mut self, expected: Option, timeout: Duration) -> Result { let len = self.rx.count() .map_err(|_| ModemError::CommandError("Error getting RX fifo length".to_owned()))?; println!("Reading {} bytes from serial RX ...", len); let mut response = String::new(); let now = Instant::now(); loop { let len = self.rx.count().unwrap(); if len == 0 { if now + timeout > Instant::now() { break; } thread::sleep(Duration::from_secs(1)); continue; } println!("Reading {} bytes from serial RX ...", len); for _ in 0..len { nb::block!(self.rx.read()) .map(|b| response.push(b as char)) .map_err(|_| ModemError::CommandError("Error reading from RX".to_owned()))?; } } let response = response.trim(); println!("Received: {}", response); expected.map(|expected_end| { if response.ends_with(&expected_end) { Ok(response.to_owned()) } else { let res = String::from_iter(response.chars().rev().take(expected_end.len())); Err(ModemError::CommandError(format!("Got invalid response end {} instead of expected {}", res, expected_end))) } }).unwrap_or(Ok(response.to_owned())) } fn send_command(&mut self, cmd: Command) -> Result { for b in cmd.text.as_bytes().iter() { nb::block!(self.tx.write(*b)).map_err(|_| ModemError::CommandError("error writing to serial".to_owned()))?; } self.read_response(cmd.ends_with, cmd.timeout) } pub fn connect_to_gprs_ap(&mut self, apn: &str, username: &str, password: &str)-> Result<()> { println!("connecting to {} with {}:{}", apn, username, password); let _ = self.send_command(Command::setapn(apn))?; let _ = self.send_command(Command::setuser(username))?; let _ = self.send_command(Command::setpwd(password))?; let _ = self.send_command(Command::opengprs())?; self.is_connected = true; Ok(()) } pub fn modem_info(&mut self)-> Result { println!("testing modem with AP command"); self.send_command(Command::modeminfo()) } } /// Initialize the modem (sim800l in this case). The initialization process sets all pins in the /// required state so that the modem is turned on, then resets it a couple of times (beats me) and /// sleeps for 3 seconds, which is enough for the modem to come online. /// /// Below is an example for sim800l pins on a LilyGo TTGO T-Call. /// /// # Examples /// /// ``` /// let mut modem_pwrkey = dp.pins.gpio4.into_output().unwrap(); /// let mut modem_rst = dp.pins.gpio5.into_output().unwrap(); /// let mut modem_power = dp.pins.gpio23.into_output().unwrap(); /// /// modem::init(modem_pwrkey, modem_rst, modem_power); /// ``` pub fn init(mut pwrkey: impl OutputPin, mut rst: impl OutputPin, mut power: impl OutputPin) -> Result<()> { println!("Turning SIM800L on ..."); power.set_high().map_err(|_| ModemError::SetupError("Error setting POWER to high.".to_owned()))?; rst.set_high().map_err(|_| ModemError::SetupError("Error setting RST to high.".to_owned()))?; // Pull down PWRKEY for more than 1 second according to manual requirements pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_owned()))?; thread::sleep(Duration::from_millis(100)); pwrkey.set_low().map_err(|_| ModemError::SetupError("Error setting PWRKEY to low.".to_owned()))?; thread::sleep(Duration::from_millis(1000)); pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_owned()))?; println!("Waiting 3s for sim module to come online ..."); thread::sleep(Duration::from_millis(3000)); Ok(()) }