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// No install, it's ready #include // Install library PubSubClient #include // 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, LOW); digitalWrite(greenPin, LOW); // Apagar el LED verde digitalWrite(bluePin, HIGH); // 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.
#include// No install, it's ready #include // Install library PubSubClient // Setup your WIFI with SSID/Password const char* ssid = "CHAYITOS"; const char* password = "CHIHUAY123"; // Setup your MQTT Protocol //const char* mqtt_server = "test.mosquitto.org"; const char* mqttServer = "broker.emqx.io"; const char* mqttClient = "evkusi"; const char* mqttTopicPub = "fabacademy/grupal"; WiFiClient espClient; PubSubClient client(espClient); // Setup your process float varNum = 0; long nowTime, lastTime; 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()); // Connecting to MQTT client.setServer(mqttServer, 1883); connectMQTT(); } void connectMQTT() { Serial.print("Connecting to MQTT Server ... "); if (client.connect(mqttClient)) { Serial.println("Connected"); } 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(); client.publish(mqttTopicPub, "open"); delay(5000); client.publish(mqttTopicPub, "closed"); delay(5000); }
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.
As mentioned earlier, the millis() function allows background counting, allowing the device to operate without blocking, only performing the task of counting.
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.
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// No install, it's ready #include // Install library PubSubClient #include // Install Adafruit NeoPixel library // Setup your WIFI with SSID/Password const char* ssid = "CHAYITOPOWER"; const char* password = "CHIHUAY123"; // Setup your MQTT Protocol const char* mqttServer = "broker.emqx.io"; const char* mqttClient = "evkusi"; const char* mqttTopicSub = "fabacademy/neopixel"; WiFiClient espClient; PubSubClient client(espClient); // Setup NeoPixel Pin #define PIN A0 // Pin de datos al que está conectada la tira NeoPixel #define NUM_PIXELS 30 // Número de LEDs en la tira (ajústalo a tus necesidades) #define BRILLO 255 // Brillo máximo (puedes ajustar este valor) 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(); // Apaga todos los LEDs al inicio } void callback(char* topic, byte* message, unsigned int length) { String messageTemp = ""; for (int i = 0; i < length; i++) { messageTemp += (char)message[i]; } Serial.print("Arrived on "); Serial.print(topic); Serial.print(" the message "); Serial.println(messageTemp); // Store the message to process in the loop mqttMessage = messageTemp; // Create algorithm for controlling LEDs if (String(topic) == mqttTopicSub) { if (mqttMessage == "on") { strip.fill(strip.Color(0, 255, 0)); // Verde strip.show(); } else if (mqttMessage == "off") { strip.fill(strip.Color(0, 0, 0)); // Apagar strip.show(); } else if (mqttMessage == "random") { cambiarColorAleatorio(); } } } void connectMQTT() { 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(); } client.loop(); } // Function to set a random color to all NeoPixels void cambiarColorAleatorio() { for (int i = 0; i < strip.numPixels(); i++) { // Generate random values for red, green, and blue (from 0 to 255) int rojo = random(0, 256); int verde = random(0, 256); int azul = random(0, 256); // Set the color of each LED with the random values strip.setPixelColor(i, strip.Color(rojo, verde, azul)); } 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 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:
#include// Librería WiFi #include // Librería para el protocolo MQTT #include // Librería NeoPixel // 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); // Configuración de NeoPixel #define TOUCH_PIN 2 // Pin del sensor táctil #define NEOPIXEL_PIN 4 // Pin de datos para el NeoPixel (ajustado al pin 4) #define NUM_PIXELS 1 // Solo un LED #define BRILLO 255 // Brillo máximo Adafruit_NeoPixel strip(NUM_PIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); // 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(); // Inicializar el NeoPixel strip.begin(); strip.show(); // Apagar el LED al inicio // Configurar pin del sensor táctil pinMode(TOUCH_PIN, INPUT); } 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(TOUCH_PIN); // 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 strip.setPixelColor(0, strip.Color(255, 0, 0)); // LED rojo strip.show(); client.publish(mqttTopicPub, "on"); // Publicar estado "on" status = "on"; // Cambiar el estado a "on" } else { // Si el LED está encendido, apagarlo strip.setPixelColor(0, strip.Color(0, 0, 0)); // Apagar LED strip.show(); 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.
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.