embassy_net_esp_hosted/
control.rs

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/// Errors reported by control.
9#[derive(Copy, Clone, PartialEq, Eq, Debug)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11pub enum Error {
12    /// The operation failed with the given error code.
13    Failed(u32),
14    /// The operation timed out.
15    Timeout,
16    /// Internal error.
17    Internal,
18}
19
20/// Handle for managing the network and WiFI state.
21pub struct Control<'a> {
22    state_ch: ch::StateRunner<'a>,
23    shared: &'a Shared,
24}
25
26/// WiFi mode.
27#[allow(unused)]
28#[derive(Copy, Clone, PartialEq, Eq, Debug)]
29#[cfg_attr(feature = "defmt", derive(defmt::Format))]
30enum WifiMode {
31    /// No mode.
32    None = 0,
33    /// Client station.
34    Sta = 1,
35    /// Access point mode.
36    Ap = 2,
37    /// Repeater mode.
38    ApSta = 3,
39}
40
41pub use proto::CtrlWifiSecProt as Security;
42
43/// WiFi status.
44#[derive(Clone, Debug)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46pub struct Status {
47    /// Service Set Identifier.
48    pub ssid: String<32>,
49    /// Basic Service Set Identifier.
50    pub bssid: [u8; 6],
51    /// Received Signal Strength Indicator.
52    pub rssi: i32,
53    /// WiFi channel.
54    pub channel: u32,
55    /// Security mode.
56    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    /// Initialize device.
85    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    /// Get the current status.
103    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    /// Connect to the network identified by ssid using the provided password.
117    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    /// Disconnect from any currently connected network.
131    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    /// duration in seconds, clamped to [10, 3600]
139    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
200// WHY IS THIS A STRING? WHYYYY
201fn 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}