[](https://crates.io/crates/esp-hosted)
[](https://docs.rs/esp-hosted)
# ESP Hosted
For connecting to an [ESP-Hosted-MCU](https://github.com/espressif/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](/esp_hosted_protocol.md). For a list of Wi-Fi commands and dat structures available, reference the
[ESP32 IDF API Reference, Wi-Fi section](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/network/esp_wifi.html). For BLE commands, reference the [HCI docs](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html).
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:
```rust
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:
- The payload header. This contains generic data, and you may not need to use it.
- A struct from this library containing the RPC header. This determines if a request, response, or event, and the Rpc ID being used.
- The rpc payload, as a `&[u8]`
- A struct generated by the `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.
```rust
#[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:
```rust
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)?;
}
```
### Example Wi-Fi initialization
You will run steps like this prior to using Wi-Fi functionality in many cases:
```rust
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)?;
```
## Building the proto file
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](https://github.com/espressif/esp-hosted-mcu/blob/main/common/proto/esp_hosted_rpc.proto) using
[Micropb](https://github.com/YuhanLiin/micropb)
To build:
- **1**: Install the [protoc](https://grpc.io/docs/protoc-installation/) application, and place its on your system's path.
- **2:** Run the `build_proto` sub application with `cargo run` from its directory. This will place `esp_hosed_proto.rs` in this program's `src` folder.