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
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.
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 dataesp_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 addressslave.peer_addr
, the data pointer&data
, and the data sizesizeof(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. -
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 dataesp_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 thenewData
variablememcpy(&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.
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.
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!