Crates.io | esp-hosted |
lib.rs | esp-hosted |
version | 0.1.12 |
created_at | 2025-06-21 18:48:03.042894+00 |
updated_at | 2025-09-11 19:21:59.866796+00 |
description | Support for the ESP-Hosted firmware, with an STM32 host. |
homepage | |
repository | https://github.com/David-OConnor/esp-hosted |
max_upload_size | |
id | 1721020 |
size | 1,216,647 |
For connecting to an ESP-Hosted-MCU from a Host MCU with firmware written in rust.
Compatible with ESP-Hosted-MCU 2.0.6 and ESP IDF 5.4.1 (And likely anything newer), and any host MCU and architecture. For details on ESP-HOSTED-MCU's protocol see this document. For a list of Wi-Fi commands and dat structures available, reference the ESP32 IDF API Reference, Wi-Fi section. For BLE commands, reference the HCI docs.
This library includes two approaches: A high-level API using data structures from this library, and full access to the native protobuf structures (Wi-Fi) and HCI interface (BLE). The native API is easier to work with, but only implements a portion of functionality. The protobuf API is complete, but more cumbersome.
This library does not use an allocator. This makes integrating it simple, but it uses a significant amount of flash
for static buffers. These are configured in the build_proto/src/main.rs
script on a field-by-field basis.
It's transport agnostic; compatible with SPI, SDIO, and UART. It does this by allowing the application firmware to pass
a generic write
function, and reads are performed as functions that act on buffers passed by the firmware.
Example use:
use esp_hosted::{self, wifi};
fn init(buf: &mut [u8], uart: &mut Uart) {
// Write could also be SPI, dma etc.
let mut write = |buf: &[u8]| {
uart.write(buf).map_err(|e| {
println!("Uart write error: {:?}", e);
EspError::Comms
})
};
let heartbeat_cfg = RpcReqConfigHeartbeat {
enable: true,
duration: 10,
};
esp_hosted::cfg_heartbeat(buf, &mut write, 0, &heartbeat_cfg)?;
// Configure Wi-Fi settings as-required, using functions in the `esp_hosted::wifi` module.
wifi::start(buf, &mut write, 1).is_err()?;
}
In your UART, SPI etc reception handling (e.g. an interrupt handler), you can parse incoming messages from the Esp.
The parse_msg
function returns a MsgParsed
enum of two varieties. One for Wi-Fi, the other for HCI (Bluetooth).
The Wi-Fi message containing the following:
&[u8]
micropb
library, which contains the full RPC data, in a raw, but complete format.The HCI (BLE) message contains a plain byte array of the payload received. We may change this later to parse it. For now, it's bring-your-own-HCI tools.
The auto-generated structs are rough: They don't include documentation, use numerical values directly vice
enums, and use i32
for many integer types that are u8
in practice, and as defined by ESP-IDF.
This example demonstrates how to read messages sent by the ESP asynchronously.
#[interrupt]
fn USART2() {
// Configure your I/O hardware here to start and stop transfers etc.
// ...
let msg = esp_hosted::parse_msg(buf)?;
println!("\nHeader: {:?}", msg.header);
println!("RPC: {:?}", msg.rpc);
println!("Data buf: {:?}", msg.data_buf);
match msg {
MsgParsed::Wifi(wifi_msg) => {
match wifi_msg.msg_id {
// Example using native parsing and direct payload.
RpcId::EventHeartbeat => {
println!("Heartbeat data: {:?}", rpc.data);
}
_ => ()
}
// For access to the full set of responses, parsed from the .proto file:
if let Some(pl) = &wifi_msg.rpc_parsed.payload {
match pl {
Rpc_::Payload::EventHeartbeat(hb) => {
println!("Heartbeat data: {:?}", hb.hb_num);
}
_ => (), // etc
}
}
}
MsgParsed::Hci(hci) => {}
}
}
To perform specific actions, there are functions like wifi::get_protocol
, wifi::start
, wifi::get_mode
etc. There
take a write
fn and uid
as parameters, and others on a per-message basis. These are set up using structs that
are part of this library.
To access the full functionality supported by ESP-Hosted, create a RpcP
struct, then
pass it, and a write fn to the write_rpc_proto
. Constructing these RpcP
structs is done IOC the micropb
lib. Here's
an example, using the same heartbeat config as above. This is more verbose than our high-level API, but is more flexible:
use esp_hosted::{RpcP, RpcTypeP, RpcIdP, Rpc_};
fn init(buf: &mut [u8], uart: &mut Uart) {
// let write = ... (Same as above)
let mut hb_msg = RpcP::default();
hb_msg.uid = 0;
hb_msg.msg_type = RpcTypeP::Req;
hb_msg.msg_id = RpcIdP::ReqConfigHeartbeat;
let mut hb_cfg = Rpc_Req_ConfigHeartbeat::default();
hb_cfg.enable = true;
hb_cfg.duration = 10;
hb_msg.payload = Some(Rpc_::Payload::ReqConfigHeartbeat(hb_cfg));
esp_hosted::write_rpc_proto(buf, &mut write, hb_msg)?;
}
You will run steps like this prior to using Wi-Fi functionality in many cases:
wifi::init(&mut buf, &mut write, 0, &wifi::InitConfig::default())?;
wifi::set_mode(&mut buf, &mut write, 0, WifiMode::Ap)?;
wifi::start(&mut buf, &mut write, 0)?;
This is not required if installing from crates.io; only applicable if working with the source directly.
The module proto.rs
is not included directly in the source code; it's built from
esp_hosted_rpc.proto using
Micropb
To build:
build_proto
sub application with cargo run
from its directory. This will place esp_hosed_proto.rs
in this program's src
folder.