01 · Punto de partida
El hola mundo del IoT
Con la plataforma elegida en el tema anterior, llega el momento de escribir código. El objetivo de este tema es modesto y revelador: un dispositivo que enciende, se conecta al Wi-Fi, informa su estado por el puerto serie y vuelve a conectarse solo si la red desaparece. No envía datos a ningún servidor todavía; eso viene con HTTP y MQTT. Primero hay que lograr que el enlace funcione de manera confiable.
Casi cualquier tutorial muestra la conexión a Wi-Fi en cinco líneas, y casi todos esos programas fallan en el mundo real. Si el router tarda en volver después de un corte de luz, el programa del tutorial espera para siempre, o prueba una vez y se queda sin red. Aprender el primer dispositivo con la mentalidad correcta evita meses de problemas después.
Los fragmentos de C++ de este tema siguen el estilo del entorno Arduino para ESP32; los conceptos valen para cualquier plataforma.
02 · Herramientas
Herramientas para programar
Para un mismo chip hay varios entornos de desarrollo. Ninguno es el mejor; cambian el nivel de control, la comodidad y el camino hacia un producto.
| Entorno | Lenguaje | Ventaja | Limitación | Cuándo conviene |
|---|---|---|---|---|
| Arduino IDE y arduino-cli | C++ con bibliotecas de Arduino | Muchísimos ejemplos y bibliotecas; arranque rápido | Poca visibilidad de la configuración interna | Aprender y construir prototipos |
| PlatformIO | C++ (Arduino o ESP-IDF) | Proyectos con archivos de configuración, varias placas, pruebas automáticas, integración con editores | Más conceptos por aprender al inicio | Proyectos que crecen y se mantienen en equipo |
| ESP-IDF (u otro SDK del fabricante) | C y C++ | Acceso a todo el chip; FreeRTOS, seguridad, particiones, actualización remota | Curva de aprendizaje mayor | Productos y optimización de energía |
| MicroPython / CircuitPython | Python | Se escribe y prueba al instante, sin compilar | Más memoria y más energía; menor velocidad | Prototipos rápidos y enseñanza |
Una trayectoria frecuente: prototipar con Arduino o MicroPython, pasar a PlatformIO cuando el proyecto tiene varios archivos y dependencias, y bajar a ESP-IDF cuando hace falta controlar la energía, las particiones o la seguridad. Lo que no cambia es el conocimiento de fondo: redes, protocolos y sensores son los mismos.
Además de las herramientas de código, tené a mano un cable USB con datos (muchos solo cargan y no transmiten), un multímetro y, para medir el consumo real, un instrumento que registre corriente. Y un control de versiones desde el primer día: un proyecto de firmware es código y debe guardarse como tal.
03 · Flujo de trabajo
El ciclo compilar, grabar y observar
Programar un microcontrolador no es ejecutar un programa: es producir un archivo binario, copiarlo a la memoria flash del dispositivo y observar qué hace. Ese ciclo se repite cientos de veces.
Tres pasos concentran los inconvenientes de los principiantes:
- Compilar. El compilador cruzado corre en tu computadora y genera código para otro procesador. Los errores de compilación son los más fáciles: el mensaje indica el archivo y la línea.
- Grabar. Una herramienta envía el binario por el puerto serie al cargador de arranque (bootloader) del chip. Si el puerto no aparece, casi siempre el cable no transmite datos o falta el controlador del conversor USB-serie. Algunas placas exigen mantener un botón (BOOT) apretado al iniciar la grabación.
- Observar. Sin pantalla ni depurador, la herramienta fundamental son los mensajes por el puerto serie. Imprimir el estado en cada paso importante («conectando», «conectado, IP …», «lectura = …») es la depuración más simple y más usada.
Más adelante vas a necesitar medir el consumo y depurar con un instrumento de hardware, pero los mensajes por el puerto serie siguen siendo la primera herramienta. Convienen con un formato regular, con marca de tiempo y nivel de importancia, para poder leerlos después de horas de funcionamiento.
04 · Primer programa
Un firmware que se conecta
Este es el esqueleto mínimo de un dispositivo conectado. Hace tres cosas: se conecta con un tiempo límite, informa qué logró y repite el ciclo de medir y reportar.
#include <WiFi.h>
#include "secretos.h" // define WIFI_SSID y WIFI_CLAVE; no se sube al repositorio
const uint32_t TIMEOUT_CONEXION_MS = 15000; // no esperar para siempre
bool conectarWifi() {
WiFi.mode(WIFI_STA); // modo estación: se conecta a un punto de acceso
WiFi.begin(WIFI_SSID, WIFI_CLAVE);
uint32_t inicio = millis();
while (WiFi.status() != WL_CONNECTED) {
if (millis() - inicio > TIMEOUT_CONEXION_MS) {
return false; // se agotó el tiempo: avisar, no colgarse
}
delay(100);
}
return true;
}
void setup() {
Serial.begin(115200);
Serial.println("Arrancando...");
if (conectarWifi()) {
Serial.print("Conectado. IP: ");
Serial.println(WiFi.localIP());
Serial.print("Señal (RSSI): ");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
} else {
Serial.println("No se pudo conectar");
}
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Sin red, reintentando...");
conectarWifi();
}
// acá irían: medir, armar el mensaje y enviarlo
delay(10000);
}
El programa tiene tres decisiones de diseño que importan. Una: la función conectarWifi devuelve un resultado en lugar de bloquear para siempre. Dos: el tiempo límite se mide con millis() y no con una cantidad fija de delay, para que no dependa de cuánto tarde cada paso. Tres: loop vuelve a verificar la conexión en cada vuelta, porque el enlace puede caerse en cualquier momento.
Éste todavía es un primer paso: reintenta pero sin estrategia, y bloquea el programa durante el intento. En el apartado de reconexión lo mejoramos.
05 · Diagnóstico
Lo que falla la primera vez
Los problemas del primer arranque repiten un patrón. Saber distinguir la causa ahorra horas.
| Síntoma | Causa probable | Qué hacer |
|---|---|---|
| La computadora no ve el puerto serie | Cable que solo carga, o falta el controlador del conversor USB-serie | Probar otro cable con datos y otro puerto; instalar el controlador |
| Falla la grabación | La placa no entró en modo de descarga | Mantener el botón BOOT al iniciar; revisar la velocidad del puerto |
| Se reinicia en bucle al activar el Wi-Fi | Fuente débil: la radio exige picos de corriente y la tensión cae («brownout») | Usar otra fuente o cable; agregar un capacitor cerca del chip |
| Nunca conecta a la red | Nombre o clave mal escritos, o red de 5 GHz (muchos chips solo usan 2,4 GHz) | Revisar mayúsculas y espacios; usar la banda de 2,4 GHz |
| Conecta, pero no hay Internet | Red con portal cautivo, filtros por dirección o sin salida | Probar con otra red o con el punto de acceso del celular |
| Funciona cerca del router y falla lejos | Señal débil o antena mal ubicada | Leer el RSSI; acercar, reorientar o cambiar de antena |
| Funciona unos minutos y se cuelga | Bucle bloqueante, pérdida de memoria o falta de tiempo para tareas internas | Evitar esperas largas; vigilar la memoria libre; usar un watchdog |
El síntoma más engañoso es el reinicio al activar el Wi-Fi. Parece un error del programa y suele ser un problema eléctrico: el prototipo funciona con el cable corto del escritorio y falla con una fuente de baja calidad. Una parte importante de los problemas del IoT son problemas de energía, no de software.
06 · Robustez
Reconectar sin castigar la batería
Imaginá un corte de luz en una oficina. Cuando vuelve, los dispositivos y el router arrancan a la vez, pero el router tarda uno o dos minutos en estar listo. ¿Qué hacen los dispositivos en ese tiempo? La respuesta define si el sistema se recupera solo o necesita que alguien vaya a reiniciarlo. Hay tres estrategias típicas:
- Un solo intento. Prueba al arrancar y, si falla, sigue sin red. Es el comportamiento del programa del tutorial: el dispositivo queda inutilizable hasta el próximo reinicio.
- Reintento fijo. Vuelve a probar cada pocos segundos. Se recupera, pero gasta energía de forma constante mientras el punto de acceso no responde: con batería es un desperdicio.
- Espera creciente (backoff exponencial). Cada fallo duplica la espera hasta un tope (por ejemplo, 2, 4, 8, 16, 32 y 60 segundos). Si la red vuelve pronto, se conecta enseguida; si tarda, gasta cada vez menos.
Hay una cuarta pieza para flotas: el retardo aleatorio o jitter. Si cien dispositivos arrancan juntos con la misma política, intentan reconectar al mismo tiempo, una y otra vez, y saturan al punto de acceso o al servidor. Sumar a cada espera un valor al azar (por ejemplo, ±20 %) dispersa los intentos. El problema se conoce como thundering herd (estampida).
En el firmware esto se expresa mejor como una máquina de estados que como esperas dentro de un bucle: el dispositivo está desconectado, conectando, conectado o en espera antes de reintentar, y en cada vuelta de loop decide qué hacer según el estado y el reloj. Así puede seguir leyendo sensores o atendiendo botones mientras espera. Es el mismo patrón que ya estudiaste en Sistemas Embebidos.
07 · Configuración
Credenciales y configuración
Un error clásico del primer proyecto es escribir el nombre y la clave del Wi-Fi directamente en el código, y subirlo a un repositorio público. Quedan expuestos en el historial para siempre. Hay tres niveles de manejo, de menos a más adecuado:
- Archivo de secretos fuera del control de versiones. Las credenciales viven en un archivo como
secretos.h, incluido en la lista de archivos ignorados del repositorio. Sirve para prototipos propios. - Configuración en memoria no volátil. El dispositivo guarda las credenciales en una zona especial de la flash (NVS en el ESP32) y las lee al arrancar. Se pueden cambiar sin recompilar.
- Aprovisionamiento. El usuario o la fábrica entregan las credenciales al equipo de forma segura (por Bluetooth, por un punto de acceso temporal con una página de configuración o por un código QR). Es la solución para productos y la estudiamos en el tema 42.
Dos reglas prácticas para cualquier nivel: nunca subir secretos al repositorio y nunca compartir el mismo secreto entre todos los equipos. Si un dispositivo se pierde o se compromete, no debe poner en riesgo al resto.
08 · Analogía con programación
Simular la reconexión en Python
Para comparar las estrategias de reconexión alcanza con modelar el tiempo. El punto de acceso vuelve a responder en router_vuelve_s segundos. Cada intento que falla mantiene la radio encendida 5 segundos a 80 mA; el que conecta, 3 segundos. La estrategia unica no reintenta; fija espera 10 segundos entre intentos; backoff duplica la espera (2, 4, 8, 16, 32) hasta un tope de 60 segundos. El programa informa a los cuántos segundos queda en línea, cuántos intentos hizo y cuánta carga gastó.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
CORRIENTE_MA = 80 # corriente media con la radio de Wi-Fi activa
FALLA_S = 5 # segundos con la radio activa en un intento que falla
EXITO_S = 3 # segundos con la radio activa en un intento que conecta
HORIZONTE_S = 1800 # se deja de intentar a los 30 minutos
ESPERAS = {"fija": [10], "backoff": [2, 4, 8, 16, 32, 60]} # segundos de espera tras cada fallo
def simular(estrategia, router_vuelve_s):
t, intentos, activo = 0, 0, 0
while t < HORIZONTE_S:
intentos += 1
if t >= router_vuelve_s: # el punto de acceso ya responde
activo += EXITO_S
return t + EXITO_S, intentos, activo * CORRIENTE_MA / 3600
activo += FALLA_S
if estrategia == "unica":
break # no vuelve a intentarlo
esperas = ESPERAS[estrategia]
espera = esperas[min(intentos - 1, len(esperas) - 1)]
t += FALLA_S + espera
return None, intentos, activo * CORRIENTE_MA / 3600
print("estrategia router vuelve en línea a los intentos energía")
for estrategia in ("unica", "fija", "backoff"):
for router in (0, 30, 120, 600):
en_linea, intentos, mah = simular(estrategia, router)
texto = "nunca" if en_linea is None else f"{en_linea} s"
print(f"{estrategia:<10} {router:>8} s {texto:>14} {intentos:>8} {mah:.2f} mAh")
Resultado con los valores iniciales: si el router ya está disponible, las tres estrategias conectan a los 3 s con 1 intento y 0,07 mAh. Si tarda 30 s, el intento único queda sin red para siempre; el reintento fijo conecta a los 33 s con 3 intentos y 0,29 mAh, y la espera creciente a los 53 s con 5 intentos y 0,51 mAh. Con 120 s: fijo, 123 s, 9 intentos y 0,96 mAh; creciente, 155 s, 7 intentos y 0,73 mAh. Con 600 s la diferencia aparece: el reintento fijo hace 41 intentos y gasta 4,51 mAh, y la espera creciente solo 14 intentos y 1,51 mAh, a cambio de conectar apenas 7 s más tarde (610 s contra 603 s).
El modelo deja dos lecciones. Una: la peor estrategia es no reintentar, porque convierte una falla momentánea en una falla permanente. Otra: la espera creciente casi no se nota cuando la red vuelve pronto y ahorra mucho cuando tarda: paga unos segundos de demora a cambio de dividir por tres el gasto.
09 · Exploración interactiva
Un arranque con el router caído, de extremo a extremo
Este laboratorio ejecuta el mismo modelo del código anterior. Elegí la estrategia, cuánto tarda en volver el punto de acceso y cuántos dispositivos arrancan a la vez. La línea de tiempo muestra la zona naranja con el punto de acceso apagado, los intentos fallidos en rojo y la conexión lograda en celeste. Los tiempos y corrientes son valores didácticos.
Estrategia de conexión
Elegí los parámetros y presioná «Simular el arranque».
Recorrido guiado
- Con los valores iniciales (espera creciente, router a los 120 s, un equipo), simulá. Queda en línea a los 155 s con 7 intentos y 0,73 mAh. Los intentos se van espaciando en la línea de tiempo.
- Cambiá a un solo intento. El equipo falla una vez y queda sin red: «Nunca». Con el router en 0 s, en cambio, conecta a los 3 s: el prototipo de escritorio funciona porque el router siempre está.
- Pasá a reintento fijo con 120 s: conecta a los 123 s con 9 intentos y 0,96 mAh, mientras que la espera creciente tardaba 155 s con 7 intentos y 0,73 mAh. Con redes que vuelven pronto ambas funcionan; la fija es más veloz, pero gasta más.
- Subí el router a 600 s. Con reintento fijo: 41 intentos y 4,51 mAh, «Gasta de más». Con espera creciente: 14 intentos y 1,51 mAh, y solo 7 s más tarde.
- Con espera creciente y router a 120 s, elegí 1000 dispositivos. El resultado cambia a «Riesgo de avalancha»: 7000 intentos en total, y en cada ronda los 1000 intentan a la vez porque tienen la misma política. Sumar un retardo aleatorio resolvería el problema.
- Con 1000 dispositivos y reintento fijo de 600 s, mirá los intentos totales: 41.000. Una flota con una política ingenua puede generar una carga enorme justo cuando la infraestructura se está recuperando.
Desafío: cuánto aporta el retardo aleatorio
Sin retardo, 1000 dispositivos con espera creciente intentan en los mismos instantes: 0, 7, 16, 29, 50, 87 y 152 s (en el modelo), con 1000 intentos simultáneos en cada ronda. Si a cada espera se le suma un valor aleatorio de hasta el 20 %, los intentos de cada ronda se reparten en una ventana: la ronda de la espera de 32 s, por ejemplo, se extiende unos 6 s, y los 1000 intentos pasan de ocurrir en un instante a repartirse a razón de unos 160 por segundo. El jitter no cambia el consumo medio de cada equipo, pero cambia la carga máxima que ve la infraestructura. Es una de las técnicas más baratas y efectivas en IoT.
10 · Método de trabajo
Lista de verificación del primer dispositivo
Antes de dar por terminado el «hola mundo», comprobá cada punto. Con ellos el prototipo se parece a un producto:
- Arranca solo y llega al estado de operación sin intervención.
- Informa su estado por el puerto serie con un formato regular (marca de tiempo, nivel, mensaje).
- La conexión tiene tiempo límite y el programa nunca queda esperando para siempre.
- Reintenta con espera creciente y sin bloquear el resto del programa.
- Sobrevive al corte de la red y se recupera sin reiniciar el equipo. Probalo apagando el router.
- Sobrevive al corte de la luz y retoma al encender, con la configuración guardada.
- Registra la señal (RSSI) y el motivo de cada fallo.
- No lleva secretos en el código ni en el repositorio.
Una prueba sencilla y reveladora: apagá el router un par de minutos, volvelo a encender y observá el monitor serie. Si el dispositivo no se recupera solo, aún no está listo para ser desplegado.
11 · Comprensión
Confusiones frecuentes
«Si conecta una vez, ya funciona»
Conectar una vez con todo a favor demuestra poco. El valor de un dispositivo está en lo que hace cuando el router se reinicia, la señal fluctúa o la fuente se debilita. Esos escenarios se prueban a propósito.
«Reintentar más rápido es mejor»
Reintentar sin pausa gasta batería, satura la red y no acelera nada, porque el problema no está en el dispositivo. La espera creciente se conecta casi igual de rápido cuando la red vuelve pronto y cuesta mucho menos cuando tarda.
«El delay es una buena forma de esperar»
Mientras dura un delay, el programa no hace nada más: no lee sensores, no atiende botones ni responde. Para esperas largas se usa el reloj (millis()) o una máquina de estados, o se duerme el procesador a propósito.
«Los errores de conexión son culpa de la red»
A veces, pero también pueden ser de la fuente, de la antena, de la clave, de la banda de frecuencia o del propio programa. Registrar el motivo del fallo y la señal es lo que permite distinguirlos.
«Poner la clave en el código es práctico y no pasa nada»
En cuanto el código se comparte, se sube a un repositorio o se publica un binario, la clave queda expuesta. Y si el binario se copia a cientos de equipos con la misma clave, comprometer uno compromete todos.
«MicroPython es solo para juguetes»
Es una herramienta excelente para prototipos y para aprender, y algunos productos lo usan. Su límite es el consumo y la memoria; para un sensor con batería de años suele ser mejor C o C++. Se elige por requisitos, no por prejuicio.
12 · Práctica guiada
Predecí antes de comprobar
1. Elegir el entorno
Proponé un entorno para: (a) un taller de un día para estudiantes que nunca programaron un microcontrolador; (b) un producto con cuatro desarrolladores, pruebas automáticas y actualización remota; (c) un sensor que debe durar años con batería y exige optimizar cada microamperio.
Ver solución
(a) Arduino IDE o MicroPython: se arranca en minutos. (b) PlatformIO o ESP-IDF, con control de versiones y pruebas automáticas. (c) ESP-IDF (o el SDK del fabricante), porque permite manejar los modos de sueño y la radio al detalle.
2. Diagnóstico
El dispositivo se reinicia una y otra vez justo cuando enciende el Wi-Fi, pero funciona bien sin la radio. ¿Cuál es la causa más probable y cómo la verificarías?
Ver solución
Lo más probable es un problema de alimentación: al activar la radio la corriente tiene picos, la tensión cae por debajo del mínimo y el chip se reinicia (brownout). Se verifica probando con otra fuente o un cable más corto y grueso, midiendo la tensión con un osciloscopio y, como solución, agregando un capacitor cerca del chip.
3. Un solo intento
Un sensor intenta conectar una vez al arrancar y, si falla, sigue funcionando sin red. Hay un corte de luz y el router tarda 90 segundos en volver. ¿Qué pasa con el sensor?
Ver solución
El sensor arranca junto con el router, falla su único intento y queda sin red de forma indefinida, aunque el router vuelva a los 90 segundos. Hasta que alguien lo reinicie no reporta nada. La solución es reintentar con espera creciente mientras no haya conexión.
4. Cuenta de energía
Con la radio a 80 mA, ¿cuánta carga gasta un intento que falla (5 s) y uno que conecta (3 s)? ¿Y 41 intentos fallidos más uno exitoso?
Ver solución
Fallido: 80 × 5 / 3600 ≈ 0,111 mAh. Exitoso: 80 × 3 / 3600 ≈ 0,067 mAh. En el modelo, el reintento fijo con el router a 600 s hace 40 intentos fallidos y 1 exitoso: 40 × 0,111 + 0,067 ≈ 4,51 mAh, que coincide con el laboratorio.
5. Calcular la espera
Una política de espera creciente empieza en 2 s, duplica en cada fallo y tiene un tope de 60 s. Escribí las primeras siete esperas.
Ver solución
2, 4, 8, 16, 32, 60 y 60 segundos. La sexta espera sería 64 s, pero el tope la limita a 60. Desde ahí, el dispositivo reintenta una vez por minuto (más los 5 segundos del propio intento).
6. La avalancha
Cinco mil dispositivos de una misma tanda reintentan con la misma política tras una caída del servidor. ¿Qué problema aparece y cómo se reduce?
Ver solución
Todos reintentan en los mismos instantes, así que el servidor, apenas se recupera, recibe cinco mil conexiones simultáneas y puede caerse de nuevo. Se reduce con retardo aleatorio (jitter) en cada espera, con tiempos de arranque escalonados y con un tope razonable en el ritmo de reintentos.
13 · Síntesis
Ideas para recordar
- El «hola mundo» del IoT es un dispositivo que arranca, se conecta, informa su estado y se recupera solo si la red falla.
- Hay varios entornos (Arduino, PlatformIO, ESP-IDF, MicroPython); se elige según el nivel de control, la comodidad y el camino al producto.
- El trabajo diario es un ciclo de compilar, grabar por USB y observar, con el monitor serie como herramienta principal de depuración.
- Una conexión se hace con tiempo límite, devolviendo un resultado, y se vuelve a verificar en cada vuelta del programa.
- Muchos fallos del primer arranque son eléctricos o de configuración (cable sin datos, fuente débil, banda de 5 GHz, clave mal escrita), no del código.
- Un solo intento convierte una falla momentánea en permanente; el reintento fijo gasta energía de más; la espera creciente cuesta pocos segundos y ahorra mucho.
- El retardo aleatorio evita que una flota reintente toda a la vez y sature la infraestructura que se está recuperando.
- Las credenciales no van en el código ni en el repositorio, y cada equipo debe tener las suyas.
- Un dispositivo no está terminado hasta que sobrevive a apagar y encender el router y la fuente.
¿Listo para el tema 6?
Antes de avanzar, armá el primer dispositivo y probá el escenario de cortes: encendelo, apagá el router, esperá un par de minutos, volvelo a encender y registrá cuánto tardó en recuperarse y cuántos intentos hizo. Si se recupera solo y con pocos intentos, tenés una base sólida.
En el próximo tema cerramos el bloque con los requisitos de un producto IoT: alcance, autonomía, costo, seguridad y mantenimiento, y cómo convertirlos en criterios medibles para toda la vida útil del dispositivo.