Leer y escribir datos de un S7 con Python y Snap7

Leer y escribir datos de un S7 con Python y Snap7

«Leer un S7 desde Python» es una consulta que recientemente me ha llegado, así que vamos a ver el método que usa mucha gente cuando quiere hablar con el PLC sin montar un servidor OPC: Snap7.

Snap7 es una librería que implementa el protocolo S7 (el mismo que usa el propio TIA Portal para comunicar con la CPU). Con su binding para Python, python-snap7, te conectas directo a la CPU por Ethernet y lees o escribes bytes de un DB. Sin servidor intermedio, sin licencias. Para un script rápido de registro de datos o una prueba de laboratorio va de lujo.

Qué es Snap7 y cuándo usarlo (y cuándo no)

Snap7 habla el protocolo S7comm a través de la comunicación PUT/GET, la misma que se usa entre PLCs con los bloques PUT y GET. Eso tiene consecuencias importantes que conviene tener claras antes de escribir una línea:

  • Solo accede a DBs no optimizados. Los DBs con "Acceso optimizado al bloque" activado no exponen direcciones absolutas de byte, y Snap7 trabaja con direcciones absolutas. Esto es lo que más gente pilla por sorpresa.

  • No hay cifrado ni autenticación de verdad. PUT/GET es comunicación de PLC legacy. Para producción con requisitos de seguridad, OPC UA es el camino correcto.

  • Es cómodo para prototipos, data logging y pruebas. Si necesitas algo robusto, auditable y con tipos de datos autodescritos, tira de OPC UA.

Regla rápida: script de fin de semana, registro de valores, banco de pruebas, Snap7. Sistema de producción que va a auditar el cliente, OPC UA.

Instalar python-snap7

Con Python 3 instalado, desde la terminal:

Ver código · 1 línea
pip install python-snap7

Las versiones recientes de python-snap7 ya incluyen la librería nativa snap7 compilada dentro del paquete, así que en Windows no necesitas copiar la DLL a mano como pasaba antaño. Si te da un error de librería no encontrada, revisa la documentación del paquete en PyPI para tu sistema operativo.

Configurar el S7-1200 / S7-1500 en TIA Portal

Aquí está el 90% del trabajo. Si no habilitas los permisos correctos en la CPU, el script conectará pero no leerá nada, o directamente te rechazará la conexión.

1. Permitir el acceso PUT/GET

En las propiedades de la CPU, ve a Protección y seguridad y marca la casilla "Permitir acceso vía comunicación PUT/GET del interlocutor remoto". Sin esto, olvídate.

2. Crear un DB con acceso no optimizado

Crea un bloque de datos global, botón derecho sobre él, Propiedades, pestaña Atributos y desmarca "Acceso al bloque optimizado". Al desmarcarlo el DB pasa a direccionamiento absoluto por offset de byte, que es lo que Snap7 necesita.

Para el ejemplo voy a montar un DB10 con esta estructura:

  • Temperatura : REAL en offset 0.0 (ocupa 4 bytes, 0.0 a 3.0)

  • Contador : INT en offset 4.0 (2 bytes)

  • Marcha : BOOL en offset 6.0

Anota bien los offsets de la columna "Offset" del editor de DB. Son la clave para leer y escribir después.

3. Nivel de acceso y firewall

Comprueba que el nivel de acceso de la CPU permite HMI/lectura-escritura y que la red entre tu PC y el PLC deja pasar el puerto TCP 102 (ISO on TCP). Un antivirus corporativo o un firewall bloqueando el 102 es un clásico de tarde perdida.

Conectar y leer un DB desde Python

La conexión necesita la IP de la CPU, el número de rack y el número de slot. Para un S7-1200 y S7-1500 con CPU integrada, lo habitual es rack 0, slot 1. Confirma tu configuración concreta si tienes dudas.

Ver código · 24 líneas
import snap7
from snap7.util import get_real, get_int, get_bool

IP = "192.168.0.1"
RACK = 0
SLOT = 1

plc = snap7.client.Client()
plc.connect(IP, RACK, SLOT)

print("Conectado:", plc.get_connected())

# Leemos 7 bytes del DB10 empezando en el offset 0
data = plc.db_read(10, 0, 7)

