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
25mod 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;
43
44pub use crate::rpc::*;
45use crate::{
46    header::{HEADER_SIZE, InterfaceType, PL_HEADER_SIZE},
47    proto_data::RpcReqConfigHeartbeat,
48};
49
50#[macro_export]
51macro_rules! parse_le {
52    ($bytes:expr, $t:ty, $range:expr) => {{ <$t>::from_le_bytes($bytes[$range].try_into().unwrap()) }};
53}
54
55#[macro_export]
56macro_rules! copy_le {
57    ($dest:expr, $src:expr, $range:expr) => {{ $dest[$range].copy_from_slice(&$src.to_le_bytes()) }};
58}
59
60const AP_BUF_MAX: usize = 100;
61const BLE_BUF_MAX: usize = 100;
62
63const ESP_ERR_HOSTED_BASE: u16 = 0x2f00;
64
65/// A simple error enum for our host-side protocol
66#[derive(Format)]
67pub enum EspError {
68    // #[cfg(feature = "hal")]
69    // Uart(UartError),
70    /// e.g. uart, spi etc.
71    Comms,
72    UnexpectedResponse(u8),
73    CrcMismatch,
74    Timeout,
75    InvalidData,
76    Proto,
77    Capacity,
78    // todo: Put back. flash limit problem.
79    Esp(EspCode),
80}
81
82// #[cfg(feature = "hal")]
83// impl From<UartError> for EspError {
84//     fn from(e: UartError) -> Self {
85//         EspError::Uart(e)
86//     }
87// }
88
89/// Minimum of 10s.
90pub fn cfg_heartbeat<W>(
91    buf: &mut [u8],
92    mut write: W,
93    uid: u32,
94    cfg: &RpcReqConfigHeartbeat,
95) -> Result<(), EspError>
96// todo: Typedef this if able. (Unstable feature)
97where
98    W: FnMut(&[u8]) -> Result<(), EspError>,
99{
100    let rpc = Rpc::new_req(RpcId::ReqConfigHeartbeat, uid);
101
102    let mut data = [0; 5]; // Seems to be 4 in for small duration values.
103    let data_size = cfg.to_bytes(&mut data);
104
105    // unsafe {
106    let frame_len = setup_rpc(buf, &rpc, &data[..data_size]);
107    write(&buf[..frame_len])?;
108    // }
109
110    Ok(())
111}
112
113pub struct WifiMsg<'a> {
114    pub header: PayloadHeader,
115    pub rpc: Rpc,
116    pub data: &'a [u8],
117    // pub rpc_raw: Option<RpcP>,
118    pub rpc_parsed: RpcP,
119}
120
121pub struct HciMsg<'a> {
122    pub data: &'a [u8],
123}
124
125pub enum MsgParsed<'a> {
126    Wifi(WifiMsg<'a>),
127    Hci(HciMsg<'a>),
128}
129
130/// Parse the payload header, and separate the RPC bytes from the whole message. Accepts
131/// the whole message received.
132pub fn parse_msg(buf: &[u8]) -> Result<MsgParsed, EspError> {
133    let header = PayloadHeader::from_bytes(&buf[..HEADER_SIZE])?;
134    let total_size = header.len as usize + PL_HEADER_SIZE;
135
136    if total_size >= buf.len() {
137        return Err(EspError::Capacity);
138    }
139
140    if header.if_type == InterfaceType::Hci {
141        return Ok(MsgParsed::Hci(HciMsg {
142            data: &buf[PL_HEADER_SIZE..],
143        }));
144    }
145
146    if HEADER_SIZE >= total_size {
147        println!("Error: Invalid RPC packet: {:?}", buf[0..24]);
148        return Err(EspError::InvalidData);
149    }
150
151    let rpc_buf = &buf[HEADER_SIZE..total_size];
152    let (rpc, data_start_i, _data_len_rpc) = Rpc::from_bytes(rpc_buf)?;
153    let data = &rpc_buf[data_start_i..];
154
155    // Parsing the proto data from the generated mod.
156    let mut decoder = PbDecoder::new(rpc_buf);
157    let mut rpc_parsed = RpcP::default();
158
159    rpc_parsed
160        .decode(&mut decoder, rpc_buf.len())
161        .map_err(|_| EspError::Proto)?;
162
163    Ok(MsgParsed::Wifi(WifiMsg {
164        header,
165        rpc,
166        data,
167        rpc_parsed,
168    }))
169}