Ingesta de Datos en Tiempo Real con Python, SQL e Ignition SCADA
En la convergencia IT/OT moderna, capturar telemetría industrial de alta frecuencia y procesarla sin cuellos de botella requiere una arquitectura desacoplada. Descubre cómo orquestar la ingesta en streaming desde emisores Python, persistir en PostgreSQL y renderizar métricas reactivas mediante Ignition SCADA (Gateway, Designer y Perspective).
Hitos Arquitectónicos Clave
Matriz de Decisión: Patrones de Ingesta y Consulta en SCADA
Al diseñar flujos de telemetría entre sistemas de planta y plataformas SCADA empresariales, la elección del mecanismo de lectura/escritura impacta directamente en la latencia, concurrencia y saturación del Gateway.
| Patrón | Latencia | Carga en Gateway | Seguridad / Gobernanza | Caso de Uso Recomendado |
|---|---|---|---|---|
| Named Query Parametrizada | Baja (< 15ms) | Mínima (Caché + Pool JDBC) | Máxima (RBAC + Tipado estricto) | Dashboards analíticos y tablas Perspective en producción. |
| Dynamic SQL via Jython | Media | Moderada (Evaluación en runtime) | Vulnerable a SQLi si no se sanea | Prototipos rápidos o consultas con JOINs dinámicos complejos. |
| OPC-UA Tag Polling | Sub-segundo | Baja a Nivel de Tags | Alta (Estándar Industrial) | Control de lazo cerrado y monitoreo directo de PLCs. |
| SQL Pushdown Directo | Muy Baja | Nula en Ignition (Carga en DB) | Gobernada por Base de Datos | Cálculo de promedios, agregaciones y ventanas temporales. |
Antipatrones Críticos en Arquitecturas OT/IT
Implementación Práctica del Pipeline
1. Infraestructura con Docker Compose
Definición del entorno con base de datos PostgreSQL e Ignition Gateway exponiendo el puerto web 8088 y el puerto de base de datos 5432:
services:
scada_db:
image: postgres:15
container_name: scada_postgres_db
environment:
POSTGRES_USER: scada_user
POSTGRES_PASSWORD: scada_password
POSTGRES_DB: scada_database
ports:
- "5432:5432"
ignition:
image: inductiveautomation/ignition:latest
container_name: ignition_scada_demo
ports:
- "8088:8088"
environment:
IGNITION_EDITION: standard
ACCEPT_IGNITION_EULA: "Y"
depends_on:
- scada_db
2. Productor de Telemetría en Tiempo Real (Python)
Script que simula mediciones periódicas (temperatura y presión) de motores industriales y las inserta con marca de tiempo precisa en PostgreSQL:
import psycopg2
import time
import random
from datetime import datetime
# Configuración de conexión relacional
conn = psycopg2.connect(
dbname="scada_database",
user="scada_user",
password="scada_password",
host="localhost",
port="5432"
)
cursor = conn.cursor()
# Creación de tabla para ingesta de sensores
cursor.execute("""
CREATE TABLE IF NOT EXISTS sensor_data (
id SERIAL PRIMARY KEY,
machine_name VARCHAR(50),
temperature FLOAT,
pressure FLOAT,
timestamp TIMESTAMP
);
""")
conn.commit()
# Simulación de transmisión en streaming continuo
machine_id = "Motor_A"
print(f"Iniciando streaming de telemetría para {machine_id}...")
try:
while True:
temp = round(random.uniform(50.0, 60.0), 2)
press = round(random.uniform(2.0, 3.5), 2)
current_time = datetime.now()
cursor.execute(
"INSERT INTO sensor_data (machine_name, temperature, pressure, timestamp) VALUES (%s, %s, %s, %s)",
(machine_id, temp, press, current_time)
)
conn.commit()
print(f"[{current_time}] Ingestado -> Temp: {temp} °C, Presión: {press} bar")
time.sleep(2)
except KeyboardInterrupt:
cursor.close()
conn.close()
print("Transmisión finalizada de forma segura.")
3. Lógica Analítica en Ignition Scripting Library
Script modular en la librería de Ignition para consumir la Named Query parametrizada, computar métricas y generar logs estructurados en el Gateway:
def analyzeMachine(machine_name):
# Ejecución de Named Query parametrizada
params = {"machineParam": machine_name}
dataset = system.db.runNamedQuery("GetMachineData", params)
if dataset.getRowCount() == 0:
return 0.0, 0.0
total_temp = 0.0
max_pressure = 0.0
for row in range(dataset.getRowCount()):
temp = dataset.getValueAt(row, "temperature")
pressure = dataset.getValueAt(row, "pressure")
total_temp += temp
if pressure > max_pressure:
max_pressure = pressure
avg_temp = total_temp / dataset.getRowCount()
# Registro en el visor de diagnósticos del Gateway
logger = system.util.getLogger("analytics")
logger.info("Análisis {} -> Temp Media: {:.2f} °C, Presión Max: {:.2f} bar".format(
machine_name, avg_temp, max_pressure
))
return avg_temp, max_pressure
Framework de Implementación Paso a Paso
- Levantar el stack: Ejecuta
docker compose up -de instala las dependencias de cliente (psycopg2,faker). - Configurar Database Connection en Ignition: Accede a
http://localhost:8088, ve a Config > Database > Connections y crea la conexión JDBC apuntando a PostgreSQL. - Abrir Designer Launcher: Lanza el proyecto e implementa la Named Query (
GetMachineData) con el parámetromachineParamde tipo String. - Diseñar la vista en Perspective: Inserta un componente Table, añade un Binding a la Named Query con sondeo periódico (Polling de 2 segundos) y retorno en formato Dataset.
- Ejecutar el simulador Python: Inicia el script
stream.pyy verifica la reactividad instantánea tanto en la vista web como en los logs de diagnóstico de Ignition.
Preguntas Frecuentes Técnicas
Las Named Queries aíslan la lógica SQL en el Gateway, previenen vulnerabilidades de inyección SQL mediante parámetros fuertemente tipados, habilitan caché centralizado y permiten asignar permisos basados en roles de usuario (RBAC).
El motor relacional (ej. PostgreSQL) procesa los datos en disco utilizando índices optimizados en C compilado, minimizando la transferencia por red y reduciendo el consumo de memoria heap en la JVM de Ignition.
Se recomienda establecer intervalos de sondeo razonables (2s a 5s) en los bindings de Perspective o implementar suscripciones por eventos y Tag Historian para no saturar el pool de conexiones JDBC del Gateway.
