Production-focused EtherNet/IP library for Allen-Bradley CompactLogix and ControlLogix PLCs.
Why this project exists
Why Rust for the core
EtherNet/IP runs on factory floors where a dropped packet or an out-of-bounds parse can stop a production line. Rust was chosen for the core because it provides:
- memory safety with no garbage collector — no GC pauses during high-rate scan loops
- predictable latency and low overhead, important for sub-100 ms tag polling
- a strong type system that pushes wire-protocol mistakes to compile time instead of to runtime in front of a real PLC
- a single statically-linked binary that drops into industrial PCs and edge gateways without a managed runtime
The same library can therefore serve both the embedded edge — where C and C++ have historically dominated — and higher-level integrations, without rewriting the protocol layer for each consumer.
Why a C# wrapper
The Allen-Bradley world is overwhelmingly a Windows and .NET world: HMIs, MES integrations, SCADA front-ends, OPC servers, and integrator-built operator software are usually written in C#. Most engineers on the plant floor are not going to write Rust, and they should not have to. The NuGet-packaged RustEtherNetIp wrapper lets those teams consume the Rust core through a familiar API (client.ReadDint("Tag")) while the protocol work still runs in the native layer.
Why a Python wrapper
Data engineering, analytics, historian ingestion, MES bridges, and machine learning on the plant floor are predominantly Python. A Python wrapper means a data scientist or integration engineer can pull live PLC data into pandas, into a Kafka producer, or into a Docker-deployed collector service, without rewriting the protocol stack or routing through OPC.
Vision and open source
There is no widely-adopted, modern, open-source EtherNet/IP library for Allen-Bradley PLCs that is production-credible across the Rust, .NET, and Python ecosystems at the same time. Existing options tend to be closed-source vendor SDKs with restrictive licensing, aging C libraries with thin or stale language bindings, or per-team rewrites that never get hardened against real PLC firmware quirks.
This project exists to fill that gap with a single, MIT-licensed protocol implementation the industrial automation community can build on, audit, and extend — and to make the firmware-imposed limitations (STRING writes, UDT array element writes, route-path quirks) explicit and documented rather than rediscovered by every new integrator.
Version Status
- Current working line:
1.0.0draft — bundles SemVer-major cleanup (CODEX-K release-window), actor-backed cloneable client (CODEX-P), service-layer helpers (CODEX-Q), connection event stream (CODEX-R), retry primitive (CODEX-S), fleet pool (CODEX-T), and sibling-crate workspace structure (CODEX-U). Not yet tagged or published. - Last published stable release:
0.7.0(crates.io + NuGet) - Previous stable release:
0.6.3 - Real-hardware validation evidence is included for the release
Release snapshot:
- Rust + C# + Python full-coverage hardware exercisers all pass against ControlLogix 1756-L75 fw33 (2026-05-24): 2299/2299 reads, 2206/2206 writes, 2206/2206 verify on the C# and Python paths (Rust 1806/1806 writes; the gap is exerciser-side classification, not a library defect).
- v1.0.0 crates.io publish requires claiming
rust-ethernet-ip-{types,protocol,tag-path,udt}on crates.io at tag time (each is an independently SemVer-versioned artifact going forward). Release-day publish order:types+tag-path, thenprotocol+udt, thenrust-ethernet-ip. NuGet wrapper publish is unaffected.
Project Focus
- Rust core library
- C# wrapper via NuGet (
RustEtherNetIp) - Python wrapper for data collection, analytics, and service integrations
- Industrial PC applications, with current NuGet packaging focused on Windows
win-x64 - 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
- Schema export and diagnostics snapshot surfaces
- C# wrapper for .NET integration
- Python wrapper and service/data-pipeline examples
Known PLC/Firmware Limitations
Some write behaviors are restricted by PLC firmware (not library protocol implementation):
- Direct writes to standalone
STRINGtags can fail on some controllers - Direct writes to
STRINGmembers 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, firmware35, at192.168.0.1:44818 - Validated on
1756-L81ES, firmware37, via1756-EN3TRslot0at192.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
STRINGwrites, which can surface as batch-level0x1Eor extended0x2107 - direct writes to UDT array element members, which surface as
0x2107
- direct
Recommended pattern for restricted cases: read-modify-write the full UDT/array element.
Detailed technical background and examples:
- AB String/UDT write limitations
- CompactLogix real-PLC validation record
- CompactLogix C# wrapper validation record
- ControlLogix real-PLC validation record
- ControlLogix C# wrapper validation record
Installation
Rust
[]
= "0.7.0"
= { = "1", = ["full"] }
C#
Or from the CLI:
Current NuGet packaging note:
RustEtherNetIp0.7.0is published on NuGet- the packaged native runtime asset is currently targeted at Windows
win-x64 - the managed package currently targets
.NET 10
Python
The Python wrapper is currently in-repo as a working 1.0.0 draft layer and is not published to PyPI yet.
See:
- python/README.md
- Integration and deployment guide
- docs/PYTHON_WRAPPER_STRATEGY.md
- docs/DOCKER_EXAMPLE_STACKS.md
Integration and Deployment
If you are evaluating the library for production use, start here:
That guide covers:
- when to use Rust vs C# vs Python
- step-by-step integration into each stack
- native runtime deployment expectations
- routed ControlLogix usage
- troubleshooting and rollout checks
Quick Start (Rust)
use ;
async
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 ;
// Batch write
let writes = vec!;
let write_results = client.write_tags_batch.await?;
// Mixed batch
let ops = vec!;
let mixed_results = client.execute_batch.await?;
Notes:
read_tags_batch(...)andwrite_tags_batch(...)preserve tag association in their return values.execute_batch(...)may regroup mixed operations for packet optimization, so correlate results by the returned operation metadata rather than assuming strict mixed-input ordering.
Tag Group Event Handling
Rust
use ;
let mut client = connect.await?;
client
.upsert_tag_group
.await?;
let sub = client.subscribe_tag_group.await?;
while let Some = sub.wait_for_update.await
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
Examples
.NET
&&
&&
&&
Rust
Python
PYTHONPATH=python
PYTHONPATH=python
Documentation
- API docs (docs.rs)
- Programmer manual (Rust + C#)
- Integration and deployment guide
- Python wrapper guide
- Official sources traceability
- PLC/simulator compatibility matrix (0.7.0)
- C# wrapper guide
- Tag introspection
- Troubleshooting
- Changelog
Community and Support
Project collaboration is open for:
- priority issue handling
- priority feature sponsorship
- integration support for real deployments
- OEM and system-integrator feedback
- companies willing to provide specific hardware access for validation
If your team wants to collaborate on one of those paths, start with a GitHub Discussion or issue and describe:
- controller model and firmware
- direct vs routed topology
- target application type
- required feature set and timeline
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.