Week 11 - Networking and Communications
This week we have the following tasks to complete:
- design, build, and connect wired or wireless node(s) with network or bus addresses and local input &/or output device(s)
- send a message between two projects
Group Assignment
The weekly group assignment can be accessed here. For that Matthias and I used our Xiao ESP32C3 to control the brightness from the Neopixel connected to the other Xiao.
I2C
In Week 10 and Week 11, I already used the I2C protocol to communicate with a BME688 environmental sensor and several OLED displays.
ESP Now
The base idea is to get a XIAO ESP32-C3 as an remote with some rotary encoder as input devices and an indicator led that represent the color of the led strips. The receiver is an esp32-C3 WRoom dev kit 3 which controlled two RGB LED-strips and some LED-spots with some MOSFET's with a MOSFET driver. The PCB from the receiver already exist, this is also why I will use a this specific board and not another one like a XIAO ESP32-C3. I designed the receiver a few months ago before I know about the FabInventory and its handy MOSFET's that can directly controlled with a microcontroller.
To get a better idea how to use ESP now I looked up the documentation from Espressif and also for further information and understanding I looked up at Seedstudio's example and at Randomnerdstutorial.
I followed the instructions to get the MAC Address of both ESP32 boards and used the following code:
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <WiFi.h> // include wifi library to activate and use wifi from the ESP32-C3 chip
#include <esp_wifi.h> // include esp wifi library to read out the MAC Address from the ESP32-C3 chip
void readMacAddress(){ // function to read out the MAC Address
uint8_t baseMac[6]; // initialize a 6 byte array to store the chars from the MAC Address
esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac); // read out MAC Address im Station mode
if (ret == ESP_OK) { // if it was possible to read out a MAC Address it will sent to the serial monitor in a formation like xx:xx:xx:xx:xx:xx, where every x is replaced by a letter or digit
Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
baseMac[0], baseMac[1], baseMac[2],
baseMac[3], baseMac[4], baseMac[5]);
} else { // if it was not possible to read out a MAC Address it will sent to the serial monitor an error message
Serial.println("Failed to read MAC address");
}
}
void setup(){
Serial.begin(115200); // initialize serial communication with a baudrate of 115200
WiFi.mode(WIFI_STA); // ESP is set in to station mode and not into access point
WiFi.STA.begin(); // start the Wifi in station mode, probably not need it and optional but I'm not sure
}
void loop(){
Serial.print("[DEFAULT] ESP32 Board MAC Address: "); // this lines are original in the void setup function which didn't give me anything out, it just send a message to the serial monitor
readMacAddress(); // this lines are original in the void setup function which didn't give me anything out, call up the readMacAddress function
}
After flashing the XIAO ESP32-C3 I opened the Serial Monitor in the Arduino IDE and note down the Mac Address. I tried to repeated this for the ESP32-Wrover-E module but for some reasons my PC's could recognized the board, so I designed a small adapter pcb to use a XIAO ESP32-C3 on the footprint of the ESP32-Wrover-E module. I firstly wanted to use the Wroover module because it was already used in the receiver part of the project but after I encounter the connection problem I switched to an XIAO ESP32-C3.
XIAO 1: ec:da:3b:be:74:1c
XIAO 2: ec:da:3b:be:68:10
XIAO 3: 54:32:04:89:8e:00
Before setting up the connection it self I checked the functionality of the remote, for that I use following code:
#include <Adafruit_NeoPixel.h> // library for NeoPixels
#include <Encoder.h> // library for rotary encoder
#define PIN_NEO_PIXEL D0 // Xiao pin that connects to NeoPixel
#define NUM_PIXELS 4 // number of LEDs
#define dt1 D5 // Xiao pin that connects to the first rotary encoder dt
#define clk1 D6 // Xiao pin that connects to the first rotary encoder clk
#define sw1 D2 // Xiao pin that connects to the first rotary encoder sw
#define dt2 D3 // Xiao pin that connects to the second rotary encoder dt
#define clk2 D4 // Xiao pin that connects to the second rotary encoder clk
#define dt3 D8 // Xiao pin that connects to the third rotary encoder dt
#define clk3 D7 // Xiao pin that connects to the third rotary encoder clk
#define dt4 D10 // Xiao pin that connects to the fourth rotary encoder dt
#define clk4 D9 // Xiao pin that connects to the fourth rotary encoder clk
Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800); // set the NeoPixel initialization
Encoder encoder_red(dt1,clk1); // initiate object rotary encoder with the name encoder_red
Encoder encoder_green(dt2,clk2); // initiate object rotary encoder with the name encoder_green
Encoder encoder_blue(dt3,clk3); // initiate object rotary encoder with the name encoder_blue
Encoder encoder_brightness(dt4,clk4); // initiate object rotary encoder with the name encoder_brightness
int red = 0; // red value for the NeoPixel
int green = 0; // green value for the NeoPixel
int blue = 0; // blue value for the NeoPixel
int encoder_value = 0; // value of rotary encoder
int brightness_value = 255; // brightness value for the NeoPixel
int brightness_value_old = 0; // previous brightness value for the NeoPixel
bool sw_control = false; // switch control variable to check if the switch is reactivated
void setup() {
NeoPixel.begin(); // initialize the NeoPixel strip object
pinMode(sw1, INPUT); // set pin for rotary encoder button as an input
}
void IRAM_ATTR fallout() { // fallout function
if (sw_control == false){ // if the button is not pressed
brightness_value = brightness_value_old; // overwrite the momentary brightness value with the old one
sw_control = true; // set the control value to true
}
else if (sw_control == true){ // if the button is pressed
brightness_value_old = brightness_value; // overwrite the old brightness value with the momentary one
brightness_value = 0; // set the momentary brightness value to 0
sw_control = false; // set the control value to false
}
}
void loop() {
if (encoder_value != encoder_brightness.read()){ // if the stored encoder value for the brightness is different then the momentary encoder value
encoder_value = encoder_brightness.read(); // overwrite the encoder value
brightness_value = encoder_value; // overwrite the brightness value with the encoder value
}
red = encoder_red.read(); // read out the encoder value for red
green = encoder_green.read(); // read out the encoder value for green
blue = encoder_blue.read(); // read out the encoder value for blue
for (int i = 0; i <= 3; i++){ // cycle through the 4 NeoPixels
NeoPixel.setPixelColor(i, red, green, blue); // set color
NeoPixel.setBrightness(brightness_value); // set brightness
NeoPixel.show(); // output the color and brightness
}
attachInterrupt(sw1, fallout, RISING); // open the interrupt function fallout, if the switch have a raising slope
}
With this information I can setup the ESP-Now connection. For that I followed further the instruction from Random Nerds Tutorials to setup the ESP-now. Beginning with the transmitter with the following code:
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <esp_now.h> // include ESP-NOW library to use ESP-Now
#include <WiFi.h> // include wifi library to activate and use wifi from the ESP32-C3 chip
uint8_t broadcastAddress[] = {0x54, 0x32, 0x04, 0x89, 0x8E, 0x00}; // replaced with the receiver XIAO's MAC Address that I read out earlier it set up the broadcast MAC Address
typedef struct struct_message { // Structure example to send data that must match the receiver structure
char a[32];
int b;
float c;
bool d;
} struct_message;
struct_message myData; // Create a struct_message called myData
esp_now_peer_info_t peerInfo;
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { // callback function which tracks if data is sent
Serial.print("\r\nLast Packet Send Status:\t"); // send a message to a serial monitor
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); // send a message to a serial monitor if the delivery of the data was a success or if it fails
}
void setup() {
Serial.begin(115200); // initialize serial communication with a baudrate of 115200
WiFi.mode(WIFI_STA); // ESP is set in to station mode and not into access point
if (esp_now_init() != ESP_OK) { // initialize ESP-NOW
Serial.println("Error initializing ESP-NOW"); // if the ESP encounter any setup errors it will send a error message to a serial monitor
return; // exit function
}
esp_now_register_send_cb(OnDataSent); // after a successful initialization of ESP-NOW it will call up the OnDataSent function from earlier
memcpy(peerInfo.peer_addr, broadcastAddress, 6); // Register another XIAO to peer with
peerInfo.channel = 0; // set peer chanel to 0, multiple channels are possible to make sure that they are no interference
peerInfo.encrypt = false; // don't encrypt the transmit messages
if (esp_now_add_peer(&peerInfo) != ESP_OK){ // function to send a MAC Address to the receiver to add a peer
Serial.println("Failed to add peer"); // send an error message to the serial monitor if the connection setup failed
return; // exit function
}
}
void loop() {
strcpy(myData.a, "THIS IS A CHAR"); // set values to send
myData.b = random(1,20);
myData.c = 1.2;
myData.d = false;
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); // send message via ESP-NOW
if (result == ESP_OK) { // send positive message to a serial monitor if the transmit was successful
Serial.println("Sent with success");
}
else { // send error message to a serial monitor if the transmit was not successful
Serial.println("Error sending the data");
}
delay(2000); // set a delay between sending messages
}
After this I setup the receiver XIAO with the following code:
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <esp_now.h> // include ESP-NOW library to use ESP-Now
#include <WiFi.h> // include wifi library to activate and use wifi from the ESP32-C3 chip
typedef struct struct_message { // Structure example to send data that must match the transmitter structure
char a[32];
int b;
float c;
bool d;
} struct_message;
struct_message myData; // Create a struct_message called myData
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { // callback function that will be executed when data is received
memcpy(&myData, incomingData, sizeof(myData));
Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("Char: ");
Serial.println(myData.a);
Serial.print("Int: ");
Serial.println(myData.b);
Serial.print("Float: ");
Serial.println(myData.c);
Serial.print("Bool: ");
Serial.println(myData.d);
Serial.println();
}
void setup() {
Serial.begin(115200); // initialize serial communication with a baudrate of 115200
WiFi.mode(WIFI_STA); // ESP is set in to station mode and not into access point
if (esp_now_init() != ESP_OK) { // initialize ESP-NOW
Serial.println("Error initializing ESP-NOW"); // if the ESP encounter any setup errors it will send a error message to a serial monitor
return; // exit function
}
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv)); // after a successful initialization of ESP-NOW it will call up the OnDataRecv function from earlier to get a package information about the received data
}
void loop() {
Serial.println(myData.a);
Serial.println(myData.b);
Serial.println(myData.c);
Serial.println(myData.d);
}
The next step is to test if the setup works, which I did with two ArduinoIDE serial monitors in different windows with different com ports.
The result looks like that:
Light Controller
The next step would be to combine the example code for the transmitter with the test code from the remote and the receiver code with the light controller code from week 09.
I designed a small pcb for a the remote, which consist out of a XIAO ESP32-C3, four Rotary Encoder and four Neopixels as an indicator.
The code for the remote looks like that:
#include <Adafruit_NeoPixel.h> // library for NeoPixels
#include <Encoder.h> // library for rotary encoder
#define PIN_NEO_PIXEL D0 // Xiao pin that connects to NeoPixel
#define NUM_PIXELS 4 // number of LEDs
#define dt1 D5 // Xiao pin that connects to rotary encoder dt
#define clk1 D6 // Xiao pin that connects to rotary encoder clk
#define sw1 D2 // Xiao pin that connects to rotary encoder sw
#define dt2 D3 // Xiao pin that connects to rotary encoder dt
#define clk2 D4 // Xiao pin that connects to rotary encoder clk
#define dt3 D8 // Xiao pin that connects to rotary encoder dt
#define clk3 D7 // Xiao pin that connects to rotary encoder clk
#define dt4 D10 // Xiao pin that connects to rotary encoder dt
#define clk4 D9 // Xiao pin that connects to rotary encoder clk
Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800); // set the NeoPixel initialization
Encoder encoder_red(dt1,clk1); // initiate object rotary encoder with the name encoder_red
Encoder encoder_green(dt2,clk2); // initiate object rotary encoder with the name encoder_green
Encoder encoder_blue(dt3,clk3); // initiate object rotary encoder with the name encoder_blue
Encoder encoder_brightness(dt4,clk4); // initiate object rotary encoder with the name encoder_brightness
int red1 = 0; // red value for the first LED strip
int green1 = 0; // green value for the first LED strip
int blue1 = 0; // blue value for the first LED strip
int red2 = 0; // red value for the second LED strip
int green2 = 0; // green value for the second LED strip
int blue2 = 0; // blue value for the second LED strip
int brightness_led1 = 0; // brightness value for the first LED strip
int brightness_led2 = 0; // brightness value for the second LED strip
int brightness_spot1 = 0; // brightness value for the first LED spot
int brightness_spot2 = 0; // brightness value for the second LED spot
bool sw_control = false; // control variable for the button
int sw_counter = 1; // count button presses
void setup() {
Serial.begin(115200); // initial serial communication with a baud rate of 115200
NeoPixel.begin(); // initialize the NeoPixel strip object
pinMode(sw1, INPUT); // set pin for rotary encoder button as an input
}
void IRAM_ATTR mode_switch() {
if (sw_counter <= 4){
sw_counter ++;
} if else (sw_counter > 4){
sw_counter = 1;
}
}
void loop() {
switch (sw_counter){
case 1:
red1 = encoder_red.read();
green1 = encoder_green.read();
blue1 = encoder_blue.read();
brightness_led1 = encoder_brightness.read();
break;
case 2:
red2 = encoder_red.read();
green2 = encoder_green.read();
blue2 = encoder_blue.read();
brightness_led2 = encoder_brightness.read();
break;
case 3:
brightness_spot1 = encoder_brightness.read();
break;
case 4:
brightness_spot2 = encoder_brightness.read();
break;
}
NeoPixel.setPixelColor(0, red1, green1, blue1); // indicate the color from the first LED strip
NeoPixel.setBrightness(brightness_led1); // indicate the brightness from the first LED strip
NeoPixel.setPixelColor(1, red2, green2, blue2); // indicate the color from the first LED strip
NeoPixel.setBrightness(brightness_led2); // indicate the brightness from the first LED strip
NeoPixel.setPixelColor(2, 255, 255, 255); // define led indicator for the first LED spot
NeoPixel.setBrightness(brightness_spot1); // indicate the brightness from the first LED spot
NeoPixel.setPixelColor(3, 255, 255, 255); // define led indicator for the second LED spot
NeoPixel.setBrightness(brightness_spot2); // indicate the brightness from the second LED spot
NeoPixel.show(); // output on the NeoPixel
attachInterrupt(sw1, mode_switch, RISING);
}
WIP
#include <Adafruit_NeoPixel.h> // library for NeoPixels
#include <Encoder.h> // library for rotary encoder
#include <esp_now.h> // include ESP-NOW library to use ESP-Now
#include <WiFi.h> // include wifi library to activate and use wifi from the ESP32-C3 chip
#define PIN_NEO_PIXEL D0 // Xiao pin that connects to NeoPixel
#define NUM_PIXELS 4 // number of NeoPixels
#define dt1 D5 // Xiao pin that connects to rotary encoder dt
#define clk1 D6 // Xiao pin that connects to rotary encoder clk
#define sw1 D2 // Xiao pin that connects to rotary encoder sw
#define dt2 D3 // Xiao pin that connects to rotary encoder dt
#define clk2 D4 // Xiao pin that connects to rotary encoder clk
#define dt3 D8 // Xiao pin that connects to rotary encoder dt
#define clk3 D7 // Xiao pin that connects to rotary encoder clk
#define dt4 D10 // Xiao pin that connects to rotary encoder dt
#define clk4 D9 // Xiao pin that connects to rotary encoder clk
uint8_t broadcastAddress[] = {0x54, 0x32, 0x04, 0x89, 0x8E, 0x00}; // replaced with the receiver XIAO's MAC Address that I read out earlier it set up the broadcast MAC Address
Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800); // set the NeoPixel initialization
Encoder encoder_red(dt1,clk1); // initiate object rotary encoder with the name encoder_red
Encoder encoder_green(dt2,clk2); // initiate object rotary encoder with the name encoder_green
Encoder encoder_blue(dt3,clk3); // initiate object rotary encoder with the name encoder_blue
Encoder encoder_brightness(dt4,clk4); // initiate object rotary encoder with the name encoder_brightness
typedef struct struct_message { // Structure example to send data that must match the receiver structure
int red1 = 0; // red value for the first LED strip
int green1 = 0; // green value for the first LED strip
int blue1 = 0; // blue value for the first LED strip
int red2 = 0; // red value for the second LED strip
int green2 = 0; // green value for the second LED strip
int blue2 = 0; // blue value for the second LED strip
int brightness_led1 = 0; // brightness value for the first LED strip
int brightness_led2 = 0; // brightness value for the second LED strip
int brightness_spot1 = 0; // brightness value for the first LED spot
int brightness_spot2 = 0; // brightness value for the second LED spot
} struct_message;
struct_message myData; // Create a struct_message called myData
esp_now_peer_info_t peerInfo;
bool sw_control = false; // control variable for the button
int sw_counter = 1; // count button presses
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { // callback function which tracks if data is sent
Serial.print("\r\nLast Packet Send Status:\t"); // send a message to a serial monitor
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); // send a message to a serial monitor if the delivery of the data was a success or if it fails
}
void setup() {
Serial.begin(115200); // initial serial communication with a baud rate of 115200
NeoPixel.begin(); // initialize the NeoPixel strip object
pinMode(sw1, INPUT); // set pin for rotary encoder button as an input
WiFi.mode(WIFI_STA); // ESP is set in to station mode and not into access point
if (esp_now_init() != ESP_OK) { // initialize ESP-NOW
Serial.println("Error initializing ESP-NOW"); // if the ESP encounter any setup errors it will send a error message to a serial monitor
return; // exit function
}
esp_now_register_send_cb(OnDataSent); // after a successful initialization of ESP-NOW it will call up the OnDataSent function from earlier
memcpy(peerInfo.peer_addr, broadcastAddress, 6); // Register another XIAO to peer with
peerInfo.channel = 0; // set peer chanel to 0, multiple channels are possible to make sure that they are no interference
peerInfo.encrypt = false; // don't encrypt the transmit messages
if (esp_now_add_peer(&peerInfo) != ESP_OK){ // function to send a MAC Address to the receiver to add a peer
Serial.println("Failed to add peer"); // send an error message to the serial monitor if the connection setup failed
return; // exit function
}
}
void IRAM_ATTR mode_switch() { // interrupt function triggered by pressing the button from the first rotary encoder
if (sw_counter <= 4){ // let the switch counter count between 1 and 4
sw_counter ++; // by each activation increase the switch counter
} if else (sw_counter > 4){ // if the counter reaches 5
sw_counter = 1; // reset the counter to 1
}
}
void loop() {
switch (sw_counter){ // switch case to control the different light settings related to the selection
case 1: // in case 1 set parameter for the first LED strip
myData.red1 = encoder_red.read();
myData.green1 = encoder_green.read();
myData.blue1 = encoder_blue.read();
myData.brightness_led1 = encoder_brightness.read();
break;
case 2: // in case 2 set parameter for the second LED strip
myData.red2 = encoder_red.read();
myData.green2 = encoder_green.read();
myData.blue2 = encoder_blue.read();
myData.brightness_led2 = encoder_brightness.read();
break;
case 3: // in case 3 set brightness for the first raw of LED spot
myData.brightness_spot1 = encoder_brightness.read();
break;
case 4: // in case 4 set brightness for the second raw of LED spot
myData.brightness_spot2 = encoder_brightness.read();
break;
}
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); // send message via ESP-NOW
if (result == ESP_OK) { // send positive message to a serial monitor if the transmit was successful
Serial.println("Sent with success");
} else { // send error message to a serial monitor if the transmit was not successful
Serial.println("Error sending the data");
}
NeoPixel.setPixelColor(0, red1, green1, blue1); // indicate the color from the first LED strip
NeoPixel.setBrightness(brightness_led1); // indicate the brightness from the first LED strip
NeoPixel.setPixelColor(1, red2, green2, blue2); // indicate the color from the first LED strip
NeoPixel.setBrightness(brightness_led2); // indicate the brightness from the first LED strip
NeoPixel.setPixelColor(2, 255, 255, 255); // define led indicator for the first LED spot
NeoPixel.setBrightness(brightness_spot1); // indicate the brightness from the first LED spot
NeoPixel.setPixelColor(3, 255, 255, 255); // define led indicator for the second LED spot
NeoPixel.setBrightness(brightness_spot2); // indicate the brightness from the second LED spot
NeoPixel.show(); // output on the NeoPixel
attachInterrupt(sw1, mode_switch, RISING); // enter interrupt function to switch the mode on a rising slope
}
The code for the light controller looks like that:
What I Learned This Week?
- SoftwareSerial Library is to big for the Attiny412
- The XIAO ESP32-C3 is most of the time enough for everything you need and allows to easily communicate wirelessly with other XIAO ESP32-C3.
- Esp Now is a great and fast way to communicate wirelessly between ESP32 modules.
What I want to improve next week
Design Files
light controller project
remote schematic
remote traces
remote holes outline
To create this page, I used ChatGPT to check my syntax and grammar.
Copyright 2025 < Benedikt Feit > - Creative Commons Attribution Non Commercial
Source code hosted at gitlab.fabcloud.org