Ir al contenido

Sistemas SCADA - Supervisión y Control Industrial

Publicado: a las  10:00 a. m.

Sistemas SCADA - Supervisión y Control Industrial

🎯 ¿Qué es SCADA?

SCADA significa Supervisory Control And Data Acquisition (Supervisión, Control y Adquisición de Datos).

Es un sistema que permite:

Analogía simple:

Imaginate que sos el gerente de una fábrica de jugos. Sin SCADA, tendrías que:

Con SCADA:


🏭 Arquitectura de un Sistema SCADA

┌─────────────────────────────────────────────────┐
│           NIVEL 3: GESTIÓN                      │
│  (ERP, MES, Reportes, Análisis)                │
└────────────────┬────────────────────────────────┘
                 │ Ethernet
┌────────────────▼────────────────────────────────┐
│           NIVEL 2: SUPERVISIÓN                  │
│  (Servidor SCADA, HMI, Bases de Datos)         │
└────────────────┬────────────────────────────────┘
                 │ Ethernet Industrial
┌────────────────▼────────────────────────────────┐
│           NIVEL 1: CONTROL                      │
│  (PLCs, RTUs, Controladores)                   │
└────────────────┬────────────────────────────────┘
                 │ Señales I/O
┌────────────────▼────────────────────────────────┐
│           NIVEL 0: CAMPO                        │
│  (Sensores, Actuadores, Válvulas, Motores)     │
└─────────────────────────────────────────────────┘

Componentes principales:

  1. Nivel de Campo: Sensores y actuadores físicos
  2. Nivel de Control: PLCs que ejecutan la lógica
  3. Nivel de Supervisión: Servidores SCADA y HMIs
  4. Nivel de Gestión: Sistemas empresariales (ERP, MES)

🖥️ HMI - Interfaz Humano-Máquina

El HMI (Human-Machine Interface) es la pantalla donde los operadores ven y controlan el proceso.

Principios de diseño:

1. Claridad visual

2. Jerarquía de información

Pantalla Principal (Overview)
    ├── Área 1 (Detalle)
    │   ├── Tanque 1
    │   ├── Tanque 2
    │   └── Bomba 1
    ├── Área 2 (Detalle)
    └── Alarmas

3. Navegación intuitiva

Ejemplo de pantalla HMI:

┌──────────────────────────────────────────────────┐
│ PLANTA DE TRATAMIENTO DE AGUA    🔔 3 Alarmas   │
├──────────────────────────────────────────────────┤
│                                                  │
│  ┌─────────┐      ┌─────────┐      ┌─────────┐ │
│  │TANQUE 1 │──▶───│ BOMBA 1 │──▶───│TANQUE 2 │ │
│  │ 75.5%   │      │ 450 RPM │      │ 45.2%   │ │
│  │ 25°C    │      │ ON      │      │ 22°C    │ │
│  └─────────┘      └─────────┘      └─────────┘ │
│                                                  │
│  Presión: 7.2 bar    Caudal: 125 L/min         │
│                                                  │
│  [INICIO] [PARADA] [MANUAL] [AUTOMÁTICO]       │
└──────────────────────────────────────────────────┘

📊 Tags y Variables

En SCADA, todo se maneja con tags (etiquetas que representan variables).

Tipos de tags:

1. Tags de entrada (Input)

Datos que vienen del PLC o dispositivos:

TK001_Nivel         // Nivel del tanque 1
TK001_Temperatura   // Temperatura del tanque 1
BM001_Estado        // Estado de la bomba 1 (ON/OFF)
BM001_Velocidad     // Velocidad de la bomba 1

2. Tags de salida (Output)

Comandos que se envían al PLC:

BM001_CMD_Start     // Comando para arrancar bomba 1
VLV001_CMD_Abrir    // Comando para abrir válvula 1

3. Tags internos (Memory)

Variables calculadas o auxiliares:

