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:
- Supervisar procesos industriales en tiempo real
- Controlar equipos y procesos de forma remota
- Adquirir datos de sensores y dispositivos
- Almacenar información histórica
- Generar alarmas y reportes
- Visualizar el estado de la planta
Analogía simple:
Imaginate que sos el gerente de una fábrica de jugos. Sin SCADA, tendrías que:
- Caminar por toda la planta para ver cada tanque
- Anotar manualmente cada lectura
- Llamar por radio a los operadores para cambiar algo
Con SCADA:
- Ves TODO desde una pantalla
- Los datos se guardan automáticamente
- Controlás todo con un click
- Recibís alertas si algo anda mal
🏭 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:
- Nivel de Campo: Sensores y actuadores físicos
- Nivel de Control: PLCs que ejecutan la lógica
- Nivel de Supervisión: Servidores SCADA y HMIs
- 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
- Colores significativos (verde=OK, rojo=alarma, amarillo=advertencia)
- Tamaño de texto legible
- Iconos intuitivos
- Sin saturación de información
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
- Botones claros
- Breadcrumbs (rastro de navegación)
- Accesos rápidos
- Consistencia entre pantallas
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:
| Protocolo | Tipo | Velocidad | Uso |
|---|---|---|---|
| Modbus TCP/IP | Ethernet | Rápido | Más usado, simple |
| Modbus RTU | Serial | Lento | Equipos antiguos |
| OPC UA | Ethernet | Rápido | Estándar moderno |
| Profinet | Ethernet | Muy rápido | Siemens |
| EtherNet/IP | Ethernet | Muy rápido | Allen-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()
📈 Trending y Gráficos Históricos
Una función clave de SCADA es graficar datos en el tiempo.
Tipos de gráficos:
1. Trending en tiempo real
Temperatura (°C)
100 ┤ ╭─╮
90 ┤ ╭─────╯ ╰─
80 ┤ ╭─────╯
70 ┤ ╭─────╯
60 ┤ ╭─────╯
50 ┤ ╭─────╯
40 ┤╭─────╯
└┴─────┴─────┴─────┴─────┴─────┴─────┴──▶
10:00 10:10 10:20 10:30 10:40 10:50 11:00
2. Trending histórico
Permite analizar datos pasados:
- Últimas 24 horas
- Última semana
- Último mes
- Rango personalizado
Configuración de trending:
// 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:
| Prioridad | Color | Sonido | Descripción |
|---|---|---|---|
| Crítica | 🔴 Rojo | Continuo | Peligro inmediato |
| Alta | 🟠 Naranja | Intermitente | Requiere acción urgente |
| Media | 🟡 Amarillo | Un beep | Advertencia |
| Baja | 🔵 Azul | Silencio | Informació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:
- Datos de proceso: Valores de sensores
- Eventos: Arranques, paradas, cambios de modo
- Alarmas: Historial completo
- Producción: Contadores, totalizadores
- 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).
- Modelo: Publicar/Suscribir (Pub/Sub)
- Uso: Conectar sensores y dispositivos con bajo consumo de ancho de banda.
- Broker: Servidor central que gestiona los mensajes (ej. Mosquitto).
2. Node-RED
Herramienta de programación visual basada en flujo.
- Ideal para: Conectar hardware, APIs y servicios online.
- Interfaz: Drag-and-drop (arrastrar y soltar).
- Integración: Funciona perfecto con MQTT y PLCs.
3. Flet (Python)
Framework para construir aplicaciones web, de escritorio y móviles en tiempo real con Python.
- Uso en SCADA: Crear dashboards modernos y responsivos.
- Ventaja: Todo el poder de Python con una UI atractiva (basada en Flutter).
4. Siemens LOGO! 8.2
PLC compacto ideal para educación y pequeños proyectos de automatización.
- Pantalla integrada: Para visualización básica.
- Web Server: Permite crear un mini-SCADA web sin software adicional.
- Programación: Diagrama de bloques (FBD) o escalera (LAD).
🎨 Proyecto: Sistema SCADA Simplificado
Vamos a crear un sistema SCADA básico usando Python y herramientas open-source.
Componentes:
- Simulador de PLC: Python con Modbus
- HMI: Tkinter o web (Flask)
- Base de datos: SQLite
- Gráficos: Matplotlib
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:
- Simplicidad: Menos es más
- Consistencia: Mismo estilo en todas las pantallas
- Colores significativos: Verde=OK, Rojo=Alarma
- Jerarquía clara: Overview → Detalle → Control
- Feedback visual: Confirmar acciones del usuario
✅ Gestión de alarmas:
- Priorización: No todas las alarmas son iguales
- Agrupación: Evitar avalancha de alarmas
- Mensajes claros: Describir el problema y la acción
- Registro completo: Timestamp, usuario, acción
- Análisis periódico: Revisar alarmas frecuentes
✅ Bases de datos:
- Compresión: Datos históricos ocupan mucho espacio
- Respaldo: Backup automático diario
- Índices: Para consultas rápidas
- Limpieza: Eliminar datos antiguos innecesarios
- Validación: Verificar calidad de datos
🔧 Software SCADA Profesional
Open Source:
- ScadaBR: SCADA completo en Java
- OpenSCADA: Plataforma modular
- Mango Automation: Web-based
Comercial:
- Wonderware InTouch: Líder del mercado
- Ignition by Inductive Automation: Moderno, web-based
- Siemens WinCC: Integrado con PLCs Siemens
- Rockwell FactoryTalk: Para Allen-Bradley
🎓 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
- Practicá con los ejercicios propuestos
- Instalá un SCADA open-source (ScadaBR)
- Experimentá con simuladores de PLC
- Aprendé sobre ciberseguridad industrial
- Investigá sobre Industry 4.0 e IoT industrial
¿Tenés dudas? ¡Preguntá en clase! 🙋♂️
¡A supervisar procesos! 💪📊