diff options
Diffstat (limited to 'embassy-net-esp-hosted/src/control.rs')
| -rw-r--r-- | embassy-net-esp-hosted/src/control.rs | 107 |
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 @@ | |||
| 1 | use embassy_net_driver_channel as ch; | 1 | use embassy_net_driver_channel as ch; |
| 2 | use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; | 2 | use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; |
| 3 | use heapless::String; | 3 | use heapless::String; |
| 4 | use micropb::{MessageDecode, MessageEncode, PbEncoder}; | ||
| 4 | 5 | ||
| 5 | use crate::ioctl::Shared; | 6 | use crate::ioctl::Shared; |
| 6 | use crate::proto::{self, CtrlMsg}; | 7 | use crate::proto::{self, CtrlMsg}; |
| @@ -38,7 +39,7 @@ enum WifiMode { | |||
| 38 | ApSta = 3, | 39 | ApSta = 3, |
| 39 | } | 40 | } |
| 40 | 41 | ||
| 41 | pub use proto::CtrlWifiSecProt as Security; | 42 | pub 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 { | |||
| 59 | macro_rules! ioctl { | 60 | macro_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 | |||
| 226 | fn trim_nulls<const N: usize>(s: &mut String<N>) { | ||
| 227 | while s.chars().rev().next() == Some(0 as char) { | ||
| 228 | s.pop(); | ||
| 229 | } | ||
| 230 | } | ||