TK001_Volumen       // Volumen calculado del tanque
Produccion_Diaria   // Producción acumulada del día

Convención de nombres:

[EQUIPO]_[VARIABLE]_[TIPO]

Ejemplos:
TK001_Nivel_PV      // Process Value (valor actual)
TK001_Nivel_SP      // SetPoint (valor deseado)
TK001_Nivel_HH      // High-High alarm
TK001_Nivel_LL      // Low-Low alarm
BM001_Estado_FB     // Feedback (confirmación)

🔗 Comunicación con PLCs

Los sistemas SCADA se comunican con PLCs usando protocolos industriales.

Protocolos comunes:

ProtocoloTipoVelocidadUso
Modbus TCP/IPEthernetRápidoMás usado, simple
Modbus RTUSerialLentoEquipos antiguos
OPC UAEthernetRápidoEstándar moderno
ProfinetEthernetMuy rápidoSiemens
EtherNet/IPEthernetMuy rápidoAllen-Bradley

Ejemplo: Modbus TCP

# Pseudocódigo de lectura Modbus
from pymodbus.client import ModbusTcpClient

# Conectar al PLC
client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()

# Leer registros (holding registers)
# Dirección 40001: Nivel del tanque
# Dirección 40002: Temperatura
result = client.read_holding_registers(address=0, count=2, unit=1)

if not result.isError():
    nivel = result.registers[0] / 10.0  # Escalar a %
    temperatura = result.registers[1] / 10.0  # Escalar a °C

    print(f"Nivel: {nivel}%")
    print(f"Temperatura: {temperatura}°C")

# Escribir comando (arrancar bomba)
# Coil 00001: Comando bomba
client.write_coil(address=0, value=True, unit=1)

client.close()

Una función clave de SCADA es graficar datos en el tiempo.

Tipos de gráficos:

Temperatura (°C)
100 ┤                                    ╭─╮
 90 ┤                              ╭─────╯ ╰─
 80 ┤                        ╭─────╯
 70 ┤                  ╭─────╯
 60 ┤            ╭─────╯
 50 ┤      ╭─────╯
 40 ┤╭─────╯
    └┴─────┴─────┴─────┴─────┴─────┴─────┴──▶
    10:00 10:10 10:20 10:30 10:40 10:50 11:00

Permite analizar datos pasados:

// Ejemplo de configuración
{
    "tag": "TK001_Nivel",
    "timeRange": "24h",
    "sampleRate": "1min",
    "yAxis": {
        "min": 0,
        "max": 100,
        "unit": "%"
    },
    "alarms": {
        "high": 90,
        "low": 10
    }
}

🚨 Sistema de Alarmas

Las alarmas notifican condiciones anormales que requieren atención.

Prioridades de alarmas:

PrioridadColorSonidoDescripción
Crítica🔴 RojoContinuoPeligro inmediato
Alta🟠 NaranjaIntermitenteRequiere acción urgente
Media🟡 AmarilloUn beepAdvertencia
Baja🔵 AzulSilencioInformación

Ejemplo de configuración:

Alarma: TK001_Nivel_Alto
├── Condición: TK001_Nivel > 90%
├── Prioridad: Alta
├── Mensaje: "Nivel alto en Tanque 1"
├── Acción: Cerrar válvula de entrada
└── Notificación: Email + SMS

Alarma: TK001_Nivel_Bajo
├── Condición: TK001_Nivel < 10%
├── Prioridad: Crítica
├── Mensaje: "Nivel crítico en Tanque 1"
├── Acción: Detener bomba de salida
└── Notificación: Email + SMS + Llamada

Estados de alarmas:

NORMAL → ACTIVA → RECONOCIDA → NORMAL
  ↓         ↓          ↓
Verde    Rojo     Amarillo

💾 Bases de Datos Industriales

SCADA almacena datos en bases de datos especializadas.

