EtherNet/IP
Zeus 的 EtherNet/IP 模块面向 Allen-Bradley / Rockwell PLC,支持 TCP 44818 上的 Register Session、SendRRData、CIP Read Tag / Write Tag,以及 Get Attribute Single / Set Attribute Single。当前覆盖常用标量标签,适合把 PLC 标签接入点表采集和按点名写回。
没硬件时先跑虚拟 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);
这段代码里:
| 代码 | 含义 |
|---|---|
EtherNetIpSlaveMemory | 虚拟 PLC 的标签和 CIP 属性内存映像 |
EtherNetIpSlaveResponder(memory) | 模拟 EtherNet/IP Register Session 与 CIP 标签应答 |
.Int(..., 0.1) | Temperature 原始 INT 乘以 0.1 后进入点表;写回时反算 |
.Writable(...) | 允许通过 app.Points.WriteAsync(...) 按点名写回 |
省略 memory 时,默认虚拟 PLC 会预置 Temperature、Running 和 Program:Main.Speed 三个演示标签,便于 JSON 示例直接跑通。
可运行示例:samples/Zeus.Samples.Console.EtherNetIp。
连接真实 PLC
EtherNet/IP 常用端口是 44818。通道只负责 TCP 收发,CIP 会话由 EtherNetIpDevice 处理:
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"));
});
如果标签在 Program 作用域下,标签名按 PLC 工程里的完整路径写,例如 Program:Main.Speed。Zeus 会按点号拆成 CIP Symbol Segment。
读写 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 });
支持的标量类型:
| JSON / API | CIP 类型 |
|---|---|
bool / Bool | BOOL 0x00C1 |
sint / SInt | SINT 0x00C2 |
int / Int | INT 0x00C3 |
dint / DInt | DINT 0x00C4 |
lint / LInt | LINT 0x00C5 |
usint / USInt | USINT 0x00C6 |
uint / UInt | UINT 0x00C7 |
udint / UDInt | UDINT 0x00C8 |
ulint / ULInt | ULINT 0x00C9 |
real / Real | REAL 0x00CA |
lreal / LReal | LREAL 0x00CB |
点表采集
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"));
EtherNet/IP 标签本身已经有名称,点表名可以更偏业务,例如把 PLC 的 Program:Main.Speed 暴露为 Zeus 点 speed。scale 仅用于数值标签,BOOL 不允许配置报警限或缩放。
JSON 配置
{
"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" }
]
}
]
}
虚拟 EtherNet/IP PLC:
{ "name": "enip-link", "type": "virtual", "responder": "ethernet-ip" }
如果 tag / tagName 省略,Zeus 会使用 name 作为 PLC 标签名。
常见问题
| 现象 | 最可能原因 | 处理 |
|---|---|---|
| 一直超时 | IP、端口 44818、防火墙或 PLC EtherNet/IP 服务不可达 | 先用 EtherNetIpSlaveResponder 跑通主站逻辑,再核对现场网络 |
返回 CIP 状态 0x04 | 标签路径错误或标签不存在 | 核对标签名、Program 作用域和大小写 |
| 类型不匹配 | JSON / API 的 dataType 和 PLC 标签类型不同 | 按 PLC 工程里的标签类型设置 EtherNetIpDataType |
| 写回没执行 | 点没有 .Writable(...) 或 JSON 没设 writable: true | 标记点为可写,并确认 PLC 标签允许外部写入 |
| BOOL 配了 scale 或报警限 | BOOL 不是数值点 | 移除 scale、lowAlarmLimit、highAlarmLimit |
下一步:周期读取数据看 点表与采集。