aboutsummaryrefslogtreecommitdiff
path: root/embassy-net-esp-hosted/src/control.rs
diff options
context:
space:
mode:
authorRaul Alimbekov <[email protected]>2025-12-16 09:05:22 +0300
committerGitHub <[email protected]>2025-12-16 09:05:22 +0300
commitc9a04b4b732b7a3b696eb8223664c1a7942b1875 (patch)
tree6dbe5c02e66eed8d8762f13f95afd24f8db2b38c /embassy-net-esp-hosted/src/control.rs
parentcde24a3ef1117653ba5ed4184102b33f745782fb (diff)
parent5ae6e060ec1c90561719aabdc29d5b6e7b8b0a82 (diff)
Merge branch 'main' into main
Diffstat (limited to 'embassy-net-esp-hosted/src/control.rs')
-rw-r--r--embassy-net-esp-hosted/src/control.rs107
1 files changed, 79 insertions, 28 deletions
diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs
index b1838a425..eb79593f6 100644
--- a/embassy-net-esp-hosted/src/control.rs
+++ b/embassy-net-esp-hosted/src/control.rs
@@ -1,6 +1,7 @@
1use embassy_net_driver_channel as ch; 1use embassy_net_driver_channel as ch;
2use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; 2use embassy_net_driver_channel::driver::{HardwareAddress, LinkState};
3use heapless::String; 3use heapless::String;
4use micropb::{MessageDecode, MessageEncode, PbEncoder};
4 5
5use crate::ioctl::Shared; 6use crate::ioctl::Shared;
6use crate::proto::{self, CtrlMsg}; 7use crate::proto::{self, CtrlMsg};
@@ -38,7 +39,7 @@ enum WifiMode {
38 ApSta = 3, 39 ApSta = 3,
39} 40}
40 41
41pub use proto::CtrlWifiSecProt as Security; 42pub use proto::Ctrl_WifiSecProt as Security;
42 43
43/// WiFi status. 44/// WiFi status.
44#[derive(Clone, Debug)] 45#[derive(Clone, Debug)]
@@ -59,19 +60,20 @@ pub struct Status {
59macro_rules! ioctl { 60macro_rules! ioctl {
60 ($self:ident, $req_variant:ident, $resp_variant:ident, $req:ident, $resp:ident) => { 61 ($self:ident, $req_variant:ident, $resp_variant:ident, $req:ident, $resp:ident) => {
61 let mut msg = proto::CtrlMsg { 62 let mut msg = proto::CtrlMsg {
62 msg_id: proto::CtrlMsgId::$req_variant as _, 63 msg_id: proto::CtrlMsgId::$req_variant,
63 msg_type: proto::CtrlMsgType::Req as _, 64 msg_type: proto::CtrlMsgType::Req,
64 payload: Some(proto::CtrlMsgPayload::$req_variant($req)), 65 payload: Some(proto::CtrlMsg_::Payload::$req_variant($req)),
66 req_resp_type: 0,
67 uid: 0,
65 }; 68 };
66 $self.ioctl(&mut msg).await?; 69 $self.ioctl(&mut msg).await?;
67 #[allow(unused_mut)] 70 #[allow(unused_mut)]
68 let Some(proto::CtrlMsgPayload::$resp_variant(mut $resp)) = msg.payload 71 let Some(proto::CtrlMsg_::Payload::$resp_variant(mut $resp)) = msg.payload else {
69 else {
70 warn!("unexpected response variant"); 72 warn!("unexpected response variant");
71 return Err(Error::Internal); 73 return Err(Error::Internal);
72 }; 74 };
73 if $resp.resp != 0 { 75 if $resp.resp != 0 {
74 return Err(Error::Failed($resp.resp)); 76 return Err(Error::Failed($resp.resp as u32));
75 } 77 }
76 }; 78 };
77} 79}
@@ -101,57 +103,107 @@ impl<'a> Control<'a> {
101 103
102 /// Get the current status. 104 /// Get the current status.
103 pub async fn get_status(&mut self) -> Result<Status, Error> { 105 pub async fn get_status(&mut self) -> Result<Status, Error> {
104 let req = proto::CtrlMsgReqGetApConfig {}; 106 let req = proto::CtrlMsg_Req_GetAPConfig {};
105 ioctl!(self, ReqGetApConfig, RespGetApConfig, req, resp); 107 ioctl!(self, ReqGetApConfig, RespGetApConfig, req, resp);
106 trim_nulls(&mut resp.ssid); 108 let ssid = core::str::from_utf8(&resp.ssid).map_err(|_| Error::Internal)?;
109 let ssid = String::try_from(ssid.trim_end_matches('\0')).map_err(|_| Error::Internal)?;
110 let bssid_str = core::str::from_utf8(&resp.bssid).map_err(|_| Error::Internal)?;
107 Ok(Status { 111 Ok(Status {
108 ssid: resp.ssid, 112 ssid,
109 bssid: parse_mac(&resp.bssid)?, 113 bssid: parse_mac(bssid_str)?,
110 rssi: resp.rssi as _, 114 rssi: resp.rssi as _,
111 channel: resp.chnl, 115 channel: resp.chnl as u32,
112 security: resp.sec_prot, 116 security: resp.sec_prot,
113 }) 117 })
114 } 118 }
115 119
116 /// Connect to the network identified by ssid using the provided password. 120 /// Connect to the network identified by ssid using the provided password.
117 pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> { 121 pub async fn connect(&mut self, ssid: &str, password: &str) -> Result<(), Error> {
118 let req = proto::CtrlMsgReqConnectAp { 122 const WIFI_BAND_MODE_AUTO: i32 = 3; // 2.4GHz + 5GHz
123
124 let req = proto::CtrlMsg_Req_ConnectAP {
119 ssid: unwrap!(String::try_from(ssid)), 125 ssid: unwrap!(String::try_from(ssid)),
120 pwd: unwrap!(String::try_from(password)), 126 pwd: unwrap!(String::try_from(password)),
121 bssid: String::new(), 127 bssid: String::new(),
122 listen_interval: 3, 128 listen_interval: 3,
123 is_wpa3_supported: true, 129 is_wpa3_supported: true,
130 band_mode: WIFI_BAND_MODE_AUTO,
124 }; 131 };
125 ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); 132 ioctl!(self, ReqConnectAp, RespConnectAp, req, resp);
133
134 // TODO: in newer esp-hosted firmwares that added EventStationConnectedToAp
135 // the connect ioctl seems to be async, so we shouldn't immediately set LinkState::Up here.
126 self.state_ch.set_link_state(LinkState::Up); 136 self.state_ch.set_link_state(LinkState::Up);
137
127 Ok(()) 138 Ok(())
128 } 139 }
129 140
130 /// Disconnect from any currently connected network. 141 /// Disconnect from any currently connected network.
131 pub async fn disconnect(&mut self) -> Result<(), Error> { 142 pub async fn disconnect(&mut self) -> Result<(), Error> {
132 let req = proto::CtrlMsgReqGetStatus {}; 143 let req = proto::CtrlMsg_Req_GetStatus {};
133 ioctl!(self, ReqDisconnectAp, RespDisconnectAp, req, resp); 144 ioctl!(self, ReqDisconnectAp, RespDisconnectAp, req, resp);
134 self.state_ch.set_link_state(LinkState::Down); 145 self.state_ch.set_link_state(LinkState::Down);
135 Ok(()) 146 Ok(())
136 } 147 }
137 148
149 /// Initiate a firmware update.
150 pub async fn ota_begin(&mut self) -> Result<(), Error> {
151 let req = proto::CtrlMsg_Req_OTABegin {};
152 ioctl!(self, ReqOtaBegin, RespOtaBegin, req, resp);
153 Ok(())
154 }
155
156 /// Write slice of firmware to a device.
157 ///
158 /// [`ota_begin`] must be called first.
159 ///
160 /// The slice is split into chunks that can be sent across
161 /// the ioctl protocol to the wifi adapter.
162 pub async fn ota_write(&mut self, data: &[u8]) -> Result<(), Error> {
163 for chunk in data.chunks(256) {
164 let req = proto::CtrlMsg_Req_OTAWrite {
165 ota_data: heapless::Vec::from_slice(chunk).unwrap(),
166 };
167 ioctl!(self, ReqOtaWrite, RespOtaWrite, req, resp);
168 }
169 Ok(())
170 }
171
172 /// End the OTA session.
173 ///
174 /// [`ota_begin`] must be called first.
175 ///
176 /// NOTE: Will reset the wifi adapter after 5 seconds.
177 pub async fn ota_end(&mut self) -> Result<(), Error> {
178 let req = proto::CtrlMsg_Req_OTAEnd {};
179 ioctl!(self, ReqOtaEnd, RespOtaEnd, req, resp);
180 self.shared.ota_done();
181 // Wait for re-init
182 self.init().await?;
183 Ok(())
184 }
185
138 /// duration in seconds, clamped to [10, 3600] 186 /// duration in seconds, clamped to [10, 3600]
139 async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> { 187 async fn set_heartbeat(&mut self, duration: u32) -> Result<(), Error> {
140 let req = proto::CtrlMsgReqConfigHeartbeat { enable: true, duration }; 188 let req = proto::CtrlMsg_Req_ConfigHeartbeat {
189 enable: true,
190 duration: duration as i32,
191 };
141 ioctl!(self, ReqConfigHeartbeat, RespConfigHeartbeat, req, resp); 192 ioctl!(self, ReqConfigHeartbeat, RespConfigHeartbeat, req, resp);
142 Ok(()) 193 Ok(())
143 } 194 }
144 195
145 async fn get_mac_addr(&mut self) -> Result<[u8; 6], Error> { 196 async fn get_mac_addr(&mut self) -> Result<[u8; 6], Error> {
146 let req = proto::CtrlMsgReqGetMacAddress { 197 let req = proto::CtrlMsg_Req_GetMacAddress {
147 mode: WifiMode::Sta as _, 198 mode: WifiMode::Sta as _,
148 }; 199 };
149 ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp); 200 ioctl!(self, ReqGetMacAddress, RespGetMacAddress, req, resp);
150 parse_mac(&resp.mac) 201 let mac_str = core::str::from_utf8(&resp.mac).map_err(|_| Error::Internal)?;
202 parse_mac(mac_str)
151 } 203 }
152 204
153 async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> { 205 async fn set_wifi_mode(&mut self, mode: u32) -> Result<(), Error> {
154 let req = proto::CtrlMsgReqSetMode { mode }; 206 let req = proto::CtrlMsg_Req_SetMode { mode: mode as i32 };
155 ioctl!(self, ReqSetWifiMode, RespSetWifiMode, req, resp); 207 ioctl!(self, ReqSetWifiMode, RespSetWifiMode, req, resp);
156 208
157 Ok(()) 209 Ok(())
@@ -160,12 +212,17 @@ impl<'a> Control<'a> {
160 async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> { 212 async fn ioctl(&mut self, msg: &mut CtrlMsg) -> Result<(), Error> {
161 debug!("ioctl req: {:?}", &msg); 213 debug!("ioctl req: {:?}", &msg);
162 214
163 let mut buf = [0u8; 128]; 215 // Theoretical max overhead is 29 bytes. Biggest message is OTA write with 256 bytes.
216 let mut buf = [0u8; 256 + 29];
217 let buf_len = buf.len();
164 218
165 let req_len = noproto::write(msg, &mut buf).map_err(|_| { 219 let mut encoder = PbEncoder::new(&mut buf[..]);
220 msg.encode(&mut encoder).map_err(|_| {
166 warn!("failed to serialize control request"); 221 warn!("failed to serialize control request");
167 Error::Internal 222 Error::Internal
168 })?; 223 })?;
224 let remaining = encoder.into_writer();
225 let req_len = buf_len - remaining.len();
169 226
170 struct CancelOnDrop<'a>(&'a Shared); 227 struct CancelOnDrop<'a>(&'a Shared);
171 228
@@ -187,8 +244,8 @@ impl<'a> Control<'a> {
187 244
188 ioctl.defuse(); 245 ioctl.defuse();
189 246
190 *msg = noproto::read(&buf[..resp_len]).map_err(|_| { 247 msg.decode_from_bytes(&buf[..resp_len]).map_err(|_| {
191 warn!("failed to serialize control request"); 248 warn!("failed to deserialize control response");
192 Error::Internal 249 Error::Internal
193 })?; 250 })?;
194 debug!("ioctl resp: {:?}", msg); 251 debug!("ioctl resp: {:?}", msg);
@@ -222,9 +279,3 @@ fn parse_mac(mac: &str) -> Result<[u8; 6], Error> {
222 } 279 }
223 Ok(res) 280 Ok(res)
224} 281}
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}