363 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! Helper utility to configure a specific modem context.
 | |
| use core::net::IpAddr;
 | |
| use core::str::FromStr;
 | |
| 
 | |
| use at_commands::builder::CommandBuilder;
 | |
| use at_commands::parser::CommandParser;
 | |
| use embassy_time::{Duration, Timer};
 | |
| use heapless::Vec;
 | |
| 
 | |
| /// Provides a higher level API for controlling a given context.
 | |
| pub struct Control<'a> {
 | |
|     control: crate::Control<'a>,
 | |
|     cid: u8,
 | |
| }
 | |
| 
 | |
| /// Configuration for a given context
 | |
| pub struct Config<'a> {
 | |
|     /// Desired APN address.
 | |
|     pub apn: &'a [u8],
 | |
|     /// Desired authentication protocol.
 | |
|     pub auth_prot: AuthProt,
 | |
|     /// Credentials.
 | |
|     pub auth: Option<(&'a [u8], &'a [u8])>,
 | |
|     /// SIM pin
 | |
|     pub pin: Option<&'a [u8]>,
 | |
| }
 | |
| 
 | |
| /// Authentication protocol.
 | |
| #[derive(Clone, Copy, PartialEq, Debug)]
 | |
| #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 | |
| #[repr(u8)]
 | |
| pub enum AuthProt {
 | |
|     /// No authentication.
 | |
|     None = 0,
 | |
|     /// PAP authentication.
 | |
|     Pap = 1,
 | |
|     /// CHAP authentication.
 | |
|     Chap = 2,
 | |
| }
 | |
| 
 | |
| /// Error returned by control.
 | |
| #[derive(Clone, Copy, PartialEq, Debug)]
 | |
| #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 | |
| pub enum Error {
 | |
|     /// Not enough space for command.
 | |
|     BufferTooSmall,
 | |
|     /// Error parsing response from modem.
 | |
|     AtParseError,
 | |
|     /// Error parsing IP addresses.
 | |
|     AddrParseError,
 | |
| }
 | |
| 
 | |
