Skip to content

Task 2: Individual Assignment

This week’s task: - design - build - connect wired or wireless node(s) with network or bus addresses and a local interface

alt text

As I mentioned, for my final project - I would require two boards (one hosting the input device and the other output device) to communicate with each other. I tried using UART protocol from the group assignment at first since the protocol is pretty straightforward… but for whatever reason I failed to get two XIAOs to communicate to each other. Rico suggested that since I am using two XIAO ESP32s to communicate to each other… I could use the wireless ESPNOW protocol - so for my individual task and final project I decided to take a crack on this

ESP-NOW

ESP-NOW is a wireless communication protocol defined by Espressif, which enables the direct, quick and low-power control of smart devices, without the need of a router. ESP-NOW can work with Wi-Fi and Bluetooth LE, and supports the ESP8266, ESP32, ESP32-S and ESP32-C series of SoCs. ESP-NOW offers faster transmission with lower latency compared to traditional WiFi and is ideal for trasmitting small data readings (up to 250bytes). Hencewhy it’s widely used in smart-home appliances, remote controlling, sensors, etc.

Souce

Programming ESP-NOW

Rico sent me his ESP-NOW code for me to understand how the programming works. To make this code worked, all you need is two ESPs together.

alt text

Transmitter Code

#include <esp_now.h>
#include <WiFi.h>

#define Channel 1 // TX and RX must be on same channel


esp_now_peer_info_t slave; // stores info about slave, incl MAC Address

uint8_t data = 0; // initial data sent...unsigned integer...250 bytes or less

void setup() {
  Serial.begin(115200);

  // pinMode(buttonPin, INPUT);

  WiFi.mode(WIFI_STA); // set TX mode as Wifi Station
  esp_now_init();
  esp_now_register_send_cb(OnDataSent); // run Call-back function called OnDataSent
  ScanForSlave(); // find slave MAC address at specific SSID
  esp_now_add_peer(&slave); // point to stored slave data...based on discovered MAC address
 }

void loop() {
  esp_now_send(slave.peer_addr, &data, sizeof(data)); // send to slave address, data, data length
  data++;
  delay(3000);

  // int state = digitalRead(buttonPin);
}

void ScanForSlave(){
int8_t scanResults = WiFi.scanNetworks();

for (int i = 0; i < scanResults; ++i){
  String SSID = WiFi.SSID(i);
  String BSSIDstr = WiFi.BSSIDstr(i);


  if (SSID.indexOf("RX") == 0){ // Looks for matching network name
    int mac[6];
    if (6 == sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5])){
      for (int ii = 0; ii < 6; ++ii){
        slave.peer_addr[ii] = (uint8_t) mac[ii];
      }
    }

    slave.channel = Channel;
    slave.encrypt = 0;
    break;
  }
 }
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status){
  Serial.print("I sent my data >  ");
  Serial.println(data);
}
Libraries, Constants , & Variables
  • Firstly, include necessary libraries <esp_now.h> <WiFi.h>
  • Define the communication channel. Both transmitter (TX) and receiver (RX) need to be on the same channel.
  • The esp_now_peer_info_t is used to store information about a peer device (typically the receiver) that you want to communicate with - which we refer to as slave
  • uint8_t is a data type to represent an unsigned 8-bit integer. We set the data to 0, an unsigned integer
Setup

In the setup function we will be mostly setting up the WiFi function on the transmitter device

  • Begin serial communication Serial.begin(115200)
  • We will be setting the transmitter device as the WiFi Station WiFi.mode(WIFI_STA)
  • Initializes ESP-NOW library esp_now_init().
  • Register a callback function OnDataSent to be called after sending data esp_now_register_send_cb(OnDataSent).
  • ScanForSlave() is a function to find the MAC address of the receiver.
  • esp_now_add_peer(&slave) adds the discovered slave information to the ESP-NOW peer list for communication.
Loop

Loop is where the data will get transmitted to the receiver device.

  • Sends data to the slave device using esp_now_send(). It takes the slave’s MAC address slave.peer_addr, the data pointer &data, and the data size sizeof(data) as arguments.
  • data++ increments the data value after each transmission.
  • Adds a delay of 3 seconds (delay(3000)) between transmissions.
Custom Functions

These functions are meant to set the WiFi capabilities of your transmitter device. I would suggest preferably to keep this code untouched.

  • ScanForSlave: Scans for available WiFi networks using WiFi.scanNetworks(). and checks if the network SSID (name) starts with “RX” (presumably the name of the receiver’s network). Match then breaks out of the loop after finding the slave device.
  • OnDataSent: prints a confirmation and data to the serial monitor indicating successful data transmission Serial.print(“I sent my data > x “)

