Salta el contingut

EtherNet/IP - Llibreries i exemples

Introducció

EtherNet/IP (Industrial Protocol) és un protocol industrial que utilitza CIP (Common Industrial Protocol) sobre TCP/IP i UDP/IP. És el protocol natiu dels PLCs Allen-Bradley (Rockwell Automation) i també és suportat per Omron Sysmac i altres fabricants.

Llibreries disponibles

Llibreria Llenguatge PLCs suportats Llicència
pycomm3 Python Allen-Bradley (ControlLogix, CompactLogix, Micro800) MIT
libplctag C, Python, .NET Allen-Bradley, Omron, Modbus LGPL/MPL
ethernet-ip Node.js Allen-Bradley, genèric CIP MIT
node-red-contrib-cip-ethernet-ip Node-RED Allen-Bradley MIT

Python: pycomm3

La llibreria més popular i ben mantinguda per a Allen-Bradley en Python.

Instal·lació

pip install pycomm3

PLCs suportats

  • ControlLogix (L6x, L7x, L8x)
  • CompactLogix (L1x, L2x, L3x)
  • Micro800 (810, 820, 830, 850)

Exemple bàsic

from pycomm3 import LogixDriver

# Connexió a un ControlLogix/CompactLogix
with LogixDriver('192.168.1.100') as plc:
    # Llegir un tag
    result = plc.read('MyTag')
    print(f"Valor: {result.value}")
    print(f"Tipus: {result.type}")

    # Escriure un valor
    plc.write('MyTag', 1234)

Llegir i escriure múltiples tags

from pycomm3 import LogixDriver

with LogixDriver('192.168.1.100') as plc:
    # Llegir múltiples tags (més eficient que llegir un a un)
    results = plc.read('Tag1', 'Tag2', 'Tag3')
    for tag in results:
        print(f"{tag.tag}: {tag.value}")

    # Escriure múltiples tags
    plc.write(('Tag1', 100), ('Tag2', 200), ('Tag3', 300))

Treballar amb arrays

from pycomm3 import LogixDriver

with LogixDriver('192.168.1.100') as plc:
    # Llegir un array complet
    array_data = plc.read('MyArray')
    print(array_data.value)  # Llista de valors

    # Llegir una porció de l'array (10 elements des de l'índex 5)
    partial = plc.read('MyArray[5]{10}')

    # Escriure a un array
    plc.write('MyArray', [1, 2, 3, 4, 5])

    # Escriure a una posició específica
    plc.write('MyArray[0]', 100)

Treballar amb estructures (UDT)

from pycomm3 import LogixDriver

with LogixDriver('192.168.1.100') as plc:
    # Llegir una estructura completa
    recipe = plc.read('Recipe1')
    print(recipe.value)  # Retorna un diccionari amb tots els membres
    # Exemple: {'Name': 'Recipe A', 'Temperature': 75.5, 'Time': 120}

    # Llegir un membre específic
    temp = plc.read('Recipe1.Temperature')
    print(f"Temperatura: {temp.value}")

    # Escriure a un membre de l'estructura
    plc.write('Recipe1.Temperature', 80.0)

    # Escriure estructura completa
    plc.write('Recipe1', {'Name': 'Recipe B', 'Temperature': 85.0, 'Time': 150})

Obtenir llista de tags del PLC

from pycomm3 import LogixDriver

with LogixDriver('192.168.1.100') as plc:
    # Obtenir tots els tags
    tags = plc.get_tag_list()

    for tag in tags:
        print(f"Nom: {tag['tag_name']}")
        print(f"Tipus: {tag['data_type']}")
        print(f"Dimensió: {tag.get('dimensions', 'N/A')}")
        print("---")

    # Filtrar per programa
    program_tags = plc.get_tag_list(program='MainProgram')

Connexió amb slot específic

from pycomm3 import LogixDriver

# Per defecte, slot 0
with LogixDriver('192.168.1.100') as plc:
    pass

# Especificar slot (per exemple, slot 2)
with LogixDriver('192.168.1.100/2') as plc:
    pass

# Ruta completa per a configuracions més complexes
with LogixDriver('192.168.1.100', path='1/0/2/192.168.2.100/0') as plc:
    pass

Gestió d'errors

from pycomm3 import LogixDriver
from pycomm3.exceptions import CommError, ResponseError

try:
    with LogixDriver('192.168.1.100') as plc:
        result = plc.read('TagQueNoExisteix')

        if result.error:
            print(f"Error llegint tag: {result.error}")
        else:
            print(f"Valor: {result.value}")

except CommError as e:
    print(f"Error de comunicació: {e}")
except ResponseError as e:
    print(f"Error de resposta del PLC: {e}")

Python: libplctag

Llibreria més genèrica que suporta múltiples fabricants.

Instal·lació

pip install libplctag

Exemple bàsic

from libplctag import Tag

# Configuració del tag
tag_path = "protocol=ab-eip&gateway=192.168.1.100&path=1,0&cpu=LGX&name=MyTag&elem_type=DINT"

# Crear tag
tag = Tag()
tag.create(tag_path)

# Llegir
tag.read()
value = tag.get_int32(0)
print(f"Valor: {value}")