| impl From<at_commands::parser::ParseError> for Error {
 | |
|     fn from(_: at_commands::parser::ParseError) -> Self {
 | |
|         Self::AtParseError
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Status of a given context.
 | |
| #[derive(PartialEq, Debug)]
 | |
| pub struct Status {
 | |
|     /// Attached to APN or not.
 | |
|     pub attached: bool,
 | |
|     /// IP if assigned.
 | |
|     pub ip: Option<IpAddr>,
 | |
|     /// Gateway if assigned.
 | |
|     pub gateway: Option<IpAddr>,
 | |
|     /// DNS servers if assigned.
 | |
|     pub dns: Vec<IpAddr, 2>,
 | |
| }
 | |
| 
 | |
| #[cfg(feature = "defmt")]
 | |
| impl defmt::Format for Status {
 | |
|     fn format(&self, f: defmt::Formatter<'_>) {
 | |
|         defmt::write!(f, "attached: {}", self.attached);
 | |
|         if let Some(ip) = &self.ip {
 | |
|             defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip));
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<'a> Control<'a> {
 | |
|     /// Create a new instance of a control handle for a given context.
 | |
|     ///
 | |
|     /// Will wait for the modem to be initialized if not.
 | |
|     pub async fn new(control: crate::Control<'a>, cid: u8) -> Self {
 | |
|         control.wait_init().await;
 | |
|         Self { control, cid }
 | |
|     }
 | |
| 
 | |
|     /// Perform a raw AT command
 | |
|     pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize {
 | |
|         self.control.at_command(req, resp).await
 | |
|     }
 | |
| 
 | |
|     /// Configures the modem with the provided config.
 | |
|     ///
 | |
|     /// NOTE: This will disconnect the modem from any current APN and should not
 | |
|     /// be called if the configuration has not been changed.
 | |
|     ///
 | |
|     /// After configuring, invoke [`enable()`] to activate the configuration.
 | |
|     pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> {
 | |
|         let mut cmd: [u8; 256] = [0; 256];
 | |
|         let mut buf: [u8; 256] = [0; 256];
 | |
| 
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CFUN")
 | |
|             .with_int_parameter(0)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
 | |
| 
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CGDCONT")
 | |
|             .with_int_parameter(self.cid)
 | |
|             .with_string_parameter("IP")
 | |
|             .with_string_parameter(config.apn)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         // info!("RES1: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) });
 | |
|         CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
 | |
| 
 | |
|         let mut op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CGAUTH")
 | |
|             .with_int_parameter(self.cid)
 | |
|             .with_int_parameter(config.auth_prot as u8);
 | |
|         if let Some((username, password)) = config.auth {
 | |
|             op = op.with_string_parameter(username).with_string_parameter(password);
 | |
|         }
 | |
|         let op = op.finish().map_err(|_| Error::BufferTooSmall)?;
 | |
| 
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         // info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) });
 | |
|         CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
 | |
| 
 | |
|         if let Some(pin) = config.pin {
 | |
|             let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|                 .named("+CPIN")
 | |
|                 .with_string_parameter(pin)
 | |
|                 .finish()
 | |
|                 .map_err(|_| Error::BufferTooSmall)?;
 | |
|             let _ = self.control.at_command(op, &mut buf).await;
 | |
|             // Ignore ERROR which means no pin required
 | |
|         }
 | |
| 
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     /// Attach to the PDN
 | |
|     pub async fn attach(&self) -> Result<(), Error> {
 | |
|         let mut cmd: [u8; 256] = [0; 256];
 | |
|         let mut buf: [u8; 256] = [0; 256];
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CGATT")
 | |
|             .with_int_parameter(1)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     /// Read current connectivity status for modem.
 | |
|     pub async fn detach(&self) -> Result<(), Error> {
 | |
|         let mut cmd: [u8; 256] = [0; 256];
 | |
|         let mut buf: [u8; 256] = [0; 256];
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CGATT")
 | |
|             .with_int_parameter(0)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     async fn attached(&self) -> Result<bool, Error> {
 | |
|         let mut cmd: [u8; 256] = [0; 256];
 | |
|         let mut buf: [u8; 256] = [0; 256];
 | |
| 
 | |
|         let op = CommandBuilder::create_query(&mut cmd, true)
 | |
|             .named("+CGATT")
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         let (res,) = CommandParser::parse(&buf[..n])
 | |
|             .expect_identifier(b"+CGATT: ")
 | |
|             .expect_int_parameter()
 | |
|             .expect_identifier(b"\r\nOK")
 | |
|             .finish()?;
 | |
|         Ok(res == 1)
 | |
|     }
 | |
| 
 | |
|     /// Read current connectivity status for modem.
 | |
|     pub async fn status(&self) -> Result<Status, Error> {
 | |
|         let mut cmd: [u8; 256] = [0; 256];
 | |
|         let mut buf: [u8; 256] = [0; 256];
 | |
| 
 | |
|         let op = CommandBuilder::create_query(&mut cmd, true)
 | |
|             .named("+CGATT")
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         let (res,) = CommandParser::parse(&buf[..n])
 | |
|             .expect_identifier(b"+CGATT: ")
 | |
|             .expect_int_parameter()
 | |
|             .expect_identifier(b"\r\nOK")
 | |
|             .finish()?;
 | |
|         let attached = res == 1;
 | |
|         if !attached {
 | |
|             return Ok(Status {
 | |
|                 attached,
 | |
|                 ip: None,
 | |
|                 gateway: None,
 | |
|                 dns: Vec::new(),
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CGPADDR")
 | |
|             .with_int_parameter(self.cid)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         let (_, ip1, _ip2) = CommandParser::parse(&buf[..n])
 | |
|             .expect_identifier(b"+CGPADDR: ")
 | |
|             .expect_int_parameter()
 | |
|             .expect_optional_string_parameter()
 | |
|             .expect_optional_string_parameter()
 | |
|             .expect_identifier(b"\r\nOK")
 | |
|             .finish()?;
 | |
| 
 | |
|         let ip = if let Some(ip) = ip1 {
 | |
|             let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?;
 | |
|             Some(ip)
 | |
|         } else {
 | |
|             None
 | |
|         };
 | |
| 
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CGCONTRDP")
 | |
|             .with_int_parameter(self.cid)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n])
 | |
|             .expect_identifier(b"+CGCONTRDP: ")
 | |
|             .expect_int_parameter()
 | |
|             .expect_optional_int_parameter()
 | |
|             .expect_optional_string_parameter()
 | |
|             .expect_optional_string_parameter()
 | |
|             .expect_optional_string_parameter()
 | |
|             .expect_optional_string_parameter()
 | |
|             .expect_optional_string_parameter()
 | |
|             .expect_optional_int_parameter()
 | |
|             .expect_optional_int_parameter()
 | |
|             .expect_optional_int_parameter()
 | |
|             .expect_optional_int_parameter()
 | |
|             .expect_optional_int_parameter()
 | |
|             .expect_identifier(b"\r\nOK")
 | |
|             .finish()?;
 | |
| 
 | |
|         let gateway = if let Some(ip) = gateway {
 | |
|             if ip.is_empty() {
 | |
|                 None
 | |
|             } else {
 | |
|                 Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
 | |
|             }
 | |
|         } else {
 | |
|             None
 | |
|         };
 | |
| 
 | |
|         let mut dns = Vec::new();
 | |
|         if let Some(ip) = dns1 {
 | |
|             dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
 | |
|                 .unwrap();
 | |
|         }
 | |
| 
 | |
|         if let Some(ip) = dns2 {
 | |
|             dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
 | |
|                 .unwrap();
 | |
|         }
 | |
| 
 | |
|         Ok(Status {
 | |
|             attached,
 | |
|             ip,
 | |
|             gateway,
 | |
|             dns,
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     async fn wait_attached(&self) -> Result<Status, Error> {
 | |
|         while !self.attached().await? {
 | |
|             Timer::after(Duration::from_secs(1)).await;
 | |
|         }
 | |
|         let status = self.status().await?;
 | |
|         Ok(status)
 | |
|     }
 | |
| 
 | |
|     /// Disable modem
 | |
|     pub async fn disable(&self) -> Result<(), Error> {
 | |
|         let mut cmd: [u8; 256] = [0; 256];
 | |
|         let mut buf: [u8; 256] = [0; 256];
 | |
| 
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CFUN")
 | |
|             .with_int_parameter(0)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
 | |
| 
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     /// Enable modem
 | |
|     pub async fn enable(&self) -> Result<(), Error> {
 | |
|         let mut cmd: [u8; 256] = [0; 256];
 | |
|         let mut buf: [u8; 256] = [0; 256];
 | |
| 
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("+CFUN")
 | |
|             .with_int_parameter(1)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
 | |
| 
 | |
|         // Make modem survive PDN detaches
 | |
|         let op = CommandBuilder::create_set(&mut cmd, true)
 | |
|             .named("%XPDNCFG")
 | |
|             .with_int_parameter(1)
 | |
|             .finish()
 | |
|             .map_err(|_| Error::BufferTooSmall)?;
 | |
|         let n = self.control.at_command(op, &mut buf).await;
 | |
|         CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     /// Run a control loop for this context, ensuring that reaattach is handled.
 | |
|     pub async fn run<F: Fn(&Status)>(&self, reattach: F) -> Result<(), Error> {
 | |
|         self.enable().await?;
 | |
|         let status = self.wait_attached().await?;
 | |
|         let mut fd = self.control.open_raw_socket().await;
 | |
|         reattach(&status);
 | |
| 
 | |
|         loop {
 | |
|             if !self.attached().await? {
 | |
|                 trace!("detached");
 | |
| 
 | |
|                 self.control.close_raw_socket(fd).await;
 | |
|                 let status = self.wait_attached().await?;
 | |
|                 trace!("attached");
 | |
|                 fd = self.control.open_raw_socket().await;
 | |
|                 reattach(&status);
 | |
|             }
 | |
|             Timer::after(Duration::from_secs(10)).await;
 | |
|         }
 | |
|     }
 | |
| }
 |