diff --git a/.github/configs/sdkconfig.defaults b/.github/configs/sdkconfig.defaults index 2e584fa2821..8868bc0c0d5 100644 --- a/.github/configs/sdkconfig.defaults +++ b/.github/configs/sdkconfig.defaults @@ -17,6 +17,12 @@ CONFIG_ETH_SPI_ETHERNET_DM9051=y CONFIG_ETH_SPI_ETHERNET_W5500=y CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y +# GSM +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048 +CONFIG_LWIP_PPP_ENABLE_IPV6=n + + # We don't have an example for classic BT - yet - we need to enable class BT # specifically to workaround this bug in ESP IDF v5.2 (fixed in ESP IDF v5.2.1+): # https://github.com/espressif/esp-idf/issues/13113 diff --git a/.gitignore b/.gitignore index 881f58740f1..06487911946 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /target /Cargo.lock **/*.rs.bk +/.devcontainer diff --git a/Cargo.toml b/Cargo.toml index 659ce3baf70..2dbf822ca39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,8 @@ embedded-svc = { version = "0.28", default-features = false } esp-idf-hal = { version = "0.44", default-features = false } embassy-time-driver = { version = "0.1", optional = true, features = ["tick-hz-1_000_000"] } embassy-futures = "0.1" +atat = {version="0.23.0", default-features = false, features=["bytes","derive","heapless","serde_at"]} +at-commands = "0.5.4" [build-dependencies] embuild = "0.32" diff --git a/examples/lte_modem.rs b/examples/lte_modem.rs new file mode 100644 index 00000000000..df1ca3bf87b --- /dev/null +++ b/examples/lte_modem.rs @@ -0,0 +1,55 @@ +//! Example of using blocking wifi. +//! +//! Add your own ssid and password + +use core::convert::TryInto; + +use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration}; + +use esp_idf_hal::gpio; +use esp_idf_hal::uart::UartDriver; +use esp_idf_hal::units::Hertz; +use esp_idf_svc::hal::prelude::Peripherals; +use esp_idf_svc::log::EspLogger; +use esp_idf_svc::modem::EspModem; +use esp_idf_svc::wifi::{BlockingWifi, EspWifi}; +use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition}; + +use log::info; + +// const SSID: &str = env!("WIFI_SSID"); +// const PASSWORD: &str = env!("WIFI_PASS"); + +fn main() -> anyhow::Result<()> { + esp_idf_svc::sys::link_patches(); + EspLogger::initialize_default(); + + let peripherals = Peripherals::take()?; + let sys_loop = EspSystemEventLoop::take()?; + let nvs = EspDefaultNvsPartition::take()?; + let serial = peripherals.uart2; + let tx = peripherals.pins.gpio17; + let rx = peripherals.pins.gpio16; + let mut serial = UartDriver::new( + serial, + tx, + rx, + Option::::None, + Option::::None, + &esp_idf_hal::uart::UartConfig { + baudrate: Hertz(115200), + ..Default::default() + }, + )?; + log::error!("Hello"); + let mut modem = EspModem::new(&mut serial)?; + + match modem.setup_data_mode() { + Err(x) => log::error!("Error: {:?}", x), + Ok(x) => (), + } + + std::thread::sleep(core::time::Duration::from_secs(5)); + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index bc32b802251..2b4708055d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ pub mod mdns; esp_idf_comp_mqtt_enabled, esp_idf_comp_esp_event_enabled ))] +pub mod modem; pub mod mqtt; #[cfg(esp_idf_lwip_ipv4_napt)] pub mod napt; diff --git a/src/modem.rs b/src/modem.rs new file mode 100644 index 00000000000..d6831d154d2 --- /dev/null +++ b/src/modem.rs @@ -0,0 +1,259 @@ +use at_commands::{builder::CommandBuilder, parser::CommandParser}; +use atat::{self, AtatCmd}; +use core::{borrow::BorrowMut, ffi::c_void, marker::PhantomData}; +use esp_idf_hal::{delay::TickType, uart::UartDriver}; + +use crate::{ + handle::RawHandle, + netif::{EspNetif, NetifStack}, + sys::*, +}; + +pub struct PppNetif<'d, T> +where + T: BorrowMut>, +{ + serial: T, + base: esp_netif_driver_base_t, + netif: EspNetif, + _d: PhantomData<&'d ()>, +} + +impl<'d, T> PppNetif<'d, T> +where + T: BorrowMut>, +{ + pub fn new(serial: T) -> Result { + let netif = EspNetif::new(NetifStack::Ppp)?; + let mut base = esp_netif_driver_base_t { + netif: netif.handle(), + post_attach: Some(Self::post_attach), + }; + let base_ptr: *mut c_void = &mut base as *mut _ as *mut c_void; + esp!(unsafe { esp_netif_attach(netif.handle(), base_ptr) })?; + Ok(Self { + serial, + netif, + base, + _d: PhantomData, + }) + } + + unsafe extern "C" fn post_attach(netif: *mut esp_netif_obj, args: *mut c_void) -> i32 { + let driver= unsafe{std::ptr::slice_from_raw_parts_mut(args, size_of:: +where + T: BorrowMut>, +{ + serial: T, + + _d: PhantomData<&'d ()>, +} + +impl<'d, T> EspModem<'d, T> +where + T: BorrowMut>, +{ + pub fn new(serial: T) -> Self { + Self { + serial, + _d: PhantomData, + } + } + + pub fn send_cmd(&mut self, cmd: &CMD) -> Result { + let mut buff = [0u8; 64]; + // flush the channel + // self.serial + // .borrow_mut() + // .clear_rx() + // .map_err(|_err| atat::Error::Write)?; + + // write the command to the uart + let len = cmd.write(&mut buff); + log::info!("about to write {:?}", &buff[..len]); + self.serial + .borrow_mut() + .write(&buff[..len]) + .map_err(|_err| atat::Error::Write)?; + + // now read the uart to get the response + + let len = self + .serial + .borrow_mut() + .read(&mut buff, TickType::new_millis(1000).ticks()) + .map_err(|_err| atat::Error::Read)?; + log::info!("got response {:?}", &buff[..len]); + cmd.parse(Ok(&buff[..len])) + } + + pub fn setup_data_mode(&mut self) -> Result<(), EspError> { + self.reset()?; + //disable echo + self.set_echo(false)?; + + // check pdp network reg + self.read_gprs_registration_status()?; + + //configure apn + self.set_pdp_context()?; + + // start ppp + self.set_data_mode()?; + + // now in ppp mode. + // self.netif. + + Ok(()) + } + + fn get_signal_quality(&mut self) -> Result<(), EspError> { + let mut buff = [0u8; 64]; + let cmd = CommandBuilder::create_execute(&mut buff, true) + .named("+CSQ") + .finish() + .unwrap(); + self.serial.borrow_mut().write(cmd)?; + let len = self + .serial + .borrow_mut() + .read(&mut buff, TickType::new_millis(1000).ticks())?; + log::info!("got response {:?}", &buff[..len]); + + // \r\n+CSQ: 19,99\r\n\r\nOK\r\n + let (rssi, ber) = CommandParser::parse(&buff[..len]) + .expect_identifier(b"\r\n+CSQ: ") + .expect_int_parameter() + .expect_int_parameter() + .expect_identifier(b"\r\n\r\nOK\r\n") + .finish() + .unwrap(); + log::info!("Signal Quality: rssi: {} ber: {}", rssi, ber); + Ok(()) + } + + fn reset(&mut self) -> Result<(), EspError> { + let mut buff = [0u8; 64]; + let cmd = CommandBuilder::create_execute(&mut buff, false) + .named("ATZ0") + .finish() + .unwrap(); + self.serial.borrow_mut().write(cmd)?; + let len = self + .serial + .borrow_mut() + .read(&mut buff, TickType::new_millis(1000).ticks())?; + log::info!("got response {:?}", &buff[..len]); + CommandParser::parse(&buff[..len]) + .expect_identifier(b"ATZ0\r") + .expect_identifier(b"\r\nOK\r\n") + .finish() + .unwrap(); + Ok(()) + } + + fn set_echo(&mut self, echo: bool) -> Result<(), EspError> { + let mut buff = [0u8; 64]; + let cmd = CommandBuilder::create_execute(&mut buff, false) + .named(format!("ATE{}", i32::from(echo))) + .finish() + .unwrap(); + self.serial.borrow_mut().write(cmd)?; + let len = self + .serial + .borrow_mut() + .read(&mut buff, TickType::new_millis(1000).ticks())?; + log::info!("got response {:?}", &buff[..len]); + + CommandParser::parse(&buff[..len]) + .expect_identifier(b"ATE0\r") + .expect_identifier(b"\r\nOK\r\n") + .finish() + .unwrap(); + Ok(()) + } + + fn read_gprs_registration_status(&mut self) -> Result<(), EspError> { + let mut buff = [0u8; 64]; + let cmd = CommandBuilder::create_query(&mut buff, true) + .named("+CGREG") + .finish() + .unwrap(); + self.serial.borrow_mut().write(cmd)?; + let len = self + .serial + .borrow_mut() + .read(&mut buff, TickType::new_millis(1000).ticks())?; + log::info!("got response {:?}", &buff[..len]); + + let (n, stat, lac, ci) = CommandParser::parse(&buff[..len]) + .expect_identifier(b"\r\n+CGREG: ") + .expect_int_parameter() + .expect_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_identifier(b"\r\n\r\nOK\r\n") + .finish() + .unwrap(); + log::info!( + "CGREG: n: {}stat: {}, lac: {:?}, ci: {:?} ", + n, + stat, + lac, + ci + ); + Ok(()) + } + + fn set_pdp_context(&mut self) -> Result<(), EspError> { + let mut buff = [0u8; 64]; + let cmd = CommandBuilder::create_set(&mut buff, true) + .named("+CGDCONT") + .with_int_parameter(1) // context id + .with_string_parameter("IP") // pdp type + .with_string_parameter("internet") + .finish() + .unwrap(); + self.serial.borrow_mut().write(cmd)?; + let len = self + .serial + .borrow_mut() + .read(&mut buff, TickType::new_millis(1000).ticks())?; + log::info!("got response {:?}", &buff[..len]); + + CommandParser::parse(&buff[..len]) + .expect_identifier(b"\r\nOK\r\n") + .finish() + .unwrap(); + + Ok(()) + } + + fn set_data_mode(&mut self) -> Result<(), EspError> { + let mut buff = [0u8; 64]; + let cmd = CommandBuilder::create_execute(&mut buff, false) + .named("ATD*99#") + .finish() + .unwrap(); + self.serial.borrow_mut().write(cmd)?; + let len = self + .serial + .borrow_mut() + .read(&mut buff, TickType::new_millis(1000).ticks())?; + log::info!("got response {:?}", &buff[..len]); + + let (connect_parm,) = CommandParser::parse(&buff[..len]) + .expect_identifier(b"\r\nCONNECT ") + .expect_optional_raw_string() + .expect_identifier(b"\r\n") + .finish() + .unwrap(); + log::info!("connect {:?}", connect_parm); + Ok(()) + } +}