Internet de las Cosas - IoT¶
_[Actualizado a 13 de abril de 2026]

Hardware¶
El hardware disponible en el taller es:
- Placas compatibles con Arduino Uno de Keyestudio.
- Placas ESP32
Arduino Uno.¶
Además de la placa mostrada antes y con el pinout:
El LED-BUILTIN se encuentra en el pin 13
ESP32¶
DevKit¶
El del aula:

NodeMCU¶
El realizado por el equipo NodeMCU para la programación en LUA

WT32-ETH01¶
El WT32-ETH01
También válido para el ETH02 que es el ETH01 con Power Over Ethernet -POE- integrado.
El LED-BUILTIN se encuentra en el pin 5.
Preparación¶
Antes del taller debes revisar la siguiente documentación que utilizaremos en el taller.
- Introducción a arduino 15 min
Software¶
Se puede desarrollar también en VSCode con algunos plugins, pero NO lo haremos en este taller.
El puerto¶
Windows¶
Deberemos averiguar el puerto consultando el administrador de dispositivos ejecutando devmgmt.msc

GNU/Linux¶
Con el comando sudo dmesg | tail
Donde podemos leer:
1. El primer comando nos indic que se a conectado un dispositivo cp210x en el puerto ttyUSB0, por lo que en el Arduino IDE elegiremos /dev/ttyUSB0
2. El segundo arroja que se ha conectado un dispositivo ch341-uart en el puerto ttyUSB1
Boards soportadas¶
Deberemos instalar el soporte para nuestra placa:

Simulación¶
Si queréis probrarlo en casa y no tenéis el hardware podemos simularlo con:

Firmware¶
El firmware es un programa informático que establece la lógica de más bajo nivel que controla los circuitos electrónicos de un dispositivo de cualquier tipo. Está fuertemente integrado con la electrónica del dispositivo (en un PC sería la BIOS).
Fuente: Wikipedia
Lenguajes soportados: Arduino (basado en C y C++), Micropython y Javascript.
En todos los casos, los programas se dividen en 2 partes fundamentales: inicio y bucle.
Nos centramos en C++ y concretamente en ellos tenemos 2 funciones principales: void setup() y void loop() para las funciones inicio y bucle respectivamente.
void setup()¶
En esta función configuraremos el microcontrolador antes de arrancar el programa con los distintos elementos hardware a utilizar.
Pines¶
Emplearemos la función pinMode(pin, {{MODO}});, donde {{MODO}} será:
- De entrada, para lectura:
INPUT - De entrada para lectura segura:
INPUT_PULLUP - De salida:
OUTPUT
Otras config¶
- UART
- Temporizadores
- Salidas analógicas
- Interrupciones
Transmisor-Receptor Asíncrono Universal -UART- es un tipo de comunicación realmente universal que tiene disponible prácticamente todo dispositivo que tenga un procesador o controlador. Por ejemplo: televisores, radios, patinetes, enchufes wifi, etc.
void loop()¶
Esta función será el equivalente al main() de C o Java, con la diferencia que es un bucle infinito. Esto es, imaginemos que loop() será algo como:
int main(){
while (true){
// Aquí el programa que nosotros metemos en loop()
// ... por lo que no para nunca.
}
}
Aunque la democratización de los microcontroladores ha diversificado la forma de programarlos, desde el punto de vista más tradicional de la electrónica el ciclo de ejecución de un microcontrolador es:
- Lectura de entradas
- Calculo de estados
- Actuación de salidas
Esta forma de programar permite 2 cosas:
- Determinación del tiempo de ciclo y por tanto, ejecución en tiempo real^(1)^.
- Programación de máquinas de estados finitos (más predecibles).
^(1)^ La programación en tiempo real no es necesariamente más rápida, pero sí determinista en el tiempo. Esto es, la programación en tiempo real nos puede indicar que el tiempo completo de ciclo es exactamente 1 segundo, y por tanto modificar las salidas cada segundo para ajustarnos a las necesidades. Si estamos controlando el ABS de un coche es claramente insuficiente, pero si es la dirección de un barco es más que aceptable.
Lectura de entradas¶
Cuando un pin es de entrada lo normal es realizar medidas digitales con digitalRead(pin) para leer pulsadores o similar, pero también podemos realizar medidas analógicas con analogRead(pin) (siempre que el pin lo permita) para leer magnitudes físicas como temperatura.
Escritura de salidas¶
Digitales¶
De salida podremos excitar pequeños elementos como leds o reles. El límite está en 40mA, pero lo recomendado es 20mA.
Además, el límite total de intensidad suministrada es de 300 mA (todas las salidas).
PWM “analógicas”¶
Es una “simulación” de salida analógica con analogWrite(analogOutPin, outputValue), pero requiere el empleo de temporizadores por lo que deberá estudiarse detenidamente.
Macros¶
En C++ muy a menudo comenzamos los programas definiendo una serie de “constantes” y “funciones auxiliares” que serán sustituidas por sus valores en la compilación del progrma.
Entonces, ¿que podemos hacer?
#define LED 15;
#define TRUE 1;
#define AREA(ancho, alto) \
ancho*alto
Aunque las 2 primeras se recomienda realizarlas con constantes ya que nos permite tiparlas:
const int LED = 15;
const int TRUE = 1;
_Aunque he empleado el ejemplo de TRUE como entero, en c++ SI tenemos booleanos bool (true o false).
Funciones en Arduino¶
Ejercicios¶
Blink (interno)¶
Comenzaremos con lo más sencillo: un parpadeo del led integrado.
Para el acercamiento vamos a empezar por el simulador para acercarnos a los problemas de la vida real.
El firmware básico es:
// Macros
#define LED_BUILTIN 15 // Led interno
// Constantes globales
// Variables globales
bool encendido = false;
// Configuración
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // configuramos PIN de salida
}
// "main()"
void loop() {
if (encendido){
digitalWrite(LED_BUILTIN, HIGH); // encendemos led
encendido = false; // preparamos valor
} else {
digitalWrite(LED_BUILTIN, LOW); // apagamos led
encendido = true; // preparamos valor
}
delay(1000); // pausa
}
Nota: NodeMCU-32S clónico que disponemos en aula NO tiene ningún LED accesible. Los NodeMCU-32S originales tienen accesible el LED-BUILTIN en el GPIO2 (pin 15).
Blink¶