Tipos de datos almacenados:

  1. Datos de proceso: Valores de sensores
  2. Eventos: Arranques, paradas, cambios de modo
  3. Alarmas: Historial completo
  4. Producción: Contadores, totalizadores
  5. Calidad: Parámetros de producto

Ejemplo de tabla de datos:

-- Tabla de datos históricos
CREATE TABLE DatosHistoricos (
    Timestamp DATETIME,
    TagName VARCHAR(50),
    Value FLOAT,
    Quality INT,
    User VARCHAR(50)
);

-- Consulta: Temperatura promedio por hora
SELECT
    DATE_FORMAT(Timestamp, '%Y-%m-%d %H:00') AS Hora,
    AVG(Value) AS TempPromedio
FROM DatosHistoricos
WHERE TagName = 'TK001_Temperatura'
  AND Timestamp >= NOW() - INTERVAL 24 HOUR
GROUP BY Hora
ORDER BY Hora;

📊 Reportes

Los reportes transforman datos en información útil para la gestión.

Tipos de reportes:

1. Reporte de producción

REPORTE DIARIO DE PRODUCCIÓN
Fecha: 2025-03-20

┌──────────────┬──────────┬──────────┬──────────┐
│ Turno        │ Litros   │ Calidad  │ Paradas  │
├──────────────┼──────────┼──────────┼──────────┤
│ Mañana       │ 12,500   │ 98.5%    │ 2        │
│ Tarde        │ 11,800   │ 99.1%    │ 1        │
│ Noche        │ 10,200   │ 97.8%    │ 3        │
├──────────────┼──────────┼──────────┼──────────┤
│ TOTAL        │ 34,500   │ 98.5%    │ 6        │
└──────────────┴──────────┴──────────┴──────────┘

Observaciones:
- Parada en turno noche por mantenimiento preventivo
- Calidad dentro de especificaciones

2. Reporte de alarmas

RESUMEN DE ALARMAS - Última semana

┌──────────────────────────┬──────┬──────────┐
│ Alarma                   │ Cant │ Duración │
├──────────────────────────┼──────┼──────────┤
│ TK001_Nivel_Alto         │ 15   │ 2.5 h    │
│ BM001_Sobrecarga         │ 3    │ 0.3 h    │
│ VLV002_Falla_Apertura    │ 1    │ 0.1 h    │
└──────────────────────────┴──────┴──────────┘

Recomendaciones:
- Revisar control de nivel TK001
- Verificar carga de bomba BM001

🚀 Tecnologías Modernas y Educativas

En la tecnicatura también trabajamos con herramientas de vanguardia y educativas:

1. MQTT (Message Queuing Telemetry Transport)

Protocolo ligero de mensajería para IoT (Internet of Things).

2. Node-RED

Herramienta de programación visual basada en flujo.

3. Flet (Python)

Framework para construir aplicaciones web, de escritorio y móviles en tiempo real con Python.

4. Siemens LOGO! 8.2

PLC compacto ideal para educación y pequeños proyectos de automatización.


🎨 Proyecto: Sistema SCADA Simplificado

Vamos a crear un sistema SCADA básico usando Python y herramientas open-source.

Componentes:

Código del simulador de PLC:

from pymodbus.server import StartTcpServer
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.datastore import ModbusSequentialDataBlock
import random
import threading
import time

class PlantaSimulador:
    def __init__(self):
        # Registros Modbus
        self.nivel_tanque = 50.0  # %
        self.temperatura = 25.0    # °C
        self.bomba_estado = False

    def actualizar_proceso(self):
        """Simula el comportamiento de la planta"""
        while True:
            # Simular llenado/vaciado del tanque
            if self.bomba_estado:
                self.nivel_tanque += 0.5  # Llenar
            else:
                self.nivel_tanque -= 0.2  # Vaciar natural

            # Limitar nivel
            self.nivel_tanque = max(0, min(100, self.nivel_tanque))

            # Simular temperatura con ruido
            self.temperatura = 25 + random.uniform(-2, 2)

            # Actualizar registros Modbus
            context[0].setValues(3, 0, [int(self.nivel_tanque * 10)])
            context[0].setValues(3, 1, [int(self.temperatura * 10)])

            time.sleep(1)

    def leer_comandos(self):
        """Lee comandos del SCADA"""
        while True:
            # Leer coil de comando bomba
            bomba_cmd = context[0].getValues(1, 0, count=1)[0]
            self.bomba_estado = bool(bomba_cmd)
            time.sleep(0.1)

