Home About Getting Started Firmware Contact Blog Buy



ESP-NOW peer-to-peer communication

By Majodi Ploegmakers

While still waiting for the two pre-run boards to arrive, as promised last time here’s our second tutorial on communication, this time between two SwarmDrives independent of any network availability or not. This is a kindof peer-to-peer communication for which we use ESP-NOW. This protocol, developed by Espressif, enables ESP32-based devices to send small data payloads to one another without using Wi-Fi. It does so by using its on-board radio to establish a secure, low-power, 2.4GHz link.

There are many easy to find examples available that demonstrate how to use the ESP-NOW API (included with ESP-IDF). In this write-up, I will be using the bare minimum code needed to send just one integer between devices. But you can, of course, send any small data payload (less than 250 bytes) you need for your own implementation.

For simplicity’s sake, this example code doesn’t use any encryption, simply broadcasting its data in the open. In a real-life use case, you would probably have an extra discovery layer where your device tries to find its partner devices. You would also need some sort of functionality where each device sends out its own MAC address, plus some status information, while at the same time trying to receive similar data from other devices in close proximity (which, tests have shown, can be hundreds of meters). This would all be part of a “swarm” implementation.

The code

First of all, the ESP32 is put into station mode and prevented from connecting to Wi-Fi. In fact, you can turn off Wi-Fi explicitly as it cannot be used simultaneously. Next, the ESP-NOW API is initialized and peer info and a receive callback are registered:


    if (esp_now_init() != ESP_OK) {
        printf("Error initializing ESP-NOW\n");
        return;
    }
    memcpy(peerInfo.peer_addr, broadcastAddress, 6);
    peerInfo.channel = 0;  
    peerInfo.encrypt = false;
    if (esp_now_add_peer(&peerInfo) != ESP_OK){
        printf("Failed to add peer\n");
        return;
    }
    esp_now_register_recv_cb(onReceiveData);
    

The minimum setup for the peerInfo structure requires a channel and a peer (MAC) address. The channel can be chosen based on local interference levels. (There are many free tools available for checking how busy channels are in your neighborhood.) Again, for simplicity, encryption is turned off and the broadcast address is used as peer address.

After setting up the peer (broadcast in this example), a receive callback is registered. This handles the receiving side. Besides having access to the data received, it also has the sender of the data and data length available. The received data, MAC address, and data length will be logged to the serial monitor.

Now the only thing left is to send something:


    esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &x, sizeof(int));
    

We’re just sending one integer (4 bytes) to the broadcast address to which other SwarmDrive boards are listening. There, the receive callback will show what is received over the serial monitor:

receiver output

The data is sent just once. To send it again you’d need to reset the sender or you’d repeat the send function at some time interval.

Now, I know this is a very crude and simple piece of code. It is just intended as a quick example of how easy the communication side of SwarmDrive can be. Of course, it would take a lot more code to have construct a real Swarm intelligence setup. But this example clearly shows the value of having a capable MCU on board.

Code:


    #include <Arduino.h>
    #include <WiFi.h>
    #include <AsyncUDP.h>
    #include <esp_now.h>
    
    uint8_t broadcastAddress[] = {0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF};
    esp_now_peer_info_t peerInfo;
    
    void onReceiveData(const uint8_t *mac, const uint8_t *data, int len) {
        printf("** Data Received **\n\n");
        printf("Received from MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        printf("Length: %d byte(s)\n", len);
        printf("Data: %d\n\n", data[0]);
    }
    
    void initESP_NOW() {
        if (esp_now_init() != ESP_OK) {
            printf("Error initializing ESP-NOW\n");
            return;
        }
        memcpy(peerInfo.peer_addr, broadcastAddress, 6);
        peerInfo.channel = 0;  
        peerInfo.encrypt = false;
        if (esp_now_add_peer(&peerInfo) != ESP_OK){
            printf("Failed to add peer\n");
            return;
        }
        esp_now_register_recv_cb(onReceiveData);
    }
    
    extern "C" void app_main()
    {
        initArduino();
        WiFi.mode(WIFI_STA);
        initESP_NOW();
        int x = 65;
    
        esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &x, sizeof(int));
    
        if (result == ESP_OK) {
            printf("Data sent successfully\n");
        }
        else {
            printf("Error sending the data\n");
        }
    
        while (1) {
            vTaskDelay(10 / portTICK_PERIOD_MS);
        };
    }
    

I hope it will inspire you to start writing your own, more sophisticated code. In the next update, we will explore the AS5048B position sensor since it is probably the first thing you’d want to implement when working with BLDC motors.

Until next time, Majodi