// Macros
# define LED 5 // Led en pin 5
// Constantes globales
// Variables globales
bool encendido = false;
// Configuración
void setup() {
pinMode(LED, OUTPUT);
}
// --------------
void loop() {
// Escribimos
digitalWrite(LED, encendido? HIGH: LOW);
// Negamos el booleano
encendido = !encendido;
delay(1000); // pausa
}
E incluso más sencillo con digitalWrite(LED, encendido); ya que HIGH es un 1 y LOW un 0, al igual que los booleanos true y false.
NOTAs:
- El valor de la resistencia a añadir es el que haga que la corriente sea menor a 20 mA. 1 KΩ es adecuada.
- El led tiene polaridad. Sólo encenderá si la corriente circula en el sentido correcto.
Depuración básica. Blink mejorado¶
Dado que la placa Arduino Uno no tiene soporte directo para depuración, utilizaremos la UART0 (la interna) para facilitar la programación mediante envío de mensajes utilizando el monitor serie:
// Macros
# define LED 15 // Pin del led
// Constantes globales
// Variables globales
bool encendido = false;
// Configuración
void setup() {
pinMode(LED, OUTPUT);
// Para depuración
Serial.begin(115200);
Serial.println("ESP32 funcionando!");
}
// --------------
void loop() {
// Escribimos
digitalWrite(LED, encendido? HIGH: LOW);
// Negamos el booleano
encendido = !encendido;
// Depuración
String msg = encendido? "Está encendido": "Está apagado";
Serial.println( msg );
delay(1000); // pausa
}
Mejora de depuración
Y mejorando el proceso mediante macros o funciones://------------------------
// Macros
//------------------------
#define DEBUGMSG(X) \
Serial.print("DEBUG: "); \
Serial.print(__PRETTY_FUNCTION__); \
Serial.print(' '); \
Serial.print(__FILE__); \
Serial.print(':'); \
Serial.print(__LINE__); \
Serial.print(' '); \
Serial.println(X);
//------------------------
// Constantes del sistema
//------------------------
// Pin a utiliar
// Pasos de incremento y periodo total
//------------------------
// Variables globales
//------------------------
// Tiempo up
// ¿Cada vez más tiempo en Up?
//------------------------
// Configuración del sistema
//------------------------
void setup() {
// Pin de salida
// Config puerto (hay versión ampliada)
Serial.begin(9600);
// Esperar a que esté disponible. El puerto es lento, mejor esperarlo si es algo crítico.
while (!Serial) {
; // wait for serial port to connect. Needed for native USB
}
}
//------------------------
// Programa principal
//------------------------
// A lo main() de Java, pero dentro de un bucle infinito
void loop() {
/* En cada ciclo debe estar un tiempo apagado y otro encendido. Dichos tiempos deben sumar el periodo.
Además, en cada ciclo, el tiempo encendido deber ir incrementándose hasta llegar al periodo, y luego irá decrementando, hasta llegar al paso mínimo.
Para facilitar el desarrollo, puedes emplear la función de depuración
debugger(__PRETTY_FUNCTION__, __FILE__, __LINE__, tiempoUp) o la macro DEBUGMSG(X)
*/
}
//------------------------
// Funciones
//------------------------
// Mensajes de depuración con funciones
void debugger(String FUNCION, String FILE, int LINEA, int VALOR){
Serial.print("DEBUG: ");
Serial.print(FUNCION);
Serial.print(' ');
Serial.print(FILE);
Serial.print(':');
Serial.print(LINEA);
Serial.print(' ');
Serial.println(VALOR);
}
Comunicaciones Dev-to-Dev¶
Emplearemos el Transmisor-Receptor Asíncrono Universal -UART- para comunicar dos dispositivos.
El ejercicio consistirá en crear un sistema de telegrafo:
- 2 nodos iguales
- Cada nodo:
- 1 esp32
- 1 protoboard
- 1 led que encenderá cuando reciba la pulsación del vecino
- 1 switch que enviará al vecino el mensaje “on” u “off” cuando haya algún cambio
- Gestión de interrupciones
Config¶
Requerimos la librería #include <HardwareSerial.h> (antes de setup()) que nos simplifica la gestión del puerto, y lo configuraremos (en setup() con SerialPort.begin (BaudRate, SerialMode, RX_pin, TX_pin)
Para la comunicación, consulta UART - Luis Llamas.
Comunicaciones Wifi directo¶
Info: ESP Now - Luis Llamas
Repetir el programa telegrafo con el protocolo ESP-Now (sin cables)
Vamos a realizar una conexión punto-multipunto de transmisión de mensajes... tipo radio.
Main¶
Firware del emisor
/*
Original: ESP-NOW Broadcast Master de Lucas Saavedra Vaz - 2024
Adaptación: Luis Ferreira Gordillo
This sketch demonstrates how to broadcast messages to all devices within the ESP-NOW network.
This example is intended to be used with the ESP-NOW Broadcast Slave example.
The master device will broadcast a message every 5 seconds to all devices within the network.
This will be done using by registering a peer object with the broadcast address.
The slave devices will receive the broadcasted messages and print them to the Serial Monitor.
*/
#include "ESP32_NOW.h"
#include "WiFi.h"
#include <esp_mac.h> // For the MAC2STR and MACSTR macros
/* Definitions */
#define ESPNOW_WIFI_CHANNEL 6
/* Classes */
// Creating a new class that inherits from the ESP_NOW_Peer class is required.
class ESP_NOW_Broadcast_Peer : public ESP_NOW_Peer {
public:
// Constructor of the class using the broadcast address
ESP_NOW_Broadcast_Peer(uint8_t channel, wifi_interface_t iface, const uint8_t *lmk) : ESP_NOW_Peer(ESP_NOW.BROADCAST_ADDR, channel, iface, lmk) {}
// Destructor of the class
~ESP_NOW_Broadcast_Peer() {
remove();
}
// Function to properly initialize the ESP-NOW and register the broadcast peer
bool begin() {
if (!ESP_NOW.begin() || !add()) {
log_e("Failed to initialize ESP-NOW or register the broadcast peer");
return false;
}
return true;
}
// Function to send a message to all devices within the network
bool send_message(const uint8_t *data, size_t len) {
if (!send(data, len)) {
log_e("Failed to broadcast message");
return false;
}
return true;
}
};
/* Global Variables */
uint32_t msg_count = 0;
// Create a broadcast peer object
ESP_NOW_Broadcast_Peer broadcast_peer(ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, nullptr);
/* Main */
void setup() {
Serial.begin(115200);
// Initialize the Wi-Fi module
WiFi.mode(WIFI_STA);
WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
while (!WiFi.STA.started()) {
delay(100);
}
Serial.println("ESP-NOW Example - Broadcast Master");
Serial.println("Wi-Fi parameters:");
Serial.println(" Mode: STA");
Serial.println(" MAC Address: " + WiFi.macAddress());
Serial.printf(" Channel: %d\n", ESPNOW_WIFI_CHANNEL);
// Register the broadcast peer
if (!broadcast_peer.begin()) {
Serial.println("Failed to initialize broadcast peer");
Serial.println("Reebooting in 5 seconds...");
delay(5000);
ESP.restart();
}
Serial.printf("ESP-NOW version: %d, max data length: %d\n", ESP_NOW.getVersion(), ESP_NOW.getMaxDataLen());
Serial.println("Setup complete. Broadcasting messages every 5 seconds.");
}
void loop() {
// Broadcast a message to all devices within the network
char data[32]; // Buffer
int i = 0; // Inicializo contador de letras
while (Serial.available() > 0 && i < 32) {
// Mientras que haya datos que leer y el buffer no se haya llenado leemos un carácter
data[i++] = (char) Serial.read();
}
if (i > 0){
// Si leímos algún caracter incrementamos el contador de mensaje
msg_count++;
Serial.printf("Broadcasting message: %s\n", data);
// Mandamos el mensaje
if (!broadcast_peer.send_message((uint8_t *)data, sizeof(data))) {
Serial.println("Failed to broadcast message");
}
data[0] = '\0'; // Borrramos el buffer
}
delay(1000);
}
Nota: En C, el carácter \0 (null) es el indicativo de que hemos llegado al final de la cadena de caracteres, por lo que si lo asignamos en la posición 0 indicamos que la cadena está vacía.
Hay otras formas de hacerlo más elegantes como strcpy(data, "");, pero C me permite tomar este tipo de atajos.
Secondary¶
Firware de los receptores
/*
ESP-NOW Broadcast Slave
Lucas Saavedra Vaz - 2024
This sketch demonstrates how to receive broadcast messages from a master device using the ESP-NOW protocol.
The master device will broadcast a message every 5 seconds to all devices within the network.
The slave devices will receive the broadcasted messages. If they are not from a known master, they will be registered as a new master
using a callback function.
*/
#include "ESP32_NOW.h"
#include "WiFi.h"
#include <esp_mac.h> // For the MAC2STR and MACSTR macros
#include <vector>
/* Definitions */
#define ESPNOW_WIFI_CHANNEL 6
/* Classes */
// Creating a new class that inherits from the ESP_NOW_Peer class is required.
class ESP_NOW_Peer_Class : public ESP_NOW_Peer {
public:
// Constructor of the class
ESP_NOW_Peer_Class(const uint8_t *mac_addr, uint8_t channel, wifi_interface_t iface, const uint8_t *lmk) : ESP_NOW_Peer(mac_addr, channel, iface, lmk) {}
// Destructor of the class
~ESP_NOW_Peer_Class() {}
// Function to register the master peer
bool add_peer() {
if (!add()) {
log_e("Failed to register the broadcast peer");
return false;
}
return true;
}
// Function to print the received messages from the master
void onReceive(const uint8_t *data, size_t len, bool broadcast) {
Serial.printf("Received a message from master " MACSTR " (%s)\n", MAC2STR(addr()), broadcast ? "broadcast" : "unicast");
Serial.printf(" Message: %s\n", (char *)data);
}
};
/* Global Variables */
// List of all the masters. It will be populated when a new master is registered
// Note: Using pointers instead of objects to prevent dangling pointers when the vector reallocates
std::vector<ESP_NOW_Peer_Class *> masters;
/* Callbacks */
// Callback called when an unknown peer sends a message
void register_new_master(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
if (memcmp(info->des_addr, ESP_NOW.BROADCAST_ADDR, 6) == 0) {
Serial.printf("Unknown peer " MACSTR " sent a broadcast message\n", MAC2STR(info->src_addr));
Serial.println("Registering the peer as a master");
ESP_NOW_Peer_Class *new_master = new ESP_NOW_Peer_Class(info->src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, nullptr);
if (!new_master->add_peer()) {
Serial.println("Failed to register the new master");
delete new_master;
return;
}
masters.push_back(new_master);
Serial.printf("Successfully registered master " MACSTR " (total masters: %zu)\n", MAC2STR(new_master->addr()), masters.size());
} else {
// The slave will only receive broadcast messages
log_v("Received a unicast message from " MACSTR, MAC2STR(info->src_addr));
log_v("Igorning the message");
}
}
/* Main */
void setup() {
Serial.begin(115200);
// Initialize the Wi-Fi module
WiFi.mode(WIFI_STA);
WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
while (!WiFi.STA.started()) {
delay(100);
}
Serial.println("ESP-NOW Example - Broadcast Slave");
Serial.println("Wi-Fi parameters:");
Serial.println(" Mode: STA");
Serial.println(" MAC Address: " + WiFi.macAddress());
Serial.printf(" Channel: %d\n", ESPNOW_WIFI_CHANNEL);
// Initialize the ESP-NOW protocol
if (!ESP_NOW.begin()) {
Serial.println("Failed to initialize ESP-NOW");
Serial.println("Reeboting in 5 seconds...");
delay(5000);
ESP.restart();
}
Serial.printf("ESP-NOW version: %d, max data length: %d\n", ESP_NOW.getVersion(), ESP_NOW.getMaxDataLen());
// Register the new peer callback
ESP_NOW.onNewPeer(register_new_master, nullptr);
Serial.println("Setup complete. Waiting for a master to broadcast a message...");
}
void loop() {
// Print debug information every 10 seconds
static unsigned long last_debug = 0;
if (millis() - last_debug > 10000) {
last_debug = millis();
Serial.printf("Registered masters: %zu\n", masters.size());
for (size_t i = 0; i < masters.size(); i++) {
if (masters[i]) {
Serial.printf(" Master %zu: " MACSTR "\n", i, MAC2STR(masters[i]->addr()));
}
}
}
delay(100);
}
Comunicaciones Dev-to-PC¶
Necesitamos un arduino y un conversor USB - UART.
Partiendo del programa blink mejorado, añadiremos que nuestro Arduino escuche en el puerto UART y pare en caso de recibir el comando “stop” y se reactive si recibe “start”.
Firmware del Arduino:¶
firware
//------------------------
// Constantes del sistema
//------------------------
// Comandos
const String STOP = String("stop");
const String START = String("start");
// Pin a utiliar
// Pasos de incremento y periodo total
//------------------------
// Variables globales
//------------------------
//------------------------
// Configuración del sistema
//------------------------
void setup() {
// Aquí config
}
}
//------------------------
// Programa principal
//------------------------
// A lo main() de Java, pero dentro de un bucle infinito
void loop() {
// Zona blink
// Zona comunicaciones
String request = "";
// Mientras haya datos disponibles leerlos y guardarlos en variable
while(Serial.available()) {
request += Serial.readString();
}
// Zona de control
if ( request == STOP ) {
habilitado = false;
DEBUGMSG("Debemos parar");
} else if ( request == START ) {
habilitado = true;
DEBUGMSG("Debemos reemprender");
} else if ( request.length() != 0 ){
DEBUGMSG("Otro COMANDO:"+request+"-"+request.length());
}
}
Programa del PC¶
En Java (síncrono)¶
Software
package uart;
import com.fazecast.jSerialComm.SerialPort;
import java.util.Arrays;
import java.util.Scanner;
public class ConexionUART{
// Puerto UART de la placa
private static final String PORT = "ttyUSB0";
public static void main(String[] args) {
ConexionUART conexionUART = new ConexionUART();
conexionUART.start();
}
private void start(){
// Capturamos los puertos UART disponibles
SerialPort[] puertos = SerialPort.getCommPorts();
System.out.println("Lista de puertos: \n" + Arrays.toString(puertos) );
// Buscamos el puerto UART deseado (el ttyUSB0)
SerialPort uart = null;
for (SerialPort puerto : puertos) {
String descripcionDelPuerto = String.format("%s, %s, %s, %s", puerto.getSystemPortName(), puerto.getPortDescription(), puerto.getSystemPortName(), puerto.getManufacturer());
System.out.println(descripcionDelPuerto);
if (puerto.getSystemPortName().equals(PORT)) {
uart = puerto;
}
}
// Si existe el puerto UART deseado, y podemos abrirlo
if ( uart != null && uart.openPort() ){
String comando;
do {
// Capturamos comando y enviamos
System.out.println("Envía comando 'start/stop' para iniciar/detener:");
Scanner sc = new Scanner(System.in);
comando = sc.nextLine();
// Salir del programa
if (comando.equals("quit")) { break; }
uart.writeBytes(comando.getBytes(), comando.length());
} while ( ! comando.equals("quit") );
uart.closePort();
}
}
}
Dependencias pom.xml:
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>[2.0.0,3.0.0)</version>
</dependency>
Arduino y UART en Java [25 min]
En Python¶
Crear entorno virtual:
- Crearlo:
python3 -m venv serial-env - Activarlo:
- En GNU/Linux:
source serial-env/bin/activate - En Windows - cmd:
venv\Scripts\activate.bat - En Windows - PS:
venv\Scripts\Activate.ps1 - Desactivarlo:
deactivate - Instalar dependencias:
python -m pip install -r requirements.txt
Protocolos IoT. MQTT¶
Como propuesta, crea una red de sensores que centralice la recepción y la pase al PC para su tratamiento posterior.
