Every Mesh-NOW message is serialized to MessagePack and sent as a binary ESP-NOW frame.
Wire Format
Messages use a binary envelope: a 32-byte header followed by a MessagePack-encoded payload. The full binary layout is in Wire Format. The canonical protocol specification is mesh_now.ksy at the repository root.
Internal Structure
The library works with this structure internally:
typedef struct {
uint8_t type; // Message type (0-9)
uint8_t flags; // Bitfield: REQUIRES_ACK, ENCRYPTED, HAS_NODE_NAME
uint8_t group_id; // Group identifier (0-255)
uint8_t hop_limit; // Max hops for this frame
uint8_t hop_count; // Hops taken so far
uint32_t message_id; // Unique message identifier
uint32_t reply_to; // Message id being acknowledged (ACK frames only)
uint8_t sender_mac[6]; // Sender MAC address
uint8_t target_mac[6]; // Target MAC (direct messages)
uint32_t timestamp; // Network time in ms
char message[MAX_MESH_MESSAGE_LEN]; // Payload (null-terminated)
char node_name[MESH_NOW_NODE_NAME_MAX + 1]; // Node name (beacons/RREQ/RREP)
uint8_t neighbor_count; // Zone announce: count of advertised one-hop peers
uint8_t neighbor_macs[CONFIG_MESH_NOW_MAX_BEACON_NEIGHBORS][6];
char neighbor_names[CONFIG_MESH_NOW_MAX_BEACON_NEIGHBORS][MESH_NOW_NODE_NAME_MAX + 1];
} mesh_message_t;
mesh_message_t is an internal representation. On the wire, messages are serialized to MessagePack with variable-length fields, never as raw struct bytes.
Field Reference
| Field | Size | Description |
|---|---|---|
type |
1 byte | Message type identifier (see below) |
flags |
1 byte | Bitfield: ACK required, encrypted, has node name |
group_id |
1 byte | Group membership filter (0 = no group) |
hop_limit |
1 byte | Max hops, set at origin |
hop_count |
1 byte | Hops taken; dropped when it reaches hop_limit |
message_id |
4 bytes | Unique ID from a monotonic counter (random seed) |
reply_to |
4 bytes | For ACK frames, the message_id being acknowledged; 0 otherwise |
sender_mac |
6 bytes | MAC address of the originating node |
target_mac |
6 bytes | MAC address of the intended recipient |
timestamp |
4 bytes | Network time in ms, synced from beacons |
message |
variable | Payload string (null-terminated) |
node_name |
variable | Node name (beacons with HAS_NODE_NAME flag) |
neighbor_count |
1 byte | Number of one-hop peers advertised (beacons only) |
neighbor_macs |
variable | Advertised one-hop peer MACs (beacons only) |
neighbor_names |
variable | Advertised peer names (beacons only) |
Message Types
| Value | Name | Description | ACK | Relay |
|---|---|---|---|---|
| 0 | MSG_TYPE_BEACON |
Peer discovery broadcast | No | No (single hop, not relayed) |
| 1 | MSG_TYPE_CHAT |
Broadcast chat message | No | Yes |
| 2 | MSG_TYPE_DIRECT |
Point-to-point message | Yes | Yes (toward target) |
| 3 | MSG_TYPE_ACK |
Acknowledgment response | No | Yes (toward target) |
| 4 | MSG_TYPE_GROUP |
Group-scoped broadcast | No | Yes |
| 5 | MSG_TYPE_PRESENCE |
Status announcement | No | Yes |
| 6 | MSG_TYPE_TYPING |
Typing indicator | No | Conditional (toward target) |
| 7 | MSG_TYPE_ROUTE_REQUEST |
Route discovery flood | No | Yes (flood while TTL remains) |
| 8 | MSG_TYPE_ROUTE_REPLY |
Route discovery reply | No | Yes (toward originator) |
| 9 | MSG_TYPE_ROUTE_ERROR |
Broken next-hop announcement | No | Yes |
Flags
| Flag | Value | Meaning |
|---|---|---|
MSG_FLAG_REQUIRES_ACK |
0x01 |
Sender expects an ACK response |
MSG_FLAG_ENCRYPTED |
0x02 |
Payload is AES-128-GCM encrypted |
MSG_FLAG_HAS_NODE_NAME |
0x04 |
Beacon includes a node name string |
Sending Functions
// Broadcast to all peers
esp_err_t mesh_now_send_broadcast(const char *message);
// Alias for broadcast
esp_err_t mesh_now_send_message(const char *message);
// Direct to a specific peer (with ACK + retransmit)
esp_err_t mesh_now_send_direct(const uint8_t *target_mac, const char *message);
// Group-scoped broadcast
esp_err_t mesh_now_send_group(uint8_t group_id, const char *message);
// Presence announcement
esp_err_t mesh_now_send_presence(const char *status);
// Typing indicator to a specific peer
esp_err_t mesh_now_send_typing(const uint8_t *target_mac, bool typing);
Node Naming
Nodes can set a human-readable name that is broadcast in beacons:
mesh_now_set_name("Sensor-01");
When a beacon with MSG_FLAG_HAS_NODE_NAME arrives, the name is stored in the peer table and shows up in mesh_peer_t.node_name.
The library assigns message_id itself from a counter seeded once with esp_random(). You never set it by hand. After the counter goes through UINT32_MAX and wraps to zero, the next allocation re-seeds from esp_random(), so IDs stay unpredictable across runs.