GCU Sentinel
por Kalev Pineda ·

Monitoreo térmico autónomo de la unidad central de control (GCU) en aerogeneradores
GCU Sentinel es una plataforma de inspección termográfica continua para la GCU (unidad central de control del generador): los paneles y gabinetes de control ubicados en el pie de la torre del aerogenerador. Una cámara térmica montada sobre un servo recorre ambos paneles, detecta puntos calientes en tiempo real, se centra sobre la anomalía, captura evidencia radiométrica y la envía a la nube, donde un dashboard permite analizarla, visualizarla en 3D y apoyarse en IA para el diagnóstico.
El sistema se construye sobre HeatStream: el servicio en C++ para Raspberry Pi que lee el buffer crudo de la cámara térmica y publica la matriz de temperaturas por ZeroMQ PUB/SUB. GCU Sentinel es uno de los suscriptores de ese flujo: lo consume para detectar y alertar sobre puntos calientes.
Problemática
La GCU concentra, en gabinetes eléctricos en el pie de la torre, los elementos que gobiernan y protegen la turbina: controlador (PLC), fuentes de alimentación, interruptores, contactores, relevadores, borneras, cableado de potencia y de control, y el sistema de ventilación del gabinete. Es un punto crítico: si la GCU falla, la turbina se detiene, aunque el rotor, el generador y la red estén en perfecto estado.
Por qué es un punto débil:
- Carga cíclica y variable. La potencia de un aerogenerador cambia con el viento minuto a minuto. Conductores, contactores y borneras pasan por ciclos térmicos constantes que dilatan y contraen las conexiones y, con el tiempo, las aflojan.
- Una conexión floja es un punto caliente. Un borne mal apretado, un contacto desgastado o un terminal oxidado aumentan la resistencia de contacto. Más resistencia produce más calor, y más calor acelera la oxidación: un ciclo que termina en arco eléctrico, conductores quemados o incendio del gabinete.
- Enfriamiento deficiente. Ventiladores dañados o filtros saturados de polvo elevan la temperatura interna de todo el gabinete y reducen la vida útil de fuentes, electrónica y aislamientos.
- Las fallas avisan antes de ocurrir. Casi todas empiezan como zonas con temperatura anómala, detectables por termografía mucho antes de un disparo o una falla catastrófica. Por eso la termografía es una práctica estándar de mantenimiento predictivo de tableros eléctricos (NFPA 70B).
- Ubicación de difícil acceso. Los parques están en zonas remotas; la inspección termográfica manual es periódica, cara y solo captura un instante. Un punto caliente que aparece entre dos inspecciones pasa desapercibido.
- Tiempo muerto costoso. Cada falla de la GCU implica turbina detenida, traslado de personal técnico y pérdida de generación.
Objetivo del proyecto: sustituir la inspección puntual por vigilancia térmica continua, autónoma y de bajo costo de los paneles de la GCU, que detecte el punto caliente cuando aparece, lo documente con datos radiométricos reales (no solo una foto) y lo notifique de inmediato.
Repositorios que componen el sistema
| Repositorio | Rol en el sistema | Tecnología |
|---|---|---|
HeatStream (thermal_daemon) | Adquisición: lee la cámara térmica y publica la matriz °C por ZeroMQ | C++17, libuvc, ZeroMQ |
gsu-sentinel-core | Orquestador en el borde: consume el stream térmico, detecta puntos calientes, dirige la cámara, captura evidencia y la sube a la nube | Rust, Tokio, ZeroMQ, MQTT |
gnu_camera_control | Control de movimiento: firmware de la ESP32 que estabiliza el servo SG90 con un PID propio y ejecuta el paneo entre GCU | C, ESP-IDF, FreeRTOS, MQTT, Julia (simulación) |
gnu_sentinel_cloud | Backend en la nube: recibe telemetría y evidencias, guarda capturas .npz, expone la API y distribuye la configuración remota | Rust, Axum |
gnu_dashboard | Centro de mando web: estado en vivo, análisis de capturas (2D, 3D, IA), descargas y configuración | React, TypeScript, Vite, Three.js, Recharts, Gemini |
Algunos repositorios conservan en su nombre los prefijos
gsu/gnude versiones anteriores del proyecto; todos forman parte de GCU Sentinel.
Arquitectura general
Principio de diseño: cada capa está desacoplada por un protocolo estándar.
- ZeroMQ PUB/SUB separa la adquisición del análisis. ThermaCast no sabe quién consume los datos, y el Core podría reemplazarse o duplicarse sin tocar la captura.
- MQTT separa la decisión (Core) de la actuación (ESP32).
- HTTP separa el borde de la nube. Si se pierde internet, el borde sigue inspeccionando.
Flujo de funcionamiento
Máquina de estados del orquestador (gsu-sentinel-core)
-
Patrullaje (
Panning). El Core mueve la cámara de un panel al otro en pasos depan_step_degrees, publicando el setpoint por MQTT cada ≥50 ms. En cada extremo esperascan_wait_time_sec(IdleAtEndpoint). -
Detección. Con cada frame recibido por ZeroMQ (~25 FPS) calcula la temperatura máxima de la matriz. Si supera
max_temp_trigger, pasa aTracking. -
Seguimiento visual (
Tracking). Localiza la columna del píxel más caliente y calcula cuánto debe girar la cámara para centrarlo:rust// 4.5 px por grado: calibración del campo de visión de la cámara correction = -(hotspot_x - width/2) / PIXELS_PER_DEGREE; angle += correction * 0.4; // ganancia < 1 para no oscilarEs un lazo de visual servoing: la cámara térmica funciona como sensor de posición del objetivo.
-
Captura de evidencia (
CollectingData). Con el punto caliente centrado, acumula 3 s de frames, guarda el frame central como matriz radiométrica.npz(float32 en °C, no una imagen) y la sube a la nube con el ángulo y el ID de turbina. -
Telemetría y configuración remota. Cada segundo envía un heartbeat con modo, ángulo y temperatura máxima. La nube responde con la configuración vigente (umbral, tiempos, paso, ON/OFF), así que un cambio hecho en el dashboard llega al borde en ≤1 s sin abrir puertos entrantes en la turbina.
Secuencia de una alerta
Ingeniería inversa
El SG90 es un micro-servo de juguete: engranes de nylon, un potenciómetro de carbón y un chip de control analógico. Tiene una sola entrada (el ancho de pulso PWM) y ninguna salida: el microcontrolador ordena una posición y supone que el servo llegó. Para apuntar una cámara térmica a un transformador a varios metros, ese supuesto no alcanza.
1. Diagnóstico: por qué falla la implementación común
La forma habitual de usar un SG90 (la primera versión del firmware, en el README del repo) es mandar el PWM del ángulo deseado y esperar:
set_servo_pwm(ANGLE_LEFT_GSU); // "ve a 125°"
vTaskDelay(pdMS_TO_TICKS(SCAN_INTERVAL_MS)); // ...y confíaCon la cámara montada, este lazo abierto presenta cuatro problemas:
| Problema | Origen físico | Efecto en la inspección |
|---|---|---|
| Banda muerta | El chip interno ignora errores pequeños (~1–2°) para no vibrar | La cámara se detiene cerca del objetivo, no en él |
| Error estacionario bajo carga | El lazo interno es solo proporcional; el peso y el cable USB de la cámara ejercen un par constante | Desviación sistemática que cambia según hacia dónde gire |
| Sobreimpulso | Ganancia interna alta y poca amortiguación | La cámara se pasa del objetivo y regresa, y las primeras capturas salen movidas |
| Holgura de engranes (backlash) | Juego mecánico entre engranes de nylon | La posición final depende de la dirección de llegada |
El sistema no tiene forma de enterarse de nada de esto, porque no mide la posición real.
2. El hack de hardware: dar al servo un sentido del tacto
Dentro del SG90, el potenciómetro acoplado al eje de salida es el sensor de posición que usa el chip interno. La modificación consistió en:
- Abrir el servo y localizar las tres terminales del potenciómetro.
- Soldar un cable al cursor (wiper), la terminal central, cuyo voltaje es proporcional al ángulo real del eje.
- Llevar esa señal a la ESP32, al GPIO 34 (ADC1_CH6), configurado a 12 bits (0–4095) con atenuación de 11 dB para cubrir el rango de voltaje.
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
float read_angle_sensor() {
int raw = adc1_get_raw(ADC1_CHANNEL_6);
return ((float)raw / 4095.0f) * 180.0f; // calibración lineal ADC → grados
}Con esto, un servo de lazo abierto se convierte en un actuador con retroalimentación de posición, como un servo industrial, sin cambiar de hardware.
3. Modelado antes de programar
Antes de escribir el controlador se modeló la planta en Julia (DifferentialEquations.jl), en tres niveles de detalle (carpeta sim/):
| Modelo | Archivo | Ecuación |
|---|---|---|
| Servo comercial como sistema de 2.º orden | servo2.jl | θ̈ = ωₙ²(θ_ref − θ) − 2ζωₙ θ̇ con ωₙ = 12 rad/s, ζ = 0.7 |
| Motor DC mecánico sin control interno | servo3.jl | J θ̈ + b θ̇ = Kₘ·u |
| Motor DC electromecánico completo | servo_dynamic.jl | J ω̇ = K·i − B·ω ; L i̇ = u − R·i − K·ω |
Las simulaciones sirvieron para elegir el periodo de muestreo (20 ms), comprobar que el PID digital con saturación y anti-windup converge en trayectorias multi-escalón (45° → 90° → 20° → 120°), y ver en el espacio de fases cómo cambia la trayectoria del estado.
4. El controlador: PID externo + feed-forward
El firmware final (main/main.c) cierra un segundo lazo por fuera del lazo interno del servo:
- Feed-forward: traduce directamente el ángulo deseado al pulso nominal (0° = 500 µs, 180° = 2400 µs → 10.55 µs/°). Hace el ~95 % del trabajo de inmediato.
- PID: solo corrige lo que el feed-forward no puede prever (banda muerta, carga, holgura). El término integral acumula el error residual hasta "empujar" el comando más allá de la banda muerta del chip interno, y así elimina el error estacionario que el lazo interno (solo proporcional) no puede eliminar.
- Anti-windup: la integral se limita a ±100 para que un recorrido largo (55° → 125°) no la cargue y provoque sobreimpulso al llegar.
- Tiempo real: el PID corre en una tarea de FreeRTOS con
vTaskDelayUntila exactamente 50 Hz (el mismo periodo del PWM del servo) y prioridad 5. La lógica de negocio (auto/manual) corre en otra tarea con prioridad 4, y la comunicación MQTT nunca retrasa el lazo de control.
float feed_forward = 500.0f + (target_setpoint * 10.55f);
float final_signal = feed_forward + output; // output = P + I + D
uint32_t duty = (uint32_t)((final_signal / 20000.0f) * 8191.0f); // µs → duty 13 bits5. Resultado: diagrama de fases
El diagrama de fases (posición θ contra velocidad ω) muestra la trayectoria completa del estado del servo. Un buen controlador describe una curva limpia que termina exactamente en el punto objetivo (ω = 0, θ = θ*). Uno deficiente se pasa, da vueltas alrededor del objetivo o se queda a un lado.

