diff --git a/src/command.rs b/src/command.rs index 6de5fa6..0a64681 100644 --- a/src/command.rs +++ b/src/command.rs @@ -3,215 +3,231 @@ use std::time::Duration; pub struct Command { pub text: String, pub timeout: Duration, - pub ends_with: Option, + pub contains: Option, } impl Command { pub fn initgprs() -> Command { Command { - text: "AT+SAPBR=3,1,\"Contype\",\"GPRS\"".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+SAPBR=3,1,\"Contype\",\"GPRS\"".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn modeminfo() -> Command { Command { - text: "ATI".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "ATI".to_string(), + timeout: Duration::from_millis(6000), + contains: Some("+CIEV".to_string()), } } pub fn fwrevision() -> Command { Command { - text: "AT+CGMR".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+CGMR".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn battery() -> Command { Command { - text: "AT+CBC".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+CBC".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn scan() -> Command { Command { - text: "AT+COPS=?".to_owned(), - timeout: Duration::from_millis(60), - ends_with: Some("OK".to_owned()), + text: "AT+COPS=?".to_string(), + timeout: Duration::from_millis(60000), + contains: Some("OK".to_string()), } } pub fn network() -> Command { Command { - text: "AT+COPS?".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+COPS?".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn signal() -> Command { Command { - text: "AT+CSQ".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+CSQ".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn checkreg() -> Command { Command { - text: "AT+CREG?".to_owned(), - timeout: Duration::from_millis(3), - ends_with: None, + text: "AT+CREG?".to_string(), + timeout: Duration::from_millis(3000), + contains: None, } } pub fn opengprs() -> Command { Command { - text: "AT+SAPBR=1,1".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+SAPBR=1,1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn getbear() -> Command { Command { - text: "AT+SAPBR=2,1".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+SAPBR=2,1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn inithttp() -> Command { Command { - text: "AT+HTTPINIT".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+HTTPINIT".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn sethttp() -> Command { Command { - text: "AT+HTTPPARA=\"CID\",1".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+HTTPPARA=\"CID\",1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn checkssl() -> Command { Command { - text: "AT+CIPSSL=?".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+CIPSSL=?".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn enablessl() -> Command { Command { - text: "AT+HTTPSSL=1".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+HTTPSSL=1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn disablessl() -> Command { Command { - text: "AT+HTTPSSL=0".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+HTTPSSL=0".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn initurl() -> Command { Command { - text: "AT+HTTPPARA=\"URL\",\"{}\"".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+HTTPPARA=\"URL\",\"{}\"".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn doget() -> Command { Command { - text: "AT+HTTPACTION=0".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("+HTTPACTION".to_owned()), + text: "AT+HTTPACTION=0".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("+HTTPACTION".to_string()), } } pub fn setcontent() -> Command { Command { - text: "AT+HTTPPARA=\"CONTENT\",\"{}\"".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+HTTPPARA=\"CONTENT\",\"{}\"".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn postlen() -> Command { Command { - text: "AT+HTTPDATA={}5000".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("DOWNLOAD".to_owned()), + text: "AT+HTTPDATA={}5000".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("DOWNLOAD".to_string()), } } pub fn dopost() -> Command { Command { - text: "AT+HTTPACTION=1".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("+HTTPACTION".to_owned()), + text: "AT+HTTPACTION=1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("+HTTPACTION".to_string()), } } pub fn getdata() -> Command { Command { - text: "AT+HTTPREAD".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+HTTPREAD".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn closehttp() -> Command { Command { - text: "AT+HTTPTERM".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+HTTPTERM".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn closebear() -> Command { Command { - text: "AT+SAPBR=0,1".to_owned(), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + text: "AT+SAPBR=0,1".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn setapn(apn: &str) -> Command { Command { text: format!("AT+SAPBR=3,1,\"APN\",\"{}\"", apn), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn setuser(user: &str) -> Command { Command { text: format!("AT+SAPBR=3,1,\"USER\",\"{}\"", user), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), } } pub fn setpwd(password: &str) -> Command { Command { text: format!("AT+SAPBR=3,1,\"PWD\",\"{}\"", password), - timeout: Duration::from_millis(3), - ends_with: Some("OK".to_owned()), + timeout: Duration::from_millis(3000), + contains: Some("OK".to_string()), + } + } + + pub fn probe() -> Command { + Command { + text: "AT".to_string(), + timeout: Duration::from_millis(3000), + contains: Some("+CIEV".to_string()), + } + } + + pub fn is_gprs_attached() -> Command { + Command { + text: "AT+CGATT?".to_string(), + timeout: Duration::from_millis(1000), + contains: Some("OK".to_string()), } } } diff --git a/src/main.rs b/src/main.rs index 36f1e2a..7393a33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,10 @@ use anyhow; use esp_idf_hal::prelude::*; use esp_idf_hal::peripherals::Peripherals; use esp_idf_hal::serial; +use std::time::Duration; +use std::thread; + +use std::sync::mpsc::{channel, Receiver, Sender}; fn main() -> anyhow::Result<()> { esp_idf_sys::link_patches(); @@ -38,16 +42,18 @@ fn main() -> anyhow::Result<()> { let modem_power = dp.pins.gpio23.into_output().unwrap(); modem::init(modem_pwrkey, modem_rst, modem_power)?; - let (tx, rx) = serial.split(); let mut mdm = modem::Modem::new(tx, rx); - let _ = mdm.modem_info()?; + let _ = mdm.probe()?; let _ = mdm.connect_to_gprs_ap( config::A1_GPRS_AP.apn, config::A1_GPRS_AP.username, config::A1_GPRS_AP.password, )?; + if mdm.is_gprs_attached()? { + println!("GPRS ATTACHEEEDDDDDDDDD!!!!!!!!!!!!!!!1"); + } Ok(()) } diff --git a/src/modem.rs b/src/modem.rs index 17407d0..65f2db2 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -1,18 +1,19 @@ use crate::command::Command; -use std::iter::FromIterator; use std::thread; +use std::io::{Error as IoError, ErrorKind, BufRead}; 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}; +use esp_idf_hal::serial::{self, Rx, Tx, SerialError}; +pub type Result = std::result::Result; pub struct Modem { is_connected: bool, - rx: Rx, + rx: MyRx, tx: Tx, } @@ -20,6 +21,7 @@ pub struct Modem { pub enum ModemError { CommandError(String), SetupError(String), + TimeoutError, } impl Error for ModemError {} @@ -30,13 +32,39 @@ impl std::fmt::Display for ModemError { } } -pub type Result = std::result::Result; +struct MyRx(Rx); + +impl MyRx { + fn read(&mut self) -> nb::Result { + self.0.read() + } + + fn read_blocking(&mut self) -> std::result::Result { + nb::block!(self.read()) + } +} + +impl std::io::Read for MyRx { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let mut read_bytes: usize = 0; + loop { + let b = self.read_blocking() + .map_err(|err| IoError::new(ErrorKind::Other, format!("Serial error {:?}", err)))?; + buf[read_bytes] = b; + read_bytes += 1; + if b == '\0' as u8 || b == '\n' as u8 { + break; + } + }; + Ok(read_bytes) + } +} impl Modem { pub fn new(tx: Tx, rx: Rx) -> Self { Self { is_connected: false, - rx, + rx: MyRx(rx), tx, } } @@ -51,69 +79,86 @@ impl Modem { /// 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); - + fn read_response(&mut self, contains: Option, timeout: Duration) -> Result { let mut response = String::new(); let now = Instant::now(); + let match_text: String = contains.unwrap_or("".to_string()); + + let mut reader = std::io::BufReader::new(&mut self.rx); loop { - let len = self.rx.count().unwrap(); - if len == 0 { + let mut line = String::new(); + reader.read_line(&mut line) + .map_err(|err| ModemError::CommandError(format!("read line failed: {}", err)))?; + println!("Read {} from serial ...", line.trim()); + + // check if empty first because it's much simpler and more frequent + if line == "" { if now + timeout > Instant::now() { + return Err(ModemError::TimeoutError); + } + thread::sleep(Duration::from_millis(1000)); + continue; + } else { + response.push_str(&line); + if line.contains(&match_text) { + println!("Found match {} for line {} ... exiting response reader now!", match_text, line); + println!("-----------------------------------------------------------"); 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())) + Ok(response.to_string()) } 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()))?; + nb::block!(self.tx.write(*b)).map_err(|_| ModemError::CommandError("error writing to serial".to_string()))?; } - self.read_response(cmd.ends_with, cmd.timeout) + nb::block!(self.tx.write('\r' as u8)).map_err(|_| ModemError::CommandError("error writing to serial".to_string()))?; + self.read_response(cmd.contains.clone(), cmd.timeout.clone()) + } + + pub fn get_ip_addr(&mut self) -> Result { + println!("getting ip addres ..."); + let res = self.send_command(Command::getbear())?; + println!("{}", res); + Ok(res) } pub fn connect_to_gprs_ap(&mut self, apn: &str, username: &str, password: &str)-> Result<()> { + println!("init gprs ..."); + let _ = self.send_command(Command::initgprs())?; + 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())?; + + println!("open gprs ..."); + let _ = self.send_command(Command::initgprs())?; self.is_connected = true; Ok(()) } - pub fn modem_info(&mut self)-> Result { - println!("testing modem with AP command"); + pub fn info(&mut self)-> Result { + println!("getting modem info with API command"); self.send_command(Command::modeminfo()) } + + pub fn probe(&mut self)-> Result { + println!("probing modem with AP command"); + self.send_command(Command::probe()) + } + + pub fn is_gprs_attached(&mut self)-> Result { + println!("testing gprs connection ..."); + let res = self.send_command(Command::is_gprs_attached())?; + + Ok(res.contains("+CGATT: 1")) + } } /// Initialize the modem (sim800l in this case). The initialization process sets all pins in the @@ -133,14 +178,14 @@ impl Modem { /// ``` 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()))?; + power.set_high().map_err(|_| ModemError::SetupError("Error setting POWER to high.".to_string()))?; + rst.set_high().map_err(|_| ModemError::SetupError("Error setting RST to high.".to_string()))?; // 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()))?; + pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; thread::sleep(Duration::from_millis(100)); - pwrkey.set_low().map_err(|_| ModemError::SetupError("Error setting PWRKEY to low.".to_owned()))?; + pwrkey.set_low().map_err(|_| ModemError::SetupError("Error setting PWRKEY to low.".to_string()))?; thread::sleep(Duration::from_millis(1000)); - pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_owned()))?; + pwrkey.set_high().map_err(|_| ModemError::SetupError("Error setting PWRKEY to high.".to_string()))?; println!("Waiting 3s for sim module to come online ..."); thread::sleep(Duration::from_millis(3000)); Ok(())