1use embassy_net_driver_channel as ch;
2use embassy_net_driver_channel::driver::{HardwareAddress, LinkState};
3use heapless::String;
4
5use crate::ioctl::Shared;
6use crate::proto::{self, CtrlMsg};
7
8#[derive(Copy, Clone, PartialEq, Eq, Debug)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11pub enum Error {
12 Failed(u32),
14 Timeout,
16 Internal,
18}
19
20pub struct Control<'a> {
22 state_ch: ch::StateRunner<'a>,
23 shared: &'a Shared,
24}
25
26#[allow(unused)]
28#[derive(Copy, Clone, PartialEq, Eq, Debug)]
29#[cfg_attr(feature = "defmt", derive(defmt::Format))]
30enum WifiMode {
31 None = 0,
33 Sta = 1,
35 Ap = 2,
37 ApSta = 3,
39}
40
41pub use proto::CtrlWifiSecProt as Security;
42
43#[derive(Clone, Debug)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46pub struct Status {
47 pub ssid: String<32>,
49 pub bssid: [u8; 6],
51 pub rssi: i32,
53 pub channel: u32,
55 pub security: Security,
57}
58
59macro_rules! ioctl {
60 ($self:ident, $req_variant:ident, $resp_variant:ident, $req:ident, $resp:ident) => {
61 let mut msg = proto::CtrlMsg {
62 msg_id: proto::CtrlMsgId::$req_variant as _,
63 msg_type: proto::CtrlMsgType::Req as _,
64 payload: Some(proto::CtrlMsgPayload::$req_variant($req)),
65 };
66 $self.ioctl(&mut msg).await?;
67 #[allow(unused_mut)]
68 let Some(proto::CtrlMsgPayload::$resp_variant(mut $resp)) = msg.payload
69 else {
70 warn!("unexpected response variant");
71 return Err(Error::Internal);
72 };
73 if $resp.resp != 0 {
74 return Err(Error::Failed($resp.resp));
75 }
76 };
77}
78
79impl<'a> Control<'a> {
80 pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self {
81 Self { state_ch, shared }
82 }
83
84 pub async fn init(&mut self) -> Result<(), Error> {
86 debug!("wait for init event...");
87 self.shared.init_wait().await;
88
89 debug!("set heartbeat");
90 self.set_heartbeat(10).await?;
91
92 debug!("set wifi mode");
93 self.set_wifi_mode(WifiMode::Sta as _).await?;
94
95 let mac_addr = self.get_mac_addr().await?;
96 debug!("mac addr: {:02x}", mac_addr);
97 self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr));
98
99 Ok(())
100 }
101
102 pub async fn get_status(&mut self) -> Result<Status, Error> {
104 let req = proto::CtrlMsgReqGetApConfig {};
105 ioctl!(self, ReqGetApConfig, RespGetApConfig, req, resp);
106 trim_nulls(&mut resp.ssid);
107 Ok(Status {
108 ssid: resp.ssid,
109 bssid: parse_mac(&resp.bssid)?,
110 rssi: resp.rssi as _,
111 channel: resp.chnl,
112 security: resp.sec_prot,
113 })
114 }
115
116 pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> {
118 let req = proto::CtrlMsgReqConnectAp {
119 ssid: unwrap!(String::try_from(ssid)),
120 pwd: unwrap!(String::try_from(password)),
121 bssid: String::new(),
122 listen_interval: 3,
123 is_wpa3_supported: true,
124 };
125 ioctl!(self, ReqConnectAp, RespConnectAp, req, resp);
126 self.state_ch.set_link_state(LinkState::Up);
127 Ok(())
128 }
129
130 pub async fn disconnect(&mut self) -> Result<(), Error> {
132 let req = proto::CtrlMsgReqGetStatus {};
133 ioctl!(self, ReqDisconnectAp, RespDisconnectAp, req, resp);
134 self.state_ch.set_link_state(LinkState::Down);
135 Ok(())
136 }
137
138 async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> {
140 let req = proto::CtrlMsgReqConfigHeartbeat { enable: true, duration };
141 ioctl!(self, ReqConfigHeartbeat, RespConfigHeartbeat, req, resp);
142 Ok(())
143 }
144
145 async fn get_mac_addr(&mut self) -> Result<[u8; 6], Error> {
146 let req = proto::CtrlMsgReqGetMacAddress {
147 mode: WifiMode::Sta as _,
148 };
149 ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp);
150 parse_mac(&resp.mac)
151 }
152
153 async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> {
154 let req = proto::CtrlMsgReqSetMode { mode };
155 ioctl!(self, ReqSetWifiMode, RespSetWifiMode, req, resp);
156
157 Ok(())
158 }
159
160 async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> {
161 debug!("ioctl req: {:?}", &msg);
162
163 let mut buf = [0u8; 128];
164
165 let req_len = noproto::write(msg, &mut buf).map_err(|_| {
166 warn!("failed to serialize control request");
167 Error::Internal
168 })?;
169
170 struct CancelOnDrop<'a>(&'a Shared);
171
172 impl CancelOnDrop<'_> {
173 fn defuse(self) {
174 core::mem::forget(self);
175 }
176 }
177
178 impl Drop for CancelOnDrop<'_> {
179 fn drop(&mut self) {
180 self.0.ioctl_cancel();
181 }
182 }
183
184 let ioctl = CancelOnDrop(self.shared);
185
186 let resp_len = ioctl.0.ioctl(&mut buf, req_len).await;
187
188 ioctl.defuse();
189
190 *msg = noproto::read(&buf[..resp_len]).map_err(|_| {
191 warn!("failed to serialize control request");
192 Error::Internal
193 })?;
194 debug!("ioctl resp: {:?}", msg);
195
196 Ok(())
197 }
198}
199
200fn parse_mac(mac: &str) -> Result<[u8; 6], Error> {
202 fn nibble_from_hex(b: u8) -> Result<u8, Error> {
203 match b {
204 b'0'..=b'9' => Ok(b - b'0'),
205 b'a'..=b'f' => Ok(b + 0xa - b'a'),
206 b'A'..=b'F' => Ok(b + 0xa - b'A'),
207 _ => {
208 warn!("invalid hex digit {}", b);
209 Err(Error::Internal)
210 }
211 }
212 }
213
214 let mac = mac.as_bytes();
215 let mut res = [0; 6];
216 if mac.len() != 17 {
217 warn!("unexpected MAC length");
218 return Err(Error::Internal);
219 }
220 for (i, b) in res.iter_mut().enumerate() {
221 *b = (nibble_from_hex(mac[i * 3])? << 4) | nibble_from_hex(mac[i * 3 + 1])?
222 }
223 Ok(res)
224}
225
226fn trim_nulls<const N: usize>(s: &mut String<N>) {
227 while s.chars().rev().next() == Some(0 as char) {
228 s.pop();
229 }
230}