Simulación del ciclo de inspección 55° ↔ 125° con un modelo del SG90 que incluye banda muerta, fricción de Coulomb, par de carga de la cámara, holgura de engranes, ruido del potenciómetro y cuantización del ADC.
| Métrica (llegada al panel GCU 1, 125°) | Implementación común | Controlador propio |
|---|---|---|
| Error estacionario medio | 1.07° | 0.30° |
| Sobreimpulso | 2.32° | 0.31° |
Cómo leer la figura:
- Panel A (común): la trayectoria se pasa del objetivo (el lazo se abre más allá de 125°) y vuelve para quedarse a un lado del punto objetivo, dentro de la banda muerta. Ahí se queda: nada lo corrige.
- Panel B (controlador propio): la llegada tiene un sobreimpulso mínimo, y el término integral hace pequeñas correcciones finales que llevan la trayectoria hasta el punto objetivo.
- Panel C: en el tiempo, la curva común se asienta visiblemente por debajo del setpoint; la del controlador queda encima de él.
¿Por qué importa 1° en este proyecto? Con la calibración de la cámara (4.5 px/°), 1° de error equivale a ~4–5 píxeles térmicos: suficiente para que un borne caliente quede fuera del centro, que el lazo de seguimiento visual del Core oscile, o que dos capturas del mismo GCU no sean comparables entre sí.
Simulaciones originales del repositorio (Julia):
Servo sin lazo externo (servo2.jl) | PID multi-escalón (servo3.jl) |
|---|---|
![]() | ![]() |
Protocolos e interfaces
ZeroMQ — ThermaCast → Core (tcp://127.0.0.1:5555)
Mensaje multiparte en little-endian:
| Parte | Contenido |
|---|---|
| 0 · Header (≥ 20 B) | u64 timestamp · i32 filas · i32 columnas |
| 1 · Payload | float32[filas × columnas] en °C (192 × 256 × 4 = 196 608 B) |
MQTT — Core ↔ ESP32 (Mosquitto :1883)
| Tópico | Dirección | Payload | Uso |
|---|---|---|---|
gsu/control/setpoint | Core → ESP32 | "97.4" | Ángulo objetivo (paneo y seguimiento) |
gsu/control/mode | → ESP32 | auto / manual | Rutina automática o control manual |
gsu/control/manual | → ESP32 | left / right | Paso de ±5° en modo manual |
gsu/data/status | ESP32 → | 1 / 2 | Panel GCU que se está observando |
HTTP — Borde ↔ Nube (gnu_sentinel_cloud, :8080)
| Endpoint | Método | Quién lo usa | Descripción |
|---|---|---|---|
/ingest/heartbeat | POST | Core | Estado en vivo; responde con RemoteConfig |
/ingest/upload | POST multipart | Core | Sube la evidencia .npz y crea la alerta |
/api/live | GET | Dashboard | Estado en vivo (offline si no hay heartbeat en 5 s) |
/api/config | GET / POST | Dashboard | Leer / cambiar umbral, tiempos, paso y ON/OFF |
/api/alerts | GET | Dashboard | Últimas 50 alertas |
/api/files | GET | Dashboard | Capturas almacenadas |
/api/matrix/:file/:frame | GET | Dashboard | Matriz cruda + min/max para renderizar |
/api/evolution/:file | GET | Dashboard | Máx / promedio de la captura |
/api/download/:file | GET | Dashboard | Descarga forzada del .npz |
Configuración dinámica (editable desde el dashboard)
| Parámetro | Significado |
|---|---|
max_temp_trigger | Umbral de alerta (°C) |
scan_wait_time_sec | Tiempo de observación en cada panel |
pan_step_degrees | Velocidad de paneo (grados por frame) |
system_enabled | Pausa / reanuda la inspección de forma remota |
Dashboard
| Sección | Qué ofrece |
|---|---|
| Centro de Mando | Temperatura máxima en vivo, ángulo de la cámara, ID de turbina, última sincronización, modo actual |
| Análisis y Alertas | Lista de capturas con cinco vistas: resumen temporal, matriz 2D a color, escala de grises, superficie 3D (Three.js) y análisis con IA (Gemini 2.5 Flash) |
| Archivos y Exportación | Descarga de las capturas radiométricas .npz para análisis externo (NumPy, MATLAB, Julia) |
| Configuración | Umbral, tiempo de espera, velocidad angular, correo de alertas y API key |
En producción se sirve detrás de Nginx, que redirige /api al puerto 8080 y evita problemas de CORS y de mixed content.
Especificaciones de los dispositivos
Sitio de instalación
| Parámetro | Valor |
|---|---|
| Equipo monitoreado | 2 paneles de la GCU en el pie de la torre |
| Área de gabinetes (referencia) | 2 m profundidad × 2.5 m ancho × 3 m alto |
| Ubicación de la cámara | Entrada de la torre |
| Altura del soporte | 1.70 m (centro visual de los paneles) |
| Inclinación (pitch) | −5° a −8° |
| Paneo (yaw) | Panel GCU 1 (izq.) ≈ 125° · Panel GCU 2 (der.) ≈ 55° |
Hardware
| Componente | Especificación | Función |
|---|---|---|
| Cámara térmica | InfiRay Tiny1-C, 256 × 192 px, USB UVC, ~25 FPS | Sensor radiométrico |
| Computadora de borde | Raspberry Pi [modelo / RAM] | ThermaCast + Mosquitto + gsu-sentinel-core |
| Microcontrolador | ESP32 (ESP-IDF v5, FreeRTOS, Wi-Fi) | PID del servo + cliente MQTT |
| Actuador | TowerPro SG90 modificado (cable al cursor del potenciómetro) | Paneo de la cámara |
| Señal PWM | GPIO 18 · 50 Hz · LEDC 13 bits | Comando al servo |
| Feedback | GPIO 34 · ADC1_CH6 · 12 bits · 11 dB | Posición real del eje |
| Alimentación servo | 5 V (VIN), tierra común con la ESP32 | — |
| Servidor en la nube | VPS Linux + Nginx | Sentinel Cloud + Dashboard |
Software
| Capa | Stack |
|---|---|
| Adquisición | C++17 · libuvc · ZeroMQ |
| Orquestación en el borde | Rust (edition 2024) · Tokio · zeromq · rumqttc · reqwest · ndarray / ndarray-npy |
| Firmware | C · ESP-IDF v5 · FreeRTOS · esp-mqtt · LEDC · ADC1 |
| Simulación | Julia · DifferentialEquations.jl · Plots.jl |
| Nube | Rust · Axum 0.7 · tower-http · ndarray-npy |
| Frontend | React 18 · TypeScript · Vite · Three.js · Recharts · @google/genai |
Relación con HeatStream
HeatStream resolvió cómo obtener temperaturas reales de una cámara térmica sin SDK, y cómo compartirlas con cualquier software mediante PUB/SUB. GCU Sentinel es la primera aplicación completa construida sobre ese flujo:
HeatStream ──(ZeroMQ PUB/SUB)──► Sentinel Core ──► alerta de punto caliente
"¿cuánto mide cada píxel?" "¿esto es un problema?, ¿dónde?, ¿qué hago?"
Gracias al desacoplamiento, el mismo stream puede alimentar al mismo tiempo el Core, la API FastAPI de HeatStream, un visor local o un modelo de visión artificial, sin cambiar una línea del código de adquisición.
Resultado
Con componentes de bajo costo (una cámara térmica USB, una Raspberry Pi, una ESP32 y un servo de juguete estabilizado con ingeniería inversa y teoría de control), GCU Sentinel convierte la inspección termográfica de los paneles de control (GCU) de un aerogenerador en un proceso continuo, autónomo y trazable:
- detecta el punto caliente cuando aparece, no en la siguiente visita programada;
- centra la cámara sobre la anomalía por sí solo;
- guarda evidencia radiométrica (°C por píxel) en lugar de una simple fotografía;
- permite ajustar el comportamiento del sistema de forma remota desde un dashboard web con análisis 3D e IA.
gcu-sentinel-sim


