OmronPlcRx 1.0.12

dotnet add package OmronPlcRx --version 1.0.12
                    
NuGet\Install-Package OmronPlcRx -Version 1.0.12
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OmronPlcRx" Version="1.0.12" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OmronPlcRx" Version="1.0.12" />
                    
Directory.Packages.props
<PackageReference Include="OmronPlcRx" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OmronPlcRx --version 1.0.12
                    
#r "nuget: OmronPlcRx, 1.0.12"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OmronPlcRx@1.0.12
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OmronPlcRx&version=1.0.12
                    
Install as a Cake Addin
#tool nuget:?package=OmronPlcRx&version=1.0.12
                    
Install as a Cake Tool

OmronPlcRx

A Reactive Omron PLC communications library for .NET (netstandard2.0, net8.0, net9.0, net10.0).

OmronPlcRx provides a high-level, reactive, strongly typed interface for interacting with Omron PLCs over the FINS protocol using TCP or UDP. It handles:

  • Connection setup & initialization (controller model / version discovery)
  • Bit and word memory area reads & writes
  • PLC clock & scan cycle time access (now exposed via public wrapper methods)
  • Reactive polling of configured PLC tag addresses
  • Type handling for: bool, byte, short, ushort, int (2 words), uint (2 words), float (2 words IEEE 754), double (4 words IEEE 754), string (variable length, ASCII, packed 2 chars/word), BCD numeric wrappers: Bcd16, BcdU16 (1 word), Bcd32, BcdU32 (2 words)
  • Error propagation via reactive streams
  • Automatic PLC capability limits (read/write length, area availability)

Contents:

  • Features
  • Installation
  • Quick Start
  • Addressing Guide
  • Supported Types & Encoding
  • BCD Types
  • Reactive Tag & System API
  • Code Samples
  • Direct Read/Write Core API Overview
  • PLC Clock & Cycle Time
  • Error Handling
  • PLC Types & Limits
  • FAQ
  • Contributing
  • License / Disclaimer

Features

  • TCP or UDP transport selection
  • Automatic controller identification (model, version, PLC type classification)
  • Reactive IObservable<T> streams per tag and an aggregate stream of all tag changes
  • Background polling loop with configurable interval (default 100 ms)
  • Safe concurrent access with internal caching
  • Strong typing for tag values; built-in conversion for common primitives & BCD wrappers
  • Bit addressing (e.g. D10.3, W100.7, CIO200.0)
  • Word + multi‑word (32/64‑bit) assembly (High word first for multi-word numerics)
  • Public accessors for PLC real-time clock & scan cycle time (ReadClockAsync, WriteClockAsync, ReadCycleTimeAsync)
  • Exception surfacing via Errors observable
  • Targets legacy & modern runtimes

Installation

NuGet:

dotnet add package OmronPlcRx

OR (Package Manager):

Install-Package OmronPlcRx

Quick Start

using System;
using System.Reactive.Linq;
using OmronPlcRx;
using OmronPlcRx.Enums;
using OmronPlcRx.Core.Types; // BCD wrappers

class Program
{
    static async Task Main()
    {
        var plc = new OmronPlcRx.OmronPlcRx(
            localNodeId: 11,
            remoteNodeId: 1,
            connectionMethod: ConnectionMethod.UDP,
            remoteHost: "192.168.250.1",
            port: 9600,
            timeout: 2000,
            retries: 1,
            pollInterval: TimeSpan.FromMilliseconds(200));

        // Register tags (variable = PLC address, tagName = logical name)
        plc.AddUpdateTagItem<bool>("D100.0", "MotorRun");
        plc.AddUpdateTagItem<short>("D200", "TemperatureRaw");
        plc.AddUpdateTagItem<int>("D300", "BatchCounter");          // D300,D301
        plc.AddUpdateTagItem<float>("D400", "TankLevel");            // D400,D401
        plc.AddUpdateTagItem<double>("D500", "TotalizedFlow");       // D500..D503
        plc.AddUpdateTagItem<string>("D600[20]", "LineName");        // 20 ASCII chars max (10 words)
        plc.AddUpdateTagItem<Bcd16>("D700", "BcdTemp");              // 1 word BCD
        plc.AddUpdateTagItem<Bcd32>("D710", "BcdCount");             // 2 word BCD
        plc.AddUpdateTagItem<uint>("D720", "UnsignedCounter");       // 2 words
        plc.AddUpdateTagItem<byte>("D730", "SmallFlagByte");         // stored in low byte of word

        // Observe single tag
        var sub1 = plc.Observe<bool>("MotorRun")
            .DistinctUntilChanged()
            .Subscribe(v => Console.WriteLine($"MotorRun -> {v}"));

        // Observe all tag changes
        var subAll = plc.ObserveAll
            .Subscribe(tag => Console.WriteLine($"Tag {tag?.TagName} = {tag?.Value}"));

        // Observe errors
        var errSub = plc.Errors.Subscribe(e => Console.WriteLine($"ERROR: {e?.Message}"));

        // Write (async fire-and-forget)
        plc.Value("MotorRun", true);
        plc.Value("LineName", "Filling Line 1");
        plc.Value("BcdTemp", new Bcd16(235)); // writes BCD 0235

        // Read last cached value
        var tempRaw = plc.Value<short>("TemperatureRaw");
        Console.WriteLine($"Temp raw = {tempRaw}");

        // Direct clock & cycle time access
        var clock = await plc.ReadClockAsync();
        Console.WriteLine($"PLC Clock: {clock.Clock:o}");
        var cycle = await plc.ReadCycleTimeAsync();
        Console.WriteLine($"Cycle Times: Min={cycle.MinimumCycleTime} Max={cycle.MaximumCycleTime} Avg={cycle.AverageCycleTime}");

        Console.ReadKey();
        plc.Dispose();
    }
}

