Mesh-NOW discovers peers through periodic beacon broadcasts. No manual peer configuration is needed.

Beacon Mechanism

Every node runs a beacon_task that broadcasts a MSG_TYPE_BEACON every 5 seconds by default (CONFIG_MESH_NOW_BEACON_INTERVAL_MS). Each beacon carries the sender’s node name plus a zone announce: up to CONFIG_MESH_NOW_MAX_BEACON_NEIGHBORS of the sender’s one-hop peers. That lets two-hop nodes learn routes to those peers proactively. The announce is trimmed to the densest set that fits the 250-byte ESP-NOW frame cap (shortest names first), so a full neighbor set never overflows a frame.

// Simplified beacon construction
mesh_message_t beacon = {0};
beacon.type = MSG_TYPE_BEACON;
beacon.flags |= MSG_FLAG_HAS_NODE_NAME;
strncpy(beacon.node_name, "Sensor-01", MESH_NOW_NODE_NAME_MAX);
// The beacon task fills in neighbor_macs[]/neighbor_names[] from the
// active peer table, then encodes and broadcasts the frame.

When a node receives a beacon, it adds the sender to its peer table:

if (mesh_msg.type == MSG_TYPE_BEACON) {
    mesh_now_add_peer(mesh_msg.sender_mac);
}

Peer Table

The peer table is a fixed-size array of mesh_peer_t entries:

typedef struct {
    uint8_t peer_addr[6];  // MAC address
    bool active;           // Whether this peer is valid
    int64_t last_seen;     // Monotonic us of last contact
    char node_name[17];    // Human-readable name (from beacons)
} mesh_peer_t;

#define MAX_PEERS 20

Managing Peers

// Add a peer (called automatically on beacon/chat receipt)
mesh_now_add_peer(const uint8_t *mac);

// Remove a peer
mesh_now_remove_peer(const uint8_t *mac);

// Get current peer count
int mesh_now_get_peer_count(void);

// Get pointer to peer table (internal state, not thread-safe)
mesh_peer_t* mesh_now_get_peers(void);

// Thread-safe copy of the peer table
int mesh_now_snapshot_peers(mesh_peer_t *out, size_t max_out);
Self-Exclusion

mesh_now_add_peer() ignores your own MAC address. You cannot add yourself as a peer.

Discovery Flow

sequenceDiagram participant A as Node A participant B as Node B Note over A: Boots, starts beacon_task Note over B: Boots, starts beacon_task A->>B: Beacon (sender_mac=A) Note over B: add_peer(A) B->>A: Beacon (sender_mac=B) Note over A: add_peer(B) Note over A,B: Peers connected, can now send messages

Peer Expiration

Each peer records last_seen (monotonic us) whenever it is contacted. The beacon_task periodically expels peers that have not been seen within PEER_EXPIRY_US (default 30 seconds, set by CONFIG_MESH_NOW_PEER_EXPIRY_SEC). Expired peers are removed from the active table, and the table compacts so peer_count reflects only active entries.

// Expired when no beacon/message received within the window:
(now - peers[i].last_seen) > PEER_EXPIRY_US
Expiry vs. Removal

Expiry drops a peer from the active table, compacts it out, and calls esp_now_del_peer() to free the ESP-NOW registration slot. If the same node contacts the mesh again, mesh_now_add_peer() reactivates the entry (re-registering it) and refreshes last_seen. Because expired entries are compacted out, mesh_now_get_peer_count() and mesh_now_snapshot_peers() report only online-tracked entries, and dead peers can never starve the table.

Peer Events

Peers are also discovered (and added) when receiving any of these message types:

Message Type Peer Added?
MSG_TYPE_BEACON Yes
MSG_TYPE_CHAT Yes
MSG_TYPE_DIRECT Yes (target only)
MSG_TYPE_GROUP Yes
MSG_TYPE_PRESENCE Yes
MSG_TYPE_TYPING No (routed, not added)
MSG_TYPE_ACK No

Next Steps