Alarma IoT con Rust, MQTT, ESPHome y Telegram
Un sistema de alarma IoT completo que integra hardware y software utilizando tecnologías modernas de código abierto.
Leer más: Alarma IoT con Rust, MQTT, ESPHome y TelegramEn el área metropolitana de Buenos Aires, el 40,6 % del agua potable que se entrega no se contabiliza. No es que se pierda toda: es que nadie sabe dónde va. Y solo el 19 % de las unidades funcionales tiene un medidor conectado, así que la mayoría de los usuarios paga una tarifa catastral, calculada por las características del inmueble y no por lo que consume. Sin medición no hay forma de detectar una fuga domiciliaria, ni de gestionar la demanda, ni de saber cuánto de ese 40 % es pérdida de red y cuánto es derroche.
Ese es el problema que tomamos para nuestro trabajo profesional de Ingeniería Electrónica en la FIUBA: diseñar, construir y validar un sistema de telemedición completo, desde el dispositivo que se instala en la caja del medidor hasta el servidor que recibe los datos. Lo defiendo el 7 de septiembre, y quería dejar escrito qué construimos y, sobre todo, qué se rompió en el camino.
El proyecto lo hicimos entre dos. Yo me ocupé del servidor central y del circuito impreso; Gonzalo Puy se ocupó del firmware, en el que también metí mano. El informe lo escribimos entre los dos.
El sistema tiene cuatro piezas. Un dispositivo IoT alimentado a batería que se instala dentro de la caja domiciliaria del medidor y se conecta al emisor de pulsos del caudalímetro: cada pulso equivale a una cantidad fija de litros. Un firmware que cuenta esos pulsos y despierta una vez por día para reportar. Una PCB propia con el microcontrolador, el módulo celular y su antena. Y un servidor central, el HES, que recibe los datos, los persiste y los deja disponibles para la empresa proveedora.
La restricción que ordena todo el diseño es que la caja del medidor no tiene electricidad. El dispositivo funciona a batería y tiene que durar más de ocho años sin que nadie lo toque, porque mandar una cuadrilla a cambiar pilas casa por casa destruye la economía del proyecto. Todo lo demás —la elección del microcontrolador, la del módem, el diseño del protocolo— sale de ahí.
El dispositivo pasa casi toda su vida dormido. Un STM32L031K6, un Cortex-M0+ con 32 KB de flash, queda en modo Stop contando pulsos por interrupción, y el módulo celular Quectel BG95-M3 está completamente apagado. Una vez por día el reloj de tiempo real lo despierta, prende el módem, se adjunta a la red, manda el reporte y vuelve a apagar todo.
Caracterizamos el consumo con un Nordic Power Profiler Kit II, midiendo por separado el dominio del microcontrolador y el del módem. Los números del ciclo completo:
Contra una batería de litio-cloruro de tionilo de 17 Ah, esos 0,3 Ah al año dejan el objetivo de ocho años cumplido con margen amplio. El resultado interesante es cuál pasa a ser el factor limitante: ya no es el consumo del dispositivo sino la autodescarga de la propia batería. Cuando el diseño llega a ese punto, seguir optimizando firmware deja de tener sentido.
Los sistemas de medición inteligente estándar hablan DLMS/COSEM, la familia de normas IEC 62056. La pila completa es grande: para un dispositivo con 32 KB de flash y 8 KB de RAM, implementarla entera no entraba, y forzarla hubiera comido el presupuesto de memoria que necesitábamos para el resto.
La decisión fue quedarnos con la semántica de la norma y no con su pila: los objetos
COSEM y el direccionamiento OBIS, sobre un protocolo binario propio y mínimo. Un dispositivo nuestro no
interopera con un concentrador DLMS comercial, pero los datos que produce y la forma de pedirlos son
conceptualmente los mismos, así que migrar a la pila completa más adelante es un cambio de transporte y no
un rediseño. La sesión periódica son siete mensajes: HANDSHAKE, READ,
WRITE y el ACK de cierre, con sus respuestas.
El HES está escrito en Rust. La pregunta razonable es por qué un prototipo de tesis necesita un clúster, y la respuesta está en el párrafo anterior sobre la batería: si el servidor se cae o se satura, los dispositivos no completan la sesión, se quedan esperando y reintentan. Cada reintento prende la radio. Un problema de disponibilidad del servidor se paga en batería en todo el parque, y esa batería no se puede reponer sin mandar una cuadrilla.
Los nodos se descubren por gossip y se vigilan con detección de fallos SWIM; cuando un nodo cae, sus dispositivos se delegan a otro. El scheduler reparte el parque en 48 buckets horarios y asigna cada dispositivo nuevo al menos cargado, de modo que las sesiones se distribuyen en el día en lugar de amontonarse. La persistencia es PostgreSQL y todo el sistema publica métricas a Prometheus, con tableros de Grafana.
Para validarlo levantamos tres nodos —uno en el servidor de desarrollo y dos en una estación local, unidos por una red privada— contra una única base de datos, y les tiramos 80 dispositivos simulados en tres oleadas. En modo de prueba el scheduler programa las sesiones a los cinco minutos en lugar del horario diario, así se observan muchos ciclos completos en poco tiempo.
Esta es la parte que más me sirvió del proyecto, y la que no aparece en ningún diagrama de arquitectura.
El protocolo estaba diseñado como pull: el HES abre la conexión hacia el dispositivo en su ventana de despertar. Es el modelo correcto, porque deja al servidor decidir cuándo habla cada medidor. Presupone una sola cosa: que el dispositivo sea alcanzable en una dirección estable.
Con una SIM comercial sobre APN público, eso no se cumple. La operadora asigna direcciones IPv6 dinámicas que cambian entre activaciones, así que la dirección que el dispositivo anuncia al registrarse ya no sirve en la siguiente ventana. Y el tráfico atraviesa un CGNAT: una conexión iniciada desde el servidor no encuentra ninguna traducción abierta y se descarta antes de llegar al dispositivo. El diseño era correcto y la red simplemente no lo permitía.
La salida fue invertir temporalmente quién inicia la sesión. El dispositivo ya mantiene
abierto un socket saliente hacia el servidor, y ese socket sí atraviesa el CGNAT dejando un
pinhole por el que el servidor puede responder. Agregamos un mensaje,
SESSION_START_REQUEST, cuya única función es avisarle al HES que dispare la sesión sobre ese
socket. El resto del protocolo y toda la máquina de estados del firmware se reutilizan sin tocar nada.
Lo que más me importó fue que no dejara deuda. Toda esa lógica está condicionada a la compilación, detrás de un flag en el firmware y de una feature en el HES: se compila sin ellos y vuelve el modelo original, intacto. La solución de producción no es de software sino comercial: una APN privada con IP fija por dispositivo y una interconexión privada con la operadora. La lección es esa: en un sistema IoT, el aprovisionamiento de red es una decisión de arquitectura y se negocia al principio del proyecto, no cuando ya tenés el prototipo andando.
El STM32L031K6 se eligió por consumo, y para la operación normal del medidor la elección fue buena: la lógica de aplicación, las máquinas de estado del módem y el protocolo entran cómodos. El límite apareció con dos funciones que el protocolo ya tenía previstas. La actualización remota necesita alojar dos imágenes de firmware a la vez más un bootloader que las valide, y eso no entra en 32 KB de banco único. La autenticación de mensajes compite por esa misma memoria, sobre un núcleo sin aceleración criptográfica.
Acá no hubo solución elegante: hubo un compromiso aceptado y documentado. Las dos funciones quedaron
pendientes, con el lugar reservado en el protocolo —la operación EXECUTE y el campo del MAC— y
un camino de resolución concreto: una segunda revisión con un STM32L4, que el margen de autonomía medido
banca sin comprometer los ocho años. La lección, cara: la memoria de un microcontrolador se
dimensiona para la funcionalidad de todo el ciclo de vida del producto, no para la de la primera
versión.
La primera implementación del receptor de mensajes era secuencial: recibía un datagrama, lo procesaba entero —validación, escritura en la base, respuesta— y recién después volvía a escuchar. Alcanzaba para las pruebas funcionales y era claramente insuficiente para una ráfaga: un despliegue masivo, o todos los dispositivos reintentando juntos después de un corte.
Mientras el bucle está ocupado escribiendo en PostgreSQL, los datagramas UDP se acumulan en el buffer del socket hasta desbordarlo, y el sistema operativo los descarta sin avisar. Cada mensaje perdido es un dispositivo que agota su ventana de espera, reintenta y vuelve a prender la radio. Ahí es donde la cosa se pone interesante: en un sistema alimentado a batería, una decisión de concurrencia del servidor se traduce en años de vida útil en el campo.
El receptor se rediseñó como despachador concurrente sobre Tokio: el bucle principal recibe y decodifica, y delega cada mensaje en una tarea independiente. Un semáforo con tope configurable da la contrapresión para que una ráfaga no agote la memoria ni las conexiones a la base. Validado con oleadas de registros concurrentes, sin pérdida de mensajes.
La PCB la diseñé en KiCad: microcontrolador, módulo celular con su adaptación de impedancia y antena, alimentación desde la batería con el supercapacitor de reservorio que absorbe los picos de 411 mA de la transmisión, y los planos de masa. Tiene que entrar en la caja domiciliaria, ser estanca y cumplir las normas de compatibilidad electromagnética.
El informe completo desarrolla todo esto con el detalle que un post no permite: la comparativa de tecnologías LPWAN, el presupuesto energético teórico, el diseño del protocolo mensaje por mensaje, el diseño del PCB y el análisis de viabilidad económica.
Descargar el informePDF, unos 14 MB
Lo que más me llevo no es ninguna de las piezas por separado, sino haber tenido que hacerlas funcionar juntas: electrónica de bajo consumo, radiofrecuencia, firmware embebido, diseño de protocolos, sistemas distribuidos y evaluación económica. Son disciplinas que en la carrera se ejercitan por separado, y el proyecto no dejaba escaparse de ninguna. Los tres problemas que conté salieron todos de la misma raíz: una decisión tomada en una de esas capas apareciendo, muchos meses después, como un síntoma en otra.
La defensa es el 7 de septiembre de 2026.
Un sistema de alarma IoT completo que integra hardware y software utilizando tecnologías modernas de código abierto.
Leer más: Alarma IoT con Rust, MQTT, ESPHome y Telegram