Receiver Code

#include <esp_now.h>
#include <WiFi.h>

#define Channel 1 // same channel as TX

uint8_t newData;

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_AP); // set wifi to AP mode
  WiFi.softAP("RX_1", "RX_1_Password", Channel, 0); // SSID that TX can recognize

  esp_now_init();
  esp_now_register_recv_cb(OnDataRecv);
}

void loop() {
  Serial.print("I did this to data >> ");
  Serial.println(newData * 5); //making use of incoming data
  delay(3000);
}

void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len){
  Serial.print("I just received >> ");
  Serial.println(*data);
  memcpy(&newData, data, sizeof(newData)); //copy data from membory (memory copy) for use in loop
}
Libraries, , & Variables
  • Include the same necessary libraries <esp_now.h> <WiFi.h>
  • Make sure that both transmitter (TX) and receiver (RX) are on the same channel.
  • Set uint8_t newData newData stores the received data from the transmitter.
Setup

In the setup function we will be mostly setting up the WiFi access point function on the receiver device

  • Begin serial communication Serial.begin(115200)
  • We will be setting the receiver WiFi Access point WiFi.mode(WIFI_AP). Usually when you upload a code into the ESP32 board - you can see the MAC Address. alt text

  • Another method to check your MAC Address is from this tutorial

  • Initializes ESP-NOW library esp_now_init().
  • Register a callback function OnDataRecv when receiving data esp_now_register_recv_cb(OnDataRecv).
Loop

Loop is where the receiver device accepts data from the transmitter device

  • Prints a message to the serial monitor indicating that data has received (newData) and multiply it by 5 (Serial.print(“I did this to data >> x*5”);). Adds a delay of 3 seconds (delay(3000)) between processing cycles.
Custom Functions

I would suggest preferably to keep this code untouched.

  • OnDataRecv: prints a confirmation and data to the serial monitor indicating data has been received.
  • Uses memcpy to copy the received data (data) into the newData variable memcpy(&newData, data, sizeof(newData)). This allows the loop function to access and manipulate the received data.

This is how it looks like when you put them side by side. It looks like there were some slight skips but data is transmitting. alt text

ESP-NOW embedded with Input & Output

Next I basically appropriated the code to fit the my input and output device for the final project. This is my system diagram for the final project. alt text

Transmitter Code

Following the same logic of the aforementioned Transmitter Code but with addeINA219 code. In the setup and loop function, input INA219 code that measures electrical property of the load.

//Elaine FP 
//Current Sensor + ESP32 data > TX via ESP NOW
//Elaine Regina + Rico Kanthatham, Fablab Bali/Skylabworkshop, Fabacademy June 2024
//refactored from code by...Adafruit (INA219), Ruis Santos (ESP NOW), 
//Transmitter code
//sends current sensor data wirelessly to a receiver board

//all required libraries
#include <Wire.h> //i2c library...check address? default hex40
#include <Adafruit_INA219.h> //current sensor library
#include <esp_now.h> //ESP NOW library
#include <WiFi.h> //WiFi library

//instance of ina219 current sensor object
Adafruit_INA219 myCurrentSensor; 

//ESP NOW parameters 
#define Channel 1 //Specify ESP NOW communication channel
esp_now_peer_info_t slave; // stores info about slave, incl MAC Address
//float data = 0; // initial data sent...unsigned integer...250 bytes or less

//create a struct variable to contain all data to be sent wirelessly
struct SensorData {
  float loadV;
  float currA;
  float pwrW;
};

struct SensorData sensorData;

void setup() {
  Serial.begin(115200); // Initialize serial communication
  //myCurrentSensor.begin(); //Initialize current sensor

  //current sensor error checking
  if (!myCurrentSensor.begin()) {
    Serial.println("Failed to find INA219 chip");
    while (1) {
      delay(10);
    }
  }

  //WiFi Communication initialization protocol
  WiFi.mode(WIFI_STA); // set TX mode as Wifi Station
  esp_now_init();
  esp_now_register_send_cb(OnDataSent); // run Call-back function called OnDataSent
  ScanForSlave(); // find slave MAC address at specific SSID
  esp_now_add_peer(&slave); // point to stored slave data...based on discovered MAC address
}