# Escriure
tag.set_int32(0, 5678)
tag.write()

# Alliberar recursos
tag.destroy()

Tipus de CPU suportats

Paràmetre cpu Descripció
LGX ControlLogix, CompactLogix
PLC5 PLC-5
SLC SLC 500
MLGX MicroLogix

Exemple per Omron

from libplctag import Tag

# Omron NJ/NX via EtherNet/IP
tag_path = "protocol=ab-eip&gateway=192.168.1.100&path=18,10.10.10.1,1,0&cpu=omron-njnx&name=MyVar"

tag = Tag()
tag.create(tag_path)
tag.read()
value = tag.get_float32(0)
print(f"Valor: {value}")
tag.destroy()

Node.js: ethernet-ip

Instal·lació

npm install ethernet-ip

Exemple bàsic

const { Controller, Tag } = require('ethernet-ip');

const plc = new Controller();

// Connexió
plc.connect('192.168.1.100', 0).then(() => {
    console.log('Connectat al PLC');
    console.log(`Nom: ${plc.properties.name}`);
    console.log(`Slot: ${plc.properties.slot}`);

    // Crear un tag
    const myTag = new Tag('MyTag');

    // Llegir
    plc.readTag(myTag).then(() => {
        console.log(`Valor: ${myTag.value}`);
        console.log(`Tipus: ${myTag.type}`);
    });

}).catch(err => {
    console.error('Error de connexió:', err);
});

Llegir i escriure

const { Controller, Tag } = require('ethernet-ip');

async function main() {
    const plc = new Controller();

    try {
        await plc.connect('192.168.1.100', 0);

        // Llegir
        const tempTag = new Tag('Temperature');
        await plc.readTag(tempTag);
        console.log(`Temperatura actual: ${tempTag.value}`);

        // Escriure
        const setpointTag = new Tag('Setpoint');
        setpointTag.value = 75.5;
        await plc.writeTag(setpointTag);
        console.log('Setpoint actualitzat');

    } catch (err) {
        console.error('Error:', err);
    } finally {
        plc.disconnect();
    }
}

main();

Grups de tags

const { Controller, Tag, TagGroup } = require('ethernet-ip');

async function main() {
    const plc = new Controller();
    await plc.connect('192.168.1.100', 0);

    // Crear grup de tags per lectura/escriptura eficient
    const group = new TagGroup();
    group.add(new Tag('Temperature'));
    group.add(new Tag('Pressure'));
    group.add(new Tag('FlowRate'));

    // Llegir tots els tags del grup (una sola petició)
    await plc.readTagGroup(group);

    group.forEach(tag => {
        console.log(`${tag.name}: ${tag.value}`);
    });

    // Modificar i escriure
    group.forEach(tag => tag.value = 0);
    await plc.writeTagGroup(group);

    plc.disconnect();
}

main();

Subscripció a canvis (polling)

const { Controller, Tag } = require('ethernet-ip');

const plc = new Controller();

plc.connect('192.168.1.100', 0).then(() => {
    // Afegir tags a monitoritzar
    plc.subscribe(new Tag('Temperature'));
    plc.subscribe(new Tag('Pressure'));
    plc.subscribe(new Tag('AlarmStatus'));

    // Configurar interval de polling (ms)
    plc.scan_rate = 500;

    // Event quan un tag canvia de valor
    plc.on('TagChanged', (tag, prevValue) => {
        console.log(`${tag.name}: ${prevValue} -> ${tag.value}`);
    });

    // Event d'error
    plc.on('error', (err) => {
        console.error('Error de comunicació:', err);
    });
});

// Per aturar el polling
// plc.scan_rate = 0;

TypeScript

import { Controller, Tag, TagGroup } from 'ethernet-ip';

interface ProcessData {
    temperature: number;
    pressure: number;
    running: boolean;
}

async function readProcessData(): Promise<ProcessData> {
    const plc = new Controller();
    await plc.connect('192.168.1.100', 0);

    const tempTag = new Tag('Temperature');
    const pressTag = new Tag('Pressure');
    const runTag = new Tag('Running');

    await plc.readTag(tempTag);
    await plc.readTag(pressTag);
    await plc.readTag(runTag);

    plc.disconnect();

    return {
        temperature: tempTag.value as number,
        pressure: pressTag.value as number,
        running: runTag.value as boolean
    };
}

Node-RED

Per a Node-RED existeix el paquet node-red-contrib-cip-ethernet-ip.

Instal·lació

cd ~/.node-red
npm install node-red-contrib-cip-ethernet-ip

Nodes disponibles

  • eth-ip-endpoint: Configuració de connexió al PLC
  • eth-ip-read: Llegir tags
  • eth-ip-write: Escriure tags

Comparativa de llibreries

Característica pycomm3 libplctag ethernet-ip
Llenguatge Python Multi Node.js
Facilitat d'ús Alta Mitjana Alta
Suport UDT Parcial Bàsic
Multi-fabricant No (AB només) No (AB només)
Async/await No No
Documentació Excel·lent Bona Bona
Manteniment Actiu Actiu Actiu

Recursos