Logo GCP con Eduardo

GCP con Eduardo

Ingesta de Datos en Tiempo Real con Python, SQL e Ignition SCADA
✦ Guía Técnica & Arquitectura

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).

Docker Compose Ignition SCADA 8.1+ PostgreSQL 15 Python 3 (psycopg2 & Faker) Named Queries Perspective Live Binding Jython Gateway Scripting

Hitos Arquitectónicos Clave

Aprovisionamiento Ágil: Stack completo containerizado con networking interno para PostgreSQL e Ignition.
Ingesta Streaming Continua: Generación y persistencia de métricas de motores (temperatura, presión y timestamp).
Seguridad con Named Queries: Parametrización estricta y aislamiento de consultas en Gateway.
Reactividad en Perspective: Enlace de componentes UI mediante sondeo controlado (polling) y analítica en servidor.

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

Antipatrón: Procesamiento Analítico en Bucles Jython sobre Datasets Extensos Recuperar miles de registros hacia Ignition y computar medias o valores máximos iterando con `dataset.getValueAt()` dentro de scripts de Gateway/Client sobrecarga el heap de la máquina virtual Java. La solución de nivel Architect consiste en delegar la agregación al motor SQL (`AVG()`, `MAX()`, `GROUP BY`) o utilizar Named Queries con SQL nativo pushdown, reduciendo el payload de red a un único registro de resultado.

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

  1. Levantar el stack: Ejecuta docker compose up -d e instala las dependencias de cliente (psycopg2, faker).
  2. Configurar Database Connection en Ignition: Accede a http://localhost:8088, ve a Config > Database > Connections y crea la conexión JDBC apuntando a PostgreSQL.
  3. Abrir Designer Launcher: Lanza el proyecto e implementa la Named Query (GetMachineData) con el parámetro machineParam de tipo String.
  4. 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.
  5. Ejecutar el simulador Python: Inicia el script stream.py y verifica la reactividad instantánea tanto en la vista web como en los logs de diagnóstico de Ignition.

Preguntas Frecuentes Técnicas

¿Por qué usar Named Queries en lugar de consultas SQL dinámicas en Ignition?

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).

¿Cuál es la ventaja de delegar agregaciones a SQL vs Jython en Ignition?

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.

¿Cómo optimizar el Polling en Perspective para interfaces con alta concurrencia?

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.

Sobre el Autor: Eduardo Martínez Agrelo
AI & Data Architect

Especialista en diseño de arquitecturas de datos de alto rendimiento, modernización de plataformas analíticas e integración end-to-end entre sistemas operacionales (OT) e infraestructuras cloud empresariales (IT).