Addressing Guide

Supported memory area prefixes:

  • D / DM : Data Memory (bits & words)
  • C / CIO : Common IO area
  • W : Work area
  • H : Holding area
  • A : Auxiliary area (restricted on some models)

Bit address syntax: <Area><Word>.<Bit> where Bit = 0..15
Examples: D100.0, W50.7, CIO200.15

Word address syntax: <Area><Word>
Examples: D200, H10, CIO500

String address syntax: <Area><Word>[<Length>] (length in characters). Example: D600[20] reserves 20 ASCII chars (10 words). If no length specified, default = 16 chars. Strings are ASCII, packed 2 chars per word (high-byte then low-byte). Null padding is applied if shorter.


Supported Types & Encoding

Type Words Notes
bool 1 bit or 1 word If bit address used reads single bit; if word address treats non-zero as true
byte 1 word Stored in low byte (high byte = 0). Read masks low 8 bits
short 1 word Signed 16-bit
ushort 1 word Unsigned 16-bit
int 2 words High word first
uint 2 words High word first
float 2 words IEEE 754 single; word order high/low; bytes swapped for host endianness
double 4 words IEEE 754 double; high word first
string N words ASCII, 2 chars per word, length via [len] suffix (default 16)
Bcd16 / BcdU16 1 word Packed BCD (4 digits max)
Bcd32 / BcdU32 2 words Packed BCD (8 digits max)

Multi-word numeric order: the library consistently treats the first configured word as the high-order word for 32/64-bit and BCD 32-bit values.


BCD Types

BCD wrappers provide type safety and avoid ambiguity with standard binary-coded integer storage.

  • Bcd16 : signed 16-bit decimal (1 word)
  • BcdU16: unsigned 16-bit decimal (1 word)
  • Bcd32 : signed 32-bit decimal (2 words, high word first)
  • BcdU32: unsigned 32-bit decimal (2 words)

Example:

plc.AddUpdateTagItem<Bcd32>("D800", "BatchNumber");
plc.Observe<Bcd32>("BatchNumber")
   .Subscribe(v => Console.WriteLine($"BatchNumber -> {v?.Value}"));
plc.Value("BatchNumber", new Bcd32(12345678));

Reactive Tag & System API (IOmronPlcRx)

Methods / Properties:

  • Tag management: AddUpdateTagItem<T>(variable, tagName)
  • Tag observation: IObservable<T?> Observe<T>(tagName)
  • Aggregated tag stream: IObservable<IPlcTag?> ObserveAll
  • Cached value access: T? Value<T>(tagName)
  • Async write (fire & forget): Value<T>(tagName, value)
  • Error stream: IObservable<OmronPLCException?> Errors
  • Disposal state: bool IsDisposed
  • PLC identity: PLCType PLCType, ControllerModel, ControllerVersion
  • Clock / cycle time: ReadClockAsync(), WriteClockAsync(DateTime), WriteClockAsync(DateTime,int), ReadCycleTimeAsync()

Clock/cycle methods return strongly typed result structs (Bytes/Packets sent/received, Duration and payload data).


