wip: esp-hosted net driver.
This commit is contained in:
		
							
								
								
									
										20
									
								
								embassy-net-esp-hosted/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								embassy-net-esp-hosted/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| [package] | ||||
| name = "embassy-net-esp-hosted" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| [dependencies] | ||||
| defmt = { version = "0.3", optional = true } | ||||
| log = { version = "0.4.14", optional = true } | ||||
|  | ||||
| embassy-time = { version = "0.1.0", path = "../embassy-time" } | ||||
| embassy-sync = { version = "0.2.0", path = "../embassy-sync"} | ||||
| embassy-futures = { version = "0.1.0", path = "../embassy-futures"} | ||||
| embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} | ||||
|  | ||||
| embedded-hal = { version = "1.0.0-alpha.10" } | ||||
| embedded-hal-async = { version = "=0.2.0-alpha.1" } | ||||
|  | ||||
| noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] } | ||||
| #noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] } | ||||
| heapless = "0.7.16" | ||||
							
								
								
									
										141
									
								
								embassy-net-esp-hosted/src/control.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								embassy-net-esp-hosted/src/control.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| use ch::driver::LinkState; | ||||
| use defmt::Debug2Format; | ||||
| use embassy_net_driver_channel as ch; | ||||
| use heapless::String; | ||||
|  | ||||
| use crate::ioctl::IoctlState; | ||||
| use crate::proto::{self, CtrlMsg}; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Error { | ||||
|     pub status: u32, | ||||
| } | ||||
|  | ||||
| pub struct Control<'a> { | ||||
|     state_ch: ch::StateRunner<'a>, | ||||
|     ioctl_state: &'a IoctlState, | ||||
| } | ||||
|  | ||||
| enum WifiMode { | ||||
|     None = 0, | ||||
|     Sta = 1, | ||||
|     Ap = 2, | ||||
|     ApSta = 3, | ||||
| } | ||||
|  | ||||
| impl<'a> Control<'a> { | ||||
|     pub(crate) fn new(state_ch: ch::StateRunner<'a>, ioctl_state: &'a IoctlState) -> Self { | ||||
|         Self { state_ch, ioctl_state } | ||||
|     } | ||||
|  | ||||
|     pub async fn init(&mut self) { | ||||
|         debug!("set wifi mode"); | ||||
|         self.set_wifi_mode(WifiMode::Sta as _).await; | ||||
|         let mac_addr = self.get_mac_addr().await; | ||||
|         debug!("mac addr: {:02x}", mac_addr); | ||||
|         self.state_ch.set_ethernet_address(mac_addr); | ||||
|     } | ||||
|  | ||||
|     pub async fn join(&mut self, ssid: &str, password: &str) { | ||||
|         let req = proto::CtrlMsg { | ||||
|             msg_id: proto::CtrlMsgId::ReqConnectAp as _, | ||||
|             msg_type: proto::CtrlMsgType::Req as _, | ||||
|             payload: Some(proto::CtrlMsgPayload::ReqConnectAp(proto::CtrlMsgReqConnectAp { | ||||
|                 ssid: String::from(ssid), | ||||
|                 pwd: String::from(password), | ||||
|                 bssid: String::new(), | ||||
|                 listen_interval: 3, | ||||
|                 is_wpa3_supported: false, | ||||
|             })), | ||||
|         }; | ||||
|         let resp = self.ioctl(req).await; | ||||
|         let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; | ||||
|         debug!("======= {:?}", Debug2Format(&resp)); | ||||
|         assert_eq!(resp.resp, 0); | ||||
|         self.state_ch.set_link_state(LinkState::Up); | ||||
|     } | ||||
|  | ||||
|     async fn get_mac_addr(&mut self) -> [u8; 6] { | ||||
|         let req = proto::CtrlMsg { | ||||
|             msg_id: proto::CtrlMsgId::ReqGetMacAddress as _, | ||||
|             msg_type: proto::CtrlMsgType::Req as _, | ||||
|             payload: Some(proto::CtrlMsgPayload::ReqGetMacAddress( | ||||
|                 proto::CtrlMsgReqGetMacAddress { | ||||
|                     mode: WifiMode::Sta as _, | ||||
|                 }, | ||||
|             )), | ||||
|         }; | ||||
|         let resp = self.ioctl(req).await; | ||||
|         let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; | ||||
|         assert_eq!(resp.resp, 0); | ||||
|  | ||||
|         // WHY IS THIS A STRING? WHYYYY | ||||
|         fn nibble_from_hex(b: u8) -> u8 { | ||||
|             match b { | ||||
|                 b'0'..=b'9' => b - b'0', | ||||
|                 b'a'..=b'f' => b + 0xa - b'a', | ||||
|                 b'A'..=b'F' => b + 0xa - b'A', | ||||
|                 _ => panic!("invalid hex digit {}", b), | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let mac = resp.mac.as_bytes(); | ||||
|         let mut res = [0; 6]; | ||||
|         assert_eq!(mac.len(), 17); | ||||
|         for (i, b) in res.iter_mut().enumerate() { | ||||
|             *b = (nibble_from_hex(mac[i * 3]) << 4) | nibble_from_hex(mac[i * 3 + 1]) | ||||
|         } | ||||
|         res | ||||
|     } | ||||
|  | ||||
|     async fn get_wifi_mode(&mut self) -> u32 { | ||||
|         let req = proto::CtrlMsg { | ||||
|             msg_id: proto::CtrlMsgId::ReqGetWifiMode as _, | ||||
|             msg_type: proto::CtrlMsgType::Req as _, | ||||
|             payload: Some(proto::CtrlMsgPayload::ReqGetWifiMode(proto::CtrlMsgReqGetMode {})), | ||||
|         }; | ||||
|         let resp = self.ioctl(req).await; | ||||
|         let proto::CtrlMsgPayload::RespGetWifiMode(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; | ||||
|         assert_eq!(resp.resp, 0); | ||||
|         resp.mode | ||||
|     } | ||||
|  | ||||
|     async fn set_wifi_mode(&mut self, mode: u32) { | ||||
|         let req = proto::CtrlMsg { | ||||
|             msg_id: proto::CtrlMsgId::ReqSetWifiMode as _, | ||||
|             msg_type: proto::CtrlMsgType::Req as _, | ||||
|             payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })), | ||||
|         }; | ||||
|         let resp = self.ioctl(req).await; | ||||
|         let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else { panic!("unexpected resp") }; | ||||
|         assert_eq!(resp.resp, 0); | ||||
|     } | ||||
|  | ||||
|     async fn ioctl(&mut self, req: CtrlMsg) -> CtrlMsg { | ||||
|         let mut buf = [0u8; 128]; | ||||
|  | ||||
|         let req_len = noproto::write(&req, &mut buf).unwrap(); | ||||
|  | ||||
|         struct CancelOnDrop<'a>(&'a IoctlState); | ||||
|  | ||||
|         impl CancelOnDrop<'_> { | ||||
|             fn defuse(self) { | ||||
|                 core::mem::forget(self); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         impl Drop for CancelOnDrop<'_> { | ||||
|             fn drop(&mut self) { | ||||
|                 self.0.cancel_ioctl(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let ioctl = CancelOnDrop(self.ioctl_state); | ||||
|  | ||||
|         let resp_len = ioctl.0.do_ioctl(&mut buf, req_len).await; | ||||
|  | ||||
|         ioctl.defuse(); | ||||
|  | ||||
|         noproto::read(&buf[..resp_len]).unwrap() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										432
									
								
								embassy-net-esp-hosted/src/esp_hosted_config.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										432
									
								
								embassy-net-esp-hosted/src/esp_hosted_config.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,432 @@ | ||||
| syntax = "proto3"; | ||||
|  | ||||
| /* Enums similar to ESP IDF */ | ||||
| enum Ctrl_VendorIEType { | ||||
|     Beacon = 0; | ||||
|     Probe_req = 1; | ||||
|     Probe_resp = 2; | ||||
|     Assoc_req = 3; | ||||
|     Assoc_resp = 4; | ||||
| } | ||||
|  | ||||
| enum Ctrl_VendorIEID { | ||||
|     ID_0 = 0; | ||||
|     ID_1 = 1; | ||||
| } | ||||
|  | ||||
| enum Ctrl_WifiMode { | ||||
|     NONE = 0; | ||||
|     STA = 1; | ||||
|     AP = 2; | ||||
|     APSTA = 3; | ||||
| } | ||||
|  | ||||
| enum Ctrl_WifiBw { | ||||
|     BW_Invalid = 0; | ||||
|     HT20 = 1; | ||||
|     HT40 = 2; | ||||
| } | ||||
|  | ||||
| enum Ctrl_WifiPowerSave { | ||||
|     PS_Invalid = 0; | ||||
|     MIN_MODEM = 1; | ||||
|     MAX_MODEM = 2; | ||||
| } | ||||
|  | ||||
| enum Ctrl_WifiSecProt { | ||||
|     Open = 0; | ||||
|     WEP = 1; | ||||
|     WPA_PSK = 2; | ||||
|     WPA2_PSK = 3; | ||||
|     WPA_WPA2_PSK = 4; | ||||
|     WPA2_ENTERPRISE = 5; | ||||
|     WPA3_PSK = 6; | ||||
|     WPA2_WPA3_PSK = 7; | ||||
| } | ||||
|  | ||||
| /* enums for Control path */ | ||||
| enum Ctrl_Status { | ||||
|     Connected = 0; | ||||
|     Not_Connected = 1; | ||||
|     No_AP_Found = 2; | ||||
|     Connection_Fail = 3; | ||||
|     Invalid_Argument = 4; | ||||
|     Out_Of_Range = 5; | ||||
| } | ||||
|  | ||||
|  | ||||
| enum CtrlMsgType { | ||||
|     MsgType_Invalid = 0; | ||||
|     Req = 1; | ||||
|     Resp = 2; | ||||
|     Event = 3; | ||||
|     MsgType_Max = 4; | ||||
| } | ||||
|  | ||||
| enum CtrlMsgId { | ||||
|     MsgId_Invalid = 0; | ||||
|  | ||||
|     /** Request Msgs **/ | ||||
|     Req_Base = 100; | ||||
|  | ||||
|     Req_GetMACAddress = 101; | ||||
|     Req_SetMacAddress = 102; | ||||
|     Req_GetWifiMode = 103; | ||||
|     Req_SetWifiMode = 104; | ||||
|  | ||||
|     Req_GetAPScanList = 105; | ||||
|     Req_GetAPConfig = 106; | ||||
|     Req_ConnectAP = 107; | ||||
|     Req_DisconnectAP = 108; | ||||
|  | ||||
|     Req_GetSoftAPConfig = 109; | ||||
|     Req_SetSoftAPVendorSpecificIE = 110; | ||||
|     Req_StartSoftAP = 111; | ||||
|     Req_GetSoftAPConnectedSTAList = 112; | ||||
|     Req_StopSoftAP = 113; | ||||
|  | ||||
|     Req_SetPowerSaveMode = 114; | ||||
|     Req_GetPowerSaveMode = 115; | ||||
|  | ||||
|     Req_OTABegin = 116; | ||||
|     Req_OTAWrite = 117; | ||||
|     Req_OTAEnd = 118; | ||||
|  | ||||
|     Req_SetWifiMaxTxPower = 119; | ||||
|     Req_GetWifiCurrTxPower = 120; | ||||
|  | ||||
|     Req_ConfigHeartbeat = 121; | ||||
|     /* Add new control path command response before Req_Max | ||||
|      * and update Req_Max */ | ||||
|     Req_Max = 122; | ||||
|  | ||||
|     /** Response Msgs **/ | ||||
|     Resp_Base = 200; | ||||
|  | ||||
|     Resp_GetMACAddress = 201; | ||||
|     Resp_SetMacAddress = 202; | ||||
|     Resp_GetWifiMode = 203; | ||||
|     Resp_SetWifiMode = 204; | ||||
|  | ||||
|     Resp_GetAPScanList = 205; | ||||
|     Resp_GetAPConfig = 206; | ||||
|     Resp_ConnectAP = 207; | ||||
|     Resp_DisconnectAP = 208; | ||||
|  | ||||
|     Resp_GetSoftAPConfig = 209; | ||||
|     Resp_SetSoftAPVendorSpecificIE = 210; | ||||
|     Resp_StartSoftAP = 211; | ||||
|     Resp_GetSoftAPConnectedSTAList = 212; | ||||
|     Resp_StopSoftAP = 213; | ||||
|  | ||||
|     Resp_SetPowerSaveMode = 214; | ||||
|     Resp_GetPowerSaveMode = 215; | ||||
|  | ||||
|     Resp_OTABegin = 216; | ||||
|     Resp_OTAWrite = 217; | ||||
|     Resp_OTAEnd = 218; | ||||
|  | ||||
|     Resp_SetWifiMaxTxPower = 219; | ||||
|     Resp_GetWifiCurrTxPower = 220; | ||||
|  | ||||
|     Resp_ConfigHeartbeat = 221; | ||||
|     /* Add new control path command response before Resp_Max | ||||
|      * and update Resp_Max */ | ||||
|     Resp_Max = 222; | ||||
|  | ||||
|     /** Event Msgs **/ | ||||
|     Event_Base = 300; | ||||
|     Event_ESPInit = 301; | ||||
|     Event_Heartbeat = 302; | ||||
|     Event_StationDisconnectFromAP = 303; | ||||
|     Event_StationDisconnectFromESPSoftAP = 304; | ||||
|     /* Add new control path command notification before Event_Max | ||||
|      * and update Event_Max */ | ||||
|     Event_Max = 305; | ||||
| } | ||||
|  | ||||
| /* internal supporting structures for CtrlMsg */ | ||||
| message ScanResult { | ||||
|     bytes ssid = 1; | ||||
|     uint32 chnl = 2; | ||||
|     int32 rssi = 3; | ||||
|     bytes bssid = 4; | ||||
|     Ctrl_WifiSecProt sec_prot = 5; | ||||
| } | ||||
|  | ||||
| message ConnectedSTAList { | ||||
|     bytes mac = 1; | ||||
|     int32 rssi = 2; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Control path structures */ | ||||
| /** Req/Resp structure **/ | ||||
| message CtrlMsg_Req_GetMacAddress { | ||||
|     int32 mode = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_GetMacAddress { | ||||
|     bytes mac = 1; | ||||
|     int32 resp = 2; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_GetMode { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_GetMode { | ||||
|     int32 mode = 1; | ||||
|     int32 resp = 2; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_SetMode { | ||||
|     int32 mode = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_SetMode { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_GetStatus { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_GetStatus { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_SetMacAddress { | ||||
|     bytes mac = 1; | ||||
|     int32 mode = 2; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_SetMacAddress { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_GetAPConfig { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_GetAPConfig { | ||||
|     bytes ssid = 1; | ||||
|     bytes bssid = 2; | ||||
|     int32 rssi = 3; | ||||
|     int32 chnl = 4; | ||||
|     Ctrl_WifiSecProt sec_prot = 5; | ||||
|     int32 resp = 6; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_ConnectAP { | ||||
|     string ssid = 1; | ||||
|     string pwd = 2; | ||||
|     string bssid = 3; | ||||
|     bool is_wpa3_supported = 4; | ||||
|     int32 listen_interval = 5; | ||||
| } | ||||
|  | ||||
| message  CtrlMsg_Resp_ConnectAP { | ||||
|     int32 resp = 1; | ||||
|     bytes mac = 2; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_GetSoftAPConfig { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_GetSoftAPConfig { | ||||
|     bytes ssid = 1; | ||||
|     bytes pwd = 2; | ||||
|     int32 chnl = 3; | ||||
|     Ctrl_WifiSecProt sec_prot = 4; | ||||
|     int32 max_conn = 5; | ||||
|     bool ssid_hidden = 6; | ||||
|     int32 bw = 7; | ||||
|     int32 resp = 8; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_StartSoftAP { | ||||
|     string ssid = 1; | ||||
|     string pwd = 2; | ||||
|     int32 chnl = 3; | ||||
|     Ctrl_WifiSecProt sec_prot = 4; | ||||
|     int32 max_conn = 5; | ||||
|     bool ssid_hidden = 6; | ||||
|     int32 bw = 7; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_StartSoftAP { | ||||
|     int32 resp = 1; | ||||
|     bytes mac = 2; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_ScanResult { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_ScanResult { | ||||
|     uint32 count = 1; | ||||
|     repeated ScanResult entries = 2; | ||||
|     int32 resp = 3; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_SoftAPConnectedSTA { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_SoftAPConnectedSTA { | ||||
|     uint32 num = 1; | ||||
|     repeated ConnectedSTAList stations = 2; | ||||
|     int32 resp = 3; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_OTABegin { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_OTABegin { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_OTAWrite { | ||||
|     bytes ota_data = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_OTAWrite { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_OTAEnd { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_OTAEnd { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_VendorIEData { | ||||
|     int32 element_id = 1; | ||||
|     int32 length = 2; | ||||
|     bytes vendor_oui = 3; | ||||
|     int32 vendor_oui_type = 4; | ||||
|     bytes payload = 5;  | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_SetSoftAPVendorSpecificIE { | ||||
|     bool enable = 1; | ||||
|     Ctrl_VendorIEType type = 2; | ||||
|     Ctrl_VendorIEID idx = 3; | ||||
|     CtrlMsg_Req_VendorIEData vendor_ie_data = 4; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_SetSoftAPVendorSpecificIE { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_SetWifiMaxTxPower { | ||||
|     int32 wifi_max_tx_power = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_SetWifiMaxTxPower { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_GetWifiCurrTxPower { | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_GetWifiCurrTxPower { | ||||
|     int32 wifi_curr_tx_power = 1; | ||||
|     int32 resp = 2; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Req_ConfigHeartbeat { | ||||
|     bool enable = 1; | ||||
|     int32 duration = 2; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Resp_ConfigHeartbeat { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| /** Event structure **/ | ||||
| message CtrlMsg_Event_ESPInit { | ||||
|     bytes init_data = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Event_Heartbeat { | ||||
|     int32 hb_num = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Event_StationDisconnectFromAP { | ||||
|     int32 resp = 1; | ||||
| } | ||||
|  | ||||
| message CtrlMsg_Event_StationDisconnectFromESPSoftAP { | ||||
|     int32 resp = 1; | ||||
|     bytes mac = 2; | ||||
| } | ||||
|  | ||||
| message CtrlMsg { | ||||
|     /* msg_type could be req, resp or Event */ | ||||
|     CtrlMsgType msg_type = 1; | ||||
|  | ||||
|     /* msg id */ | ||||
|     CtrlMsgId msg_id = 2; | ||||
|  | ||||
|     /* union of all msg ids */ | ||||
|     oneof payload { | ||||
|         /** Requests **/ | ||||
|         CtrlMsg_Req_GetMacAddress req_get_mac_address = 101; | ||||
|         CtrlMsg_Req_SetMacAddress req_set_mac_address = 102; | ||||
|         CtrlMsg_Req_GetMode req_get_wifi_mode = 103; | ||||
|         CtrlMsg_Req_SetMode req_set_wifi_mode = 104; | ||||
|  | ||||
|         CtrlMsg_Req_ScanResult req_scan_ap_list = 105; | ||||
|         CtrlMsg_Req_GetAPConfig req_get_ap_config = 106; | ||||
|         CtrlMsg_Req_ConnectAP req_connect_ap = 107; | ||||
|         CtrlMsg_Req_GetStatus req_disconnect_ap = 108; | ||||
|  | ||||
|         CtrlMsg_Req_GetSoftAPConfig req_get_softap_config = 109; | ||||
|         CtrlMsg_Req_SetSoftAPVendorSpecificIE req_set_softap_vendor_specific_ie = 110; | ||||
|         CtrlMsg_Req_StartSoftAP req_start_softap = 111; | ||||
|         CtrlMsg_Req_SoftAPConnectedSTA req_softap_connected_stas_list = 112; | ||||
|         CtrlMsg_Req_GetStatus req_stop_softap = 113; | ||||
|  | ||||
|         CtrlMsg_Req_SetMode req_set_power_save_mode = 114; | ||||
|         CtrlMsg_Req_GetMode req_get_power_save_mode = 115; | ||||
|  | ||||
|         CtrlMsg_Req_OTABegin req_ota_begin = 116; | ||||
|         CtrlMsg_Req_OTAWrite req_ota_write = 117; | ||||
|         CtrlMsg_Req_OTAEnd req_ota_end = 118; | ||||
|  | ||||
|         CtrlMsg_Req_SetWifiMaxTxPower req_set_wifi_max_tx_power = 119; | ||||
|         CtrlMsg_Req_GetWifiCurrTxPower req_get_wifi_curr_tx_power = 120; | ||||
|         CtrlMsg_Req_ConfigHeartbeat req_config_heartbeat = 121; | ||||
|  | ||||
|         /** Responses **/ | ||||
|         CtrlMsg_Resp_GetMacAddress resp_get_mac_address = 201; | ||||
|         CtrlMsg_Resp_SetMacAddress resp_set_mac_address = 202; | ||||
|         CtrlMsg_Resp_GetMode resp_get_wifi_mode = 203; | ||||
|         CtrlMsg_Resp_SetMode resp_set_wifi_mode = 204; | ||||
|  | ||||
|         CtrlMsg_Resp_ScanResult resp_scan_ap_list = 205; | ||||
|         CtrlMsg_Resp_GetAPConfig resp_get_ap_config = 206; | ||||
|         CtrlMsg_Resp_ConnectAP resp_connect_ap = 207; | ||||
|         CtrlMsg_Resp_GetStatus resp_disconnect_ap = 208; | ||||
|  | ||||
|         CtrlMsg_Resp_GetSoftAPConfig resp_get_softap_config = 209; | ||||
|         CtrlMsg_Resp_SetSoftAPVendorSpecificIE resp_set_softap_vendor_specific_ie = 210; | ||||
|         CtrlMsg_Resp_StartSoftAP resp_start_softap = 211; | ||||
|         CtrlMsg_Resp_SoftAPConnectedSTA resp_softap_connected_stas_list = 212; | ||||
|         CtrlMsg_Resp_GetStatus resp_stop_softap = 213; | ||||
|  | ||||
|         CtrlMsg_Resp_SetMode resp_set_power_save_mode = 214; | ||||
|         CtrlMsg_Resp_GetMode resp_get_power_save_mode = 215; | ||||
|  | ||||
|         CtrlMsg_Resp_OTABegin resp_ota_begin = 216; | ||||
|         CtrlMsg_Resp_OTAWrite resp_ota_write = 217; | ||||
|         CtrlMsg_Resp_OTAEnd resp_ota_end = 218; | ||||
|         CtrlMsg_Resp_SetWifiMaxTxPower resp_set_wifi_max_tx_power = 219; | ||||
|         CtrlMsg_Resp_GetWifiCurrTxPower resp_get_wifi_curr_tx_power = 220; | ||||
|         CtrlMsg_Resp_ConfigHeartbeat resp_config_heartbeat = 221; | ||||
|  | ||||
|         /** Notifications **/ | ||||
|         CtrlMsg_Event_ESPInit event_esp_init = 301; | ||||
|         CtrlMsg_Event_Heartbeat event_heartbeat = 302; | ||||
|         CtrlMsg_Event_StationDisconnectFromAP event_station_disconnect_from_AP = 303; | ||||
|         CtrlMsg_Event_StationDisconnectFromESPSoftAP event_station_disconnect_from_ESP_SoftAP = 304; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										257
									
								
								embassy-net-esp-hosted/src/fmt.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								embassy-net-esp-hosted/src/fmt.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,257 @@ | ||||
| #![macro_use] | ||||
| #![allow(unused_macros)] | ||||
|  | ||||
| use core::fmt::{Debug, Display, LowerHex}; | ||||
|  | ||||
| #[cfg(all(feature = "defmt", feature = "log"))] | ||||
| compile_error!("You may not enable both `defmt` and `log` features."); | ||||
|  | ||||
| macro_rules! assert { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::assert!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::assert!($($x)*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! assert_eq { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::assert_eq!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::assert_eq!($($x)*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! assert_ne { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::assert_ne!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::assert_ne!($($x)*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! debug_assert { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::debug_assert!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::debug_assert!($($x)*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! debug_assert_eq { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::debug_assert_eq!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::debug_assert_eq!($($x)*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! debug_assert_ne { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::debug_assert_ne!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::debug_assert_ne!($($x)*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! todo { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::todo!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::todo!($($x)*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::core::unreachable!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unreachable { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unreachable!($($x)*); | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! panic { | ||||
|     ($($x:tt)*) => { | ||||
|         { | ||||
|             #[cfg(not(feature = "defmt"))] | ||||
|             ::core::panic!($($x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::panic!($($x)*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! trace { | ||||
|     ($s:literal $(, $x:expr)* $(,)?) => { | ||||
|         { | ||||
|             #[cfg(feature = "log")] | ||||
|             ::log::trace!($s $(, $x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::trace!($s $(, $x)*); | ||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||
|             let _ = ($( & $x ),*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! debug { | ||||
|     ($s:literal $(, $x:expr)* $(,)?) => { | ||||
|         { | ||||
|             #[cfg(feature = "log")] | ||||
|             ::log::debug!($s $(, $x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::debug!($s $(, $x)*); | ||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||
|             let _ = ($( & $x ),*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! info { | ||||
|     ($s:literal $(, $x:expr)* $(,)?) => { | ||||
|         { | ||||
|             #[cfg(feature = "log")] | ||||
|             ::log::info!($s $(, $x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::info!($s $(, $x)*); | ||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||
|             let _ = ($( & $x ),*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! warn { | ||||
|     ($s:literal $(, $x:expr)* $(,)?) => { | ||||
|         { | ||||
|             #[cfg(feature = "log")] | ||||
|             ::log::warn!($s $(, $x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::warn!($s $(, $x)*); | ||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||
|             let _ = ($( & $x ),*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| macro_rules! error { | ||||
|     ($s:literal $(, $x:expr)* $(,)?) => { | ||||
|         { | ||||
|             #[cfg(feature = "log")] | ||||
|             ::log::error!($s $(, $x)*); | ||||
|             #[cfg(feature = "defmt")] | ||||
|             ::defmt::error!($s $(, $x)*); | ||||
|             #[cfg(not(any(feature = "log", feature="defmt")))] | ||||
|             let _ = ($( & $x ),*); | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| macro_rules! unwrap { | ||||
|     ($($x:tt)*) => { | ||||
|         ::defmt::unwrap!($($x)*) | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[cfg(not(feature = "defmt"))] | ||||
| macro_rules! unwrap { | ||||
|     ($arg:expr) => { | ||||
|         match $crate::fmt::Try::into_result($arg) { | ||||
|             ::core::result::Result::Ok(t) => t, | ||||
|             ::core::result::Result::Err(e) => { | ||||
|                 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|     ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||||
|         match $crate::fmt::Try::into_result($arg) { | ||||
|             ::core::result::Result::Ok(t) => t, | ||||
|             ::core::result::Result::Err(e) => { | ||||
|                 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||||
| pub struct NoneError; | ||||
|  | ||||
| pub trait Try { | ||||
|     type Ok; | ||||
|     type Error; | ||||
|     fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||||
| } | ||||
|  | ||||
| impl<T> Try for Option<T> { | ||||
|     type Ok = T; | ||||
|     type Error = NoneError; | ||||
|  | ||||
|     #[inline] | ||||
|     fn into_result(self) -> Result<T, NoneError> { | ||||
|         self.ok_or(NoneError) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T, E> Try for Result<T, E> { | ||||
|     type Ok = T; | ||||
|     type Error = E; | ||||
|  | ||||
|     #[inline] | ||||
|     fn into_result(self) -> Self { | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct Bytes<'a>(pub &'a [u8]); | ||||
|  | ||||
| impl<'a> Debug for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Display for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> LowerHex for Bytes<'a> { | ||||
|     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||||
|         write!(f, "{:#02x?}", self.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "defmt")] | ||||
| impl<'a> defmt::Format for Bytes<'a> { | ||||
|     fn format(&self, fmt: defmt::Formatter) { | ||||
|         defmt::write!(fmt, "{:02x}", self.0) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										119
									
								
								embassy-net-esp-hosted/src/ioctl.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								embassy-net-esp-hosted/src/ioctl.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| use core::cell::{Cell, RefCell}; | ||||
| use core::future::poll_fn; | ||||
| use core::task::{Poll, Waker}; | ||||
|  | ||||
| use embassy_sync::waitqueue::WakerRegistration; | ||||
|  | ||||
| use crate::fmt::Bytes; | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| pub struct PendingIoctl { | ||||
|     pub buf: *mut [u8], | ||||
|     pub req_len: usize, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| enum IoctlStateInner { | ||||
|     Pending(PendingIoctl), | ||||
|     Sent { buf: *mut [u8] }, | ||||
|     Done { resp_len: usize }, | ||||
| } | ||||
|  | ||||
| struct Wakers { | ||||
|     control: WakerRegistration, | ||||
|     runner: WakerRegistration, | ||||
| } | ||||
|  | ||||
| impl Default for Wakers { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             control: WakerRegistration::new(), | ||||
|             runner: WakerRegistration::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct IoctlState { | ||||
|     state: Cell<IoctlStateInner>, | ||||
|     wakers: RefCell<Wakers>, | ||||
| } | ||||
|  | ||||
| impl IoctlState { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), | ||||
|             wakers: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn wake_control(&self) { | ||||
|         self.wakers.borrow_mut().control.wake(); | ||||
|     } | ||||
|  | ||||
|     fn register_control(&self, waker: &Waker) { | ||||
|         self.wakers.borrow_mut().control.register(waker); | ||||
|     } | ||||
|  | ||||
|     fn wake_runner(&self) { | ||||
|         self.wakers.borrow_mut().runner.wake(); | ||||
|     } | ||||
|  | ||||
|     fn register_runner(&self, waker: &Waker) { | ||||
|         self.wakers.borrow_mut().runner.register(waker); | ||||
|     } | ||||
|  | ||||
|     pub async fn wait_complete(&self) -> usize { | ||||
|         poll_fn(|cx| { | ||||
|             if let IoctlStateInner::Done { resp_len } = self.state.get() { | ||||
|                 Poll::Ready(resp_len) | ||||
|             } else { | ||||
|                 self.register_control(cx.waker()); | ||||
|                 Poll::Pending | ||||
|             } | ||||
|         }) | ||||
|         .await | ||||
|     } | ||||
|  | ||||
|     pub async fn wait_pending(&self) -> PendingIoctl { | ||||
|         let pending = poll_fn(|cx| { | ||||
|             if let IoctlStateInner::Pending(pending) = self.state.get() { | ||||
|                 Poll::Ready(pending) | ||||
|             } else { | ||||
|                 self.register_runner(cx.waker()); | ||||
|                 Poll::Pending | ||||
|             } | ||||
|         }) | ||||
|         .await; | ||||
|  | ||||
|         self.state.set(IoctlStateInner::Sent { buf: pending.buf }); | ||||
|         pending | ||||
|     } | ||||
|  | ||||
|     pub fn cancel_ioctl(&self) { | ||||
|         self.state.set(IoctlStateInner::Done { resp_len: 0 }); | ||||
|     } | ||||
|  | ||||
|     pub async fn do_ioctl(&self, buf: &mut [u8], req_len: usize) -> usize { | ||||
|         debug!("IOCTL Request: {:02x}", Bytes(&buf[..req_len])); | ||||
|  | ||||
|         self.state.set(IoctlStateInner::Pending(PendingIoctl { buf, req_len })); | ||||
|         self.wake_runner(); | ||||
|         self.wait_complete().await | ||||
|     } | ||||
|  | ||||
|     pub fn ioctl_done(&self, response: &[u8]) { | ||||
|         if let IoctlStateInner::Sent { buf } = self.state.get() { | ||||
|             debug!("IOCTL Response: {:02x}", Bytes(response)); | ||||
|  | ||||
|             // TODO fix this | ||||
|             (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); | ||||
|  | ||||
|             self.state.set(IoctlStateInner::Done { | ||||
|                 resp_len: response.len(), | ||||
|             }); | ||||
|             self.wake_control(); | ||||
|         } else { | ||||
|             warn!("IOCTL Response but no pending Ioctl"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										300
									
								
								embassy-net-esp-hosted/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								embassy-net-esp-hosted/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,300 @@ | ||||
| #![no_std] | ||||
|  | ||||
| use control::Control; | ||||
| use embassy_futures::select::{select3, Either3}; | ||||
| use embassy_net_driver_channel as ch; | ||||
| use embassy_time::{Duration, Instant, Timer}; | ||||
| use embedded_hal::digital::{InputPin, OutputPin}; | ||||
| use embedded_hal_async::digital::Wait; | ||||
| use embedded_hal_async::spi::SpiDevice; | ||||
| use ioctl::IoctlState; | ||||
|  | ||||
| use crate::ioctl::PendingIoctl; | ||||
|  | ||||
| mod proto; | ||||
|  | ||||
| // must be first | ||||
| mod fmt; | ||||
|  | ||||
| mod control; | ||||
| mod ioctl; | ||||
|  | ||||
| const MTU: usize = 1514; | ||||
|  | ||||
| macro_rules! impl_bytes { | ||||
|     ($t:ident) => { | ||||
|         impl $t { | ||||
|             pub const SIZE: usize = core::mem::size_of::<Self>(); | ||||
|  | ||||
|             #[allow(unused)] | ||||
|             pub fn to_bytes(&self) -> [u8; Self::SIZE] { | ||||
|                 unsafe { core::mem::transmute(*self) } | ||||
|             } | ||||
|  | ||||
|             #[allow(unused)] | ||||
|             pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self { | ||||
|                 let alignment = core::mem::align_of::<Self>(); | ||||
|                 assert_eq!( | ||||
|                     bytes.as_ptr().align_offset(alignment), | ||||
|                     0, | ||||
|                     "{} is not aligned", | ||||
|                     core::any::type_name::<Self>() | ||||
|                 ); | ||||
|                 unsafe { core::mem::transmute(bytes) } | ||||
|             } | ||||
|  | ||||
|             #[allow(unused)] | ||||
|             pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self { | ||||
|                 let alignment = core::mem::align_of::<Self>(); | ||||
|                 assert_eq!( | ||||
|                     bytes.as_ptr().align_offset(alignment), | ||||
|                     0, | ||||
|                     "{} is not aligned", | ||||
|                     core::any::type_name::<Self>() | ||||
|                 ); | ||||
|  | ||||
|                 unsafe { core::mem::transmute(bytes) } | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| #[repr(C, packed)] | ||||
| #[derive(Clone, Copy, Debug, Default)] | ||||
| struct PayloadHeader { | ||||
|     /// InterfaceType on lower 4 bits, number on higher 4 bits. | ||||
|     if_type_and_num: u8, | ||||
|  | ||||
|     /// Flags. | ||||
|     /// | ||||
|     /// bit 0: more fragments. | ||||
|     flags: u8, | ||||
|  | ||||
|     len: u16, | ||||
|     offset: u16, | ||||
|     checksum: u16, | ||||
|     seq_num: u16, | ||||
|     reserved2: u8, | ||||
|  | ||||
|     /// Packet type for HCI or PRIV interface, reserved otherwise | ||||
|     hci_priv_packet_type: u8, | ||||
| } | ||||
| impl_bytes!(PayloadHeader); | ||||
|  | ||||
| #[repr(u8)] | ||||
| enum InterfaceType { | ||||
|     Sta = 0, | ||||
|     Ap = 1, | ||||
|     Serial = 2, | ||||
|     Hci = 3, | ||||
|     Priv = 4, | ||||
|     Test = 5, | ||||
| } | ||||
|  | ||||
| const MAX_SPI_BUFFER_SIZE: usize = 1600; | ||||
|  | ||||
| pub struct State { | ||||
|     ioctl_state: IoctlState, | ||||
|     ch: ch::State<MTU, 4, 4>, | ||||
| } | ||||
|  | ||||
| impl State { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             ioctl_state: IoctlState::new(), | ||||
|             ch: ch::State::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub type NetDriver<'a> = ch::Device<'a, MTU>; | ||||
|  | ||||
| pub async fn new<'a, SPI, IN, OUT>( | ||||
|     state: &'a mut State, | ||||
|     spi: SPI, | ||||
|     handshake: IN, | ||||
|     ready: IN, | ||||
|     reset: OUT, | ||||
| ) -> (NetDriver<'a>, Control<'a>, Runner<'a, SPI, IN, OUT>) | ||||
| where | ||||
|     SPI: SpiDevice, | ||||
|     IN: InputPin + Wait, | ||||
|     OUT: OutputPin, | ||||
| { | ||||
|     let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); | ||||
|     let state_ch = ch_runner.state_runner(); | ||||
|  | ||||
|     let mut runner = Runner { | ||||
|         ch: ch_runner, | ||||
|         ioctl_state: &state.ioctl_state, | ||||
|         next_seq: 1, | ||||
|         handshake, | ||||
|         ready, | ||||
|         reset, | ||||
|         spi, | ||||
|     }; | ||||
|     runner.init().await; | ||||
|  | ||||
|     (device, Control::new(state_ch, &state.ioctl_state), runner) | ||||
| } | ||||
|  | ||||
| pub struct Runner<'a, SPI, IN, OUT> { | ||||
|     ch: ch::Runner<'a, MTU>, | ||||
|     ioctl_state: &'a IoctlState, | ||||
|  | ||||
|     next_seq: u16, | ||||
|  | ||||
|     spi: SPI, | ||||
|     handshake: IN, | ||||
|     ready: IN, | ||||
|     reset: OUT, | ||||
| } | ||||
|  | ||||
| impl<'a, SPI, IN, OUT> Runner<'a, SPI, IN, OUT> | ||||
| where | ||||
|     SPI: SpiDevice, | ||||
|     IN: InputPin + Wait, | ||||
|     OUT: OutputPin, | ||||
| { | ||||
|     async fn init(&mut self) {} | ||||
|  | ||||
|     pub async fn run(mut self) -> ! { | ||||
|         debug!("resetting..."); | ||||
|         self.reset.set_low().unwrap(); | ||||
|         Timer::after(Duration::from_millis(100)).await; | ||||
|         self.reset.set_high().unwrap(); | ||||
|         Timer::after(Duration::from_millis(1000)).await; | ||||
|  | ||||
|         let mut tx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; | ||||
|         let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; | ||||
|  | ||||
|         loop { | ||||
|             self.handshake.wait_for_high().await.unwrap(); | ||||
|  | ||||
|             let ioctl = self.ioctl_state.wait_pending(); | ||||
|             let tx = self.ch.tx_buf(); | ||||
|             let ev = async { self.ready.wait_for_high().await.unwrap() }; | ||||
|  | ||||
|             match select3(ioctl, tx, ev).await { | ||||
|                 Either3::First(PendingIoctl { buf, req_len }) => { | ||||
|                     tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02"); | ||||
|                     tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes()); | ||||
|                     tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]); | ||||
|  | ||||
|                     let mut header = PayloadHeader { | ||||
|                         if_type_and_num: InterfaceType::Serial as _, | ||||
|                         len: (req_len + 14) as _, | ||||
|                         offset: PayloadHeader::SIZE as _, | ||||
|                         seq_num: self.next_seq, | ||||
|                         ..Default::default() | ||||
|                     }; | ||||
|                     self.next_seq = self.next_seq.wrapping_add(1); | ||||
|  | ||||
|                     // Calculate checksum | ||||
|                     tx_buf[0..12].copy_from_slice(&header.to_bytes()); | ||||
|                     header.checksum = checksum(&tx_buf[..26 + req_len]); | ||||
|                     tx_buf[0..12].copy_from_slice(&header.to_bytes()); | ||||
|  | ||||
|                     debug!("====== SENDING IOCTL"); | ||||
|                 } | ||||
|                 Either3::Second(packet) => { | ||||
|                     tx_buf[12..][..packet.len()].copy_from_slice(packet); | ||||
|  | ||||
|                     let mut header = PayloadHeader { | ||||
|                         if_type_and_num: InterfaceType::Sta as _, | ||||
|                         len: packet.len() as _, | ||||
|                         offset: PayloadHeader::SIZE as _, | ||||
|                         seq_num: self.next_seq, | ||||
|                         ..Default::default() | ||||
|                     }; | ||||
|                     self.next_seq = self.next_seq.wrapping_add(1); | ||||
|  | ||||
|                     // Calculate checksum | ||||
|                     tx_buf[0..12].copy_from_slice(&header.to_bytes()); | ||||
|                     header.checksum = checksum(&tx_buf[..12 + packet.len()]); | ||||
|                     tx_buf[0..12].copy_from_slice(&header.to_bytes()); | ||||
|  | ||||
|                     self.ch.tx_done(); | ||||
|                 } | ||||
|                 Either3::Third(()) => { | ||||
|                     tx_buf[..PayloadHeader::SIZE].fill(0); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if tx_buf[0] != 0 { | ||||
|                 trace!("tx: {:02x}", &tx_buf[..40]); | ||||
|             } | ||||
|  | ||||
|             self.spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); | ||||
|             let delay_until = Instant::now() + Duration::from_millis(1); | ||||
|             self.handle_rx(&mut rx_buf); | ||||
|             Timer::at(delay_until).await; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn handle_rx(&mut self, buf: &mut [u8]) { | ||||
|         trace!("rx: {:02x}", &buf[..40]); | ||||
|  | ||||
|         let buf_len = buf.len(); | ||||
|         let h = PayloadHeader::from_bytes_mut((&mut buf[..PayloadHeader::SIZE]).try_into().unwrap()); | ||||
|  | ||||
|         if h.len == 0 || h.offset as usize != PayloadHeader::SIZE { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let payload_len = h.len as usize; | ||||
|         if buf_len < PayloadHeader::SIZE + payload_len { | ||||
|             warn!("rx: len too big"); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let if_type_and_num = h.if_type_and_num; | ||||
|         let want_checksum = h.checksum; | ||||
|         h.checksum = 0; | ||||
|         let got_checksum = checksum(&buf[..PayloadHeader::SIZE + payload_len]); | ||||
|         if want_checksum != got_checksum { | ||||
|             warn!("rx: bad checksum. Got {:04x}, want {:04x}", got_checksum, want_checksum); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let payload = &mut buf[PayloadHeader::SIZE..][..payload_len]; | ||||
|  | ||||
|         match if_type_and_num & 0x0f { | ||||
|             // STA | ||||
|             0 => match self.ch.try_rx_buf() { | ||||
|                 Some(buf) => { | ||||
|                     buf[..payload.len()].copy_from_slice(payload); | ||||
|                     self.ch.rx_done(payload.len()) | ||||
|                 } | ||||
|                 None => warn!("failed to push rxd packet to the channel."), | ||||
|             }, | ||||
|             // serial | ||||
|             2 => { | ||||
|                 debug!("serial rx: {:02x}", payload); | ||||
|                 if payload.len() < 14 { | ||||
|                     warn!("serial rx: too short"); | ||||
|                     return; | ||||
|                 } | ||||
|                 if &payload[..12] != b"\x01\x08\x00ctrlResp\x02" { | ||||
|                     warn!("serial rx: bad tlv"); | ||||
|                     return; | ||||
|                 } | ||||
|                 let len = u16::from_le_bytes(payload[12..14].try_into().unwrap()) as usize; | ||||
|                 if payload.len() < 14 + len { | ||||
|                     warn!("serial rx: too short 2"); | ||||
|                     return; | ||||
|                 } | ||||
|                 self.ioctl_state.ioctl_done(&payload[14..][..len]); | ||||
|             } | ||||
|             _ => warn!("unknown iftype {}", if_type_and_num), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn checksum(buf: &[u8]) -> u16 { | ||||
|     let mut res = 0u16; | ||||
|     for &b in buf { | ||||
|         res = res.wrapping_add(b as _); | ||||
|     } | ||||
|     res | ||||
| } | ||||
							
								
								
									
										598
									
								
								embassy-net-esp-hosted/src/proto.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										598
									
								
								embassy-net-esp-hosted/src/proto.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,598 @@ | ||||
| use heapless::{String, Vec}; | ||||
|  | ||||
| /// internal supporting structures for CtrlMsg | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct ScanResult { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub ssid: String<32>, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub chnl: u32, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub rssi: u32, | ||||
|     #[noproto(tag = "4")] | ||||
|     pub bssid: String<32>, | ||||
|     #[noproto(tag = "5")] | ||||
|     pub sec_prot: CtrlWifiSecProt, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct ConnectedStaList { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub mac: String<32>, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub rssi: u32, | ||||
| } | ||||
| /// * Req/Resp structure * | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqGetMacAddress { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub mode: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespGetMacAddress { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub mac: String<32>, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqGetMode {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespGetMode { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub mode: u32, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqSetMode { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub mode: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespSetMode { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqGetStatus {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespGetStatus { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqSetMacAddress { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub mac: String<32>, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub mode: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespSetMacAddress { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqGetApConfig {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespGetApConfig { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub ssid: String<32>, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub bssid: String<32>, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub rssi: u32, | ||||
|     #[noproto(tag = "4")] | ||||
|     pub chnl: u32, | ||||
|     #[noproto(tag = "5")] | ||||
|     pub sec_prot: CtrlWifiSecProt, | ||||
|     #[noproto(tag = "6")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqConnectAp { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub ssid: String<32>, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub pwd: String<32>, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub bssid: String<32>, | ||||
|     #[noproto(tag = "4")] | ||||
|     pub is_wpa3_supported: bool, | ||||
|     #[noproto(tag = "5")] | ||||
|     pub listen_interval: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespConnectAp { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub mac: String<32>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqGetSoftApConfig {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespGetSoftApConfig { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub ssid: String<32>, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub pwd: String<32>, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub chnl: u32, | ||||
|     #[noproto(tag = "4")] | ||||
|     pub sec_prot: CtrlWifiSecProt, | ||||
|     #[noproto(tag = "5")] | ||||
|     pub max_conn: u32, | ||||
|     #[noproto(tag = "6")] | ||||
|     pub ssid_hidden: bool, | ||||
|     #[noproto(tag = "7")] | ||||
|     pub bw: u32, | ||||
|     #[noproto(tag = "8")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqStartSoftAp { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub ssid: String<32>, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub pwd: String<32>, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub chnl: u32, | ||||
|     #[noproto(tag = "4")] | ||||
|     pub sec_prot: CtrlWifiSecProt, | ||||
|     #[noproto(tag = "5")] | ||||
|     pub max_conn: u32, | ||||
|     #[noproto(tag = "6")] | ||||
|     pub ssid_hidden: bool, | ||||
|     #[noproto(tag = "7")] | ||||
|     pub bw: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespStartSoftAp { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub mac: String<32>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqScanResult {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespScanResult { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub count: u32, | ||||
|     #[noproto(repeated, tag = "2")] | ||||
|     pub entries: Vec<ScanResult, 16>, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqSoftApConnectedSta {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespSoftApConnectedSta { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub num: u32, | ||||
|     #[noproto(repeated, tag = "2")] | ||||
|     pub stations: Vec<ConnectedStaList, 16>, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqOtaBegin {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespOtaBegin { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqOtaWrite { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub ota_data: Vec<u8, 1024>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespOtaWrite { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqOtaEnd {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespOtaEnd { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqVendorIeData { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub element_id: u32, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub length: u32, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub vendor_oui: Vec<u8, 8>, | ||||
|     #[noproto(tag = "4")] | ||||
|     pub vendor_oui_type: u32, | ||||
|     #[noproto(tag = "5")] | ||||
|     pub payload: Vec<u8, 64>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqSetSoftApVendorSpecificIe { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub enable: bool, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub r#type: CtrlVendorIeType, | ||||
|     #[noproto(tag = "3")] | ||||
|     pub idx: CtrlVendorIeid, | ||||
|     #[noproto(optional, tag = "4")] | ||||
|     pub vendor_ie_data: Option<CtrlMsgReqVendorIeData>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespSetSoftApVendorSpecificIe { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqSetWifiMaxTxPower { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub wifi_max_tx_power: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespSetWifiMaxTxPower { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqGetWifiCurrTxPower {} | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespGetWifiCurrTxPower { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub wifi_curr_tx_power: u32, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgReqConfigHeartbeat { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub enable: bool, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub duration: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgRespConfigHeartbeat { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
| /// * Event structure * | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgEventEspInit { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub init_data: Vec<u8, 64>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgEventHeartbeat { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub hb_num: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgEventStationDisconnectFromAp { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsgEventStationDisconnectFromEspSoftAp { | ||||
|     #[noproto(tag = "1")] | ||||
|     pub resp: u32, | ||||
|     #[noproto(tag = "2")] | ||||
|     pub mac: String<32>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] | ||||
| pub struct CtrlMsg { | ||||
|     /// msg_type could be req, resp or Event | ||||
|     #[noproto(tag = "1")] | ||||
|     pub msg_type: CtrlMsgType, | ||||
|     /// msg id | ||||
|     #[noproto(tag = "2")] | ||||
|     pub msg_id: CtrlMsgId, | ||||
|     /// union of all msg ids | ||||
|     #[noproto( | ||||
|         oneof, | ||||
|         tags = "101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 301, 302, 303, 304" | ||||
|     )] | ||||
|     pub payload: Option<CtrlMsgPayload>, | ||||
| } | ||||
|  | ||||
| /// union of all msg ids | ||||
| #[derive(Debug, Clone, Eq, PartialEq, noproto::Oneof)] | ||||
| pub enum CtrlMsgPayload { | ||||
|     /// * Requests * | ||||
|     #[noproto(tag = "101")] | ||||
|     ReqGetMacAddress(CtrlMsgReqGetMacAddress), | ||||
|     #[noproto(tag = "102")] | ||||
|     ReqSetMacAddress(CtrlMsgReqSetMacAddress), | ||||
|     #[noproto(tag = "103")] | ||||
|     ReqGetWifiMode(CtrlMsgReqGetMode), | ||||
|     #[noproto(tag = "104")] | ||||
|     ReqSetWifiMode(CtrlMsgReqSetMode), | ||||
|     #[noproto(tag = "105")] | ||||
|     ReqScanApList(CtrlMsgReqScanResult), | ||||
|     #[noproto(tag = "106")] | ||||
|     ReqGetApConfig(CtrlMsgReqGetApConfig), | ||||
|     #[noproto(tag = "107")] | ||||
|     ReqConnectAp(CtrlMsgReqConnectAp), | ||||
|     #[noproto(tag = "108")] | ||||
|     ReqDisconnectAp(CtrlMsgReqGetStatus), | ||||
|     #[noproto(tag = "109")] | ||||
|     ReqGetSoftapConfig(CtrlMsgReqGetSoftApConfig), | ||||
|     #[noproto(tag = "110")] | ||||
|     ReqSetSoftapVendorSpecificIe(CtrlMsgReqSetSoftApVendorSpecificIe), | ||||
|     #[noproto(tag = "111")] | ||||
|     ReqStartSoftap(CtrlMsgReqStartSoftAp), | ||||
|     #[noproto(tag = "112")] | ||||
|     ReqSoftapConnectedStasList(CtrlMsgReqSoftApConnectedSta), | ||||
|     #[noproto(tag = "113")] | ||||
|     ReqStopSoftap(CtrlMsgReqGetStatus), | ||||
|     #[noproto(tag = "114")] | ||||
|     ReqSetPowerSaveMode(CtrlMsgReqSetMode), | ||||
|     #[noproto(tag = "115")] | ||||
|     ReqGetPowerSaveMode(CtrlMsgReqGetMode), | ||||
|     #[noproto(tag = "116")] | ||||
|     ReqOtaBegin(CtrlMsgReqOtaBegin), | ||||
|     #[noproto(tag = "117")] | ||||
|     ReqOtaWrite(CtrlMsgReqOtaWrite), | ||||
|     #[noproto(tag = "118")] | ||||
|     ReqOtaEnd(CtrlMsgReqOtaEnd), | ||||
|     #[noproto(tag = "119")] | ||||
|     ReqSetWifiMaxTxPower(CtrlMsgReqSetWifiMaxTxPower), | ||||
|     #[noproto(tag = "120")] | ||||
|     ReqGetWifiCurrTxPower(CtrlMsgReqGetWifiCurrTxPower), | ||||
|     #[noproto(tag = "121")] | ||||
|     ReqConfigHeartbeat(CtrlMsgReqConfigHeartbeat), | ||||
|     /// * Responses * | ||||
|     #[noproto(tag = "201")] | ||||
|     RespGetMacAddress(CtrlMsgRespGetMacAddress), | ||||
|     #[noproto(tag = "202")] | ||||
|     RespSetMacAddress(CtrlMsgRespSetMacAddress), | ||||
|     #[noproto(tag = "203")] | ||||
|     RespGetWifiMode(CtrlMsgRespGetMode), | ||||
|     #[noproto(tag = "204")] | ||||
|     RespSetWifiMode(CtrlMsgRespSetMode), | ||||
|     #[noproto(tag = "205")] | ||||
|     RespScanApList(CtrlMsgRespScanResult), | ||||
|     #[noproto(tag = "206")] | ||||
|     RespGetApConfig(CtrlMsgRespGetApConfig), | ||||
|     #[noproto(tag = "207")] | ||||
|     RespConnectAp(CtrlMsgRespConnectAp), | ||||
|     #[noproto(tag = "208")] | ||||
|     RespDisconnectAp(CtrlMsgRespGetStatus), | ||||
|     #[noproto(tag = "209")] | ||||
|     RespGetSoftapConfig(CtrlMsgRespGetSoftApConfig), | ||||
|     #[noproto(tag = "210")] | ||||
|     RespSetSoftapVendorSpecificIe(CtrlMsgRespSetSoftApVendorSpecificIe), | ||||
|     #[noproto(tag = "211")] | ||||
|     RespStartSoftap(CtrlMsgRespStartSoftAp), | ||||
|     #[noproto(tag = "212")] | ||||
|     RespSoftapConnectedStasList(CtrlMsgRespSoftApConnectedSta), | ||||
|     #[noproto(tag = "213")] | ||||
|     RespStopSoftap(CtrlMsgRespGetStatus), | ||||
|     #[noproto(tag = "214")] | ||||
|     RespSetPowerSaveMode(CtrlMsgRespSetMode), | ||||
|     #[noproto(tag = "215")] | ||||
|     RespGetPowerSaveMode(CtrlMsgRespGetMode), | ||||
|     #[noproto(tag = "216")] | ||||
|     RespOtaBegin(CtrlMsgRespOtaBegin), | ||||
|     #[noproto(tag = "217")] | ||||
|     RespOtaWrite(CtrlMsgRespOtaWrite), | ||||
|     #[noproto(tag = "218")] | ||||
|     RespOtaEnd(CtrlMsgRespOtaEnd), | ||||
|     #[noproto(tag = "219")] | ||||
|     RespSetWifiMaxTxPower(CtrlMsgRespSetWifiMaxTxPower), | ||||
|     #[noproto(tag = "220")] | ||||
|     RespGetWifiCurrTxPower(CtrlMsgRespGetWifiCurrTxPower), | ||||
|     #[noproto(tag = "221")] | ||||
|     RespConfigHeartbeat(CtrlMsgRespConfigHeartbeat), | ||||
|     /// * Notifications * | ||||
|     #[noproto(tag = "301")] | ||||
|     EventEspInit(CtrlMsgEventEspInit), | ||||
|     #[noproto(tag = "302")] | ||||
|     EventHeartbeat(CtrlMsgEventHeartbeat), | ||||
|     #[noproto(tag = "303")] | ||||
|     EventStationDisconnectFromAp(CtrlMsgEventStationDisconnectFromAp), | ||||
|     #[noproto(tag = "304")] | ||||
|     EventStationDisconnectFromEspSoftAp(CtrlMsgEventStationDisconnectFromEspSoftAp), | ||||
| } | ||||
|  | ||||
| /// Enums similar to ESP IDF | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlVendorIeType { | ||||
|     #[default] | ||||
|     Beacon = 0, | ||||
|     ProbeReq = 1, | ||||
|     ProbeResp = 2, | ||||
|     AssocReq = 3, | ||||
|     AssocResp = 4, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlVendorIeid { | ||||
|     #[default] | ||||
|     Id0 = 0, | ||||
|     Id1 = 1, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlWifiMode { | ||||
|     #[default] | ||||
|     None = 0, | ||||
|     Sta = 1, | ||||
|     Ap = 2, | ||||
|     Apsta = 3, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlWifiBw { | ||||
|     #[default] | ||||
|     BwInvalid = 0, | ||||
|     Ht20 = 1, | ||||
|     Ht40 = 2, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlWifiPowerSave { | ||||
|     #[default] | ||||
|     PsInvalid = 0, | ||||
|     MinModem = 1, | ||||
|     MaxModem = 2, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlWifiSecProt { | ||||
|     #[default] | ||||
|     Open = 0, | ||||
|     Wep = 1, | ||||
|     WpaPsk = 2, | ||||
|     Wpa2Psk = 3, | ||||
|     WpaWpa2Psk = 4, | ||||
|     Wpa2Enterprise = 5, | ||||
|     Wpa3Psk = 6, | ||||
|     Wpa2Wpa3Psk = 7, | ||||
| } | ||||
|  | ||||
| /// enums for Control path | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlStatus { | ||||
|     #[default] | ||||
|     Connected = 0, | ||||
|     NotConnected = 1, | ||||
|     NoApFound = 2, | ||||
|     ConnectionFail = 3, | ||||
|     InvalidArgument = 4, | ||||
|     OutOfRange = 5, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlMsgType { | ||||
|     #[default] | ||||
|     MsgTypeInvalid = 0, | ||||
|     Req = 1, | ||||
|     Resp = 2, | ||||
|     Event = 3, | ||||
|     MsgTypeMax = 4, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] | ||||
| #[repr(u32)] | ||||
| pub enum CtrlMsgId { | ||||
|     #[default] | ||||
|     MsgIdInvalid = 0, | ||||
|     /// * Request Msgs * | ||||
|     ReqBase = 100, | ||||
|     ReqGetMacAddress = 101, | ||||
|     ReqSetMacAddress = 102, | ||||
|     ReqGetWifiMode = 103, | ||||
|     ReqSetWifiMode = 104, | ||||
|     ReqGetApScanList = 105, | ||||
|     ReqGetApConfig = 106, | ||||
|     ReqConnectAp = 107, | ||||
|     ReqDisconnectAp = 108, | ||||
|     ReqGetSoftApConfig = 109, | ||||
|     ReqSetSoftApVendorSpecificIe = 110, | ||||
|     ReqStartSoftAp = 111, | ||||
|     ReqGetSoftApConnectedStaList = 112, | ||||
|     ReqStopSoftAp = 113, | ||||
|     ReqSetPowerSaveMode = 114, | ||||
|     ReqGetPowerSaveMode = 115, | ||||
|     ReqOtaBegin = 116, | ||||
|     ReqOtaWrite = 117, | ||||
|     ReqOtaEnd = 118, | ||||
|     ReqSetWifiMaxTxPower = 119, | ||||
|     ReqGetWifiCurrTxPower = 120, | ||||
|     ReqConfigHeartbeat = 121, | ||||
|     /// Add new control path command response before Req_Max | ||||
|     /// and update Req_Max | ||||
|     ReqMax = 122, | ||||
|     /// * Response Msgs * | ||||
|     RespBase = 200, | ||||
|     RespGetMacAddress = 201, | ||||
|     RespSetMacAddress = 202, | ||||
|     RespGetWifiMode = 203, | ||||
|     RespSetWifiMode = 204, | ||||
|     RespGetApScanList = 205, | ||||
|     RespGetApConfig = 206, | ||||
|     RespConnectAp = 207, | ||||
|     RespDisconnectAp = 208, | ||||
|     RespGetSoftApConfig = 209, | ||||
|     RespSetSoftApVendorSpecificIe = 210, | ||||
|     RespStartSoftAp = 211, | ||||
|     RespGetSoftApConnectedStaList = 212, | ||||
|     RespStopSoftAp = 213, | ||||
|     RespSetPowerSaveMode = 214, | ||||
|     RespGetPowerSaveMode = 215, | ||||
|     RespOtaBegin = 216, | ||||
|     RespOtaWrite = 217, | ||||
|     RespOtaEnd = 218, | ||||
|     RespSetWifiMaxTxPower = 219, | ||||
|     RespGetWifiCurrTxPower = 220, | ||||
|     RespConfigHeartbeat = 221, | ||||
|     /// Add new control path command response before Resp_Max | ||||
|     /// and update Resp_Max | ||||
|     RespMax = 222, | ||||
|     /// * Event Msgs * | ||||
|     EventBase = 300, | ||||
|     EventEspInit = 301, | ||||
|     EventHeartbeat = 302, | ||||
|     EventStationDisconnectFromAp = 303, | ||||
|     EventStationDisconnectFromEspSoftAp = 304, | ||||
|     /// Add new control path command notification before Event_Max | ||||
|     /// and update Event_Max | ||||
|     EventMax = 305, | ||||
| } | ||||
| @@ -22,6 +22,7 @@ embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["ti | ||||
| lora-phy = { version = "1", optional = true } | ||||
| lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true } | ||||
| lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true } | ||||
| embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } | ||||
|  | ||||
| defmt = "0.3" | ||||
| defmt-rtt = "0.4" | ||||
| @@ -35,3 +36,4 @@ rand = { version = "0.8.4", default-features = false } | ||||
| embedded-storage = "0.3.0" | ||||
| usbd-hid = "0.6.0" | ||||
| serde = { version = "1.0.136", default-features = false } | ||||
| embedded-hal-async = "0.2.0-alpha.1" | ||||
|   | ||||
							
								
								
									
										143
									
								
								examples/nrf52840/src/bin/wifi_esp_hosted.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								examples/nrf52840/src/bin/wifi_esp_hosted.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| #![no_std] | ||||
| #![no_main] | ||||
| #![feature(type_alias_impl_trait)] | ||||
|  | ||||
| use defmt::{info, unwrap, warn}; | ||||
| use embassy_executor::Spawner; | ||||
| use embassy_net::tcp::TcpSocket; | ||||
| use embassy_net::{Stack, StackResources}; | ||||
| use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull}; | ||||
| use embassy_nrf::rng::Rng; | ||||
| use embassy_nrf::spim::{self, Spim}; | ||||
| use embassy_nrf::{bind_interrupts, peripherals}; | ||||
| use embassy_time::{Duration, Timer}; | ||||
| use embedded_hal_async::spi::ExclusiveDevice; | ||||
| use embedded_io::asynch::Write; | ||||
| use static_cell::make_static; | ||||
| use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; | ||||
|  | ||||
| bind_interrupts!(struct Irqs { | ||||
|     SPIM3 => spim::InterruptHandler<peripherals::SPI3>; | ||||
|     RNG => embassy_nrf::rng::InterruptHandler<peripherals::RNG>; | ||||
| }); | ||||
|  | ||||
| #[embassy_executor::task] | ||||
| async fn wifi_task( | ||||
|     runner: hosted::Runner< | ||||
|         'static, | ||||
|         ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_31>>, | ||||
|         Input<'static, AnyPin>, | ||||
|         Output<'static, peripherals::P1_03>, | ||||
|     >, | ||||
| ) -> ! { | ||||
|     runner.run().await | ||||
| } | ||||
|  | ||||
| #[embassy_executor::task] | ||||
| async fn net_task(stack: &'static Stack<hosted::NetDriver<'static>>) -> ! { | ||||
|     stack.run().await | ||||
| } | ||||
|  | ||||
| #[embassy_executor::main] | ||||
| async fn main(spawner: Spawner) { | ||||
|     info!("Hello World!"); | ||||
|  | ||||
|     let p = embassy_nrf::init(Default::default()); | ||||
|  | ||||
|     let miso = p.P0_28; | ||||
|     let sck = p.P0_29; | ||||
|     let mosi = p.P0_30; | ||||
|     let cs = Output::new(p.P0_31, Level::High, OutputDrive::HighDrive); | ||||
|     let handshake = Input::new(p.P1_01.degrade(), Pull::Up); | ||||
|     let ready = Input::new(p.P1_02.degrade(), Pull::None); | ||||
|     let reset = Output::new(p.P1_03, Level::Low, OutputDrive::Standard); | ||||
|  | ||||
|     let mut config = spim::Config::default(); | ||||
|     config.frequency = spim::Frequency::M1; | ||||
|     config.mode = spim::MODE_2; // !!! | ||||
|     let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); | ||||
|     let spi = ExclusiveDevice::new(spi, cs); | ||||
|  | ||||
|     let (device, mut control, runner) = embassy_net_esp_hosted::new( | ||||
|         make_static!(embassy_net_esp_hosted::State::new()), | ||||
|         spi, | ||||
|         handshake, | ||||
|         ready, | ||||
|         reset, | ||||
|     ) | ||||
|     .await; | ||||
|  | ||||
|     unwrap!(spawner.spawn(wifi_task(runner))); | ||||
|  | ||||
|     // TODO: wait for ESP_INIT event instead of hardcoding delay. | ||||
|     Timer::after(Duration::from_secs(3)).await; | ||||
|  | ||||
|     control.init().await; | ||||
|     control.join(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; | ||||
|  | ||||
|     let config = embassy_net::Config::dhcpv4(Default::default()); | ||||
|     // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { | ||||
|     //    address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), | ||||
|     //    dns_servers: Vec::new(), | ||||
|     //    gateway: Some(Ipv4Address::new(10, 42, 0, 1)), | ||||
|     // }); | ||||
|  | ||||
|     // Generate random seed | ||||
|     let mut rng = Rng::new(p.RNG, Irqs); | ||||
|     let mut seed = [0; 8]; | ||||
|     rng.blocking_fill_bytes(&mut seed); | ||||
|     let seed = u64::from_le_bytes(seed); | ||||
|  | ||||
|     // Init network stack | ||||
|     let stack = &*make_static!(Stack::new( | ||||
|         device, | ||||
|         config, | ||||
|         make_static!(StackResources::<2>::new()), | ||||
|         seed | ||||
|     )); | ||||
|  | ||||
|     unwrap!(spawner.spawn(net_task(stack))); | ||||
|  | ||||
|     // And now we can use it! | ||||
|  | ||||
|     let mut rx_buffer = [0; 4096]; | ||||
|     let mut tx_buffer = [0; 4096]; | ||||
|     let mut buf = [0; 4096]; | ||||
|  | ||||
|     loop { | ||||
|         let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | ||||
|         socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); | ||||
|  | ||||
|         info!("Listening on TCP:1234..."); | ||||
|         if let Err(e) = socket.accept(1234).await { | ||||
|             warn!("accept error: {:?}", e); | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         info!("Received connection from {:?}", socket.remote_endpoint()); | ||||
|  | ||||
|         loop { | ||||
|             let n = match socket.read(&mut buf).await { | ||||
|                 Ok(0) => { | ||||
|                     warn!("read EOF"); | ||||
|                     break; | ||||
|                 } | ||||
|                 Ok(n) => n, | ||||
|                 Err(e) => { | ||||
|                     warn!("read error: {:?}", e); | ||||
|                     break; | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             info!("rxd {:02x}", &buf[..n]); | ||||
|  | ||||
|             match socket.write_all(&buf[..n]).await { | ||||
|                 Ok(()) => {} | ||||
|                 Err(e) => { | ||||
|                     warn!("write error: {:?}", e); | ||||
|                     break; | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user