From 623a7c02aa91772115673dc96d1e46587f4e00cf Mon Sep 17 00:00:00 2001 From: Mathias Koch Date: Fri, 9 Aug 2024 13:25:39 +0200 Subject: [PATCH] Add control entries to control AP (#86) --- src/asynch/control.rs | 346 +++++++++++++++++++++----------------- src/command/wifi/mod.rs | 82 ++++++++- src/command/wifi/types.rs | 16 +- src/lib.rs | 1 + src/options.rs | 161 ++++++++++++++++++ 5 files changed, 441 insertions(+), 165 deletions(-) create mode 100644 src/options.rs diff --git a/src/asynch/control.rs b/src/asynch/control.rs index 67d648b..df35972 100644 --- a/src/asynch/control.rs +++ b/src/asynch/control.rs @@ -21,6 +21,7 @@ use crate::command::ping::Ping; use crate::command::system::responses::LocalAddressResponse; use crate::command::system::types::InterfaceID; use crate::command::system::GetLocalAddress; +use crate::command::wifi::types::{IPv4Mode, PasskeyR}; use crate::command::wifi::{ExecWifiStationAction, GetWifiStatus, SetWifiStationConfig}; use crate::command::OnOff; use crate::command::{ @@ -47,17 +48,12 @@ use crate::command::{ }; use crate::connection::{DnsServers, StaticConfigV4, WiFiState}; use crate::error::Error; +use crate::options::{ConnectionOptions, HotspotOptions, WifiAuthentication}; use super::runner::{MAX_CMD_LEN, URC_SUBSCRIBERS}; use super::state::LinkState; use super::{state, UbloxUrc}; -enum WifiAuthentication<'a> { - None, - Wpa2Passphrase(&'a str), - Wpa2Psk(&'a [u8; 32]), -} - const CONFIG_ID: u8 = 0; pub(crate) struct ProxyClient<'a, const INGRESS_BUF_SIZE: usize> { @@ -96,12 +92,12 @@ impl<'a, const INGRESS_BUF_SIZE: usize> atat::asynch::AtatClient let len = cmd.write(&mut buf); if len < 50 { - debug!( + trace!( "Sending command: {:?}", atat::helpers::LossyStr(&buf[..len]) ); } else { - debug!("Sending command with long payload ({} bytes)", len); + trace!("Sending command with long payload ({} bytes)", len); } if let Some(cooldown) = self.cooldown_timer.take() { @@ -307,7 +303,11 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> Ok(()) } - async fn start_ap(&self, ssid: &str, _channel: u8) -> Result<(), Error> { + pub async fn start_ap( + &self, + options: ConnectionOptions<'_>, + configuration: HotspotOptions, + ) -> Result<(), Error> { self.state_ch.wait_for_initialized().await; // Deactivate network id 0 @@ -325,101 +325,118 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> }) .await?; - // // Disable DHCP Server (static IP address will be used) - // if options.ip.is_some() || options.subnet.is_some() || options.gateway.is_some() { - // (&self.at_client) - // .send_retry(&SetWifiAPConfig { - // ap_config_id: AccessPointId::Id0, - // ap_config_param: AccessPointConfig::IPv4Mode(IPv4Mode::Static), - // }) - // .await?; - // } - - // // Network IP address - // if let Some(ip) = options.ip { - // (&self.at_client) - // .send_retry(&SetWifiAPConfig { - // ap_config_id: AccessPointId::Id0, - // ap_config_param: AccessPointConfig::IPv4Address(ip), - // }) - // .await?; - // } - // // Network Subnet mask - // if let Some(subnet) = options.subnet { - // (&self.at_client) - // .send_retry(&SetWifiAPConfig { - // ap_config_id: AccessPointId::Id0, - // ap_config_param: AccessPointConfig::SubnetMask(subnet), - // }) - // .await?; - // } - // // Network Default gateway - // if let Some(gateway) = options.gateway { - // (&self.at_client) - // .send_retry(&SetWifiAPConfig { - // ap_config_id: AccessPointId::Id0, - // ap_config_param: AccessPointConfig::DefaultGateway(gateway), - // }) - // .await?; - // } - - // (&self.at_client) - // .send_retry(&SetWifiAPConfig { - // ap_config_id: AccessPointId::Id0, - // ap_config_param: AccessPointConfig::DHCPServer(true.into()), - // }) - // .await?; - - // Wifi part - // Set the Network SSID to connect to + // Disable DHCP Server (static IP address will be used) + if options.ip.is_some() || options.subnet.is_some() || options.gateway.is_some() { + (&self.at_client) + .send_retry(&SetWifiAPConfig { + ap_config_id: AccessPointId::Id0, + ap_config_param: AccessPointConfig::IPv4Mode(IPv4Mode::Static), + }) + .await?; + } + + // Network IP address + if let Some(ip) = options.ip { + (&self.at_client) + .send_retry(&SetWifiAPConfig { + ap_config_id: AccessPointId::Id0, + ap_config_param: AccessPointConfig::IPv4Address(ip), + }) + .await?; + } + // Network Subnet mask + if let Some(subnet) = options.subnet { + (&self.at_client) + .send_retry(&SetWifiAPConfig { + ap_config_id: AccessPointId::Id0, + ap_config_param: AccessPointConfig::SubnetMask(subnet), + }) + .await?; + } + // Network Default gateway + if let Some(gateway) = options.gateway { + (&self.at_client) + .send_retry(&SetWifiAPConfig { + ap_config_id: AccessPointId::Id0, + ap_config_param: AccessPointConfig::DefaultGateway(gateway), + }) + .await?; + } + (&self.at_client) .send_retry(&SetWifiAPConfig { ap_config_id: AccessPointId::Id0, - ap_config_param: AccessPointConfig::SSID( - heapless::String::try_from(ssid).map_err(|_| Error::Overflow)?, - ), + ap_config_param: AccessPointConfig::DHCPServer(configuration.dhcp_server.into()), }) .await?; - // if let Some(pass) = options.password.clone() { - // // Use WPA2 as authentication type - // (&self.at_client) - // .send_retry(&SetWifiAPConfig { - // ap_config_id: AccessPointId::Id0, - // ap_config_param: AccessPointConfig::SecurityMode( - // SecurityMode::Wpa2AesCcmp, - // SecurityModePSK::PSK, - // ), - // }) - // .await?; - - // // Input passphrase - // (&self.at_client) - // .send_retry(&SetWifiAPConfig { - // ap_config_id: AccessPointId::Id0, - // ap_config_param: AccessPointConfig::PSKPassphrase(PasskeyR::Passphrase(pass)), - // }) - // .await?; - // } else { + // Set the Network SSID to connect to (&self.at_client) .send_retry(&SetWifiAPConfig { ap_config_id: AccessPointId::Id0, - ap_config_param: AccessPointConfig::SecurityMode( - SecurityMode::Open, - SecurityModePSK::Open, - ), + ap_config_param: AccessPointConfig::SSID(options.ssid), }) .await?; - // } - // if let Some(channel) = configuration.channel { - // (&self.at_client) - // .send_retry(&SetWifiAPConfig { - // ap_config_id: AccessPointId::Id0, - // ap_config_param: AccessPointConfig::Channel(channel as u8), - // }) - // .await?; - // } + match options.auth { + WifiAuthentication::None => { + (&self.at_client) + .send_retry(&SetWifiAPConfig { + ap_config_id: AccessPointId::Id0, + ap_config_param: AccessPointConfig::SecurityMode( + SecurityMode::Open, + SecurityModePSK::Open, + ), + }) + .await?; + } + WifiAuthentication::Wpa2Passphrase(passphrase) => { + (&self.at_client) + .send_retry(&SetWifiAPConfig { + ap_config_id: AccessPointId::Id0, + ap_config_param: AccessPointConfig::SecurityMode( + SecurityMode::Wpa2AesCcmp, + SecurityModePSK::PSK, + ), + }) + .await?; + + // Input passphrase + (&self.at_client) + .send_retry(&SetWifiAPConfig { + ap_config_id: AccessPointId::Id0, + ap_config_param: AccessPointConfig::PSKPassphrase(PasskeyR::Passphrase( + // FIXME: + heapless::String::try_from(passphrase).unwrap(), + )), + }) + .await?; + } // WifiAuthentication::Wpa2Psk(_psk) => { + // unimplemented!() + // // (&self.at_client) + // // .send_retry(&SetWifiStationConfig { + // // config_id: CONFIG_ID, + // // config_param: WifiStationConfig::Authentication(Authentication::WpaWpa2Psk), + // // }) + // // .await?; + + // // (&self.at_client) + // // .send_retry(&SetWifiStationConfig { + // // config_id: CONFIG_ID, + // // config_param: WifiStationConfig::WpaPskOrPassphrase(todo!("hex values?!")), + // // }) + // // .await?; + // } + } + + if let Some(channel) = configuration.channel { + (&self.at_client) + .send_retry(&SetWifiAPConfig { + ap_config_id: AccessPointId::Id0, + ap_config_param: AccessPointConfig::Channel(channel as u8), + }) + .await?; + } (&self.at_client) .send_retry(&WifiAPAction { @@ -431,40 +448,18 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> Ok(()) } - /// Start open access point. - pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) -> Result<(), Error> { - self.start_ap(ssid, channel).await - } - - /// Start WPA2 protected access point. - pub async fn start_ap_wpa2( - &mut self, - ssid: &str, - _passphrase: &str, - channel: u8, - ) -> Result<(), Error> { - self.start_ap(ssid, channel).await - } - /// Closes access point. pub async fn close_ap(&self) -> Result<(), Error> { - todo!() + (&self.at_client) + .send_retry(&WifiAPAction { + ap_config_id: AccessPointId::Id0, + ap_action: AccessPointAction::Deactivate, + }) + .await?; + Ok(()) } - async fn join_sta(&self, ssid: &str, auth: WifiAuthentication<'_>) -> Result<(), Error> { - self.state_ch.wait_for_initialized().await; - - if matches!(self.get_wifi_status().await?, WifiStatusVal::Connected) { - // Wifi already connected. Check if the SSID is the same - let current_ssid = self.get_connected_ssid().await?; - if current_ssid.as_str() == ssid { - self.state_ch.set_should_connect(true); - return Ok(()); - } else { - self.leave().await?; - }; - } - + pub async fn peek_join_sta(&self, options: ConnectionOptions<'_>) -> Result<(), Error> { (&self.at_client) .send_retry(&ExecWifiStationAction { config_id: CONFIG_ID, @@ -482,11 +477,11 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> (&self.at_client) .send_retry(&SetWifiStationConfig { config_id: CONFIG_ID, - config_param: WifiStationConfig::SSID(ssid), + config_param: WifiStationConfig::SSID(options.ssid), }) .await?; - match auth { + match options.auth { WifiAuthentication::None => { (&self.at_client) .send_retry(&SetWifiStationConfig { @@ -509,23 +504,59 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> config_param: WifiStationConfig::WpaPskOrPassphrase(passphrase), }) .await?; - } - WifiAuthentication::Wpa2Psk(_psk) => { - unimplemented!() - // (&self.at_client) - // .send_retry(&SetWifiStationConfig { - // config_id: CONFIG_ID, - // config_param: WifiStationConfig::Authentication(Authentication::WpaWpa2Psk), - // }) - // .await?; - - // (&self.at_client) - // .send_retry(&SetWifiStationConfig { - // config_id: CONFIG_ID, - // config_param: WifiStationConfig::WpaPskOrPassphrase(todo!("hex values?!")), - // }) - // .await?; - } + } // WifiAuthentication::Wpa2Psk(_psk) => { + // unimplemented!() + // // (&self.at_client) + // // .send_retry(&SetWifiStationConfig { + // // config_id: CONFIG_ID, + // // config_param: WifiStationConfig::Authentication(Authentication::WpaWpa2Psk), + // // }) + // // .await?; + + // // (&self.at_client) + // // .send_retry(&SetWifiStationConfig { + // // config_id: CONFIG_ID, + // // config_param: WifiStationConfig::WpaPskOrPassphrase(todo!("hex values?!")), + // // }) + // // .await?; + // } + } + + if options.ip.is_some() || options.subnet.is_some() || options.gateway.is_some() { + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::IPv4Mode(IPv4Mode::Static), + }) + .await?; + } + + // Network IP address + if let Some(ip) = options.ip { + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::IPv4Address(ip), + }) + .await?; + } + // Network Subnet mask + if let Some(subnet) = options.subnet { + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::SubnetMask(subnet), + }) + .await?; + } + // Network Default gateway + if let Some(gateway) = options.gateway { + (&self.at_client) + .send_retry(&SetWifiStationConfig { + config_id: CONFIG_ID, + config_param: WifiStationConfig::DefaultGateway(gateway), + }) + .await?; } (&self.at_client) @@ -535,26 +566,31 @@ impl<'a, const INGRESS_BUF_SIZE: usize, const URC_CAPACITY: usize> }) .await?; - self.wait_for_join(ssid, Duration::from_secs(20)).await?; - self.state_ch.set_should_connect(true); + self.wait_for_join(options.ssid, Duration::from_secs(20)) + .await?; Ok(()) } - /// Join an unprotected network with the provided ssid. - pub async fn join_open(&self, ssid: &str) -> Result<(), Error> { - self.join_sta(ssid, WifiAuthentication::None).await - } + pub async fn join_sta(&self, options: ConnectionOptions<'_>) -> Result<(), Error> { + self.state_ch.wait_for_initialized().await; - /// Join a protected network with the provided ssid and passphrase. - pub async fn join_wpa2(&self, ssid: &str, passphrase: &str) -> Result<(), Error> { - self.join_sta(ssid, WifiAuthentication::Wpa2Passphrase(passphrase)) - .await - } + if matches!(self.get_wifi_status().await?, WifiStatusVal::Connected) { + // Wifi already connected. Check if the SSID is the same + let current_ssid = self.get_connected_ssid().await?; + if current_ssid.as_str() == options.ssid { + self.state_ch.set_should_connect(true); + return Ok(()); + } else { + self.leave().await?; + }; + } + + self.peek_join_sta(options).await?; - /// Join a protected network with the provided ssid and precomputed PSK. - pub async fn join_wpa2_psk(&mut self, ssid: &str, psk: &[u8; 32]) -> Result<(), Error> { - self.join_sta(ssid, WifiAuthentication::Wpa2Psk(psk)).await + self.state_ch.set_should_connect(true); + + Ok(()) } /// Leave the wifi, with which we are currently associated. diff --git a/src/command/wifi/mod.rs b/src/command/wifi/mod.rs index 9dd319a..1062f5f 100644 --- a/src/command/wifi/mod.rs +++ b/src/command/wifi/mod.rs @@ -211,13 +211,83 @@ pub struct GetWatchdogConfig { /// be activated (Wi-Fi Access Point Configuration Action +UWAPCA) before using. /// The command will generate an error if the configuration id is active. See "Wi-Fi Access Point Configuration /// Action +UWAPCA" for instructions on how to deactivate a configuration. -#[derive(Clone, AtatCmd)] -#[at_cmd("+UWAPC", NoResponse, timeout_ms = 1000)] -pub struct SetWifiAPConfig { - #[at_arg(position = 0)] +#[derive(Clone)] +// #[at_cmd("+UWAPC", NoResponse, timeout_ms = 1000)] +pub struct SetWifiAPConfig<'a> { + // #[at_arg(position = 0)] pub ap_config_id: AccessPointId, - #[at_arg(position = 1)] - pub ap_config_param: AccessPointConfig, + // #[at_arg(position = 1)] + pub ap_config_param: AccessPointConfig<'a>, +} + +// FIXME: +#[automatically_derived] +impl<'a> atat::AtatLen for SetWifiAPConfig<'a> { + const LEN: usize = + as atat::AtatLen>::LEN + ::LEN + 1usize; +} +const ATAT_SETWIFIAPCONFIG_LEN: usize = + as atat::AtatLen>::LEN + ::LEN + 1usize; +#[automatically_derived] +impl<'a> atat::AtatCmd for SetWifiAPConfig<'a> { + type Response = NoResponse; + const MAX_TIMEOUT_MS: u32 = 1000u32; + #[inline] + fn parse( + &self, + res: Result<&[u8], atat::InternalError>, + ) -> core::result::Result { + match res { + Ok(resp) => { + atat::serde_at::from_slice::(resp).map_err(|_e| atat::Error::Parse) + } + Err(e) => Err(e.into()), + } + } + + const MAX_LEN: usize = ATAT_SETWIFIAPCONFIG_LEN + 12usize; + + fn write(&self, buf: &mut [u8]) -> usize { + match atat::serde_at::to_slice( + self, + "+UWAPC", + buf, + atat::serde_at::SerializeOptions { + value_sep: true, + cmd_prefix: "AT", + termination: "\r\n", + quote_escape_strings: true, + }, + ) { + Ok(s) => s, + Err(_) => panic!("Failed to serialize command"), + } + } +} +#[automatically_derived] +impl<'a> atat::serde_at::serde::Serialize for SetWifiAPConfig<'a> { + #[inline] + fn serialize(&self, serializer: S) -> core::result::Result + where + S: atat::serde_at::serde::Serializer, + { + let mut serde_state = atat::serde_at::serde::Serializer::serialize_struct( + serializer, + "SetWifiAPConfig", + 2usize, + )?; + atat::serde_at::serde::ser::SerializeStruct::serialize_field( + &mut serde_state, + "ap_config_id", + &self.ap_config_id, + )?; + atat::serde_at::serde::ser::SerializeStruct::serialize_field( + &mut serde_state, + "ap_config_param", + &self.ap_config_param, + )?; + atat::serde_at::serde::ser::SerializeStruct::end(serde_state) + } } /// 7.8 Wi-Fi Access point configuration +UWAPC diff --git a/src/command/wifi/types.rs b/src/command/wifi/types.rs index 25097a5..01774bc 100644 --- a/src/command/wifi/types.rs +++ b/src/command/wifi/types.rs @@ -889,7 +889,7 @@ pub enum AccessPointId { } #[derive(Clone, PartialEq, AtatEnum)] -pub enum AccessPointConfig { +pub enum AccessPointConfig<'a> { /// decides if the access point is active on start up. /// - 0 (default): Inactive /// - 1: active @@ -898,7 +898,7 @@ pub enum AccessPointConfig { /// SSID - is the Service Set identification of the access /// point. The factory-programmed value is ("UBXWifi"). #[at_arg(value = 2)] - SSID(String<64>), + SSID(#[at_arg(len = 64)] &'a str), /// is the channel. Factory programmed value is 6. #[at_arg(value = 4)] Channel(u8), @@ -982,12 +982,20 @@ pub enum AccessPointConfig { /// stations that is allowed to connect or 0 to allow all. The factory /// default is 0. #[at_arg(value = 19)] - WhiteList(String<20>, String<20>, String<20>), + WhiteList( + #[at_arg(len = 20)] &'a str, + #[at_arg(len = 20)] &'a str, + #[at_arg(len = 20)] &'a str, + ), /// Black List - ... List of MAC addresses of /// stations that will be rejected or 0 to not reject any. The factory /// default is 0. #[at_arg(value = 20)] - BlackList(String<20>, String<20>, String<20>), + BlackList( + #[at_arg(len = 20)] &'a str, + #[at_arg(len = 20)] &'a str, + #[at_arg(len = 20)] &'a str, + ), /// IPv4 Mode - to set the way to retrieve an IP address /// - 1:(default) Static #[at_arg(value = 100)] diff --git a/src/lib.rs b/src/lib.rs index 283875a..10c3a2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ compile_error!("No module feature activated. You must activate exactly one of th mod fmt; pub mod asynch; +pub mod options; mod config; mod connection; diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 0000000..c679e04 --- /dev/null +++ b/src/options.rs @@ -0,0 +1,161 @@ +use no_std_net::Ipv4Addr; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +/// Channel to broadcast wireless hotspot on. +pub enum Channel { + /// Channel 1 + One = 1, + /// Channel 2 + Two = 2, + /// Channel 3 + Three = 3, + /// Channel 4 + Four = 4, + /// Channel 5 + Five = 5, + /// Channel 6 + Six = 6, +} + +#[allow(dead_code)] +#[derive(Debug)] +/// Band type of wireless hotspot. +pub enum Band { + /// Band `A` + A, + /// Band `BG` + Bg, +} + +#[derive(Debug, Default)] +pub struct HotspotOptions { + pub(crate) channel: Option, + pub(crate) band: Option, + pub(crate) dhcp_server: bool, +} + +impl HotspotOptions { + pub fn new() -> Self { + Self { + channel: Some(Channel::One), + band: Some(Band::Bg), + dhcp_server: true, + } + } + + pub fn channel(mut self, channel: Channel) -> Self { + self.channel = Some(channel); + self + } + + pub fn band(mut self, band: Band) -> Self { + self.band = Some(band); + self + } + + pub fn dhcp_server(mut self, dhcp_server: bool) -> Self { + self.dhcp_server = dhcp_server; + self + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WifiAuthentication<'a> { + #[default] + None, + Wpa2Passphrase(&'a str), + // Wpa2Psk(&'a [u8; 32]), +} + +impl<'a> From<&'a str> for WifiAuthentication<'a> { + fn from(s: &'a str) -> Self { + Self::Wpa2Passphrase(s) + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] + +pub struct ConnectionOptions<'a> { + pub ssid: &'a str, + pub auth: WifiAuthentication<'a>, + + #[cfg_attr(feature = "defmt", defmt(Debug2Format))] + pub ip: Option, + #[cfg_attr(feature = "defmt", defmt(Debug2Format))] + pub subnet: Option, + #[cfg_attr(feature = "defmt", defmt(Debug2Format))] + pub gateway: Option, +} + +impl<'a> ConnectionOptions<'a> { + pub fn new(ssid: &'a str) -> Self { + Self { + ssid, + ..Default::default() + } + } + + pub fn no_auth(mut self) -> Self { + self.auth = WifiAuthentication::None; + self + } + + pub fn wpa2_passphrase(mut self, password: &'a str) -> Self { + self.auth = WifiAuthentication::Wpa2Passphrase(password); + self + } + + pub fn ip_address(mut self, ip_addr: Ipv4Addr) -> Self { + self.ip = Some(ip_addr); + self.subnet = if let Some(subnet) = self.subnet { + Some(subnet) + } else { + Some(Ipv4Addr::new(255, 255, 255, 0)) + }; + + self.gateway = if let Some(gateway) = self.gateway { + Some(gateway) + } else { + Some(Ipv4Addr::new(192, 168, 2, 1)) + }; + self + } + + pub fn subnet_address(mut self, subnet_addr: Ipv4Addr) -> Self { + self.subnet = Some(subnet_addr); + + self.ip = if let Some(ip) = self.ip { + Some(ip) + } else { + Some(Ipv4Addr::new(192, 168, 2, 1)) + }; + + self.gateway = if let Some(gateway) = self.gateway { + Some(gateway) + } else { + Some(Ipv4Addr::new(192, 168, 2, 1)) + }; + + self + } + + pub fn gateway_address(mut self, gateway_addr: Ipv4Addr) -> Self { + self.gateway = Some(gateway_addr); + + self.subnet = if let Some(subnet) = self.subnet { + Some(subnet) + } else { + Some(Ipv4Addr::new(255, 255, 255, 0)) + }; + + self.ip = if let Some(ip) = self.ip { + Some(ip) + } else { + Some(Ipv4Addr::new(192, 168, 2, 1)) + }; + self + } +}