Skip to main content

EtherNet/IP

The EtherNet/IP module targets Allen-Bradley / Rockwell PLCs. It supports Register Session, SendRRData, CIP Read Tag / Write Tag, and Get Attribute Single / Set Attribute Single over TCP 44818. Current coverage focuses on scalar tags for point acquisition and write-back.

Start with a Virtual PLC

var memory = new EtherNetIpSlaveMemory();
memory.SetTag("Temperature", EtherNetIpDataType.Int, (short)253);
memory.SetTag("Running", EtherNetIpDataType.Bool, true);

await using var app = ZeusHost.Create(builder =>
{
builder.AddAcquisition(TimeSpan.FromMilliseconds(100));
builder.AddVirtualChannel("enip-link", new EtherNetIpSlaveResponder(memory));
builder.AddEtherNetIp("plc", "enip-link", points: map => map
.Int("temperature", "Temperature", scale: 0.1).Writable("temperature")
.Bool("running", "Running").Writable("running"));
});

await app.StartAsync();
var temperature = app.Points.Get<double>("temperature");
await app.Points.WriteAsync("temperature", 18.6);

Run sample: samples/Zeus.Samples.Console.EtherNetIp.

Connect to a Real PLC

await using var app = ZeusHost.Create(builder =>
{
builder.AddTcpClient("enip-link", "192.168.1.10", 44818);
builder.AddEtherNetIp("plc", "enip-link", points: map => map
.Int("temperature", "Temperature", scale: 0.1)
.Bool("running", "Running"));
});

Use the full PLC tag path for Program-scope tags, for example Program:Main.Speed.

Read/Write API

var plc = app.Devices.Get<EtherNetIpDevice>("plc");

var temperature = await plc.ReadTagAsync("Temperature", EtherNetIpDataType.Int, scale: 0.1);
await plc.WriteTagAsync("Temperature", EtherNetIpDataType.Int, 18.6, scale: 0.1);

var running = await plc.ReadTagAsync("Running", EtherNetIpDataType.Bool);
await plc.WriteTagAsync("Running", EtherNetIpDataType.Bool, true);

byte[] identityName = await plc.GetAttributeSingleAsync(classId: 0x01, instanceId: 1, attributeId: 7);
await plc.SetAttributeSingleAsync(classId: 0x64, instanceId: 1, attributeId: 3, new byte[] { 0x01 });

Scalar Types

JSON / APICIP type
bool / BoolBOOL 0x00C1
sint / SIntSINT 0x00C2
int / IntINT 0x00C3
dint / DIntDINT 0x00C4
lint / LIntLINT 0x00C5
usint / USIntUSINT 0x00C6
uint / UIntUINT 0x00C7
udint / UDIntUDINT 0x00C8
ulint / ULIntULINT 0x00C9
real / RealREAL 0x00CA
lreal / LRealLREAL 0x00CB

Points

builder.AddEtherNetIp("plc", "enip-link", points: map => map
.Int("temperature", "Temperature", scale: 0.1).Writable("temperature")
.DInt("speed", "Program:Main.Speed")
.Real("flow", "Flow")
.Bool("running", "Running").Writable("running"));

Point names can be business-friendly even when PLC tag paths are longer. scale applies only to numeric tags; BOOL points cannot use scale or alarm limits.

JSON Configuration

{
"channels": [
{ "name": "enip-link", "type": "tcp", "host": "192.168.1.10", "port": 44818 }
],
"devices": [
{
"name": "plc",
"channel": "enip-link",
"type": "ethernet-ip",
"points": [
{ "name": "temperature", "tag": "Temperature", "dataType": "int", "scale": 0.1, "writable": true },
{ "name": "running", "tag": "Running", "dataType": "bool", "writable": true },
{ "name": "speed", "tagName": "Program:Main.Speed", "dataType": "dint" }
]
}
]
}

Virtual EtherNet/IP PLC:

{ "name": "enip-link", "type": "virtual", "responder": "ethernet-ip" }

If tag / tagName is omitted, Zeus uses the point name as the PLC tag name.

Common Issues

SymptomLikely causeFix
TimeoutTCP 44818, firewall, or PLC EtherNet/IP service unavailableTest with EtherNetIpSlaveResponder, then check the field network
CIP status 0x04Tag path is wrong or missingCheck tag path, Program scope, and casing
Type mismatchConfigured dataType differs from PLC tag typeMatch the PLC project type
Write-back missingPoint not writable or PLC tag is read-onlyMark writable and confirm PLC permissions
BOOL has scale/alarmBOOL is not numericRemove scale, lowAlarmLimit, and highAlarmLimit

Next: Points and Acquisition.