Skip to content

OpenDive/plaipin_espnow

Repository files navigation

ESP-NOW Communication Module

This module provides ESP-NOW-based device-to-device communication for the plaipin-device project. ESP-NOW enables fast, low-latency messaging between ESP32 devices without requiring WiFi AP connection or pairing.

Features

  • Fast Communication: ~10ms latency for local messaging
  • Connectionless: No AP required, works in STA mode
  • Low Power: Minimal overhead compared to WiFi/BLE
  • Coexistence: Works alongside WiFi and BLE
  • Structured Messages: Type-safe protocol with multiple message types
  • Peer Management: Automatic peer tracking and pairing
  • Flexible: Support for pairing, text messages, metadata, heartbeats

Architecture

┌──────────────────────────────────────────────┐
│          EspNowManager (Singleton)           │
├──────────────────────────────────────────────┤
│  • Initialize ESP-NOW stack                  │
│  • Manage peer list                          │
│  • Send/receive messages                     │
│  • Callback dispatch                         │
└──────────────────────────────────────────────┘
                    ↓
┌──────────────────────────────────────────────┐
│          EspNowProtocol                      │
├──────────────────────────────────────────────┤
│  • Message serialization/deserialization     │
│  • Message validation                        │
│  • Protocol version management               │
└──────────────────────────────────────────────┘

Message Types

1. Pairing Request (kMessageTypePairingRequest)

Sent to initiate pairing with another device.

Payload:

  • Device UUID
  • Device name
  • AgentMail inbox ID
  • Current RSSI

2. Pairing Response (kMessageTypePairingResponse)

Response to a pairing request (accept/reject).

Payload:

  • Device UUID
  • Device name
  • AgentMail inbox ID
  • Accepted (boolean)

3. Text Message (kMessageTypeTextMessage)

Plain text message for LLM processing or user display.

Payload:

  • Sender UUID
  • Sender name
  • Message text (up to 150 chars)

4. Metadata Update (kMessageTypeMetadataUpdate)

Update device information for paired devices.

Payload:

  • Device UUID
  • Device name
  • AgentMail inbox ID

5. Heartbeat (kMessageTypeHeartbeat)

Periodic keep-alive and status message.

Payload:

  • Uptime in seconds
  • Current RSSI
  • Battery level (0-100)

6. Unpair (kMessageTypeUnpair)

Request to remove pairing.

Payload:

  • Device UUID
  • Reason code

Usage

Initialization

#include "espnow/espnow_manager.h"

using namespace espnow;

// Initialize WiFi first (ESP-NOW requires WiFi to be initialized)
// ... WiFi initialization code ...

// Initialize ESP-NOW
auto& manager = EspNowManager::GetInstance();
if (!manager.Initialize()) {
    ESP_LOGE(TAG, "Failed to initialize ESP-NOW");
    return;
}

ESP_LOGI(TAG, "ESP-NOW initialized. Local MAC: %s", 
         manager.GetLocalMacString().c_str());

Sending Messages

// Get device info
std::string device_uuid = board.GetUuid();
std::string device_name = "PlaiPin-" + device_uuid.substr(0, 6);
uint8_t peer_mac[6] = {0x24, 0x0A, 0xC4, 0x12, 0x34, 0x56};

// Send pairing request
manager.SendPairingRequest(
    peer_mac, 
    device_uuid, 
    device_name, 
    "[email protected]",
    -45  // RSSI
);

// Send text message
manager.SendTextMessage(
    peer_mac,
    device_uuid,
    device_name,
    "Hello from nearby device!"
);

// Broadcast to all paired peers
manager.BroadcastTextMessage(
    device_uuid,
    device_name,
    "Broadcasting to all paired devices"
);

Receiving Messages

// Handle pairing requests
manager.OnPairingRequest([](const uint8_t* mac, const PairingRequestPayload& payload) {
    ESP_LOGI(TAG, "Pairing request from %s (%s)", 
             payload.device_name, 
             EspNowManager::MacToString(mac).c_str());
    
    // Auto-accept or prompt user
    if (should_accept_pairing) {
        manager.SendPairingResponse(mac, my_uuid, my_name, my_inbox, true);
    }
});

// Handle text messages
manager.OnTextMessage([](const uint8_t* mac, const TextMessagePayload& payload) {
    ESP_LOGI(TAG, "Message from %s: %s", 
             payload.sender_name, 
             payload.message_text);
    
    // Process message (e.g., inject into LLM)
    Application::GetInstance().ProcessTextMessage(
        payload.sender_name,
        payload.message_text
    );
});

// Handle pairing responses
manager.OnPairingResponse([](const uint8_t* mac, const PairingResponsePayload& payload) {
    if (payload.accepted) {
        ESP_LOGI(TAG, "Pairing accepted by %s", payload.device_name);
        // Store peer info in NVS
    } else {
        ESP_LOGW(TAG, "Pairing rejected by %s", payload.device_name);
    }
});

