ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
esp_now_hosted_rpc.h
Go to the documentation of this file.
1/*
2 * esp_now_hosted — ESP-NOW-over-CustomRpc wire protocol.
3 *
4 * Shared, byte-for-byte-identical contract between:
5 * - the host shim (esphome/components/esp32_hosted/esp_now_hosted.cpp)
6 * - the coprocessor firmware (esphome/esp-hosted-firmware)
7 *
8 * It rides esp-hosted's CustomRpc channel (RPC ID 388, "peer data transfer",
9 * available since esp-hosted v2.8.1), teaching the radio-less host <-> radio
10 * co-processor link to carry esp_now.h, which esp-hosted itself does not proxy
11 * (Espressif issue espressif/esp-hosted-mcu#19).
12 *
13 * KEEP THE TWO COPIES IN SYNC. The canonical copy lives here; the coprocessor
14 * firmware uses a verbatim copy. Both sides are little-endian, so these packed
15 * structs are wire-compatible with no byte-swapping.
16 */
17
18#ifndef ESP_NOW_HOSTED_RPC_H
19#define ESP_NOW_HOSTED_RPC_H
20
21#ifdef __cplusplus
22#include <cstdint>
23#else
24#include <stdint.h>
25#endif
26
27#ifdef __cplusplus
28extern "C" {
29#endif
30
31/* ── CustomRpc message IDs (any uint32_t except 0xFFFFFFFF) ──────────────────
32 * One REQ handler slot on the device; three event handler slots on the host.
33 * The bytes spell "now" + index, a private range unlikely to clash with other
34 * CustomRpc users (e.g. the stock peer_data_transfer example's 1..6). */
35#define ESP_NOW_HOSTED_MSG_REQ 0x6E6F7701u /* host -> device : request envelope */
36#define ESP_NOW_HOSTED_MSG_RESP 0x6E6F7702u /* device -> host : reply to a REQ */
37#define ESP_NOW_HOSTED_MSG_RECV 0x6E6F7703u /* device -> host : async RX frame */
38#define ESP_NOW_HOSTED_MSG_SEND 0x6E6F7704u /* device -> host : async TX status */
39
40/* ── Request opcodes ────────────────────────────────────────────────────── */
41enum {
42 ESP_NOW_HOSTED_OP_INIT = 1, /* esp_now_init + register device recv/send cbs */
43 ESP_NOW_HOSTED_OP_DEINIT = 2, /* unregister cbs + esp_now_deinit */
44 ESP_NOW_HOSTED_OP_ADD_PEER = 3, /* payload: esp_now_hosted_peer_t */
45 ESP_NOW_HOSTED_OP_DEL_PEER = 4, /* payload: 6-byte peer MAC */
46 ESP_NOW_HOSTED_OP_IS_PEER_EXIST = 5, /* payload: 6-byte MAC; ret: 1 byte bool */
47 ESP_NOW_HOSTED_OP_SEND = 6, /* payload: esp_now_hosted_send_req_t */
48 ESP_NOW_HOSTED_OP_GET_VERSION = 7, /* ret: uint32 version */
49 ESP_NOW_HOSTED_OP_SET_PMK = 8, /* payload: 16-byte PMK */
50 ESP_NOW_HOSTED_OP_MOD_PEER = 9, /* payload: esp_now_hosted_peer_t */
51};
52
53/* Largest ESP-NOW payload we forward. ESP-NOW v2 (IDF >= 5.4) is 1470 B; well
54 * under esp-hosted's 8166 B CustomRpc cap, so the shim never truncates. */
55#define ESP_NOW_HOSTED_MAX_FRAME 1470u
56/* Envelope slack for the largest opcode payload (a SEND req wrapping a frame). */
57#define ESP_NOW_HOSTED_MAX_PAYLOAD (ESP_NOW_HOSTED_MAX_FRAME + 16u)
58/* Host request/response round-trip timeout over the transport. Generous:
59 * normal RTT is sub-millisecond, but Wi-Fi/BLE contention on the co-processor
60 * can stall the RX thread. */
61#define ESP_NOW_HOSTED_TIMEOUT_MS 2000
62
63/* ── Envelopes ──────────────────────────────────────────────────────────── */
64
65/* These payloads are shared verbatim with the C co-processor firmware, so they
66 * use C's `typedef struct {...} name;` idiom rather than C++ `using` aliases,
67 * which would not compile there. Silence clang-tidy's modernize-use-using for
68 * the shared struct block. */
69// NOLINTBEGIN(modernize-use-using)
70typedef struct {
71 uint8_t opcode; /* one of ESP_NOW_HOSTED_OP_* */
72 uint8_t seq; /* wraps 0..255; echoed in the response for matching */
73 uint16_t payload_len; /* bytes of opcode-specific payload that follow */
74 uint8_t payload[]; /* flexible */
75} __attribute__((packed)) esp_now_hosted_req_t;
76
77typedef struct {
78 uint8_t opcode; /* echoes the request opcode */
79 uint8_t seq; /* echoes the request seq */
80 int32_t status; /* esp_err_t from the native call on the co-processor */
81 uint16_t ret_len; /* bytes of return payload that follow */
82 uint8_t ret[]; /* flexible (e.g. version u32, is_peer_exist bool) */
83} __attribute__((packed)) esp_now_hosted_resp_t;
84
85/* ── Opcode payloads ────────────────────────────────────────────────────── */
86
87/* esp_now_peer_info_t minus the host-only `priv` pointer, which is meaningless
88 * across the transport and never set by ESPHome's espnow component. */
89typedef struct {
90 uint8_t peer_addr[6];
91 uint8_t lmk[16];
92 uint8_t channel; /* 0 = current channel */
93 uint8_t ifidx; /* wifi_interface_t (0=STA, 1=AP) */
94 uint8_t encrypt; /* bool */
95} __attribute__((packed)) esp_now_hosted_peer_t;
96
97typedef struct {
98 uint8_t has_addr; /* 0 => peer_addr is NULL (broadcast to all peers) */
99 uint8_t peer_addr[6];
100 uint16_t data_len;
101 uint8_t data[]; /* flexible, up to ESP_NOW_HOSTED_MAX_FRAME */
102} __attribute__((packed)) esp_now_hosted_send_req_t;
103
104/* ── Async events (device -> host) ──────────────────────────────────────── */
105
106/* Reconstructed on the host into an esp_now_recv_info_t + a minimal
107 * wifi_pkt_rx_ctrl_t. ESPHome's espnow reads info->src_addr, info->des_addr,
108 * info->rx_ctrl->rssi and info->rx_ctrl->timestamp. */
109typedef struct {
110 uint8_t src_addr[6];
111 uint8_t des_addr[6];
112 int8_t rssi;
113 uint8_t channel;
114 uint16_t data_len;
115 uint8_t data[]; /* flexible */
116} __attribute__((packed)) esp_now_hosted_recv_evt_t;
117
118typedef struct {
119 uint8_t des_addr[6];
120 uint8_t status; /* esp_now_send_status_t (0 = success) */
121} __attribute__((packed)) esp_now_hosted_send_evt_t;
122// NOLINTEND(modernize-use-using)
123
124#ifdef __cplusplus
125}
126#endif
127
128#endif /* ESP_NOW_HOSTED_RPC_H */
struct @66::@67 __attribute__
Wake the main loop task from an ISR. ISR-safe.
Definition main_task.h:32
@ ESP_NOW_HOSTED_OP_INIT
@ ESP_NOW_HOSTED_OP_SET_PMK
@ ESP_NOW_HOSTED_OP_DEL_PEER
@ ESP_NOW_HOSTED_OP_GET_VERSION
@ ESP_NOW_HOSTED_OP_DEINIT
@ ESP_NOW_HOSTED_OP_IS_PEER_EXIST
@ ESP_NOW_HOSTED_OP_ADD_PEER
@ ESP_NOW_HOSTED_OP_SEND
@ ESP_NOW_HOSTED_OP_MOD_PEER
int ret
uint16_t seq