rust-ethernet-ip 0.7.0

High-performance EtherNet/IP communication library for Allen-Bradley CompactLogix and ControlLogix PLCs
Documentation

Crates.io Rust License Documentation

Production-focused EtherNet/IP library for Allen-Bradley CompactLogix and ControlLogix PLCs.

Version Status

  • Current stable release: 0.7.0
  • Previous stable release: 0.6.3
  • Real-hardware validation evidence is included for the release

Release snapshot:

  • simulator, FFI, Rust, and C# regression gates were completed for 0.7.0
  • real-hardware validation was completed on one CompactLogix and one ControlLogix target
  • 0.7.0 is the current released line

Project Focus

  • Rust core library
  • C# wrapper (RustEtherNetIp.dll)
  • Industrial PC applications (Windows/Linux/macOS)
  • Deterministic behavior and regression safety

Key Capabilities

  • Native support for all 13 common AB data types: BOOL, SINT, INT, DINT, LINT, USINT, UINT, UDINT, ULINT, REAL, LREAL, STRING, UDT
  • Advanced tag addressing: program-scoped tags, array indexing, bit access, nested UDT paths
  • Route path support for backplane/slot routing (ControlLogix)
  • Batch operations (read_tags_batch, write_tags_batch, execute_batch)
  • Tag-group polling API (upsert_tag_group, read_tag_group_once, subscribe_tag_group)
  • UDT discovery and metadata access
  • Real-time subscriptions and health-check APIs
  • C# wrapper for .NET integration

Known PLC/Firmware Limitations

Some write behaviors are restricted by PLC firmware (not library protocol implementation):

  • Direct writes to standalone STRING tags can fail on some controllers
  • Direct writes to STRING members inside UDTs can fail on some controllers
  • Direct writes to UDT array element members (for example MyUdtArray[0].Member) can fail

Real-hardware note from the 0.7.0 release validation:

  • Validated on 5069-L320ERMS3, firmware 35, at 192.168.0.1:44818
  • Validated on 1756-L81ES, firmware 37, via 1756-EN3TR slot 0 at 192.168.0.101:44818
  • On that CompactLogix target, normal reads/writes, route-path access, subscriptions, UDT reads, and batch operations are working
  • On that ControlLogix target, the same main read/write, route-path, subscription, UDT-read, and batch paths are working
  • On that same target, the remaining observed firmware-imposed limits are:
    • direct STRING writes, which can surface as batch-level 0x1E or extended 0x2107
    • direct writes to UDT array element members, which surface as 0x2107

Recommended pattern for restricted cases: read-modify-write the full UDT/array element.

Detailed technical background and examples:

Installation

Rust

[dependencies]
rust-ethernet-ip = "0.7.0"
tokio = { version = "1", features = ["full"] }

C#

<PackageReference Include="RustEtherNetIp" Version="0.7.0" />

Quick Start (Rust)

use rust_ethernet_ip::{EipClient, PlcValue, RoutePath};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Direct connect
    let mut client = EipClient::connect("192.168.1.100:44818").await?;

    // Or routed connect (example: ControlLogix slot 3)
    let route = RoutePath::new().add_slot(3);
    let mut routed = EipClient::with_route_path("192.168.1.100:44818", route).await?;

    let running = client.read_tag("Program:Main.MotorRunning").await?;
    client
        .write_tag("Program:Main.SetPoint", PlcValue::Dint(1500))
        .await?;

    let tags = vec!["Program:Main.Temp", "Program:Main.Pressure"];
    let batch = routed.read_tags_batch(&tags).await?;

    println!("running={running:?}, batch={batch:?}");
    Ok(())
}

Quick Start (C#)

using RustEtherNetIp;

using var client = new EtherNetIpClient();
if (client.Connect("192.168.1.100:44818"))
{
    bool running = client.ReadBool("Program:Main.MotorRunning");
    int count = client.ReadDint("Program:Main.ProductionCount");

    client.WriteBool("Program:Main.Start", true);
    client.WriteDint("Program:Main.SetPoint", 1500);

    Console.WriteLine($"running={running}, count={count}");
}

Batch Operations

use rust_ethernet_ip::{BatchOperation, PlcValue};

// Batch write
let writes = vec![
    ("SetPoint1", PlcValue::Real(72.5)),
    ("SetPoint2", PlcValue::Real(74.0)),
    ("Enable", PlcValue::Bool(true)),
];
let write_results = client.write_tags_batch(&writes).await?;

// Mixed batch
let ops = vec![
    BatchOperation::Read { tag_name: "ActualTemp".into() },
    BatchOperation::Write { tag_name: "SetPoint1".into(), value: PlcValue::Real(73.0) },
];
let mixed_results = client.execute_batch(&ops).await?;

Tag Group Event Handling

Rust

use rust_ethernet_ip::{EipClient, TagGroupEventKind};

let mut client = EipClient::connect("192.168.1.100:44818").await?;
client
    .upsert_tag_group(
        "cell_1",
        vec!["Program:Main.Temp".into(), "Program:Main.Pressure".into()],
        250,
    )
    .await?;

let sub = client.subscribe_tag_group("cell_1").await?;
while let Some(event) = sub.wait_for_update().await {
    match event.kind {
        TagGroupEventKind::Data => {
            // All tags read successfully
        }
        TagGroupEventKind::PartialError => {
            // Some tags failed; inspect per-tag `snapshot.values[*].error`
        }
        TagGroupEventKind::ReadFailure => {
            // Full cycle failed; inspect `event.error` and `event.failure`
        }
    }
}

C#

client.UpsertTagGroup("cell_1", new[] { "DINT_TAG", "PressureTag" }, updateRateMs: 250);
var group = client.SubscribeToTagGroup("cell_1");

group.PollingEvent += (_, evt) =>
{
    switch (evt.Kind)
    {
        case TagGroupEventKind.Data:
            // All tags good
            break;
        case TagGroupEventKind.PartialError:
            // Mixed quality; inspect evt.Errors per tag
            break;
        case TagGroupEventKind.ReadFailure:
            // Entire cycle failed; inspect evt.ErrorMessage + evt.Failure
            break;
    }
};

Build and Test

cargo fmt
cargo clippy -p rust-ethernet-ip --lib -- -D warnings
cargo test --workspace --all-targets
dotnet test csharp/RustEtherNetIp.Tests/RustEtherNetIp.Tests.csproj -v minimal

Examples

.NET

cd examples/WpfExample && dotnet run
cd examples/WinFormsExample && dotnet run
cd examples/AspNetExample && dotnet run

Rust

cargo run --example comprehensive_terminal_demo
cargo run --example stream_injection_example
cargo run --example test_discover_and_verify

Documentation

Community and Support

Contributing

See CONTRIBUTING.md.

License

MIT. See LICENSE.

Safety Notice

This software is provided "AS IS". Validate thoroughly in your own environment before production deployment, especially for industrial control systems.