esp_hosted/lib.rs
1#![no_std]
2#![allow(dead_code)]
3#![allow(non_camel_case_types)]
4#![allow(static_mut_refs)]
5
6//! # ESP Hosted
7//! For connecting to an [ESP-Hosted-MCU](https://!github.com/espressif/esp-hosted-mcu) from a Host MCU with firmware
8//! written in rust.
9//!
10//! Compatible with ESP-Hosted-MCU 2.0.6 and ESP IDF 5.4.1 (And likely anything newer), and any host MCU and architecture.
11//! For details on ESP-HOSTED-MCU's protocol see
12//! [this document](/esp_hosted_protocol.md). For how to use the commands in the library effectively, reference the
13//! [ESP32 IDF API docs](https://!docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/network/esp_wifi.html)
14//!
15//! This library includes two approaches: A high-level API using data structures from this library, and full access to
16//! the native protobuf structures. The native API is easier to work with, but only implements a small portion of functionality.
17//! The protobuf API is complete, but more cumbersome.
18//!
19//! This library does not use an allocator. This makes integrating it simple, but it uses a significant amount of flash
20//! for static buffers. These are configured in the `build_proto/src/main.rs` script on a field-by-field basis.
21//!
22//! It's transport agnostic; compatible with SPI, SDIO, and UART. It does this by allowing the application firmware to pass
23//! a generic `write` function, and reads are performed as functions that act on buffers passed by the firmware.
24
25pub mod header;
26pub mod proto_data;
27mod rpc;
28mod transport;
29pub mod wifi;
30
31pub mod ble;
32mod esp_errors;
33pub mod proto;
34mod util;
35
36// pub use ble::*;
37use defmt::{Format, println};
38pub use esp_errors::EspCode;
39pub use header::{PayloadHeader, build_frame_ble};
40use micropb::{MessageDecode, PbDecoder};
41pub use proto::{Rpc as RpcP, RpcId as RpcIdP, RpcType as RpcTypeP};
42pub use proto_data::RpcId;
43pub use transport::PacketType;
44
45pub use crate::rpc::*;
46use crate::{
47 header::{HEADER_SIZE, InterfaceType, PL_HEADER_SIZE},
48 proto_data::RpcReqConfigHeartbeat,
49};
50
51#[macro_export]
52macro_rules! parse_le {
53 ($bytes:expr, $t:ty, $range:expr) => {{ <$t>::from_le_bytes($bytes[$range].try_into().unwrap()) }};
54}
55
56#[macro_export]
57macro_rules! copy_le {
58 ($dest:expr, $src:expr, $range:expr) => {{ $dest[$range].copy_from_slice(&$src.to_le_bytes()) }};
59}
60
61const AP_BUF_MAX: usize = 100;
62const BLE_BUF_MAX: usize = 100;
63
64const ESP_ERR_HOSTED_BASE: u16 = 0x2f00;
65
66/// A simple error enum for our host-side protocol
67#[derive(Format)]
68pub enum EspError {
69 // #[cfg(feature = "hal")]
70 // Uart(UartError),
71 /// e.g. uart, spi etc.
72 Comms,
73 UnexpectedResponse(u8),
74 CrcMismatch,
75 Timeout,
76 InvalidData,
77 Proto,
78 Capacity,
79 // todo: Put back. flash limit problem.
80 Esp(EspCode),
81}
82
83// #[cfg(feature = "hal")]
84// impl From<UartError> for EspError {
85// fn from(e: UartError) -> Self {
86// EspError::Uart(e)
87// }
88// }
89
90/// Minimum of 10s.
91pub fn cfg_heartbeat<W>(
92 buf: &mut [u8],
93 mut write: W,
94 uid: u32,
95 cfg: &RpcReqConfigHeartbeat,
96) -> Result<(), EspError>
97// todo: Typedef this if able. (Unstable feature)
98where
99 W: FnMut(&[u8]) -> Result<(), EspError>,
100{
101 let rpc = Rpc::new_req(RpcId::ReqConfigHeartbeat, uid);
102
103 let mut data = [0; 5]; // Seems to be 4 in for small duration values.
104 let data_size = cfg.to_bytes(&mut data);
105
106 // unsafe {
107 let frame_len = setup_rpc(buf, &rpc, &data[..data_size]);
108 write(&buf[..frame_len])?;
109 // }
110
111 Ok(())
112}
113
114pub struct WifiMsg<'a> {
115 pub header: PayloadHeader,
116 pub rpc: Rpc,
117 pub data: &'a [u8],
118 // pub rpc_raw: Option<RpcP>,
119 /// This is a result, because sometimes it can fail, e.g. due to a capacity error,
120 /// where we're able to parse the rest of the data directly.
121 pub rpc_parsed: Result<RpcP, EspError>,
122}
123
124pub struct HciMsg<'a> {
125 pub data: &'a [u8],
126}
127
128pub enum MsgParsed<'a> {
129 Wifi(WifiMsg<'a>),
130 Hci(HciMsg<'a>),
131}
132
133/// Parse the payload with header, and separate the RPC bytes from the whole message. Accepts
134/// the whole message received.
135pub fn parse_msg_header_not_read(buf: &[u8]) -> Result<MsgParsed, EspError> {
136 // Check for a shifted packet due to jitter. For example, from late reception start.
137 // This will cut of information that may be important for Wi-Fi RPC packets, but is skippable
138 // for HCI.
139
140 if buf[0] > 8 || buf[0] == 0 {
141 // Handle a small amount of jitter (delayed reception) for BLE packets.
142 const MAX_SHIFT: usize = 6; // Index of offset len.
143
144 // Note: This approach starts by looking at byte 4, then moves the starting index left
145 // each index, effectively. It covers both forward, and reverse shifts by a few bytes.
146 for offset in 1..MAX_SHIFT {
147 if buf[offset..offset + 2] == [12, 0] && buf[offset + 6..offset + 9] == [0, 0, 4] {
148 // Align the shift with the [12, 0] we matched.
149 let shift = 4 - offset;
150
151 return Ok(MsgParsed::Hci(HciMsg {
152 data: &buf[PL_HEADER_SIZE - shift..],
153 }));
154 }
155 }
156
157 // Check for more aggressive shifts as well, without relying on the [12, 0] offset,
158 // and assuming HCI packet type = 62
159 for offset in 1..16 {
160 // println!("Checking for match T2: {:?}", buf[offset..offset + 9]);
161 // if buf[offset..offset + 4] == [0, 0, 4, 62] {
162 if offset + 3 >= buf.len() {
163 return Err(EspError::InvalidData);
164 }
165
166 if buf[offset..offset + 3] == [0, 4, 62] {
167 // Align the shift with the [12, 0] we matched.
168 let shift = 9 - offset;
169 // println!(
170 // "Shifted BLE packet Type 2. Shift: {}. Offset: {}",
171 // shift, offset
172 // );
173
174 // println!("Corrected buf T2: {:?}",&buf[PL_HEADER_SIZE - shift..30]); // todo tmep
175 return Ok(MsgParsed::Hci(HciMsg {
176 data: &buf[PL_HEADER_SIZE - shift..],
177 }));
178 }
179 }
180
181 // todo: Handle shifted Wi-Fi packets too?
182 // let mut modded_header_buf = [0; PL_HEADER_SIZE];
183 //
184 // modded_header_buf[shift..].copy_from_slice(&buf[shift..PL_HEADER_SIZE - shift]);
185 // println!("Modded header: {:?}", modded_header_buf);
186 //
187 // header = PayloadHeader::from_bytes(&mut modded_header_buf)?;
188 // header.if_type = InterfaceType::Hci;
189 }
190
191 let mut header = PayloadHeader::from_bytes(&buf[..HEADER_SIZE])?;
192 let mut total_size = header.len as usize + PL_HEADER_SIZE;
193
194 if total_size > buf.len() {
195 return Err(EspError::Capacity);
196 }
197
198 if header.if_type == InterfaceType::Hci {
199 return Ok(MsgParsed::Hci(HciMsg {
200 data: &buf[PL_HEADER_SIZE..],
201 }));
202 }
203
204 if HEADER_SIZE >= total_size {
205 // todo: Print is temp.
206 println!(
207 "Error: Invalid RPC packet. packet size: {}, buf: {:?}",
208 total_size,
209 buf[0..24]
210 );
211 return Err(EspError::InvalidData);
212 }
213
214 let rpc_buf = &buf[HEADER_SIZE..total_size];
215 let (rpc, data_start_i, _data_len_rpc) = Rpc::from_bytes(rpc_buf)?;
216 let data = &rpc_buf[data_start_i..];
217
218 // Parsing the proto data from the generated mod.
219 // let mut decoder = PbDecoder::new(&rpc_buf[0..100]);
220 let mut decoder = PbDecoder::new(rpc_buf);
221 let mut rpc_parsed = RpcP::default();
222
223 let rpc_parsed = match rpc_parsed.decode(&mut decoder, rpc_buf.len()) {
224 Ok(r) => Ok(rpc_parsed),
225 Err(e) => Err(EspError::Proto),
226 };
227 // rpc_parsed
228 // .decode(&mut decoder, rpc_buf.len())
229 // .map_err(|_| EspError::Proto)?;
230
231 Ok(MsgParsed::Wifi(WifiMsg {
232 header,
233 rpc,
234 data,
235 rpc_parsed,
236 }))
237}
238
239/// Use this for SPI, after parsing the header in the first 12-byte transaction.
240/// Assumes the header has been read already, and passed as a param.
241pub fn parse_msg_header_read(buf: &[u8], header: PayloadHeader) -> Result<MsgParsed, EspError> {
242 let mut total_size = header.len as usize;
243
244 if total_size > buf.len() {
245 return Err(EspError::Capacity);
246 }
247
248 if header.if_type == InterfaceType::Hci {
249 return Ok(MsgParsed::Hci(HciMsg { data: &buf[..] }));
250 }
251
252 let rpc_buf = &buf[..total_size];
253 let (rpc, data_start_i, _data_len_rpc) = Rpc::from_bytes(rpc_buf)?;
254 let data = &rpc_buf[data_start_i..];
255
256 println!("RPC read: {:?}", rpc); // todo temp
257
258 // Parsing the proto data from the generated mod.
259 // let mut decoder = PbDecoder::new(&rpc_buf[0..100]);
260 let mut decoder = PbDecoder::new(rpc_buf);
261 let mut rpc_parsed = RpcP::default();
262
263 let rpc_parsed = match rpc_parsed.decode(&mut decoder, rpc_buf.len()) {
264 Ok(r) => Ok(rpc_parsed),
265 Err(e) => Err(EspError::Proto),
266 };
267 // rpc_parsed
268 // .decode(&mut decoder, rpc_buf.len())
269 // .map_err(|_| EspError::Proto)?;
270
271 Ok(MsgParsed::Wifi(WifiMsg {
272 header,
273 rpc,
274 data,
275 rpc_parsed,
276 }))
277}