# Configurar servidor Modbus
store = ModbusSlaveContext(
    di=ModbusSequentialDataBlock(0, [0]*100),
    co=ModbusSequentialDataBlock(0, [0]*100),
    hr=ModbusSequentialDataBlock(0, [0]*100),
    ir=ModbusSequentialDataBlock(0, [0]*100)
)
context = ModbusServerContext(slaves=store, single=True)

# Iniciar simulador
planta = PlantaSimulador()
threading.Thread(target=planta.actualizar_proceso, daemon=True).start()
threading.Thread(target=planta.leer_comandos, daemon=True).start()

# Iniciar servidor Modbus
print("🏭 Simulador de PLC iniciado en puerto 502")
StartTcpServer(context, address=("0.0.0.0", 502))

Código del HMI (Tkinter):

import tkinter as tk
from tkinter import ttk
from pymodbus.client import ModbusTcpClient
import threading
import time
from datetime import datetime
import sqlite3

class SCADAApp:
    def __init__(self, root):
        self.root = root
        self.root.title("SCADA - Planta de Proceso")
        self.root.geometry("800x600")

        # Cliente Modbus
        self.client = ModbusTcpClient('localhost', port=502)
        self.client.connect()

        # Base de datos
        self.init_database()

        # Variables
        self.nivel = tk.DoubleVar(value=0)
        self.temperatura = tk.DoubleVar(value=0)
        self.bomba_estado = tk.BooleanVar(value=False)

        # Crear interfaz
        self.crear_interfaz()

        # Iniciar actualización
        threading.Thread(target=self.actualizar_datos, daemon=True).start()

    def init_database(self):
        """Inicializar base de datos"""
        self.conn = sqlite3.connect('scada_data.db', check_same_thread=False)
        self.cursor = self.conn.cursor()
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS datos_historicos (
                timestamp TEXT,
                tag TEXT,
                value REAL
            )
        ''')
        self.conn.commit()

    def crear_interfaz(self):
        """Crear elementos de la interfaz"""
        # Título
        titulo = tk.Label(self.root, text="🏭 SISTEMA SCADA",
                         font=("Arial", 24, "bold"), bg="#2c3e50", fg="white")
        titulo.pack(fill=tk.X, pady=10)

        # Frame principal
        main_frame = tk.Frame(self.root, bg="#ecf0f1")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)

        # Tanque
        tanque_frame = tk.LabelFrame(main_frame, text="TANQUE 1",
                                     font=("Arial", 14, "bold"))
        tanque_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")

        # Nivel
        tk.Label(tanque_frame, text="Nivel:", font=("Arial", 12)).pack()
        nivel_label = tk.Label(tanque_frame, textvariable=self.nivel,
                              font=("Arial", 36, "bold"), fg="#3498db")
        nivel_label.pack()
        tk.Label(tanque_frame, text="%", font=("Arial", 12)).pack()

        # Barra de progreso
        self.nivel_bar = ttk.Progressbar(tanque_frame, variable=self.nivel,
                                         maximum=100, length=200)
        self.nivel_bar.pack(pady=10)

        # Temperatura
        tk.Label(tanque_frame, text="Temperatura:", font=("Arial", 12)).pack()
        temp_label = tk.Label(tanque_frame, textvariable=self.temperatura,
                             font=("Arial", 24, "bold"), fg="#e74c3c")
        temp_label.pack()
        tk.Label(tanque_frame, text="°C", font=("Arial", 12)).pack()

        # Bomba
        bomba_frame = tk.LabelFrame(main_frame, text="BOMBA 1",
                                    font=("Arial", 14, "bold"))
        bomba_frame.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")

        # Estado bomba
        self.bomba_label = tk.Label(bomba_frame, text="⭕ APAGADA",
                                    font=("Arial", 18, "bold"), fg="#95a5a6")
        self.bomba_label.pack(pady=20)

        # Botones de control
        tk.Button(bomba_frame, text="▶ ARRANCAR", command=self.arrancar_bomba,
                 bg="#27ae60", fg="white", font=("Arial", 12, "bold"),
                 width=15, height=2).pack(pady=5)

        tk.Button(bomba_frame, text="⏹ DETENER", command=self.detener_bomba,
                 bg="#c0392b", fg="white", font=("Arial", 12, "bold"),
                 width=15, height=2).pack(pady=5)

        # Alarmas
        alarmas_frame = tk.LabelFrame(main_frame, text="ALARMAS",
                                      font=("Arial", 14, "bold"))
        alarmas_frame.grid(row=1, column=0, columnspan=2, padx=10, pady=10,
                          sticky="nsew")

        self.alarmas_text = tk.Text(alarmas_frame, height=5, font=("Courier", 10))
        self.alarmas_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # Configurar grid
        main_frame.grid_rowconfigure(0, weight=1)
        main_frame.grid_rowconfigure(1, weight=1)
        main_frame.grid_columnconfigure(0, weight=1)
        main_frame.grid_columnconfigure(1, weight=1)

    def actualizar_datos(self):
        """Leer datos del PLC continuamente"""
        while True:
            try:
                # Leer registros
                result = self.client.read_holding_registers(0, 2, unit=1)

                if not result.isError():
                    nivel = result.registers[0] / 10.0
                    temp = result.registers[1] / 10.0

                    # Actualizar variables
                    self.nivel.set(round(nivel, 1))
                    self.temperatura.set(round(temp, 1))

                    # Guardar en base de datos
                    timestamp = datetime.now().isoformat()
                    self.cursor.execute(
                        "INSERT INTO datos_historicos VALUES (?, ?, ?)",
                        (timestamp, 'TK001_Nivel', nivel)
                    )
                    self.cursor.execute(
                        "INSERT INTO datos_historicos VALUES (?, ?, ?)",
                        (timestamp, 'TK001_Temperatura', temp)
                    )
                    self.conn.commit()

                    # Verificar alarmas
                    self.verificar_alarmas(nivel, temp)

                # Leer estado bomba
                bomba_result = self.client.read_coils(0, 1, unit=1)
                if not bomba_result.isError():
                    bomba_on = bomba_result.bits[0]
                    self.bomba_estado.set(bomba_on)

                    # Actualizar indicador
                    if bomba_on:
                        self.bomba_label.config(text="🟢 ENCENDIDA", fg="#27ae60")
                    else:
                        self.bomba_label.config(text="⭕ APAGADA", fg="#95a5a6")

            except Exception as e:
                print(f"Error: {e}")

            time.sleep(1)

    def verificar_alarmas(self, nivel, temp):
        """Verificar condiciones de alarma"""
        alarmas = []

        if nivel > 90:
            alarmas.append(f"🔴 CRÍTICO: Nivel alto ({nivel}%)")
        elif nivel > 80:
            alarmas.append(f"🟡 ADVERTENCIA: Nivel alto ({nivel}%)")

        if nivel < 10:
            alarmas.append(f"🔴 CRÍTICO: Nivel bajo ({nivel}%)")
        elif nivel < 20:
            alarmas.append(f"🟡 ADVERTENCIA: Nivel bajo ({nivel}%)")

        if temp > 30:
            alarmas.append(f"🟠 ALTA: Temperatura alta ({temp}°C)")

        # Actualizar display de alarmas
        if alarmas:
            timestamp = datetime.now().strftime("%H:%M:%S")
            for alarma in alarmas:
                self.alarmas_text.insert(tk.END, f"[{timestamp}] {alarma}\n")
                self.alarmas_text.see(tk.END)

    def arrancar_bomba(self):
        """Enviar comando de arranque"""
        self.client.write_coil(0, True, unit=1)
        print("✅ Comando: Arrancar bomba")

    def detener_bomba(self):
        """Enviar comando de parada"""
        self.client.write_coil(0, False, unit=1)
        print("✅ Comando: Detener bomba")

    def on_closing(self):
        """Cerrar aplicación"""
        self.client.close()
        self.conn.close()
        self.root.destroy()

# Ejecutar aplicación
if __name__ == "__main__":
    root = tk.Tk()
    app = SCADAApp(root)
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()

🎯 Ejercicios Prácticos

🟢 Nivel Básico

Ejercicio 1: Diseñá una pantalla HMI en papel para un sistema de 3 tanques conectados en serie.

Ejercicio 2: Creá una lista de tags para un sistema de bombeo con 2 bombas redundantes.

Ejercicio 3: Definí 5 alarmas para un proceso de calentamiento con sus prioridades.

🟡 Nivel Medio

Ejercicio 4: Implementá un sistema de trending simple que guarde datos cada minuto en un archivo CSV.

Ejercicio 5: Creá un reporte diario de producción con totalizadores.

Ejercicio 6: Diseñá un sistema de navegación entre 3 pantallas HMI.

🔴 Nivel Avanzado

Ejercicio 7: Implementá comunicación Modbus TCP con un PLC real o simulador.

Ejercicio 8: Creá un sistema de alarmas con reconocimiento y registro en base de datos.

Ejercicio 9: Desarrollá un dashboard web con gráficos en tiempo real usando Flask y Chart.js.

Ejercicio 10: Diseñá un sistema SCADA completo para una planta de tratamiento de agua.


💡 Buenas Prácticas

✅ Diseño de HMI:

  1. Simplicidad: Menos es más
  2. Consistencia: Mismo estilo en todas las pantallas
  3. Colores significativos: Verde=OK, Rojo=Alarma
  4. Jerarquía clara: Overview → Detalle → Control
  5. Feedback visual: Confirmar acciones del usuario

✅ Gestión de alarmas:

  1. Priorización: No todas las alarmas son iguales
  2. Agrupación: Evitar avalancha de alarmas
  3. Mensajes claros: Describir el problema y la acción
  4. Registro completo: Timestamp, usuario, acción
  5. Análisis periódico: Revisar alarmas frecuentes

✅ Bases de datos:

  1. Compresión: Datos históricos ocupan mucho espacio
  2. Respaldo: Backup automático diario
  3. Índices: Para consultas rápidas
  4. Limpieza: Eliminar datos antiguos innecesarios
  5. Validación: Verificar calidad de datos

🔧 Software SCADA Profesional

Open Source:

Comercial:


🎓 Resumen

SCADA: Supervisión, Control y Adquisición de Datos

HMI: Interfaz visual para operadores

Tags: Variables que representan el proceso

Comunicación: Protocolos industriales (Modbus, OPC UA)

Trending: Gráficos históricos para análisis

Alarmas: Notificación de condiciones anormales

Reportes: Información para la gestión


🚀 Próximos Pasos

  1. Practicá con los ejercicios propuestos
  2. Instalá un SCADA open-source (ScadaBR)
  3. Experimentá con simuladores de PLC
  4. Aprendé sobre ciberseguridad industrial
  5. Investigá sobre Industry 4.0 e IoT industrial

¿Tenés dudas? ¡Preguntá en clase! 🙋‍♂️

¡A supervisar procesos! 💪📊