Peer Management

// Get all paired devices
auto paired_devices = manager.GetPairedDevices();
for (const auto& peer : paired_devices) {
    ESP_LOGI(TAG, "Peer: %s (%s)", 
             peer.name.c_str(), 
             EspNowManager::MacToString(peer.mac).c_str());
}

// Check if peer exists
if (manager.HasPeer(peer_mac)) {
    ESP_LOGI(TAG, "Peer is in list");
}

// Get specific peer info
PeerDevice peer;
if (manager.GetPeerInfo(peer_mac, peer)) {
    ESP_LOGI(TAG, "Peer RSSI: %d", peer.rssi);
}

// Remove peer
manager.RemovePeer(peer_mac);

// Clear all peers
manager.ClearAllPeers();

Configuration

Kconfig Options

Enable ESP-NOW in menuconfig:

plaipin-device Configuration
    └─> ESP-NOW Configuration
        ├─> [*] Enable ESP-NOW Communication
        ├─> [*] Enable Automatic Pairing
        ├─> (10) Pairing Duration (seconds)
        ├─> (-50) Proximity RSSI Threshold (dBm)
        ├─> [*] Enable ESP-NOW Text Messaging
        ├─> [*] Inject ESP-NOW Messages as Virtual STT
        ├─> (10) Maximum Number of Paired Peers
        ├─> (60) Heartbeat Interval (seconds)
        └─> [ ] Enable Message Encryption

sdkconfig.defaults

# ESP-NOW Configuration
CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7

Testing

Test Mode

Enable ESP-NOW test mode in menuconfig:

plaipin-device Configuration
    └─> Test Modes
        └─> [*] Enable ESP-NOW Test Mode

Build and flash:

idf.py build flash monitor

The test mode will:

  • Initialize WiFi and ESP-NOW
  • Listen for pairing requests (auto-accept)
  • Send test messages every 15 seconds to paired devices
  • Display status every 10 seconds
  • Log all received messages

Testing Between Two Devices

  1. Flash both devices with ESP-NOW test mode enabled
  2. Power on both devices
  3. Devices will automatically discover and pair (if using BLE proximity detection)
  4. Or manually trigger pairing by sending a pairing request from one device
  5. Observe message exchange in serial logs

Integration with BLE Proximity

The ESP-NOW module is designed to work alongside the BLE proximity module:

// In proximity detection callback:
proximity_manager.OnSustainedProximity([](const BleDevice& device) {
    // Device has been close for 10+ seconds
    ESP_LOGI(TAG, "Device %s close for 10 seconds, initiating pairing", 
             device.name);
    
    // Trigger ESP-NOW pairing
    auto& espnow = EspNowManager::GetInstance();
    espnow.SendPairingRequest(
        device.address,
        board.GetUuid(),
        board.GetName(),
        settings.GetString("agentmail_inbox"),
        device.rssi
    );
});

Performance Characteristics

Metric Value
Max Message Size 250 bytes
Typical Latency 10-50 ms
Max Range 200+ meters (line of sight)
Max Peers 20 (encrypted)
Bandwidth ~1 Mbps
Power Consumption ~100-150 mA (active)

WiFi/BLE Coexistence

ESP-NOW shares the 2.4GHz radio with WiFi and BLE:

  • WiFi Impact: Minimal (<5% throughput reduction)
  • BLE Impact: No noticeable impact on BLE scanning/advertising
  • Time-Division Multiplexing: Handled automatically by ESP-IDF
  • Recommendation: Use ESP-NOW for bursts, not continuous streaming

Limitations

  • Same Channel: All devices must be on the same WiFi channel
  • 2.4GHz Only: No 5GHz support
  • Message Size: 250 bytes max per message
  • Range: Limited by WiFi range and obstacles
  • No Acknowledgment Guarantee: Use heartbeats or application-level ACKs if needed

Troubleshooting

ESP-NOW init fails

Error: esp_now_init() returned ESP_ERR_ESPNOW_NOT_INIT

Solution: WiFi must be initialized first. Call esp_wifi_start() before manager.Initialize().

Messages not received

Check:

  1. Both devices on same WiFi channel
  2. Devices within range (~50m indoors)
  3. Peer is added: manager.HasPeer(mac)
  4. Callbacks registered before sending

Peer limit reached

Error: esp_now_add_peer() returned ESP_ERR_ESPNOW_FULL

Solution: Increase CONFIG_ESPNOW_MAX_PEERS or remove unused peers.

Files

  • espnow_protocol.h/cc - Message protocol and serialization
  • espnow_manager.h/cc - ESP-NOW management and peer handling
  • espnow_test.h/cc - Test mode implementation
  • README.md - This file

License

Same as plaipin-device project.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published