In this project, I teamed up with my partner Jonathan, he from Colombia and I from Peru. Sometimes, the distance seemed like a challenge, but we were able to work efficiently. Especially in this assignment on MQTT, it was very helpful. The technology allowed us to overcome the distance barrier and collaborate seamlessly. This project taught us a lot and showed us how remote connectivity can be used to accomplish complex tasks.
Initially, to avoid simply making a connection between boards, I decided to create a mini project using laser cutting on MDF. In this case, Jhonatan designed a small garage where we installed a servomotor. We had doubts about whether a microservo would be sufficient to lift the structure, so we opted for a 25 kg servomotor to have better control. Additionally, Jhonatan had already planned to control the servomotor from his PCB, so he connected everything from Colombia.
Here we can see more closely the PCB and the circuit Jhonatan has, as it includes several output devices with which we interacted in this exercise, such as the buzzer, the RGB LED, and the servomotor.
Since we are in different countries, one of the best ways to interconnect was through the Internet. In this case, we used MQTT as a communication tool. So, we created the subscriber Link in MQTTX. To understand how to install, configure, and create it, we reviewed the documentation Evelyn shared with us.
From this program, we validated the correct creation of the topic through which the messages would be sent. We performed some tests by sending messages from each home and verified that they were received correctly. For example, Evelyn sent data from Peru, and in Colombia, it was received without problems.
#include <WiFi.h> // Include WiFi library (preinstalled for ESP32) #include <PubSubClient.h> // Include MQTT client library (install required) // Wi-Fi credentials const char* ssid = "CHAYITOS"; // Wi-Fi network name (SSID) const char* password = "CHIHUAY123"; // Wi-Fi password // MQTT broker configuration const char* mqttServer = "broker.emqx.io"; // MQTT broker address const char* mqttClient = "evkusi"; // MQTT client ID const char* mqttTopicPub = "fabacademy/grupal"; // MQTT topic to publish messages to WiFiClient espClient; // Create a WiFi client PubSubClient client(espClient); // Create MQTT client using the WiFi client // Variables for timing and process control float varNum = 0; long nowTime, lastTime; void setup() { Serial.begin(115200); // Start serial communication Serial.print("Connecting to "); Serial.println(ssid); // Print SSID being connected to WiFi.begin(ssid, password); // Connect to Wi-Fi // Wait until the connection is established while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("Connected with IP "); Serial.println(WiFi.localIP()); // Print local IP address // Set MQTT server and connect client.setServer(mqttServer, 1883); // Set MQTT broker and port connectMQTT(); // Attempt to connect to MQTT broker } void connectMQTT() { Serial.print("Connecting to MQTT Server ... "); if (client.connect(mqttClient)) { Serial.println("Connected"); // Successfully connected to MQTT } else { Serial.print("failed, rc="); Serial.print(client.state()); // Print MQTT connection error code Serial.println(" try again in 5 seconds"); delay(5000); // Wait before retrying } } void loop() { // Reconnect to MQTT if disconnected if (!client.connected()) { connectMQTT(); } client.loop(); // Maintain MQTT connection and handle callbacks // Publish "open" to the topic client.publish(mqttTopicPub, "open"); delay(5000); // Wait 5 seconds // Publish "closed" to the topic client.publish(mqttTopicPub, "closed"); delay(5000); // Wait 5 seconds }
The publisher has a simpler code, with less complexity, as it subscribes to the MQTT topic and, using a millis() process to avoid blocking the board’s execution, sends an "open" or "closed" message every 5 seconds.
Getting into more detail, this publisher was uploaded to a Seeed XIAO ESP32-C3 board, and we can see the Wi-Fi configuration in the first lines of the code.
In the loop part, we used the MQTT function to publish. It is important to note that only text characters can be sent, as it does not accept numbers. After sending the messages, we wait for some time before continuing.
The process is repeated to send the "closed" message and allow the project in Colombia to open or close the garage door.
In the image below, we can see that the code was uploaded to both the ESP32 S3 and the XIAO ESP32-C3, located in different countries. After uploading the code, we proceeded to view the project’s execution on the screen.
#include <WiFi.h> // No install, it's ready #include <PubSubClient.h> // Install library PubSubClient #include <ESP32Servo.h> // Librería compatible con ESP32 para controlar el servo // Setup your WIFI with SSID/Password const char* ssid = "tatanc"; const char* password = "87654321"; // Setup your MQTT Protocol const char* mqttServer = "broker.emqx.io"; const char* mqttClient = "ESP32C3FacAcademyean007"; const char* mqttTopicSub = "fabacademy/grupal"; const char* mqttTopicSub2 = "fabacademy/led"; WiFiClient espClient; PubSubClient client(espClient); // Setup your process const int servoPin = 14; // Pin del servo const int buzzerPin = 19; // Pin del buzzer const int redPin = 5; // Pin del LED rojo const int greenPin = 4; // Pin del LED verde const int bluePin = 6; // Pin del LED azul Servo myServo; // Crea un objeto servo void setup() { Serial.begin(115200); // Connecting to Wi-Fi Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("Connected with IP "); Serial.println(WiFi.localIP()); // Connecting to MQTT client.setServer(mqttServer, 1883); client.setCallback(callback); connectMQTT(); // Setup ESP32 pins pinMode(buzzerPin, OUTPUT); pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); // Conectar el servo al pin myServo.attach(servoPin); // Inicializa el LED RGB en azul mientras espera digitalWrite(bluePin, HIGH); digitalWrite(redPin, LOW); // Apaga el LED rojo digitalWrite(greenPin, LOW); // Apaga el LED verde myServo.write(0); // Inicia el servo en 0° } void callback(char* topic, byte* message, unsigned int length) { Serial.print("Arrived on "); Serial.print(topic); Serial.print(" with message "); String messageTemp; for (int i = 0; i < length; i++) { Serial.print((char)message[i]); messageTemp += (char)message[i]; } Serial.println(); if (String(topic) == mqttTopicSub) { if (messageTemp == "closed") { closedAction(); } else if (messageTemp == "open") { openAction(); } } } void openAction() { // Mover el servo gradualmente a 75 grados for (int pos = 0; pos <= 75; pos++) { myServo.write(pos); // Mueve el servo a la posición 'pos' delay(50); // Retraso para movimiento gradual } // Emitir un beep gradualmente beepGradual(5); // Encender el LED verde digitalWrite(greenPin, HIGH); digitalWrite(redPin, LOW); // Apagar el LED rojo digitalWrite(bluePin, LOW); // Apagar el LED azul } void closedAction() { // Emitir un beep continuo for (int i = 0; i < (7*2); i++) { digitalWrite(buzzerPin, HIGH); // Enciende el buzzer delay(100); // Beep por 300ms digitalWrite(buzzerPin, LOW); // Apaga el buzzer delay(100); // Espera entre beeps } // Encender el LED rojo digitalWrite(redPin, HIGH); digitalWrite(greenPin, LOW); // Apagar el LED verde digitalWrite(bluePin, LOW); // Apagar el LED azul // Mover el servo de vuelta a 0 grados for (int pos = 75; pos >= 0; pos--) { myServo.write(pos); // Mueve el servo a la posición 'pos' delay(50); // Retraso para movimiento gradual } } void beepGradual(int times) { for (int i = 0; i < times; i++) { digitalWrite(buzzerPin, HIGH); // Enciende el buzzer delay(300); // Beep por 300ms digitalWrite(buzzerPin, LOW); // Apaga el buzzer delay(300); // Espera entre beeps } } void connectMQTT() { Serial.print("Connecting to MQTT Server ... "); if (client.connect(mqttClient)) { Serial.println("Connected"); client.subscribe(mqttTopicSub); // Subscribe to the topic } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); delay(5000); } } void loop() { if (!client.connected()) { connectMQTT(); } client.loop(); }
Now, we move on to the process of implementing it on the boards. In this exercise, the publisher is located in Peru, and the subscriber is in Colombia. Below, we can see the code used in more detail. It includes the necessary libraries, first configuring the Wi-Fi network to which the board connects, then the MQTT protocol, and finally defining the pins. What the code really does is simple: it reads what is published on the topic. If the message is "open", it opens the door, emits a sound, and turns on the green light. If it is "closed", it lowers the servomotor. In this case, "open" corresponds to a 75° angle, so it will move to 0°, emit a different beep, and turn on another light.
With the code ready, it was uploaded to an ESP32 S3 Devkit located in Bogotá, Colombia.
From Colombia's point of view, we can see on the screen when the "open" message arrives, which opens the door and changes the light, with the same happening for the "closed" message. In both cases, the buzzer emits a completely different beep.
In the next video, we can observe that the actual emitter is a XIAO from another country, sending the instructions. The instructions stop being sent when Evelyn removes the power from the board.
For this project, we will use a Neopixel (or a Neopixel strip), which is a type of addressable LED, meaning each LED on the strip can be controlled independently. We will use a board such as the ESP8266 or ESP32 to connect the Neopixel to a WiFi network, allowing it to be controlled remotely over a wireless connection.
To connect, it is necessary to fill in the general data that are essential for establishing a connection via MQTT. One of the most important parameters is the port, which in this case is 1883. This port is crucial, as it allows communication between devices and ensures that both parties can connect properly. It is important that this data remains consistent so that other users can also connect without issues.
Additionally, by generating a username, we are creating a unique identifier for each user. This username will be used to recognize and authenticate each client that connects to the MQTT server. It is essential that the username is unique and well-managed to avoid connection conflicts.
Now, we will subscribe to the topic fabacademy/neopixel. It is crucial that the topic is written correctly, as in MQTT (the messaging protocol), subscribing to the topic is case-sensitive. This means that if we write the topic incorrectly, such as FabAcademy/NeoPixel or FABACADEMY/NEOPIXEL, it will not work properly because the MQTT server expects the topic to be written exactly as it is defined. Therefore, we need to ensure the topic is written correctly: fabacademy/neopixel.
#include <WiFi.h> // Librería WiFi #include <PubSubClient.h> // Librería para el protocolo MQTT // Configuración WiFi const char* ssid = "tatanc"; const char* password = "87654321"; // Configuración MQTT const char* mqttServer = "broker.emqx.io"; const char* mqttClient = "ESP32C3FacAcademyean007"; const char* mqttTopicPub = "fabacademy/neopixel"; // Tema de publicación WiFiClient espClient; PubSubClient client(espClient); // Variables para el control de tiempo y estados int varNum = 0; long nowTime, lastTime; String status = "off"; // Estado inicial del LED bool lastTouchState = LOW; // Estado anterior del sensor táctil bool currentTouchState = LOW; // Estado actual del sensor táctil void setup() { Serial.begin(115200); // Conectar a WiFi Serial.print("Conectando a WiFi "); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(" Conectado"); Serial.println("IP: " + WiFi.localIP().toString()); // Conectar a MQTT client.setServer(mqttServer, 1883); connectMQTT(); // Configurar pin del sensor táctil pinMode(2, INPUT); // Suponiendo que el pin 2 es el sensor táctil } void connectMQTT() { Serial.print("Conectando a servidor MQTT ... "); while (!client.connected()) { if (client.connect(mqttClient)) { Serial.println("Conectado"); } else { Serial.print("fallo, rc="); Serial.print(client.state()); Serial.println(" Reintentando en 5 segundos"); delay(5000); } } client.subscribe(mqttTopicPub); // Suscribirse al tema (si es necesario) } void loop() { if (!client.connected()) { connectMQTT(); } client.loop(); // Leer el valor del sensor táctil currentTouchState = digitalRead(2); // Se usa el pin 2 para el sensor táctil // Detectar si el sensor táctil ha cambiado de estado if (currentTouchState == HIGH && lastTouchState == LOW) { // Si el sensor táctil fue presionado if (status == "off") { // Si el LED está apagado, encenderlo Serial.println("LED ON"); client.publish(mqttTopicPub, "on"); // Publicar estado "on" status = "on"; // Cambiar el estado a "on" } else { // Si el LED está encendido, apagarlo Serial.println("LED OFF"); client.publish(mqttTopicPub, "off"); // Publicar estado "off" status = "off"; // Cambiar el estado a "off" } delay(500); // Esperar medio segundo para evitar lecturas múltiples por un solo toque } lastTouchState = currentTouchState; // Guardar el estado actual del sensor táctil delay(100); // Espera de 100 ms para el siguiente ciclo }
Now, for the publisher, a similar configuration was done as for the subscriber, with the difference that key parameters, such as the WiFi settings and the MQTT client, were modified. These changes are essential for the device to publish messages instead of receiving them via MQTT.
#include <WiFi.h> // No installation needed, it’s ready for ESP32 #include <PubSubClient.h> // Install PubSubClient library for MQTT #include <Adafruit_NeoPixel.h> // Install Adafruit NeoPixel library // Setup your WIFI with SSID/Password const char* ssid = "CHAYITOPOWER"; // Wi-Fi network name (SSID) const char* password = "CHIHUAY123"; // Wi-Fi network password // Setup your MQTT Protocol const char* mqttServer = "broker.emqx.io"; // MQTT broker address const char* mqttClient = "evkusi"; // MQTT client ID const char* mqttTopicSub = "fabacademy/neopixel"; // MQTT topic for subscribing WiFiClient espClient; PubSubClient client(espClient); // Setup NeoPixel Pin #define PIN A0 // Data pin to which the NeoPixel strip is connected #define NUM_PIXELS 30 // Number of LEDs in the strip (adjust to your needs) #define BRIGHTNESS 255 // Maximum brightness (you can adjust this value) Adafruit_NeoPixel strip(NUM_PIXELS, PIN, NEO_GRB + NEO_KHZ800); // Variable to handle MQTT messages String mqttMessage = ""; void setup() { Serial.begin(115200); // Connecting to Wifi Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("Connected with IP "); Serial.println(WiFi.localIP()); // Setup MQTT client.setServer(mqttServer, 1883); client.setCallback(callback); // Initial connection to MQTT connectMQTT(); // Setup NeoPixel strip.begin(); strip.show(); // Turn off all LEDs initially } void callback(char* topic, byte* message, unsigned int length) { String messageTemp = ""; for (int i = 0; i < length; i++) { messageTemp += (char)message[i]; } // Print the received MQTT message Serial.print("Arrived on "); Serial.print(topic); Serial.print(" the message "); Serial.println(messageTemp); // Store the message to process in the loop mqttMessage = messageTemp; // Control the LEDs based on the received MQTT message if (String(topic) == mqttTopicSub) { if (mqttMessage == "on") { strip.fill(strip.Color(0, 255, 0)); // Green strip.show(); } else if (mqttMessage == "off") { strip.fill(strip.Color(0, 0, 0)); // Turn off strip.show(); } else if (mqttMessage == "random") { setRandomColor(); // Set random color } } } void connectMQTT() { // Keep trying to connect to the MQTT server if not connected while (!client.connected()) { Serial.print("Connecting to MQTT Server... "); if (client.connect(mqttClient)) { Serial.println("Connected"); // Subscribe to the MQTT topic client.subscribe(mqttTopicSub); } else { Serial.print("Failed, rc="); Serial.print(client.state()); Serial.println(" Try again in 5 seconds"); delay(5000); } } } void loop() { if (!client.connected()) { connectMQTT(); // Ensure we're connected to MQTT } client.loop(); // Process incoming messages and maintain the connection } // Function to set a random color to all NeoPixels void setRandomColor() { for (int i = 0; i < strip.numPixels(); i++) { // Generate random values for red, green, and blue (from 0 to 255) int red = random(0, 256); int green = random(0, 256); int blue = random(0, 256); // Set the color of each LED with the random values strip.setPixelColor(i, strip.Color(red, green, blue)); } strip.show(); // Update the strip with the new colors }
As a subscriber, you need to have a code that allows you to connect to the MQTT server and receive messages from the desired topic. A crucial aspect of this process is the connection to the local network, as we need to be connected to a WiFi network in order to interact with the MQTT server. To do this, you must provide the network password to gain access.
As an MQTT client, the device (in this case, the EVKUSI) will connect to the server with the necessary credentials (such as username and password), and then subscribe to the specific topic, in this case, fablab/neopixel. By subscribing to this topic, the client will be able to receive messages published under that topic.
In the Arduino IDE, after preparing the code to connect the device to the MQTT server, you need to perform key configurations:
In the results, we can see the successful connection via MQTT, even though my partner is kilometers away, specifically in Colombia, while I am in Peru. However, this challenge can be carried out without complications. As a publisher, he can send his sensor data and control the turning on and off of the LED remotely.
In this result, another function of the NeoPixel can be verified, which allows the colors of the LED strip to change dynamically. By using the random keyword, the colors were generated randomly. Every time the random function was called again, the colors would change unpredictably, creating surprising visual effects.
The objective of this group assignment for Week 11 was to demonstrate communication between two separate projects using networking protocols. Specifically, we were required to send a message from one board to another and document the process and results as a team.
For our project, we used two XIAO ESP32 C3 boards. The first board, managed by Andru, acts as the sender: every time a touch sensor connected to pin D2 is activated, it sends a message over WiFi using the MQTT protocol. The second board, managed by Michael, serves as the receiver: it listens for incoming messages and controls three LEDs (blue on D2, yellow on D3, and green on D4), lighting them up in sequence for each received message and resetting after three.
This collaborative task allowed us to explore MQTT-based communication, practical networking, and teamwork in an embedded systems context. Below, we document our process, results, and reflections.
The following materials and hardware components were used to build and test the group assignment:
Component | Quantity | Details |
---|---|---|
XIAO ESP32 C3 board | 2 | Microcontroller board for both sender and receiver |
Touch sensor | 1 | Connected to D2 on the sender board (Andru) |
LED (Blue) | 1 | Connected to D2 on the receiver board (Michael) |
LED (Yellow) | 1 | Connected to D3 on the receiver board (Michael) |
LED (Green) | 1 | Connected to D4 on the receiver board (Michael) |
Resistors (220Ω) | 3 | For limiting current to LEDs |
The communication between both boards was established using WiFi and the MQTT protocol. Below are the key network settings and parameters used for the assignment:
FabAcademy/week11
FabAcademy_Colombia_ANDRU
(Sender)FabAcademy_Colombia_MICHAEL
(Receiver)
Both boards were configured with these settings to ensure reliable communication throughout the testing process. The broker broker.emqx.io
was chosen for its ease of use and availability for public MQTT testing.
The sender board uses a XIAO ESP32 C3 connected to a capacitive touch sensor on pin D2. Each time the sensor detects a touch event, the board sends a "TOUCH" message to the MQTT topic FabAcademy/week11
via WiFi.
The following Arduino code was used on the sender board:
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "ANLI";
const char* password = "Actua1455";
const char* mqtt_server = "broker.emqx.io";
const char* mqtt_Client = "FabAcademy_Colombia_ANDRU";
const char* mqtt_Topic_Pub = "FabAcademy/week11";
#define TOUCH_PIN D2 // D2 on XIAO ESP32 C3
WiFiClient espClient;
PubSubClient client(espClient);
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(mqtt_Client)) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(TOUCH_PIN, INPUT);
setup_wifi();
client.setServer(mqtt_server, 1883);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
int touchValue = digitalRead(TOUCH_PIN);
static bool lastTouch = false;
if (touchValue == HIGH && !lastTouch) {
client.publish(mqtt_Topic_Pub, "TOUCH");
Serial.println("Touch detected, message sent!");
lastTouch = true;
delay(300); // debounce
} else if (touchValue == LOW) {
lastTouch = false;
}
}
The receiver board is also a XIAO ESP32 C3, with three LEDs connected to pins D2 (blue), D3 (yellow), and D4 (green). This board subscribes to the same MQTT topic and listens for incoming "TOUCH" messages.
FabAcademy/week11
For each received message, the board lights up the corresponding LED in sequence (blue → yellow → green), and after the third message, the cycle restarts by turning all LEDs off and beginning again.
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "ANLI";
const char* password = "Actua1455";
const char* mqtt_server = "broker.emqx.io";
const char* mqtt_Client = "FabAcademy_Colombia_MICHAEL";
const char* mqtt_Topic_Sub = "FabAcademy/week11";
#define LED_BLUE D2 // D2 on XIAO ESP32 C3
#define LED_YELLOW D3 // D3
#define LED_GREEN D4 // D4
WiFiClient espClient;
PubSubClient client(espClient);
int counter = 0;
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void turnOffAll() {
digitalWrite(LED_BLUE, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_GREEN, LOW);
}
void showLED(int count) {
turnOffAll();
if (count == 1) digitalWrite(LED_BLUE, HIGH);
else if (count == 2) digitalWrite(LED_YELLOW, HIGH);
else if (count == 3) digitalWrite(LED_GREEN, HIGH);
}
void callback(char* topic, byte* payload, unsigned int length) {
String msg;
for (unsigned int i = 0; i < length; i++) {
msg += (char)payload[i];
}
if (msg == "TOUCH") {
counter++;
if (counter > 3) counter = 1;
showLED(counter);
Serial.print("TOUCH message received. LED: ");
Serial.println(counter);
}
}
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(mqtt_Client)) {
Serial.println("connected");
client.subscribe(mqtt_Topic_Sub);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_BLUE, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
turnOffAll();
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
}
Complete source codes are also available for download in the Downloads section.
To verify the correct operation of the system, we performed several tests involving both the sender (Andru’s board) and receiver (Michael’s board). The process consisted of activating the touch sensor and observing the LED sequence on the receiver.
The MQTT subscription and communication channel were configured in advance to ensure smooth interaction between the two boards. Full details of the broker setup, topic selection, and testing process are documented on Andrés Felipe Guarnizo’s individual Week 11 assignment page.
To verify message delivery, we monitored the MQTT broker dashboard. The image below shows the broker successfully receiving the word "TOUCH" each time the touch sensor was activated.
This successful test demonstrates reliable networking between embedded devices using MQTT and validates our design approach for real-world IoT scenarios.
All resources required to replicate this group assignment are available for download below:
Please feel free to use and adapt these resources for educational purposes. For more details, refer to the individual documentation pages of the team members.