RockwellTagReader 0.1.0
dotnet add package RockwellTagReader --version 0.1.0
NuGet\Install-Package RockwellTagReader -Version 0.1.0
<PackageReference Include="RockwellTagReader" Version="0.1.0" />
<PackageVersion Include="RockwellTagReader" Version="0.1.0" />
<PackageReference Include="RockwellTagReader" />
paket add RockwellTagReader --version 0.1.0
#r "nuget: RockwellTagReader, 0.1.0"
#:package RockwellTagReader@0.1.0
#addin nuget:?package=RockwellTagReader&version=0.1.0
#tool nuget:?package=RockwellTagReader&version=0.1.0
RockwellTagReader
Pure-C# read-only EtherNet/IP + CIP client for Allen-Bradley ControlLogix / CompactLogix PLCs.
Zero native dependencies, multi-targeted from net6.0 to net10.0.
- Source: https://github.com/ewerton336/RockwellTagReader
- Issues: https://github.com/ewerton336/RockwellTagReader/issues
What it does
- Connects via EtherNet/IP encapsulation (RegisterSession / SendRRData / UnRegisterSession).
- Issues CIP Read Tag (service
0x4C) wrapped in Unconnected Send (service0x52). - Batches multiple tags into Multiple Service Packet (service
0x0A) requests, automatically splitting them to fit the ~504-byte unconnected-send budget. - Decodes atomic types:
BOOL,SINT,INT,DINT,LINT(signed and unsigned variants),REAL,LREAL,BYTE,WORD,DWORD,LWORD. - Handles Logix tag syntax:
Tag,Struct.Member,Array[N], atomic bit accessTag.N. - Resilient: lazy-connect, request serialization, per-request timeout, automatic
reconnect-and-retry on socket / EIP
0x0064(Invalid Session) errors.
What it does NOT do (yet)
- No
Write Tag. - No connected (Class 3 / Forward Open) sessions — Unconnected Send only.
- No
STRING,UDT, or array reads as a single batch (read each element individually). - Tested on ControlLogix / CompactLogix only — PLC-5 / SLC-500 are out of scope.
Install
dotnet add package RockwellTagReader
Quickstart
using Microsoft.Extensions.Logging.Abstractions;
using RockwellTagReader;
await using var plc = new RockwellPlcClient(
new RockwellPlcClientOptions
{
Host = "192.168.1.10",
Port = 44818, // default EtherNet/IP port
RoutePath = "1,0", // backplane 1, slot 0
ConnectTimeout = TimeSpan.FromSeconds(5),
RequestTimeout = TimeSpan.FromSeconds(2),
},
logger: NullLogger.Instance);
// Single tag
var speed = await plc.ReadAsync("Motor1.Speed");
Console.WriteLine($"{speed.TagName} = {speed.DecodedValue} (success={speed.IsSuccess})");
// Multiple tags in one batched call
var tags = new[] { "Motor1.Speed", "Motor1.Current", "Tank.Level", "Pump.Running.0" };
var results = await plc.ReadManyAsync(tags);
foreach (var r in results)
Console.WriteLine($"{r.TagName} = {(r.IsSuccess ? r.DecodedValue : r.ErrorMessage)}");
Tag syntax
| Form | Meaning |
|---|---|
MyTag |
Symbolic top-level tag. |
Struct.Member |
UDT/struct member access (any depth). |
Array[5] |
Array element 5. |
Mat[2].State |
Combinations. |
Flag.3 |
Bit 3 of an atomic (BOOL/SINT/INT/DINT/LINT and unsigned variants). |
| The CIP wire only carries the parent path; the bit is masked client-side. |
Trailing .N (digit-only suffix) is treated as bit access, not as an array element.
Use [N] for explicit array indexing.
API overview
RockwellPlcClient
public sealed class RockwellPlcClient : IAsyncDisposable
{
public RockwellPlcClient(RockwellPlcClientOptions options, ILogger? logger = null);
public bool IsConnected { get; }
public int ReconnectCount { get; }
public Task EnsureConnectedAsync(CancellationToken ct = default);
public Task<TagReadResult> ReadAsync(string tagName, CancellationToken ct = default);
public Task<IReadOnlyList<TagReadResult>> ReadManyAsync(
IReadOnlyList<string> tagNames, CancellationToken ct = default);
public Task<IReadOnlyList<MspRawResult>> ReadRawAsync(
IReadOnlyList<string> tagNames, CancellationToken ct = default);
}
TagReadResult (decoded)
public sealed record TagReadResult(
string TagName,
byte GeneralStatus,
ushort? TypeCode,
byte[]? ValueBytes,
int? BitNumber,
string? DecodedValue,
string? ErrorMessage)
{
public bool IsSuccess { get; } // GeneralStatus == 0 && DecodedValue != null
}
MspRawResult (raw — for ReadRawAsync)
public sealed record MspRawResult(
string TagName,
byte GeneralStatus,
ushort? TypeCode,
byte[]? ValueBytes,
string? ErrorMessage)
{
public bool IsSuccess { get; } // GeneralStatus == 0 && ValueBytes != null
}
Use ReadRawAsync when you want to decode ValueBytes yourself
(e.g. raw byte stream, custom format, or ushort/uint arithmetic without going via string).
Logging
Pass any ILogger from Microsoft.Extensions.Logging.Abstractions. null is accepted —
the client will use NullLogger.Instance internally. The client only logs warnings on
transient failures and reconnection events; there are no info-level chatter logs in the
hot path.
Threading
A single RockwellPlcClient instance owns one TCP socket and serializes all requests
through an internal semaphore. Sharing one instance across threads / tasks is safe and the
intended usage pattern; create one instance per PLC you need to talk to.
Releasing
Releases to nuget.org are automated by .github/workflows/ci.yml:
- Bump
<Version>inRockwellTagReader.csprojand add aCHANGELOG.mdentry. - Commit and push to
main. - Tag and push:
git tag v0.1.0 && git push origin v0.1.0. - The workflow's
publishjob picks up the tag, downloads the builtnupkg/snupkg, and pushes them to https://api.nuget.org/v3/index.json using theNUGET_API_KEYrepository secret.
To enable the publish step, configure on GitHub:
- Settings → Secrets and variables → Actions → add
NUGET_API_KEY(generate at https://www.nuget.org/account/apikeys). - (Optional, recommended) Settings → Environments → create an
environment called
nugetand add a required reviewer to gate the publish job behind a manual approval. The workflow already references this environment.
License
MIT — Copyright (c) 2026 Ewerton Guimarães.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.4)
-
net6.0
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.4)
-
net7.0
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.4)
-
net8.0
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.4)
-
net9.0
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 89 | 5/7/2026 |