temperatura = get_real(data, 0)   # REAL en offset 0.0
contador    = get_int(data, 4)    # INT  en offset 4.0
marcha      = get_bool(data, 6, 0)  # BOOL en offset 6.0, bit 0

print(f"Temperatura: {temperatura:.2f}")
print(f"Contador:    {contador}")
print(f"Marcha:      {marcha}")

plc.disconnect()

El método db_read(db_number, start, size) te devuelve un bytearray con los bytes crudos. Las funciones de snap7.util interpretan ese buffer según el tipo y el offset. Fíjate que para el BOOL hay que indicar byte y bit.

Escribir un valor en el DB

Para escribir haces lo inverso: lees el buffer, modificas los bytes con las funciones set_* y lo devuelves con db_write.

Ver código · 15 líneas
import snap7
from snap7.util import set_int, set_bool

plc = snap7.client.Client()
plc.connect("192.168.0.1", 0, 1)

# Leemos primero para no pisar el resto del DB
data = plc.db_read(10, 0, 7)

set_int(data, 4, 1234)          # Contador = 1234
set_bool(data, 6, 0, True)      # Marcha = True

plc.db_write(10, 0, data)

plc.disconnect()

Truco: escribe siempre sobre el buffer que acabas de leer. Si construyes un bytearray nuevo con solo los bytes que te interesan y lo escribes entero, machacas el resto del DB con ceros. Lee, modifica lo tuyo, escribe.

Ejemplo práctico: registrar valores a CSV

El caso de uso estrella de Snap7. Un script que lee la temperatura cada segundo y la va apilando en un CSV con marca de tiempo. Perfecto para un banco de pruebas o para caracterizar un proceso sin tocar el HMI.

Ver código · 32 líneas
import snap7
from snap7.util import get_real, get_int
import csv
import time
from datetime import datetime

IP = "192.168.0.1"
INTERVALO = 1.0  # segundos

plc = snap7.client.Client()
plc.connect(IP, 0, 1)

with open("registro.csv", "w", newline="") as f:
    writer = csv.writer(f, delimiter=";")
    writer.writerow(["timestamp", "temperatura", "contador"])

    try:
        while True:
            data = plc.db_read(10, 0, 7)
            temperatura = get_real(data, 0)
            contador = get_int(data, 4)
            ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

            writer.writerow([ts, f"{temperatura:.2f}", contador])
            f.flush()
            print(ts, temperatura, contador)

            time.sleep(INTERVALO)
    except KeyboardInterrupt:
        print("Registro detenido por el usuario")
    finally:
        plc.disconnect()

Uso el separador punto y coma para que Excel en español lo abra en columnas sin pelearte con las comas de los decimales. El f.flush() fuerza el guardado en disco en cada iteración, así si el script se corta no pierdes lo registrado hasta ese momento.

Buena práctica: comprueba la conexión

En un script de larga duración, la conexión se puede caer. Envuelve la lectura en un control de excepciones y reconecta con plc.connect() si plc.get_connected() devuelve False. Un PLC no espera a tu script, y una reprogramación o un corte de red te tira la sesión.

Límites de Snap7 y cuándo preferir OPC UA

Snap7 es directo y rápido de montar, pero tiene techo:

  • DBs optimizados fuera. Si tu proyecto trabaja con acceso optimizado (lo recomendable en muchos casos), tendrías que crear DBs no optimizados solo para exponerlos, lo cual ensucia el programa.

  • Offsets a mano. Cualquier cambio en la estructura del DB descoloca los offsets de tu script. Es frágil.

  • Seguridad. PUT/GET no cifra nada. En una red industrial expuesta es un riesgo.

Cuando quieras algo mantenible, con nombres de variables en lugar de offsets, tipos autodescritos, cifrado y autenticación, el servidor OPC UA integrado en el S7-1500 (y en el S7-1200 con firmware compatible) es la opción seria. Ahí te remito al post de OPC UA con Qt Designer del blog. Snap7 para lo rápido, OPC UA para lo que va a durar.

¿Más información?

Si te parece interesante y lo quieres ver en marcha, puedes acceder al curso de Python con Qt-Designer.

Ahí tienes las primeras lecciones de forma gratuita donde se ve cómo implementar lo visto en esta entrada.

Comentarios

No hay comentarios todavía. Sé el primero en dejar uno.