Refactor ESP-NOW air protocol to nanopb protobuf.

Add esp_now_messages.proto with EspNowMessage types, encode/decode helpers,
and Makefile targets to regenerate firmware and UART schemas together.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
simon 2026-05-18 22:44:57 +02:00
parent 6cdca4f3ad
commit 755bdd92d7
9 changed files with 405 additions and 100 deletions

View File

@ -3,3 +3,8 @@ default:
proto_generate_uart:
python libs/nanopb/generator/nanopb_generator.py main/proto/uart_messages.proto
proto_generate_espnow:
python libs/nanopb/generator/nanopb_generator.py main/proto/esp_now_messages.proto
proto_generate: proto_generate_uart proto_generate_espnow

View File

@ -18,8 +18,11 @@ idf_component_register(
"cmd_client_info.c"
"client_registry.c"
"esp_now_comm.c"
"esp_now_proto.c"
"proto/uart_messages.pb.c"
"proto/esp_now_messages.pb.c"
"proto/pb_encode.c"
"proto/pb_decode.c"
"proto/pb_common.c"
INCLUDE_DIRS
"."

View File

@ -64,30 +64,17 @@ Implementation: `esp_now_comm.c` / `esp_now_comm.h`.
WiFi is brought up in STA mode (no AP association). Channel = `app_config.network` (clamped to 113).
### Air protocol (compact binary, not protobuf)
### Air protocol (nanopb)
| Byte | Field |
|------|--------|
| 0 | Magic `0xA1` |
| 1 | Message type |
| 2 | Network ID (must match local config) |
| 3+ | Type-specific |
Schema: `proto/esp_now_messages.proto`. Encode/decode: `esp_now_proto.c`. The ESP-NOW payload is a single encoded `EspNowMessage` (no extra framing).
| Type | Value | Direction | Purpose |
|------|-------|-----------|---------|
| `DISCOVER` | 1 | Master → broadcast `FF:FF:FF:FF:FF:FF` | Master is searching for slaves |
| `SLAVE_INFO` | 2 | Slave → master | Slave registration |
| `HEARTBEAT` | 3 | Slave → master | Keep-alive (header only, no extra payload) |
| `EspNowMessageType` | Direction | `oneof` payload |
|---------------------|-----------|-----------------|
| `ESPNOW_DISCOVER` | Master → broadcast `FF:FF:FF:FF:FF:FF` | `EspNowDiscover` (`network`) |
| `ESPNOW_SLAVE_INFO` | Slave → master | `EspNowSlavePresence` |
| `ESPNOW_HEARTBEAT` | Slave → master | `EspNowSlavePresence` (same fields) |
`SLAVE_INFO` payload (after header bytes 02):
| Field | Type | Description |
|-------|------|-------------|
| `mac` | 6 bytes | Slave WiFi MAC |
| `version` | uint32 | `POWERPOD_FW_VERSION` (default 1) |
| `slave_id` | uint32 | Currently last byte of MAC |
| `available` | uint8 | 1 = available |
| `used` | uint8 | 0 = unused |
`EspNowSlavePresence`: `network`, `mac` (6 bytes), `version`, `slave_id`, `available`, `used`.
**Master:** task `espnow_disc` sends `DISCOVER` every **500 ms** on the configured network. Logs `slave joined id=… mac=… ver=…` when a new slave is seen (up to 16 entries). Task `espnow_mon` runs every **1 s** and marks a client **inactive** (`available = false`) if no `SLAVE_INFO` or `HEARTBEAT` was received for **3 s** (three missed 1 s heartbeats). A later heartbeat sets `available` true again and logs reactivation.
@ -164,11 +151,14 @@ Host and master speak nanopb-encoded `UartMessage` inside UART frames (byte 0 =
Regenerate C code:
```bash
make proto_generate
# or individually:
make proto_generate_uart
# or:
python libs/nanopb/generator/nanopb_generator.py main/proto/uart_messages.proto
make proto_generate_espnow
```
After generation, ensure `main/proto/*.pb.c` includes use `#include "…pb.h"` (not `main/proto/…`).
Build embeds `POWERPOD_GIT_HASH` via `git rev-parse` in `main/CMakeLists.txt`.
### VERSION command
@ -260,7 +250,9 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running `
| `cmd_client_info.c/h` | CLIENT_INFO handler |
| `client_registry.c/h` | Registered slave table |
| `led_ring.c/h` | LED digit display |
| `proto/uart_messages.proto` | Protocol schema |
| `proto/uart_messages.proto` | UART protocol schema |
| `proto/esp_now_messages.proto` | ESP-NOW protocol schema |
| `esp_now_proto.c/h` | Encode/decode `EspNowMessage` |
| `proto/*.pb.c/h` | Generated nanopb |
| `CMakeLists.txt` | Sources, `esp_wifi`, drivers, git hash |

View File

@ -1,5 +1,6 @@
#include "client_registry.h"
#include "esp_now_comm.h"
#include "esp_now_proto.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_log.h"
@ -17,10 +18,6 @@
#define POWERPOD_FW_VERSION 1u
#endif
#define ESPNOW_MAGIC 0xA1
#define ESPNOW_MSG_DISCOVER 1
#define ESPNOW_MSG_SLAVE_INFO 2
#define ESPNOW_MSG_HEARTBEAT 3
#define ESPNOW_DISCOVER_INTERVAL_MS 500
#define ESPNOW_HEARTBEAT_INTERVAL_MS 1000
#define ESPNOW_HEARTBEAT_MISS_COUNT 3
@ -33,24 +30,6 @@ static const uint8_t ESPNOW_BCAST[ESP_NOW_ETH_ALEN] = {0xff, 0xff, 0xff,
static const char *TAG = "[ESPNOW]";
typedef struct __attribute__((packed)) {
uint8_t magic;
uint8_t type;
uint8_t network;
uint8_t reserved;
} espnow_discover_packet_t;
typedef struct __attribute__((packed)) {
uint8_t magic;
uint8_t type;
uint8_t network;
uint8_t mac[ESP_NOW_ETH_ALEN];
uint32_t version;
uint32_t slave_id;
uint8_t available;
uint8_t used;
} espnow_slave_packet_t;
static app_config_t s_config;
static uint8_t s_wifi_channel;
static uint8_t s_own_mac[ESP_NOW_ETH_ALEN];
@ -98,30 +77,54 @@ static esp_err_t ensure_peer(const uint8_t *mac) {
static esp_err_t ensure_broadcast_peer(void) { return ensure_peer(ESPNOW_BCAST); }
static void build_slave_packet(espnow_slave_packet_t *pkt, uint8_t type) {
pkt->magic = ESPNOW_MAGIC;
pkt->type = type;
pkt->network = s_config.network;
memcpy(pkt->mac, s_own_mac, ESP_NOW_ETH_ALEN);
pkt->version = POWERPOD_FW_VERSION;
pkt->slave_id = s_own_mac[5];
pkt->available = 1;
pkt->used = 0;
static void fill_presence(alox_EspNowSlavePresence *presence) {
presence->network = s_config.network;
presence->version = POWERPOD_FW_VERSION;
presence->slave_id = s_own_mac[5];
presence->available = true;
presence->used = false;
esp_now_proto_setup_presence_encode(presence, s_own_mac);
}
static void send_slave_packet(const uint8_t *dest_mac, uint8_t type) {
espnow_slave_packet_t pkt;
build_slave_packet(&pkt, type);
static esp_err_t send_message(const uint8_t *dest_mac,
const alox_EspNowMessage *msg) {
uint8_t buf[ESPNOW_PB_MAX_SIZE];
size_t len = 0;
esp_err_t err = esp_now_proto_encode(msg, buf, sizeof(buf), &len);
if (err != ESP_OK) {
ESP_LOGW(TAG, "encode failed");
return err;
}
if (ensure_peer(dest_mac) != ESP_OK) {
return;
return ESP_FAIL;
}
esp_err_t err = esp_now_send(dest_mac, (const uint8_t *)&pkt, sizeof(pkt));
err = esp_now_send(dest_mac, buf, len);
if (err != ESP_OK) {
ESP_LOGW(TAG, "send type=%u failed: %s", (unsigned)type,
ESP_LOGW(TAG, "send type=%u failed: %s", (unsigned)msg->type,
esp_err_to_name(err));
}
return err;
}
static void send_presence(const uint8_t *dest_mac,
alox_EspNowMessageType type) {
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
alox_EspNowSlavePresence *presence = NULL;
msg.type = type;
if (type == alox_EspNowMessageType_ESPNOW_SLAVE_INFO) {
msg.which_payload = alox_EspNowMessage_slave_info_tag;
presence = &msg.payload.slave_info;
} else {
msg.which_payload = alox_EspNowMessage_heartbeat_tag;
presence = &msg.payload.heartbeat;
}
fill_presence(presence);
send_message(dest_mac, &msg);
}
static void slave_reset_join(void) {
@ -130,15 +133,16 @@ static void slave_reset_join(void) {
s_last_discover_ms = 0;
}
static void handle_client_packet(const espnow_slave_packet_t *pkt) {
if (pkt->network != s_config.network) {
static void handle_client_presence(const alox_EspNowSlavePresence *presence,
const uint8_t mac[CLIENT_MAC_LEN]) {
if (presence->network != s_config.network) {
return;
}
bool is_new = false;
bool reactivated = false;
esp_err_t err = client_registry_heartbeat(
pkt->mac, pkt->slave_id, pkt->version, pkt->used != 0, &is_new,
mac, presence->slave_id, presence->version, presence->used, &is_new,
&reactivated);
if (err != ESP_OK) {
ESP_LOGW(TAG, "client registry full");
@ -146,20 +150,20 @@ static void handle_client_packet(const espnow_slave_packet_t *pkt) {
}
char mac_str[18];
mac_to_str(pkt->mac, mac_str, sizeof(mac_str));
mac_to_str(mac, mac_str, sizeof(mac_str));
if (is_new) {
ESP_LOGI(TAG, "client registered id=%lu mac=%s ver=%lu",
(unsigned long)pkt->slave_id, mac_str,
(unsigned long)pkt->version);
(unsigned long)presence->slave_id, mac_str,
(unsigned long)presence->version);
} else if (reactivated) {
ESP_LOGI(TAG, "client reconnected id=%lu mac=%s",
(unsigned long)pkt->slave_id, mac_str);
(unsigned long)presence->slave_id, mac_str);
}
}
static void handle_discover(const uint8_t *sender_mac,
const espnow_discover_packet_t *pkt) {
if (pkt->network != s_config.network) {
const alox_EspNowDiscover *discover) {
if (discover->network != s_config.network) {
return;
}
@ -183,9 +187,10 @@ static void handle_discover(const uint8_t *sender_mac,
char mac_str[18];
mac_to_str(sender_mac, mac_str, sizeof(mac_str));
ESP_LOGI(TAG, "joined network %u, master %s", (unsigned)pkt->network, mac_str);
ESP_LOGI(TAG, "joined network %u, master %s", (unsigned)discover->network,
mac_str);
send_slave_packet(sender_mac, ESPNOW_MSG_SLAVE_INFO);
send_presence(sender_mac, alox_EspNowMessageType_ESPNOW_SLAVE_INFO);
}
static void slave_check_master_timeout(void) {
@ -220,7 +225,7 @@ static void slave_heartbeat_task(void *param) {
continue;
}
send_slave_packet(s_master_mac, ESPNOW_MSG_HEARTBEAT);
send_presence(s_master_mac, alox_EspNowMessageType_ESPNOW_HEARTBEAT);
}
}
@ -238,51 +243,53 @@ static void master_monitor_task(void *param) {
static void espnow_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data,
int len) {
if (info == NULL || data == NULL || len < 3) {
if (info == NULL || data == NULL || len <= 0) {
return;
}
if (data[0] != ESPNOW_MAGIC) {
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
uint8_t peer_mac[CLIENT_MAC_LEN];
esp_now_proto_setup_message_decode(&msg, peer_mac);
if (esp_now_proto_decode(data, (size_t)len, &msg) != ESP_OK) {
return;
}
switch (data[1]) {
case ESPNOW_MSG_DISCOVER:
if (!s_config.master && len >= (int)sizeof(espnow_discover_packet_t)) {
handle_discover(info->src_addr,
(const espnow_discover_packet_t *)data);
if (!s_config.master) {
if (msg.type != alox_EspNowMessageType_ESPNOW_DISCOVER ||
msg.which_payload != alox_EspNowMessage_discover_tag) {
return;
}
break;
case ESPNOW_MSG_SLAVE_INFO:
case ESPNOW_MSG_HEARTBEAT:
if (s_config.master && len >= (int)sizeof(espnow_slave_packet_t)) {
handle_client_packet((const espnow_slave_packet_t *)data);
}
break;
default:
break;
handle_discover(info->src_addr, &msg.payload.discover);
return;
}
const alox_EspNowSlavePresence *presence = esp_now_proto_get_presence(&msg);
if (presence == NULL) {
return;
}
if (msg.type != alox_EspNowMessageType_ESPNOW_SLAVE_INFO &&
msg.type != alox_EspNowMessageType_ESPNOW_HEARTBEAT) {
return;
}
handle_client_presence(presence, peer_mac);
}
static void master_discover_task(void *param) {
(void)param;
espnow_discover_packet_t pkt = {
.magic = ESPNOW_MAGIC,
.type = ESPNOW_MSG_DISCOVER,
.network = s_config.network,
.reserved = 0,
};
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
msg.type = alox_EspNowMessageType_ESPNOW_DISCOVER;
msg.which_payload = alox_EspNowMessage_discover_tag;
msg.payload.discover.network = s_config.network;
ESP_LOGI(TAG, "master discover task on network %u ch %u",
(unsigned)s_config.network, (unsigned)s_wifi_channel);
while (1) {
esp_err_t err =
esp_now_send(ESPNOW_BCAST, (const uint8_t *)&pkt, sizeof(pkt));
if (err != ESP_OK) {
ESP_LOGW(TAG, "discover broadcast failed: %s", esp_err_to_name(err));
}
send_message(ESPNOW_BCAST, &msg);
vTaskDelay(pdMS_TO_TICKS(ESPNOW_DISCOVER_INTERVAL_MS));
}
}

94
main/esp_now_proto.c Normal file
View File

@ -0,0 +1,94 @@
#include "esp_now_proto.h"
#include "client_registry.h"
#include "pb_decode.h"
#include "pb_encode.h"
#include <string.h>
static bool encode_mac(pb_ostream_t *stream, const pb_field_t *field,
void *const *arg) {
const uint8_t *mac = (const uint8_t *)*arg;
if (mac == NULL) {
return true;
}
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
return pb_encode_string(stream, mac, CLIENT_MAC_LEN);
}
static bool decode_mac(pb_istream_t *stream, const pb_field_t *field,
void **arg) {
(void)field;
uint8_t *mac = (uint8_t *)*arg;
if (mac == NULL) {
return false;
}
if (stream->bytes_left > CLIENT_MAC_LEN) {
return false;
}
return pb_read(stream, mac, stream->bytes_left);
}
esp_err_t esp_now_proto_encode(const alox_EspNowMessage *msg, uint8_t *buf,
size_t buf_size, size_t *out_len) {
if (msg == NULL || buf == NULL || out_len == NULL || buf_size == 0) {
return ESP_ERR_INVALID_ARG;
}
pb_ostream_t stream = pb_ostream_from_buffer(buf, buf_size);
if (!pb_encode(&stream, alox_EspNowMessage_fields, msg)) {
return ESP_FAIL;
}
*out_len = stream.bytes_written;
return ESP_OK;
}
void esp_now_proto_setup_presence_encode(alox_EspNowSlavePresence *presence,
const uint8_t mac[6]) {
if (presence == NULL) {
return;
}
presence->mac.funcs.encode = encode_mac;
presence->mac.arg = (void *)mac;
}
void esp_now_proto_setup_message_decode(alox_EspNowMessage *msg,
uint8_t mac_out[6]) {
if (msg == NULL || mac_out == NULL) {
return;
}
msg->payload.slave_info.mac.funcs.decode = decode_mac;
msg->payload.slave_info.mac.arg = mac_out;
msg->payload.heartbeat.mac.funcs.decode = decode_mac;
msg->payload.heartbeat.mac.arg = mac_out;
}
esp_err_t esp_now_proto_decode(const uint8_t *data, size_t len,
alox_EspNowMessage *msg) {
if (data == NULL || msg == NULL || len == 0) {
return ESP_ERR_INVALID_ARG;
}
pb_istream_t stream = pb_istream_from_buffer(data, len);
if (!pb_decode(&stream, alox_EspNowMessage_fields, msg)) {
return ESP_FAIL;
}
return ESP_OK;
}
const alox_EspNowSlavePresence *
esp_now_proto_get_presence(const alox_EspNowMessage *msg) {
if (msg == NULL) {
return NULL;
}
switch (msg->which_payload) {
case alox_EspNowMessage_slave_info_tag:
return &msg->payload.slave_info;
case alox_EspNowMessage_heartbeat_tag:
return &msg->payload.heartbeat;
default:
return NULL;
}
}

26
main/esp_now_proto.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef ESP_NOW_PROTO_H
#define ESP_NOW_PROTO_H
#include "esp_err.h"
#include "esp_now_messages.pb.h"
#include <stddef.h>
#include <stdint.h>
#define ESPNOW_PB_MAX_SIZE 250
esp_err_t esp_now_proto_encode(const alox_EspNowMessage *msg, uint8_t *buf,
size_t buf_size, size_t *out_len);
esp_err_t esp_now_proto_decode(const uint8_t *data, size_t len,
alox_EspNowMessage *msg);
void esp_now_proto_setup_presence_encode(alox_EspNowSlavePresence *presence,
const uint8_t mac[6]);
void esp_now_proto_setup_message_decode(alox_EspNowMessage *msg,
uint8_t mac_out[6]);
const alox_EspNowSlavePresence *
esp_now_proto_get_presence(const alox_EspNowMessage *msg);
#endif

View File

@ -0,0 +1,20 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-1.0.0-dev */
#include "esp_now_messages.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(alox_EspNowDiscover, alox_EspNowDiscover, AUTO)
PB_BIND(alox_EspNowSlavePresence, alox_EspNowSlavePresence, AUTO)
PB_BIND(alox_EspNowMessage, alox_EspNowMessage, AUTO)

View File

@ -0,0 +1,126 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-1.0.0-dev */
#ifndef PB_ALOX_MAIN_PROTO_ESP_NOW_MESSAGES_PB_H_INCLUDED
#define PB_ALOX_MAIN_PROTO_ESP_NOW_MESSAGES_PB_H_INCLUDED
#include <pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
typedef enum _alox_EspNowMessageType {
alox_EspNowMessageType_ESPNOW_UNKNOWN = 0,
alox_EspNowMessageType_ESPNOW_DISCOVER = 1,
alox_EspNowMessageType_ESPNOW_SLAVE_INFO = 2,
alox_EspNowMessageType_ESPNOW_HEARTBEAT = 3
} alox_EspNowMessageType;
/* Struct definitions */
typedef struct _alox_EspNowDiscover {
uint32_t network;
} alox_EspNowDiscover;
typedef struct _alox_EspNowSlavePresence {
uint32_t network;
pb_callback_t mac;
uint32_t version;
uint32_t slave_id;
bool available;
bool used;
} alox_EspNowSlavePresence;
typedef struct _alox_EspNowMessage {
alox_EspNowMessageType type;
pb_size_t which_payload;
union _alox_EspNowMessage_payload {
alox_EspNowDiscover discover;
alox_EspNowSlavePresence slave_info;
alox_EspNowSlavePresence heartbeat;
} payload;
} alox_EspNowMessage;
#ifdef __cplusplus
extern "C" {
#endif
/* Helper constants for enums */
#define _alox_EspNowMessageType_MIN alox_EspNowMessageType_ESPNOW_UNKNOWN
#define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_HEARTBEAT
#define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_HEARTBEAT+1))
#define alox_EspNowMessage_type_ENUMTYPE alox_EspNowMessageType
/* Initializer values for message structs */
#define alox_EspNowDiscover_init_default {0}
#define alox_EspNowSlavePresence_init_default {0, {{NULL}, NULL}, 0, 0, 0, 0}
#define alox_EspNowMessage_init_default {_alox_EspNowMessageType_MIN, 0, {alox_EspNowDiscover_init_default}}
#define alox_EspNowDiscover_init_zero {0}
#define alox_EspNowSlavePresence_init_zero {0, {{NULL}, NULL}, 0, 0, 0, 0}
#define alox_EspNowMessage_init_zero {_alox_EspNowMessageType_MIN, 0, {alox_EspNowDiscover_init_zero}}
/* Field tags (for use in manual encoding/decoding) */
#define alox_EspNowDiscover_network_tag 1
#define alox_EspNowSlavePresence_network_tag 1
#define alox_EspNowSlavePresence_mac_tag 2
#define alox_EspNowSlavePresence_version_tag 3
#define alox_EspNowSlavePresence_slave_id_tag 4
#define alox_EspNowSlavePresence_available_tag 5
#define alox_EspNowSlavePresence_used_tag 6
#define alox_EspNowMessage_type_tag 1
#define alox_EspNowMessage_discover_tag 2
#define alox_EspNowMessage_slave_info_tag 3
#define alox_EspNowMessage_heartbeat_tag 4
/* Struct field encoding specification for nanopb */
#define alox_EspNowDiscover_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, network, 1)
#define alox_EspNowDiscover_CALLBACK NULL
#define alox_EspNowDiscover_DEFAULT NULL
#define alox_EspNowSlavePresence_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, network, 1) \
X(a, CALLBACK, SINGULAR, BYTES, mac, 2) \
X(a, STATIC, SINGULAR, UINT32, version, 3) \
X(a, STATIC, SINGULAR, UINT32, slave_id, 4) \
X(a, STATIC, SINGULAR, BOOL, available, 5) \
X(a, STATIC, SINGULAR, BOOL, used, 6)
#define alox_EspNowSlavePresence_CALLBACK pb_default_field_callback
#define alox_EspNowSlavePresence_DEFAULT NULL
#define alox_EspNowMessage_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, type, 1) \
X(a, STATIC, ONEOF, MESSAGE, (payload,discover,payload.discover), 2) \
X(a, STATIC, ONEOF, MESSAGE, (payload,slave_info,payload.slave_info), 3) \
X(a, STATIC, ONEOF, MESSAGE, (payload,heartbeat,payload.heartbeat), 4)
#define alox_EspNowMessage_CALLBACK NULL
#define alox_EspNowMessage_DEFAULT NULL
#define alox_EspNowMessage_payload_discover_MSGTYPE alox_EspNowDiscover
#define alox_EspNowMessage_payload_slave_info_MSGTYPE alox_EspNowSlavePresence
#define alox_EspNowMessage_payload_heartbeat_MSGTYPE alox_EspNowSlavePresence
extern const pb_msgdesc_t alox_EspNowDiscover_msg;
extern const pb_msgdesc_t alox_EspNowSlavePresence_msg;
extern const pb_msgdesc_t alox_EspNowMessage_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define alox_EspNowDiscover_fields &alox_EspNowDiscover_msg
#define alox_EspNowSlavePresence_fields &alox_EspNowSlavePresence_msg
#define alox_EspNowMessage_fields &alox_EspNowMessage_msg
/* Maximum encoded size of messages (where known) */
/* alox_EspNowSlavePresence_size depends on runtime parameters */
/* alox_EspNowMessage_size depends on runtime parameters */
#define ALOX_MAIN_PROTO_ESP_NOW_MESSAGES_PB_H_MAX_SIZE alox_EspNowDiscover_size
#define alox_EspNowDiscover_size 6
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@ -0,0 +1,32 @@
syntax = "proto3";
package alox;
enum EspNowMessageType {
ESPNOW_UNKNOWN = 0;
ESPNOW_DISCOVER = 1;
ESPNOW_SLAVE_INFO = 2;
ESPNOW_HEARTBEAT = 3;
}
message EspNowDiscover {
uint32 network = 1;
}
message EspNowSlavePresence {
uint32 network = 1;
bytes mac = 2;
uint32 version = 3;
uint32 slave_id = 4;
bool available = 5;
bool used = 6;
}
message EspNowMessage {
EspNowMessageType type = 1;
oneof payload {
EspNowDiscover discover = 2;
EspNowSlavePresence slave_info = 3;
EspNowSlavePresence heartbeat = 4;
}
}