Code Samples

  1. Mirror / inverted control
plc.AddUpdateTagItem<bool>("D10.0", "SourceFlag");
plc.AddUpdateTagItem<bool>("D11.0", "TargetFlag");
plc.Observe<bool>("SourceFlag")
   .DistinctUntilChanged()
   .Subscribe(v => plc.Value("TargetFlag", !v));
  1. Unsigned counter & rollover detection
plc.AddUpdateTagItem<uint>("D300", "PulseCounter");
plc.Observe<uint>("PulseCounter")
   .Pairwise()
   .Subscribe(p =>
   {
       var (prev, curr) = (p.FirstOrDefault(), p.Last());
       if (curr < prev) Console.WriteLine("Counter rollover detected");
   });
  1. BCD production count
plc.AddUpdateTagItem<Bcd32>("D500", "ProdCount");
plc.Observe<Bcd32>("ProdCount")
   .DistinctUntilChanged()
   .Subscribe(v => Console.WriteLine($"Production Count = {v?.Value}"));
  1. Double precision accumulation
plc.AddUpdateTagItem<double>("D600", "EnergyKWh");
plc.Observe<double>("EnergyKWh")
   .Sample(TimeSpan.FromSeconds(5))
   .Subscribe(v => Console.WriteLine($"Energy = {v:F3} kWh"));
  1. ASCII string with fixed length
plc.AddUpdateTagItem<string>("D700[16]", "OperatorName");
plc.Value("OperatorName", "ALICE");
plc.Observe<string>("OperatorName")
   .DistinctUntilChanged()
   .Subscribe(n => Console.WriteLine($"Operator = {n}"));
  1. Byte & UShort handling
plc.AddUpdateTagItem<byte>("D720", "StatusByte");
plc.AddUpdateTagItem<ushort>("D721", "RawWord");
plc.Value("StatusByte", (byte)0x3A);
plc.Value("RawWord", (ushort)1234);
  1. Reading the PLC clock & writing adjustments
var clockResult = await plc.ReadClockAsync();
Console.WriteLine($"Clock: {clockResult.Clock:o}");
await plc.WriteClockAsync(DateTime.UtcNow); // sync PLC clock to current time
  1. Cycle time monitoring
var cycle = await plc.ReadCycleTimeAsync();
Console.WriteLine($"Scan Cycle ms -> Min:{cycle.MinimumCycleTime} Max:{cycle.MaximumCycleTime} Avg:{cycle.AverageCycleTime}");

PLC Clock & Cycle Time

Clock & cycle time can be read/written via public async methods:

var clock = await plc.ReadClockAsync();
await plc.WriteClockAsync(DateTime.UtcNow);
var cycle = await plc.ReadCycleTimeAsync();

Returned structs include transmission statistics and payload data.


Error Handling

Operational exceptions (initialization failure, invalid address, read/write errors, unsupported type) are pushed into Errors:

plc.Errors.Subscribe(e => Console.WriteLine($"[PLC ERR] {e?.Message}"));

Write errors also surface here because Value<T>(...) is fire-and-forget.


PLC Types & Limits

Controller type is inferred automatically. Impacts maximum read/write word lengths & area availability.

PLCType enum includes: CP1, CJ2, NJ101, NJ301, NJ501, NX1P2, NX102, NX701, NY512, NY532, NJ_NX_NY_Series, C_Series, Unknown.


FAQ

Q: Multi-word numeric reads seem incorrect.
A: Ensure the base word matches how the PLC stores multi-word values. Library expects high word first.

Q: String garbage characters?
A: Verify length spec [len] matches actual reserved word space and encoding is ASCII.

Q: BCD value off?
A: Confirm the PLC really stores the value in packed BCD and digits do not exceed capacity (4 or 8 digits).

Q: Change polling interval after creation?
A: Not currently. Dispose and recreate with new interval.

Q: Add new areas (e.g., EM / AR)?
A: Extend mapping logic in OmronPlcRx.


Contributing

PRs welcome. Ideas:

  • Additional data types (arrays, DateTime wrapper enhancements)
  • Auto-reconnect & health monitoring
  • Structured logging integration hooks

License

MIT License — see LICENSE.


Disclaimer

Not affiliated with Omron. Use at your own risk. Validate in a safe test environment before deployment to production systems.


OmronPlcRx - Empowering Industrial Automation with Reactive Technology ⚡🏭

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
1.0.12 26 9/30/2025

Compatability with Net 8 / 9 / 10 and netstandard2.0