void loop(void) {

  //measure various electrical properties
  float shuntvoltage = myCurrentSensor.getShuntVoltage_mV(); //shunt voltage
  float busvoltage = myCurrentSensor.getBusVoltage_V(); //bus voltage
  float current_mA = myCurrentSensor.getCurrent_mA(); //current
  float power_mW = myCurrentSensor.getPower_mW(); //power

  //calculate Load Voltage using Bus Voltage and Shunt Voltage
  float loadvoltage = busvoltage + (shuntvoltage / 1000);

  //display electrical measurements
  Serial.print(loadvoltage); //load voltage
  Serial.print(",");
  Serial.print(current_mA); //current
  Serial.print(",");
  Serial.print(power_mW); //power
  Serial.println("");

  //send measured data to receiver MCU
  sensorData.loadV = loadvoltage;
  sensorData.currA = current_mA;
  sensorData.pwrW = power_mW;;
  esp_now_send(slave.peer_addr, (uint8_t *)&sensorData, sizeof(sensorData)); // send to slave address, data, data length

  //set measureme & send interval
  delay(3000); //every 2 seconds
}

//function to automatically get MAC address of RX-MCU
void ScanForSlave(){
  int8_t scanResults = WiFi.scanNetworks(); //scan for all WiFi networks

  for (int i = 0; i < scanResults; ++i){
    String SSID = WiFi.SSID(i);
    String BSSIDstr = WiFi.BSSIDstr(i);

  if (SSID.indexOf("RX") == 0){ // Looks for specific network name of RX-MCU and grab MAC Address
    int mac[6];
    if (6 == sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5])){
      for (int ii = 0; ii < 6; ++ii){
        slave.peer_addr[ii] = (uint8_t) mac[ii];
      }
    }

    slave.channel = Channel;
    slave.encrypt = 0;
    break;
  }
 }
}

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status){
  Serial.print("I sent my data >  ");
  //Serial.println(sensorData); //adjust variable name?
}

Receiver Code

The same goes with the receiver code - integrate the TFT ILI9341 Display with ESP-NOW protocol

#include <esp_now.h>
#include <WiFi.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

#define Channel 1 // same channel as TX

// Define pins for the ILI9341 display
#define TFT_RST D4
#define TFT_DC  D5
#define TFT_CS  D3  // SS
#define TFT_MOSI D10  // MOSI
#define TFT_MISO D9
#define TFT_CLK D8  // SCK
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

// Structure to receive data
typedef struct struct_message {
    float voltage;
    float current;
    float power;
} struct_message;

struct_message incomingData; // Create a struct_message to hold the incoming data

// Callback function when data is received
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
    memcpy(&incomingData, data, sizeof(incomingData));
    Serial.println("Data received");

    // Print received values to Serial Monitor
    Serial.print("Voltage: "); Serial.print(incomingData.voltage); Serial.println(" V");
    Serial.print("Current: "); Serial.print(incomingData.current); Serial.println(" mA");
    Serial.print("Power: "); Serial.print(incomingData.power); Serial.println(" mW");

    // Display data on TFT screen
    tft.fillScreen(ILI9341_BLACK);
    displayVoltageCurrent(incomingData.voltage, incomingData.current);
    delay(2000);
    tft.fillScreen(ILI9341_BLACK);
    displayPower(incomingData.power);
    delay(2000);
}

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);

  // Initialize TFT screen
    tft.begin();
    tft.setRotation(3); // Adjust based on your display orientation
    tft.fillScreen(ILI9341_BLACK);

  // Set device as a Wi-Fi Access Point
  WiFi.mode(WIFI_AP); // set wifi to AP mode
  WiFi.softAP("RX_1", "RX_1_Password", Channel, 0); // SSID that TX can recognize

  // Initialize ESP-NOW
  if (esp_now_init() != ESP_OK) {
      Serial.println("Error initializing ESP-NOW");
      return;

  // Register receive callback function
  esp_now_register_recv_cb(OnDataRecv);
}

void loop() {
  // Nothing to do here, all action happens in the callback function
}

void displayVoltageCurrent(float voltage, float current) {
    tft.setTextSize(2); // Draw 2X-scale text
    tft.setTextColor(ILI9341_WHITE);
    tft.setCursor(0, 0); // Start at top-left corner
    tft.print(voltage);
    tft.print(" V");
    tft.setCursor(0, 40); // Adjust cursor position based on text size
    tft.print(current);
    tft.print(" mA");
}

void displayPower(float power) {
    tft.setTextSize(2); // Draw 2X-scale text
    tft.setTextColor(ILI9341_WHITE);
    tft.setCursor(0, 60); // Adjust cursor position based on text size
    tft.print(power);
    tft.print(" mW");
}

Outcome

As seen in the video below - the Transmitter board has successfully send data to the Receiver board. Now onto designing the interface for the receiver board!