MitsubishiRx.Reactive 3.0.1

dotnet add package MitsubishiRx.Reactive --version 3.0.1
                    
NuGet\Install-Package MitsubishiRx.Reactive -Version 3.0.1
                    
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="MitsubishiRx.Reactive" Version="3.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MitsubishiRx.Reactive" Version="3.0.1" />
                    
Directory.Packages.props
<PackageReference Include="MitsubishiRx.Reactive" />
                    
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 MitsubishiRx.Reactive --version 3.0.1
                    
#r "nuget: MitsubishiRx.Reactive, 3.0.1"
                    
#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 MitsubishiRx.Reactive@3.0.1
                    
#: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=MitsubishiRx.Reactive&version=3.0.1
                    
Install as a Cake Addin
#tool nuget:?package=MitsubishiRx.Reactive&version=3.0.1
                    
Install as a Cake Tool

MitsubishiRx

<div align="center"> <img src="Images/image-icon.png" style="width:25%;" /> </div>

Reactive Mitsubishi PLC client for MC Protocol / SLMP in C# with ReactiveUI.Primitives, ReactiveUI.Primitives.Reactive, SerialPortRx, and SerialPortRx.Reactive integration.

This README is the primary usage guide for the library. It explains:

  • which PLC families, Ethernet frame types, and serial frame types are supported
  • how to configure TCP/UDP/serial transports, binary/ASCII encodings, serial message formats, and X/Y notation
  • how to use every public feature exposed by the client
  • how to build and import a tag database so application code can use tag names instead of PLC addresses
  • what CSV format is required to initialize the tag database

Documentation map

Use this README as the complete in-repository documentation source:

Need Start here
Install and choose package basics Install
Choose between MitsubishiRx and MitsubishiRx.Reactive Package variants
Select PLC family, frame, transport, data encoding, serial format, or X/Y notation Supported PLC families and how to choose settings
Configure MitsubishiClientOptions and MitsubishiSerialOptions Core configuration
Connect, disconnect, and monitor connection state Connection lifecycle
Use every high-level PLC operation with C# examples Feature guide: every public operation
Generate strongly typed tag and group clients Generated typed client surface
Configure symbolic tags, typed helpers, groups, schema files, validation, hot reload, diffs, and rollout policies Tag database: use tag names instead of PLC addresses
Match APIs to PLC families and endpoint types PLC-family-specific usage guidance
Find concise feature-to-method mapping Feature-to-API quick map
Look up signatures, return types, models, enums, constants, advanced extension points, and generated APIs Complete API reference
Diagnose common setup and communication problems Troubleshooting notes

Install

dotnet add package MitsubishiRx

Use the MitsubishiRx.Reactive package when the consuming application is already using the ReactiveUI.Primitives.Reactive package family and wants the same Mitsubishi PLC API surface compiled against the reactive bridge packages:

dotnet add package MitsubishiRx.Reactive

Do not reference both packages from the same project unless you intentionally want both namespaces. They expose the same client type names under different namespaces.


Package variants

MitsubishiRx and MitsubishiRx.Reactive are built from the same source code. The reactive package is a linked-source shim that defines REACTIVE_SHIM, changes the public namespace to MitsubishiRx.Reactive, and swaps the project-level using aliases to the ReactiveUI.Primitives.Reactive and SerialPortRx.Reactive package families.

Package Namespace Target frameworks Reactive package family Serial package Best fit
MitsubishiRx MitsubishiRx net8.0, net9.0, net10.0, net11.0 ReactiveUI.Primitives, ReactiveUI.Primitives.Async, ReactiveUI.Primitives.Extensions SerialPortRx Default package for applications using the lean ReactiveUI.Primitives stack.
MitsubishiRx.Reactive MitsubishiRx.Reactive net8.0, net9.0, net10.0, net11.0 ReactiveUI.Primitives.Reactive, ReactiveUI.Primitives.Extensions.Reactive SerialPortRx.Reactive Applications using the System.Reactive-backed ReactiveUI.Primitives reactive bridge packages.

The API concepts are intentionally the same across both packages:

Concept MitsubishiRx type MitsubishiRx.Reactive type
Client MitsubishiRx.MitsubishiRx MitsubishiRx.Reactive.MitsubishiRx
Options MitsubishiRx.MitsubishiClientOptions MitsubishiRx.Reactive.MitsubishiClientOptions
Tag database MitsubishiRx.MitsubishiTagDatabase MitsubishiRx.Reactive.MitsubishiTagDatabase
Response envelope MitsubishiRx.Responce<T> MitsubishiRx.Reactive.Responce<T>
Reactive value envelope MitsubishiRx.MitsubishiReactiveValue<T> MitsubishiRx.Reactive.MitsubishiReactiveValue<T>
Write pipeline MitsubishiRx.MitsubishiReactiveWritePipeline<TPayload> MitsubishiRx.Reactive.MitsubishiReactiveWritePipeline<TPayload>
Custom transport MitsubishiRx.IMitsubishiTransport MitsubishiRx.Reactive.IMitsubishiTransport

Every feature shown in this README works with both packages unless a section explicitly calls out the source generator. The generated typed client currently emits the base MitsubishiRx namespace, so use the runtime tag, group, polling, and write-pipeline APIs for MitsubishiRx.Reactive projects.

Same feature, different namespace

Most code changes are limited to the namespace import and the fully qualified client name when you need one.

using MitsubishiRx;

var options = new MitsubishiClientOptions(
    Host: "192.168.0.10",
    Port: 5000,
    FrameType: MitsubishiFrameType.ThreeE,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Tcp);

await using var client = new MitsubishiRx.MitsubishiRx(options);
var words = await client.ReadWordsAsync("D100", 2);

Reactive package equivalent:

using MitsubishiRx.Reactive;

var options = new MitsubishiClientOptions(
    Host: "192.168.0.10",
    Port: 5000,
    FrameType: MitsubishiFrameType.ThreeE,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Tcp);

await using var client = new MitsubishiRx.Reactive.MitsubishiRx(options);
var words = await client.ReadWordsAsync("D100", 2);

Reactive package scheduler and trigger types

Both packages expose IObservable<T> APIs. The project files use using aliases so the shared source stays the same, but the compiled scheduler and unit trigger types differ by package:

API concept MitsubishiRx MitsubishiRx.Reactive
Scheduler constructor parameter ReactiveUI.Primitives.Concurrency.ISequencer System.Reactive.Concurrency.IScheduler
Default scheduler ReactiveUI.Primitives.Concurrency.Sequencer.Default System.Reactive.Concurrency.Scheduler.Default
Trigger unit for ObserveWordsLatest / ObserveTagGroupLatest ReactiveUI.Primitives.RxVoid System.Reactive.Unit
Observable factories used by implementation ReactiveUI.Primitives.Signals.Signal System.Reactive.Linq.Observable
Disposable factory used by implementation ReactiveUI.Primitives.Disposables.Scope System.Reactive.Disposables.Disposable

Reactive package trigger example:

using MitsubishiRx.Reactive;
using System.Reactive.Linq;

IObservable<System.Reactive.Unit> trigger =
    Observable.Interval(TimeSpan.FromSeconds(5)).Select(_ => System.Reactive.Unit.Default);

await using var client = new MitsubishiRx.Reactive.MitsubishiRx(options);

using var latest = client.ObserveWordsLatest("D100", 2, trigger)
    .Subscribe(result =>
    {
        if (result.IsSucceed)
        {
            Console.WriteLine(string.Join(", ", result.Value!));
        }
    });

What this library provides

MitsubishiRx was refactored from a low-level socket wrapper into a protocol-aware Mitsubishi PLC client that:

  • supports 1E, 3E, and 4E Ethernet frame families
  • supports 1C, 3C, and 4C serial frame families
  • supports TCP, UDP, and reactive serial transports
  • uses SerialPortRx or SerialPortRx.Reactive for reactive serial communications, depending on the selected package
  • supports binary and ASCII MC Protocol / SLMP packet encodings
  • supports direct device addressing and symbolic tag-name-based access
  • exposes high-level async APIs for reads, writes, remote control, monitor, block, random, loopback, memory, and diagnostics operations
  • exposes ReactiveUI.Primitives- or ReactiveUI.Primitives.Reactive-based polling and health streams for reactive applications
  • includes TUnit tests running on Microsoft Testing Platform

Supported PLC families and how to choose settings

The library now covers Mitsubishi Ethernet and serial MC protocol paths. Ethernet support remains the broadest and deepest implementation. Serial support is now integrated through SerialPortRx and currently provides the first verified reactive serial path.

Supported family guidance

PLC family / endpoint type Typical frame Transport Notes
A / AnS with legacy Ethernet interfaces 1E TCP/UDP Use when the target only exposes legacy A-compatible MC protocol behavior.
QnA-compatible Ethernet endpoints 3E TCP/UDP Default modern choice for most Q/QnA-compatible MC protocol use.
Q / L / iQ-R / iQ-F / FX5 with modern SLMP/MC protocol endpoints 3E or 4E TCP/UDP 3E is the normal first choice. 4E is used when serial correlation is required.
FX3 compatibility paths 1E or 3E depending on module/path TCP/UDP Use the path documented for the installed Ethernet interface or gateway.
FX3 / A-compatible serial computer-link style paths 1C Serial Use for installed bases exposing serial MC / computer link compatible message structures.
QnA-compatible serial modules 3C Serial ASCII serial MC protocol path.
QnA-compatible serial modules with extended access 4C Serial ASCII or binary serial MC protocol path, depending on configured format.

Transport selection

Transport When to use
MitsubishiTransportKind.Tcp Default choice for most PLC integrations. Use when you want connection-oriented request/response behavior.
MitsubishiTransportKind.Udp Use when the target is configured for UDP SLMP/MC protocol and you want datagram-style communication.
MitsubishiTransportKind.Serial Use for RS-232/RS-422/RS-485 MC protocol communication. The library uses SerialPortRx to provide the reactive serial transport implementation.

Data encoding selection

Encoding When to use
CommunicationDataCode.Binary Default for most applications. Smaller frames and simpler payload handling. Required for 4C serial format 5.
CommunicationDataCode.Ascii Use when the target requires ASCII MC protocol / SLMP or when matching existing ASCII integrations. Required for 1C and 3C.

Serial message format selection

Serial MC protocol communication also depends on the serial message format configured on the PLC/module side.

Serial message format Meaning Typical use
MitsubishiSerialMessageFormat.Format1 ASCII serial framing with ENQ/STX/ACK/NAK control characters Legacy 1C/3C/4C serial ASCII integrations
MitsubishiSerialMessageFormat.Format4 ASCII serial framing with CR/LF delimiters Serial endpoints configured for CR/LF terminated MC protocol
MitsubishiSerialMessageFormat.Format5 Binary serial framing using DLE/STX/ETX 4C binary serial communication

X/Y addressing notation

Mitsubishi X and Y device addressing is module/family dependent. The client makes that explicit.

Setting Meaning
XyAddressNotation.Octal Interpret X10 as octal 8. This is common for classic Mitsubishi behavior.
XyAddressNotation.Hexadecimal Interpret X10 as hexadecimal 16. Use when the Ethernet path/module is documented that way.

Core configuration

All communication starts from MitsubishiClientOptions.

using MitsubishiRx;

var options = new MitsubishiClientOptions(
    Host: "192.168.0.10",
    Port: 5000,
    FrameType: MitsubishiFrameType.ThreeE,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Tcp,
    Route: MitsubishiRoute.Default,
    MonitoringTimer: 0x0010,
    Timeout: TimeSpan.FromSeconds(4),
    CpuType: CpuType.None,
    XyNotation: XyAddressNotation.Octal);

Option reference

Option Meaning
Host PLC IP address / DNS name for Ethernet, or serial port name such as COM3 when using serial transport
Port Ethernet port exposed by the PLC/module. Use 0 for serial transport.
FrameType OneE, ThreeE, FourE, OneC, ThreeC, or FourC
DataCode Binary or Ascii
TransportKind Tcp, Udp, or Serial
Route SLMP route metadata for 3E/4E
MonitoringTimer PLC-side monitoring timer in 250 ms units
Timeout Client-side transport timeout
CpuType Optional family hint
XyNotation Octal or hexadecimal parsing for X/Y
LegacyPcNumber 1E PC number
SerialNumberProvider 4E serial number generator
Serial MitsubishiSerialOptions describing the serial port and serial MC protocol framing

Serial transport configuration

When using serial MC protocol communication, populate the Serial option and set TransportKind.Serial.

using MitsubishiRx;
using System.IO.Ports;

var serialOptions = new MitsubishiClientOptions(
    Host: "COM3",
    Port: 0,
    FrameType: MitsubishiFrameType.FourC,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Serial,
    Timeout: TimeSpan.FromSeconds(2),
    CpuType: CpuType.Fx5,
    Serial: new MitsubishiSerialOptions(
        PortName: "COM3",
        BaudRate: 9600,
        DataBits: 7,
        Parity: Parity.Even,
        StopBits: StopBits.One,
        Handshake: Handshake.None,
        MessageFormat: MitsubishiSerialMessageFormat.Format5,
        StationNumber: 0x00,
        NetworkNumber: 0x00,
        PcNumber: 0xFF,
        RequestDestinationModuleIoNumber: 0x03FF,
        RequestDestinationModuleStationNumber: 0x00,
        SelfStationNumber: 0x00,
        MessageWait: 0x00));

Serial option reference

Serial option Meaning
PortName Serial port name, such as COM3
BaudRate Configured baud rate
DataBits Configured data bits
Parity Configured serial parity
StopBits Configured stop bits
Handshake Configured hardware/software flow control
MessageFormat Serial MC message format: Format1, Format4, or Format5
StationNumber Target station number
NetworkNumber Target network number for 3C/4C
PcNumber Target PC number
RequestDestinationModuleIoNumber Request destination module I/O number for 4C routing
RequestDestinationModuleStationNumber Request destination module station number for 4C routing
SelfStationNumber Self-station number for multidrop layouts
MessageWait Serial message wait in 10 ms units
ReadBufferSize / WriteBufferSize Serial driver buffer sizing
NewLine Newline sequence used by line-oriented serial modes

Default route

For direct own-station CPU access:

var route = MitsubishiRoute.Default;

For routed access, supply explicit route values:

var route = new MitsubishiRoute(
    NetworkNumber: 0x00,
    StationNumber: 0xFF,
    ModuleIoNumber: 0x03FF,
    MultidropStationNumber: 0x00);

Creating the client

using MitsubishiRx;

await using var client = new MitsubishiRx.MitsubishiRx(options);

Legacy constructor

A compatibility constructor is also available:

var client = new MitsubishiRx.MitsubishiRx(CpuType.QnA, "192.168.0.10", 5000, timeout: 1500);

Connection lifecycle

Open / close

var open = await client.OpenAsync();
if (!open.IsSucceed)
{
    Console.WriteLine(open.Err);
}

var close = await client.CloseAsync();

Synchronous wrappers are also available:

var openSync = client.Open();
var closeSync = client.Close();

Connection state stream

using var states = client.ConnectionStates.Subscribe(state =>
{
    Console.WriteLine($"Connection state: {state}");
});

Possible values:

  • Disconnected
  • Connecting
  • Connected
  • Reconnecting
  • Faulted

Feature guide: every public operation

The sections below map directly to the client’s public API.


1. Batch word reads and writes

Read words by PLC address

var result = await client.ReadWordsAsync("D100", 2);
if (result.IsSucceed)
{
    ushort d100 = result.Value![0];
    ushort d101 = result.Value[1];
}

Read words over serial MC protocol

using MitsubishiRx;
using System.IO.Ports;

var serialOptions = new MitsubishiClientOptions(
    Host: "COM3",
    Port: 0,
    FrameType: MitsubishiFrameType.FourC,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Serial,
    Timeout: TimeSpan.FromSeconds(2),
    CpuType: CpuType.Fx5,
    Serial: new MitsubishiSerialOptions(
        PortName: "COM3",
        BaudRate: 9600,
        DataBits: 7,
        Parity: Parity.Even,
        StopBits: StopBits.One,
        Handshake: Handshake.None,
        MessageFormat: MitsubishiSerialMessageFormat.Format5));

await using var serialClient = new MitsubishiRx.MitsubishiRx(serialOptions);
var serialRead = await serialClient.ReadWordsAsync("D100", 2);

Write words by PLC address

var write = await client.WriteWordsAsync("D100", new ushort[] { 123, 456, 789 });

When to use

  • data registers like D, W, R, ZR
  • word-based timer/counter values like TN, CN, SD
  • bulk register transfers

PLC family guidance

  • 1E: use for legacy-compatible batch device operations over Ethernet
  • 3E/4E: preferred path for modern Ethernet PLCs
  • 1C/3C/4C: use when the installed connection is serial MC protocol rather than Ethernet

Serial support status

Current serial implementation status:

Serial area Status
Reactive serial transport via SerialPortRx Implemented
Serial frame modeling (1C, 3C, 4C) Implemented
Serial option/configuration surface Implemented
Batch word read over serial Implemented
Batch word write over serial Implemented
Batch bit read over serial Implemented
Batch bit write over serial Implemented
Random word read over serial Implemented for 1C, 3C, and 4C
Random word write over serial Implemented for 1C, 3C, and 4C
1C ASCII format 1/4 decode path Implemented
3C ASCII format 1/4 decode path Implemented
4C ASCII and binary format 5 decode path Implemented
Serial block read/write Implemented for 1C, 3C, and 4C
Serial monitor registration/execution Implemented for 1C, 3C, and 4C
Serial remote control commands Implemented for 1C, 3C, and 4C
Serial type-name read Implemented for 1C, tested 3C ASCII, and 4C format 5
Serial loopback Implemented for 1C, tested 3C ASCII, and 4C format 5
Serial memory / extend-unit read-write Implemented for 1C, tested 3C ASCII, and 4C format 5
Raw serial command execution Implemented for 1C, tested 3C ASCII, and 4C format 5

2. Batch bit reads and writes

Read bits by PLC address

var bits = await client.ReadBitsAsync("M10", 8);
if (bits.IsSucceed)
{
    bool m10 = bits.Value![0];
    bool m11 = bits.Value[1];
}

Write bits by PLC address

var writeBits = await client.WriteBitsAsync("M10", new[] { true, false, true, true });

Common device examples

  • M internal relays
  • X inputs
  • Y outputs
  • L, SM, TS, TC, CS, CC

X/Y notation example

var octalOptions = options with { XyNotation = XyAddressNotation.Octal };
var hexOptions = options with { XyNotation = XyAddressNotation.Hexadecimal };

3. Random reads and writes

Use random operations when you need non-contiguous word devices.

Random read words

var randomRead = await client.RandomReadWordsAsync(new[]
{
    "D100",
    "D250",
    "W10",
    "ZR200",
});

Random write words

var randomWrite = await client.RandomWriteWordsAsync(new Dictionary<string, ushort>
{
    ["D100"] = 100,
    ["D250"] = 250,
    ["W10"] = 0x1234,
});

Best fit

  • sparse register collection
  • HMI/status pages pulling scattered registers
  • writing a small set of independent values without multiple round-trips

4. Monitor registration and monitor execution

Monitoring is a two-stage operation.

Register monitor devices

var register = await client.RegisterMonitorAsync(new[]
{
    "D100",
    "D101",
    "D102",
});

Execute monitor

var monitor = await client.ExecuteMonitorAsync();
if (monitor.IsSucceed)
{
    byte[] rawMonitorPayload = monitor.Value!;
}

Best fit

  • repeated observation of a fixed register list
  • lightweight monitoring loops coordinated by your application

5. Multiple block read and write

Use block operations when you want grouped contiguous word and/or bit blocks.

Read blocks

var blockRequest = new MitsubishiBlockRequest(
    WordBlocks:
    [
        new MitsubishiWordBlock(MitsubishiDeviceAddress.Parse("D100"), new ushort[10]),
        new MitsubishiWordBlock(MitsubishiDeviceAddress.Parse("W20", XyAddressNotation.Octal), new ushort[4]),
    ],
    BitBlocks:
    [
        new MitsubishiBitBlock(MitsubishiDeviceAddress.Parse("M10"), new bool[16]),
    ]);

var blockRead = await client.ReadBlocksAsync(blockRequest);

Write blocks

var writeRequest = new MitsubishiBlockRequest(
    WordBlocks:
    [
        new MitsubishiWordBlock(MitsubishiDeviceAddress.Parse("D100"), new ushort[] { 1, 2, 3, 4 }),
    ],
    BitBlocks:
    [
        new MitsubishiBitBlock(MitsubishiDeviceAddress.Parse("M10"), new[] { true, false, true, false }),
    ]);

var blockWrite = await client.WriteBlocksAsync(writeRequest);

Best fit

  • grouped transfer plans
  • deterministic read/write structures
  • coalesced data exchange where address continuity matters

6. PLC type-name read

var typeName = await client.ReadTypeNameAsync();
if (typeName.IsSucceed)
{
    Console.WriteLine(typeName.Value!.ModelName);
    Console.WriteLine(typeName.Value.ModelCode);
}

Best fit

  • startup diagnostics
  • logging exact connected PLC/module type
  • validation that the integration is pointing at the expected target

7. Remote control operations

Remote RUN

await client.RemoteRunAsync(force: true, clearMode: false);

Remote STOP / PAUSE / RESET / LATCH CLEAR

await client.RemoteStopAsync();
await client.RemotePauseAsync();
await client.RemoteResetAsync();
await client.RemoteLatchClearAsync();

Notes

  • available behavior depends on PLC family, CPU mode, permissions, and Ethernet module configuration
  • use carefully in production systems

8. Remote password unlock / lock

await client.UnlockAsync("1234");
await client.LockAsync("1234");

Best fit

  • workflows where protected remote operations must be explicitly unlocked

9. Clear error

var clear = await client.ClearErrorAsync();

Best fit

  • acknowledging module/PLC error conditions after diagnostic handling

10. Loopback

var loop = await client.LoopbackAsync(new byte[] { 0x12, 0x34, 0x56, 0x78 });
if (loop.IsSucceed)
{
    var echoed = loop.Value!;
}

Best fit

  • link validation
  • protocol path smoke tests
  • troubleshooting Ethernet routes or gateway behavior

11. Memory read / write and intelligent-module access

These methods expose raw memory/buffer style commands.

Memory read

var memory = await client.ReadMemoryAsync(MitsubishiCommands.MemoryRead, address: 0x2000, length: 4);

Memory write

var memoryWrite = await client.WriteMemoryAsync(MitsubishiCommands.MemoryWrite, address: 0x2000, values: new ushort[] { 1, 2, 3, 4 });

Extend unit read/write

var unitRead = await client.ReadMemoryAsync(MitsubishiCommands.ExtendUnitRead, address: 0x0100, length: 8);
var unitWrite = await client.WriteMemoryAsync(MitsubishiCommands.ExtendUnitWrite, address: 0x0100, values: new ushort[] { 10, 20, 30 });

Best fit

  • intelligent function module buffer memory access
  • lower-level system data exchange where documented by Mitsubishi manuals

12. Raw command execution

For advanced or unsupported workflows, execute a raw request.

var raw = await client.ExecuteRawAsync(
    new MitsubishiRawCommandRequest(
        Command: MitsubishiCommands.DeviceRead,
        Subcommand: 0x0000,
        Body: Array.Empty<byte>(),
        Description: "Custom raw op"));

Best fit

  • experimental protocol work
  • custom command shapes
  • validating edge-case protocol scenarios

13. Reactive polling and diagnostics

Reactive features are built with ReactiveUI.Primitives.

Observe words

using var subscription = client
    .ObserveWords("D100", 2, TimeSpan.FromSeconds(1))
    .Subscribe(result =>
    {
        if (result.IsSucceed)
        {
            Console.WriteLine(string.Join(", ", result.Value!));
        }
    });

Observe bits

using var bitSubscription = client
    .ObserveBits("M10", 8, TimeSpan.FromMilliseconds(500))
    .Subscribe(result =>
    {
        if (result.IsSucceed)
        {
            Console.WriteLine(string.Join(", ", result.Value!));
        }
    });

Observe words with heartbeat

using var heartbeatSub = client
    .ObserveWordsHeartbeat(
        "D100",
        2,
        pollInterval: TimeSpan.FromSeconds(1),
        heartbeatAfter: TimeSpan.FromSeconds(2))
    .Subscribe(sample =>
    {
        if (sample.IsHeartbeat)
        {
            Console.WriteLine("Heartbeat");
            return;
        }

        Console.WriteLine(string.Join(", ", sample.Update.Value!));
    });

Observe words with stale detection

using var staleSub = client
    .ObserveWordsStale(
        "D100",
        2,
        pollInterval: TimeSpan.FromSeconds(1),
        staleAfter: TimeSpan.FromSeconds(5))
    .Subscribe(state =>
    {
        Console.WriteLine($"Is stale: {state.IsStale}");
    });

Triggered latest-only reads

using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Signals;

var trigger = new Signal<RxVoid>();
using var latestSub = client
    .ObserveWordsLatest("D100", 2, trigger)
    .Subscribe(result => Console.WriteLine(result.IsSucceed));

trigger.OnNext(RxVoid.Default);

Reactive tag-group polling

Once tag groups are defined, you can observe grouped snapshots with the same reactive patterns used by the lower-level word/bit APIs.

Observe a tag group

using var groupSub = client
    .ObserveTagGroup("Line1Overview", TimeSpan.FromSeconds(1))
    .Subscribe(result =>
    {
        if (!result.IsSucceed || result.Value is null)
        {
            return;
        }

        var snapshot = result.Value;
        Console.WriteLine($"Temp={snapshot.GetRequired<short>("SignedTemp")}");
        Console.WriteLine($"Count={snapshot.GetRequired<uint>("TotalCount")}");
        Console.WriteLine($"Message={snapshot.GetRequired<string>("OperatorMessage")}");
        Console.WriteLine($"Pump={snapshot.GetRequired<bool>("PumpRunning")}");
    });

Observe a tag group with heartbeat

using var groupHeartbeat = client
    .ObserveTagGroupHeartbeat(
        "Line1Overview",
        pollInterval: TimeSpan.FromSeconds(5),
        heartbeatAfter: TimeSpan.FromSeconds(2))
    .Subscribe(sample =>
    {
        if (sample.IsHeartbeat)
        {
            Console.WriteLine("Group heartbeat");
            return;
        }

        var snapshot = sample.Update!.Value!;
        Console.WriteLine(snapshot.GetRequired<uint>("TotalCount"));
    });

Observe a tag group with stale detection

using var groupStale = client
    .ObserveTagGroupStale(
        "Line1Overview",
        pollInterval: TimeSpan.FromSeconds(5),
        staleAfter: TimeSpan.FromSeconds(2))
    .Subscribe(state =>
    {
        Console.WriteLine($"Group stale={state.IsStale}");
    });

Triggered latest-only grouped reads

using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Signals;

var groupTrigger = new Signal<RxVoid>();
using var latestGroup = client
    .ObserveTagGroupLatest("Line1Overview", groupTrigger)
    .Subscribe(result =>
    {
        if (result.IsSucceed && result.Value is not null)
        {
            Console.WriteLine(result.Value.GetRequired<uint>("TotalCount"));
        }
    });

groupTrigger.OnNext(RxVoid.Default);

These grouped reactive APIs are useful for HMI/dashboard polling loops because they keep the application written against stable symbolic names instead of raw PLC addresses.

Reactive hot shared value streams

using var reactiveWords = client
    .ObserveReactiveWords("D100", 2, TimeSpan.FromSeconds(1))
    .Subscribe(value =>
    {
        if (value.Quality == MitsubishiReactiveQuality.Good && value.Value is not null)
        {
            Console.WriteLine($"Words: {string.Join(", ", value.Value)} @ {value.TimestampUtc:u}");
        }
    });
using var reactiveTag = client
    .ObserveReactiveTag<float>("MotorSpeed", TimeSpan.FromMilliseconds(250))
    .Subscribe(value =>
    {
        if (value.Quality == MitsubishiReactiveQuality.Good)
        {
            Console.WriteLine($"MotorSpeed={value.Value}");
        }
    });
using var reactiveGroup = client
    .ObserveReactiveTagGroup("Line1Overview", TimeSpan.FromSeconds(1))
    .Subscribe(value =>
    {
        if (value.Quality == MitsubishiReactiveQuality.Good && value.Value is not null)
        {
            Console.WriteLine(value.Value.GetRequired<uint>("TotalCount"));
        }
    });

These planner-backed reactive APIs are shared/hot streams with replay of the latest value and teardown when the final subscriber unsubscribes.

Reactive write pipelines

var setpointWrites = client.CreateReactiveTagWritePipeline<float>(
    "Setpoint",
    MitsubishiReactiveWriteMode.LatestWins,
    coalescingWindow: TimeSpan.FromMilliseconds(100));

using var writeResults = setpointWrites.Results.Subscribe(result =>
    Console.WriteLine($"Write success={result.Success} target={result.Target} error={result.Error}"));

setpointWrites.Post(12.5f);
setpointWrites.Post(13.0f);

Supported modes:

  • Queued
  • LatestWins
  • Coalescing

The same pipeline API is available from the reactive package by changing the namespace:

using MitsubishiRx.Reactive;

var setpointWrites = client.CreateReactiveTagWritePipeline<float>(
    "Setpoint",
    MitsubishiReactiveWriteMode.Coalescing,
    coalescingWindow: TimeSpan.FromMilliseconds(250));

using var writeResults = setpointWrites.Results.Subscribe(result =>
    Console.WriteLine($"{result.Target}: {result.Success}"));

setpointWrites.Post(42.0f);

Generated typed client surface

MitsubishiRx.Generators is the Roslyn incremental source generator bundled with the package. It turns a compile-time tag schema into a strongly typed facade over the normal runtime tag APIs.

You do not normally reference MitsubishiRx.Generators directly. The MitsubishiRx package includes MitsubishiRx.Generators.dll under analyzers/dotnet/cs, so SDK-style consumer projects get the analyzer automatically:

dotnet add package MitsubishiRx

The MitsubishiRx.Reactive package also carries the analyzer asset, but the generator currently emits namespace MitsubishiRx and extends the base global::MitsubishiRx.MitsubishiRx client type. For MitsubishiRx.Reactive projects, use the runtime tag APIs (ReadFloatByTagAsync, ReadTagGroupSnapshotAsync, ObserveReactiveTag<T>, CreateReactiveTagWritePipeline<T>, and related members) until generator namespace support is added for the reactive shim.

Generator feature summary
Feature Generated API Runtime API used
Schema marker MitsubishiTagClientSchemaAttribute Compile-time marker only
Client entrypoint client.Generated() Wraps the existing MitsubishiRx instance
Tag catalog client.Generated().Tags.<TagName> Per-tag generated client
Group catalog client.Generated().Groups.<GroupName> Per-group generated client
Typed tag reads ReadAsync(CancellationToken) Typed Read...ByTagAsync helpers
Typed tag writes WriteAsync(value, CancellationToken) Typed Write...ByTagAsync helpers
Typed tag observation Observe(pollInterval, minimumUpdateSpacing) ObserveReactiveTag<T>
Typed group reads ReadAsync(CancellationToken) ReadTagGroupSnapshotAsync plus generated required mapping
Optional group reads ReadOptionalAsync(CancellationToken) ReadTagGroupSnapshotAsync plus generated optional mapping
Typed group writes WriteAsync(TSnapshot, CancellationToken) WriteTagGroupSnapshotAsync
Typed group observation Observe(...) / ObserveOptional(...) ObserveReactiveTagGroup plus generated projection
Snapshot helpers FromSnapshot, TryFromSnapshot, ToSnapshot, MapReactive, MapReactiveOptional Converts between runtime and generated snapshot shapes
Compile-time validation MRTXGEN001 through MRTXGEN011 Reports invalid schemas as compiler errors
Schema attribute and JSON contract

Add [MitsubishiTagClientSchema(...)] to a class or assembly. The constructor takes one compile-time string value. Class-level usage:

using MitsubishiRx;

[MitsubishiTagClientSchema(
    """
    {
      "tags": [
        { "name": "MotorSpeed", "address": "D100", "dataType": "Float" },
        { "name": "Mode", "address": "D101", "dataType": "UInt16" }
      ],
      "groups": [
        { "name": "Line1", "tagNames": ["MotorSpeed", "Mode"] }
      ]
    }
    """)]
internal sealed class PlcSchema;

Assembly-level usage:

using MitsubishiRx;

[assembly: MitsubishiTagClientSchema(
    """
    {
      "tags": [
        { "name": "MotorSpeed", "dataType": "Float" },
        { "name": "Mode", "dataType": "UInt16" },
        { "name": "PumpRunning", "dataType": "Bit" }
      ],
      "groups": [
        { "name": "Line1", "tagNames": ["MotorSpeed", "Mode", "PumpRunning"] }
      ]
    }
    """)]

Generator schema fields:

JSON field Required Meaning
tags No Array of tag entries exposed under client.Generated().Tags.
tags[].name Yes Runtime tag name and generated identifier source. It must match a tag in client.TagDatabase at runtime.
tags[].dataType No Generated .NET type and runtime helper selection. Missing or null defaults to UInt16 / ushort.
groups No Array of group entries exposed under client.Generated().Groups.
groups[].name Yes Runtime group name and generated identifier source. It must match a group in client.TagDatabase at runtime.
groups[].tagNames Yes Ordered tag names used to build the generated snapshot record.

The generator ignores extra schema properties such as address, description, scale, offset, units, length, and byteOrder. Keep those in the runtime MitsubishiTagDatabase; generated clients call the normal tag APIs by name, and the runtime database supplies addresses, scaling, string length, byte order, and other PLC metadata.

MitsubishiTagClientSchemaAttribute targets assemblies and classes, allows multiple attributes, and is not inherited. Current generation uses the first collected non-empty schema value, so prefer one authoritative schema attribute per consuming project.

Supported generated data types

Use the casing shown in this table. Schema validation is case-insensitive, but the current generated API mapping is matched with these canonical names.

Schema dataType Generated .NET type Read method Write method
missing / null ushort ReadUInt16ByTagAsync WriteUInt16ByTagAsync
Word ushort ReadUInt16ByTagAsync WriteUInt16ByTagAsync
UInt16 ushort ReadUInt16ByTagAsync WriteUInt16ByTagAsync
Int16 short ReadInt16ByTagAsync WriteInt16ByTagAsync
Int32 int ReadInt32ByTagAsync WriteInt32ByTagAsync
DWord uint ReadDWordByTagAsync WriteDWordByTagAsync
UInt32 uint ReadDWordByTagAsync WriteDWordByTagAsync
Float float ReadFloatByTagAsync WriteFloatByTagAsync
String string ReadStringByTagAsync WriteStringByTagAsync
Bit bool ReadGeneratedBitTagAsync WriteGeneratedBitTagAsync
Generated client entrypoint and catalogs
GeneratedMitsubishiTagClient generated = client.Generated();

var tags = generated.Tags;
var groups = generated.Groups;

Generated property names are sanitized from schema names:

Schema name Generated identifier
MotorSpeed MotorSpeed
Motor Speed MotorSpeed
Line 1 Overview Line1Overview
9Mode _9Mode

If two names sanitize to the same identifier, the generator reports MRTXGEN005.

Generated tag clients

For each schema tag, the generator emits a property under Tags and a sealed tag client type:

var motorSpeed = client.Generated().Tags.MotorSpeed;

Responce<float> read = await motorSpeed.ReadAsync();
if (read.IsSucceed)
{
    Console.WriteLine($"Motor speed: {read.Value} rpm");
}

Responce write = await motorSpeed.WriteAsync(123.4f);
Console.WriteLine(write.IsSucceed ? "Speed written" : write.Err);

Generated tag client API:

API Signature Purpose
ReadAsync Task<Responce<T>> ReadAsync(CancellationToken cancellationToken = default) Reads the runtime tag by name using the generated .NET type.
WriteAsync Task<Responce> WriteAsync(T value, CancellationToken cancellationToken = default) Writes the runtime tag by name using the generated .NET type.
Observe IObservable<MitsubishiReactiveValue<T>> Observe(TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Polls the runtime tag and emits quality envelopes.

Tag observation example:

using var generatedTagSub = client.Generated().Tags.MotorSpeed
    .Observe(TimeSpan.FromMilliseconds(250))
    .Subscribe(value =>
    {
        if (value.Quality == MitsubishiReactiveQuality.Good)
        {
            Console.WriteLine(value.Value);
        }
    });

Generated tags still require client.TagDatabase to contain matching tag names at runtime:

client.TagDatabase = MitsubishiTagDatabase.FromJson("""
{
  "tags": [
    { "name": "MotorSpeed", "address": "D100", "dataType": "Float" },
    { "name": "Mode", "address": "D102", "dataType": "UInt16" },
    { "name": "PumpRunning", "address": "M10", "dataType": "Bit" }
  ],
  "groups": [
    { "name": "Line1", "tagNames": ["MotorSpeed", "Mode", "PumpRunning"] }
  ]
}
""");
Generated group clients and snapshots

For each schema group, the generator emits a group property under Groups, a sealed group client type, and a partial snapshot record named <GroupName>Snapshot. For the Line1 schema above, the snapshot shape is:

public sealed partial record Line1Snapshot(float MotorSpeed, ushort Mode, bool PumpRunning);

Read and write a generated group snapshot:

var line1 = await client.Generated().Groups.Line1.ReadAsync();
if (line1.Value is not null)
{
    Console.WriteLine($"Mode={line1.Value.Mode}");

    var updated = line1.Value with { Mode = 2 };
    await client.Generated().Groups.Line1.WriteAsync(updated);
}

Generated group client API:

API Signature Purpose
ReadAsync Task<Responce<TSnapshot>> ReadAsync(CancellationToken cancellationToken = default) Reads the runtime group and maps all values through TSnapshot.FromSnapshot.
ReadOptionalAsync Task<Responce<TSnapshot?>> ReadOptionalAsync(CancellationToken cancellationToken = default) Reads the runtime group and returns null when values are missing or wrong-typed.
WriteAsync Task<Responce> WriteAsync(TSnapshot value, CancellationToken cancellationToken = default) Converts the generated snapshot to MitsubishiTagGroupSnapshot and writes it through WriteTagGroupSnapshotAsync.
Observe IObservable<MitsubishiReactiveValue<TSnapshot>> Observe(TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Observes the runtime group and maps each value to the generated snapshot type.
ObserveOptional IObservable<MitsubishiReactiveValue<TSnapshot?>> ObserveOptional(TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Observes the runtime group and emits nullable generated snapshots when values are incomplete or mismatched.

Optional read and group observation examples:

var optionalLine1 = await client.Generated().Groups.Line1.ReadOptionalAsync();
Console.WriteLine(optionalLine1.Value?.Mode);

using var generatedGroupSub = client.Generated().Groups.Line1.Observe(TimeSpan.FromSeconds(1))
    .Subscribe(value => Console.WriteLine(value.Value?.Mode));

using var optionalGeneratedGroupSub = client.Generated().Groups.Line1.ObserveOptional(TimeSpan.FromSeconds(1))
    .Subscribe(value => Console.WriteLine(value.Value?.Mode));

Generated snapshot helper API:

API Signature Purpose
FromSnapshot static TSnapshot FromSnapshot(MitsubishiTagGroupSnapshot snapshot) Required conversion using snapshot.GetRequired<T>(tagName). Throws if a value is missing or has the wrong type.
TryFromSnapshot static TSnapshot? TryFromSnapshot(MitsubishiTagGroupSnapshot? snapshot) Optional conversion. Returns null for missing snapshots, missing tag values, or invalid casts.
ToSnapshot MitsubishiTagGroupSnapshot ToSnapshot() Converts the generated record back to a runtime group snapshot for writes.
MapReactive static MitsubishiReactiveValue<TSnapshot> MapReactive(MitsubishiReactiveValue<MitsubishiTagGroupSnapshot> value) Converts a runtime reactive group envelope to a generated typed envelope. Mapping errors become MitsubishiReactiveQuality.Error.
MapReactiveOptional static MitsubishiReactiveValue<TSnapshot?> MapReactiveOptional(MitsubishiReactiveValue<MitsubishiTagGroupSnapshot> value) Converts a runtime reactive group envelope to a nullable generated typed envelope.
MitsubishiTagGroupSnapshot runtimeSnapshot = line1.Value!.ToSnapshot();
Line1Snapshot typed = Line1Snapshot.FromSnapshot(runtimeSnapshot);
Line1Snapshot? optional = Line1Snapshot.TryFromSnapshot(runtimeSnapshot);
Compile-time diagnostics

The generator validates schema authoring mistakes before generated API use reaches runtime. Diagnostics use category MitsubishiRx.Generators and severity Error.

ID Title Meaning Fix
MRTXGEN001 Failed to generate Mitsubishi tag client JSON parsing or generation failed unexpectedly. Check that the schema string is valid JSON.
MRTXGEN002 Duplicate generated tag name Two tag entries have the same name, case-insensitively. Keep each tag name unique.
MRTXGEN003 Unknown generated group tag reference A group tagNames entry does not match any schema tag. Add the missing tag or remove/fix the group reference.
MRTXGEN004 Unsupported generated tag data type A tag uses a dataType outside the supported generator set. Use Bit, Word, DWord, Float, String, Int16, UInt16, Int32, or UInt32.
MRTXGEN005 Generated identifier collision Different tag or group names sanitize to the same C# identifier. Rename one schema item, for example avoid both Motor Speed and Motor-Speed.
MRTXGEN006 Empty generated tag name A tag name is missing, empty, or whitespace. Supply a non-empty tags[].name.
MRTXGEN007 Empty generated group name A group name is missing, empty, or whitespace. Supply a non-empty groups[].name.
MRTXGEN008 Empty generated group membership A group has no tagNames. Add at least one tag reference or remove the group.
MRTXGEN009 Duplicate generated group name Two group entries have the same name, case-insensitively. Keep each group name unique.
MRTXGEN010 Empty generated group tag reference A group contains an empty or whitespace tag reference. Remove the empty entry or replace it with a valid tag name.
MRTXGEN011 Duplicate generated group tag reference A group references the same tag more than once, case-insensitively. Keep each group membership list unique.

Invalid schema example:

[MitsubishiTagClientSchema(
    """
    {
      "tags": [
        { "name": "Motor Speed", "dataType": "Float" },
        { "name": "Motor-Speed", "dataType": "UInt16" }
      ],
      "groups": [
        { "name": "Line1", "tagNames": ["MissingTag"] }
      ]
    }
    """)]
internal sealed class InvalidSchema;

This schema reports MRTXGEN005 because both tag names sanitize to MotorSpeed, and MRTXGEN003 because Line1 references MissingTag.

Generated-client startup checklist

Use this pattern in production applications:

client.TagDatabase = MitsubishiTagDatabase.Load("plc-tags.yaml");

var validation = client.ValidateTagDatabase();
if (!validation.IsSucceed)
{
    throw new InvalidOperationException(validation.Err);
}

await client.OpenAsync();

var generated = client.Generated();
var mode = await generated.Tags.Mode.ReadAsync();
Console.WriteLine(mode.IsSucceed ? mode.Value : mode.Err);

The compile-time generator schema and runtime tag database should describe the same tag and group names. The generator gives strongly typed code; the runtime database still controls how those names map to PLC devices.

Operation logs and sampled diagnostics

using var logs = client.OperationLogs.Subscribe(log =>
{
    Console.WriteLine($"{log.TimestampUtc:u} {log.Description} success={log.Success}");
});
using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Signals;

var diagnosticTrigger = Signal.Interval(TimeSpan.FromSeconds(2)).Select(_ => new object());
using var diagnostics = client.SampleDiagnostics(diagnosticTrigger).Subscribe(log =>
{
    Console.WriteLine(log.Description);
});

Connection health

using var health = client.ObserveConnectionHealth(TimeSpan.FromSeconds(10)).Subscribe(state =>
{
    Console.WriteLine($"Connection stale={state.IsStale}, state={state.Update}");
});

Reactive operators used internally

The library meaningfully uses these ReactiveUI.Primitives.Extensions operators:

  • RetryWithBackoff(...)
  • SelectAsyncSequential(...)
  • SelectLatestAsync(...)
  • Heartbeat(...)
  • DetectStale(...)
  • Conflate(...)
  • SampleLatest(...)
  • DoOnSubscribe(...)
  • DoOnDispose(...)

Tag database: use tag names instead of PLC addresses

For production applications, raw addresses like D100 and M10 usually belong in configuration, not code.

The library now includes an in-memory tag database that maps symbolic names to PLC addresses.

What it gives you

Instead of this:

var speed = await client.ReadWordsAsync("D100", 2);
var pump = await client.ReadBitsAsync("M10", 1);
await client.WriteWordsAsync("D300", new ushort[] { 12 });
await client.RandomWriteWordsAsync(new[]
{
    new KeyValuePair<string, ushort>("D500", 100),
    new KeyValuePair<string, ushort>("D501", 200),
});

you can do this:

var speed = await client.ReadWordsByTagAsync("MotorSpeed", 2);
var pump = await client.ReadBitsByTagAsync("PumpRunning", 1);
await client.WriteWordsByTagAsync("RecipeNumber", new ushort[] { 12 });
await client.RandomWriteWordsByTagAsync(new[]
{
    new KeyValuePair<string, ushort>("RecipeSetpointA", 100),
    new KeyValuePair<string, ushort>("RecipeSetpointB", 200),
});

Tag database types

  • MitsubishiTagDefinition
  • MitsubishiTagDatabase
  • MitsubishiTagGroupDefinition
  • MitsubishiTagGroupSnapshot
  • MitsubishiRx.TagDatabase
  • ReadWordsByTagAsync(...)
  • ReadBitsByTagAsync(...)
  • WriteWordsByTagAsync(...)
  • WriteBitsByTagAsync(...)
  • RandomReadWordsByTagAsync(...)
  • RandomWriteWordsByTagAsync(...)
  • ReadInt16ByTagAsync(...)
  • WriteInt16ByTagAsync(...)
  • ReadUInt16ByTagAsync(...)
  • WriteUInt16ByTagAsync(...)
  • ReadInt32ByTagAsync(...)
  • WriteInt32ByTagAsync(...)
  • ReadDWordByTagAsync(...)
  • WriteDWordByTagAsync(...)
  • ReadFloatByTagAsync(...)
  • WriteFloatByTagAsync(...)
  • ReadScaledDoubleByTagAsync(...)
  • WriteScaledDoubleByTagAsync(...)
  • ReadStringByTagAsync(...)
  • WriteStringByTagAsync(...)
  • ValidateTagDatabase()
  • ReadTagGroupSnapshotAsync(...)

Build a tag database in code

using MitsubishiRx;

var tags = new MitsubishiTagDatabase(new[]
{
    new MitsubishiTagDefinition(
        Name: "MotorSpeed",
        Address: "D100",
        DataType: "Word",
        Description: "Main spindle RPM",
        Scale: 0.1,
        Offset: 0.0,
        Notes: "Engineering scaling 0.1 RPM per count"),

    new MitsubishiTagDefinition(
        Name: "PumpRunning",
        Address: "M10",
        DataType: "Bit",
        Description: "Coolant pump running"),

    new MitsubishiTagDefinition(
        Name: "RecipeNumber",
        Address: "D300",
        DataType: "Word",
        Description: "Selected recipe number"),

    new MitsubishiTagDefinition(
        Name: "RecipeSetpointA",
        Address: "D500",
        DataType: "Word"),

    new MitsubishiTagDefinition(
        Name: "RecipeSetpointB",
        Address: "D501",
        DataType: "Word"),
});

client.TagDatabase = tags;

Read and write using tag names

var speed = await client.ReadWordsByTagAsync("MotorSpeed", 2);
var running = await client.ReadBitsByTagAsync("PumpRunning", 1);

await client.WriteWordsByTagAsync("RecipeNumber", new ushort[] { 12 });
await client.WriteBitsByTagAsync("PumpRunning", new[] { true });

Random operations using tag names

var recipeValues = await client.RandomReadWordsByTagAsync(new[]
{
    "RecipeSetpointA",
    "RecipeSetpointB",
    "RecipeNumber",
});

await client.RandomWriteWordsByTagAsync(new[]
{
    new KeyValuePair<string, ushort>("RecipeSetpointA", 100),
    new KeyValuePair<string, ushort>("RecipeSetpointB", 200),
    new KeyValuePair<string, ushort>("RecipeNumber", 12),
});

Typed tag helpers

Use DataType to make tag intent explicit and then call the typed helpers directly.

var signedTemp = await client.ReadInt16ByTagAsync("SignedTemp");
await client.WriteInt16ByTagAsync("SignedTemp", -100);

var wordValue = await client.ReadUInt16ByTagAsync("RecipeNumber");
await client.WriteUInt16ByTagAsync("RecipeNumber", 12);

var signedTotal = await client.ReadInt32ByTagAsync("SignedTotal");
await client.WriteInt32ByTagAsync("SignedTotal", 123456);

var totalCount = await client.ReadDWordByTagAsync("TotalCount");
await client.WriteDWordByTagAsync("TotalCount", 123456u);

var processValue = await client.ReadFloatByTagAsync("ProcessValue");
await client.WriteFloatByTagAsync("ProcessValue", 12.5f);

Integer helpers supported in the current API surface:

  • ReadInt16ByTagAsync(...)
  • WriteInt16ByTagAsync(...)
  • ReadUInt16ByTagAsync(...)
  • WriteUInt16ByTagAsync(...)
  • ReadInt32ByTagAsync(...)
  • WriteInt32ByTagAsync(...)
  • ReadDWordByTagAsync(...)
  • WriteDWordByTagAsync(...)
  • ReadFloatByTagAsync(...)
  • WriteFloatByTagAsync(...)

Int32, UInt32/DWord, and Float values are encoded across two Mitsubishi words. ByteOrder controls whether those two words are interpreted as LittleEndian or BigEndian.

Scaled engineering values

If a tag carries engineering metadata in Scale and Offset, use the scaled helpers so application code can work with engineering units instead of raw PLC counts.

var headTemp = await client.ReadScaledDoubleByTagAsync("HeadTemp");
await client.WriteScaledDoubleByTagAsync("HeadTemp", 15.0d);

With this CSV row:

Name,Address,DataType,Scale,Offset
HeadTemp,D200,Word,0.1,-10

the PLC raw value 250 becomes (250 * 0.1) + (-10) = 15.0.

Scaled read/write currently supports:

  • Word
  • DWord
  • Float

PLC strings using tag names

For PLC text stored in word registers, use the string helpers with an explicit word length or let the tag schema provide it.

var message = await client.ReadStringByTagAsync("OperatorMessage", wordLength: 8);
await client.WriteStringByTagAsync("OperatorMessage", "READY", wordLength: 8);

var schemaDrivenMessage = await client.ReadStringByTagAsync("Utf8Message");
await client.WriteStringByTagAsync("Utf8Message", "Aé");

String values are packed into successive words using the configured Encoding. Each Mitsubishi word stores two bytes, and ByteOrder controls how those two bytes are packed inside each word.

How tag resolution works

  • tag names are resolved case-insensitively
  • the resolved tag supplies the PLC Address
  • DataType is validated when tags are added or imported from CSV
  • supported DataType values are:
    • Bit
    • Word
    • DWord
    • Float
    • String
  • DataType matching is case-insensitive and normalized to the canonical values above
  • the current tag-based convenience API supports:
    • ReadWordsByTagAsync(...)
    • ReadBitsByTagAsync(...)
    • WriteWordsByTagAsync(...)
    • WriteBitsByTagAsync(...)
    • RandomReadWordsByTagAsync(...)
    • RandomWriteWordsByTagAsync(...)
    • ReadDWordByTagAsync(...)
    • WriteDWordByTagAsync(...)
    • ReadFloatByTagAsync(...)
    • WriteFloatByTagAsync(...)
    • ReadScaledDoubleByTagAsync(...)
    • WriteScaledDoubleByTagAsync(...)
    • ReadStringByTagAsync(...)
    • WriteStringByTagAsync(...)
  • tag APIs are transport/frame agnostic: they work with 1E, 3E, 4E, TCP, UDP, Binary, and Ascii wherever the underlying operation is supported by the target PLC family/module
  • all tag APIs eventually resolve to the same raw address-based methods, so protocol behavior stays identical after resolution
  • other operations can still use the same database manually:
var tag = client.TagDatabase!.GetRequired("RecipeSetpointA");
await client.WriteWordsAsync(tag.Address, new ushort[] { 2500 });
  • store PLC addressing in CSV/configuration
  • load it at application startup
  • assign it to client.TagDatabase
  • keep application logic written against stable tag names
  • let maintenance teams change addresses in CSV without changing application code
  • define MitsubishiTagGroupDefinition scan classes for common screens, loops, and reporting views
  • call ValidateTagDatabase() during startup so bad addresses, missing string lengths, and broken group references fail fast

Tag groups and grouped snapshots

For higher-level workflows, define named groups of tags and read them as a single heterogeneous snapshot.

var tags = MitsubishiTagDatabase.FromCsv(File.ReadAllText("plc-tags.csv"));

tags.AddGroup(new MitsubishiTagGroupDefinition(
    Name: "Line1Overview",
    TagNames: new[]
    {
        "SignedTemp",
        "TotalCount",
        "OperatorMessage",
        "PumpRunning",
    }));

tags.AddGroup(new MitsubishiTagGroupDefinition(
    Name: "RecipeWrite",
    TagNames: new[]
    {
        "RecipeNumber",
        "OperatorMessage",
    }));

client.TagDatabase = tags;

var validation = client.ValidateTagDatabase();
if (!validation.IsSucceed)
{
    throw new InvalidOperationException(validation.Err);
}

var snapshot = await client.ReadTagGroupSnapshotAsync("Line1Overview");

var signedTemp = snapshot.Value!.GetRequired<short>("SignedTemp");
var totalCount = snapshot.Value.GetRequired<uint>("TotalCount");
var operatorMessage = snapshot.Value.GetRequired<string>("OperatorMessage");
var pumpRunning = snapshot.Value.GetRequired<bool>("PumpRunning");

Use tag groups when you want:

  • startup validation of known screen/report/scan-class dependencies
  • a single named collection for related tags
  • typed access to heterogeneous values without scattering tag names across the application

Grouped writes and setpoint commits

For HMI/setpoint workflows, validate and write only the values you want to commit.

var pendingValues = new Dictionary<string, object?>
{
    ["RecipeNumber"] = (ushort)7,
    ["OperatorMessage"] = "OK!",
};

var writeValidation = client.ValidateTagGroupWrite("RecipeWrite", pendingValues);
if (!writeValidation.IsSucceed)
{
    throw new InvalidOperationException(writeValidation.Err);
}

await client.WriteTagGroupValuesAsync("RecipeWrite", pendingValues);

You can also write a full grouped snapshot directly:

var writeSnapshot = new MitsubishiTagGroupSnapshot(
    "Line1Overview",
    new Dictionary<string, object?>
    {
        ["SignedTemp"] = (short)-100,
        ["TotalCount"] = 0x12345678u,
        ["OperatorMessage"] = "OK!",
    });

await client.WriteTagGroupSnapshotAsync(writeSnapshot);

ValidateTagGroupWrite(...) reports:

  • values whose CLR types do not match the target tag schema
  • values for tags that are not part of the named group
  • the same underlying tag/schema issues already enforced by the individual tag helpers

CSV import: initialize the tag database from a file

You can initialize the tag database directly from CSV.

Supported required/optional columns

Column Required Meaning
Name Yes Unique symbolic tag name used by application code
Address Yes Mitsubishi PLC address such as D100, M10, X20, ZR200
DataType No Type hint such as Bit, Word, DWord, Float, String, Int16, UInt16, Int32, UInt32
Description No Human-readable description
Scale No Engineering scale factor, default 1.0
Offset No Engineering offset, default 0.0
Length No Logical tag length in PLC words, mainly used by string tags and fixed-width layouts
Encoding No Text encoding hint such as Ascii, Utf8, or Utf16
Units No Engineering units label such as rpm, °C, or items
Signed No Boolean signedness hint for integer word/double-word tags, default false
ByteOrder No Multi-word and string packing order: LittleEndian or BigEndian
Notes No Free-form notes

Required CSV formatting

  • first row must be a header row
  • at minimum the header must include:
    • Name
    • Address
  • column names are matched case-insensitively
  • blank lines are ignored
  • quoted CSV fields are supported
  • embedded double quotes inside quoted fields should be escaped as ""
  • numeric Scale, Offset, and Length values should use invariant-culture formatting
  • Signed should be true or false
  • do not include engineering units inside Scale or Offset
  • Address must contain a valid Mitsubishi device address string usable by the client
  • if DataType is supplied it must be one of:
    • Bit
    • Word
    • DWord
    • Float
    • String
    • Int16
    • UInt16
    • Int32
    • UInt32
  • if Encoding is supplied it must be one of:
    • Ascii
    • Utf8
    • Utf16
  • if ByteOrder is supplied it must be one of:
    • LittleEndian
    • BigEndian
  • DataType, Encoding, and ByteOrder matching is case-insensitive when imported, but stored in canonical form
  • use one logical PLC item per CSV row
  • keep Name unique across the file so it can be used safely as the application lookup key

Example CSV file

Name,Address,DataType,Description,Scale,Offset,Length,Encoding,Units,Signed,ByteOrder,Notes
MotorSpeed,D100,Word,Main spindle RPM,0.1,0,,,rpm,false,,From commissioning sheet
PumpRunning,M10,Bit,Coolant pump running,1,0,,,,false,,
HeadTemp,D200,Word,Head temperature,0.1,-10,,,°C,true,,Signed engineering temperature tag
RecipeNumber,D300,UInt16,Selected recipe,1,0,,,recipe,false,,
TotalCount,D400,UInt32,Accumulated production count,1,0,,,items,false,LittleEndian,32-bit unsigned counter
ProcessValue,D500,Float,Engineering process value,1,0,,,bar,false,LittleEndian,IEEE754 single precision across two words
OperatorMessage,D600,String,Operator status message,1,0,8,Ascii,,false,LittleEndian,Packed text in word registers
SignedTemp,D700,Int16,Signed temperature raw count,1,0,,,counts,true,,Two's complement 16-bit value
SignedTotal,D710,Int32,Signed accumulated count,1,0,,,items,true,BigEndian,Big-endian multiword example
Utf8Message,D720,String,UTF-8 operator text,1,0,2,Utf8,,false,LittleEndian,Schema-driven string length
ServoReady,M100,Bit,Servo ready,1,0,,,,false,,
XAxisLimit,X20,Bit,X-axis forward limit,1,0,,,,false,,X uses configured XyNotation
ZoneRegister,ZR200,Word,Zone parameter register,1,0,,,,false,,

Load from a CSV string

var csv = File.ReadAllText("plc-tags.csv");
var tagDatabase = MitsubishiTagDatabase.FromCsv(csv);
client.TagDatabase = tagDatabase;

Or load the same CSV directly by file extension:

client.TagDatabase = MitsubishiTagDatabase.Load("plc-tags.csv");

Full startup example

using MitsubishiRx;

var options = new MitsubishiClientOptions(
    Host: "192.168.0.10",
    Port: 5000,
    FrameType: MitsubishiFrameType.ThreeE,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Tcp,
    Route: MitsubishiRoute.Default,
    MonitoringTimer: 0x0010,
    XyNotation: XyAddressNotation.Octal);

var csv = File.ReadAllText("plc-tags.csv");
var tags = MitsubishiTagDatabase.FromCsv(csv);

await using var client = new MitsubishiRx.MitsubishiRx(options)
{
    TagDatabase = tags,
};

var speed = await client.ReadWordsByTagAsync("MotorSpeed", 2);
var pump = await client.ReadBitsByTagAsync("PumpRunning", 1);
await client.WriteUInt16ByTagAsync("RecipeNumber", 7);
await client.RandomWriteWordsByTagAsync(new[]
{
    new KeyValuePair<string, ushort>("RecipeSetpointA", 100),
    new KeyValuePair<string, ushort>("RecipeSetpointB", 200),
});

var signedTemp = await client.ReadInt16ByTagAsync("SignedTemp");
var signedTotal = await client.ReadInt32ByTagAsync("SignedTotal");
var totalCount = await client.ReadDWordByTagAsync("TotalCount");
var processValue = await client.ReadFloatByTagAsync("ProcessValue");
var engineeringTemp = await client.ReadScaledDoubleByTagAsync("HeadTemp");
var operatorMessage = await client.ReadStringByTagAsync("OperatorMessage");
var utf8Message = await client.ReadStringByTagAsync("Utf8Message");

JSON and YAML schema workflows

CSV is useful for spreadsheets and maintenance exports, but JSON/YAML are better when you want full schema persistence, groups, and richer version-controlled configuration.

Export the full schema to JSON

client.TagDatabase!.Save("plc-tags.json");

Equivalent explicit form:

var json = client.TagDatabase!.ToJson();
File.WriteAllText("plc-tags.json", json);

Load the full schema from JSON

client.TagDatabase = MitsubishiTagDatabase.Load("plc-tags.json");

Equivalent explicit form:

var json = File.ReadAllText("plc-tags.json");
var tags = MitsubishiTagDatabase.FromJson(json);
client.TagDatabase = tags;

Export the full schema to YAML

client.TagDatabase!.Save("plc-tags.yaml");
client.TagDatabase!.Save("plc-tags.yml");

Equivalent explicit form:

var yaml = client.TagDatabase!.ToYaml();
File.WriteAllText("plc-tags.yaml", yaml);

Load the full schema from YAML

client.TagDatabase = MitsubishiTagDatabase.Load("plc-tags.yaml");
client.TagDatabase = MitsubishiTagDatabase.Load("plc-tags.yml");

Equivalent explicit form:

var yaml = File.ReadAllText("plc-tags.yaml");
var tags = MitsubishiTagDatabase.FromYaml(yaml);
client.TagDatabase = tags;

Example YAML schema with groups

tags:
  - name: SignedTemp
    address: D700
    dataType: Int16
    signed: true
    units: °C
  - name: OperatorMessage
    address: D600
    dataType: String
    length: 2
    encoding: Utf8
    byteOrder: BigEndian
groups:
  - name: Overview
    tagNames:
      - SignedTemp
      - OperatorMessage

Use JSON/YAML when you want:

  • full schema persistence including groups
  • easier code review of tag model changes in version control
  • richer metadata than a compact CSV worksheet usually carries

Use MitsubishiTagDatabase.Load(path) / Save(path) when you want one-line startup configuration or persistence with automatic detection for .csv, .json, .yaml, and .yml.

For commissioning workflows, client.LoadAndValidateTagDatabase(path) loads, validates, and applies the schema in one step, client.PreviewTagDatabaseDiff(path) shows what would change before you commit it, and client.ObserveTagDatabaseReload(path, pollInterval) / client.ObserveTagDatabaseDiff(path, pollInterval) provide reactive reload and audit streams. Use rollout policies when you want to allow metadata/group edits automatically while blocking address or datatype changes.

Load, validate, and apply a schema in one step

var loadResult = client.LoadAndValidateTagDatabase("plc-tags.yaml");
if (!loadResult.IsSucceed)
{
    throw new InvalidOperationException(loadResult.Err);
}

Reactively hot-reload a schema during commissioning

using var schemaReload = client
    .ObserveTagDatabaseReload("plc-tags.yaml", TimeSpan.FromSeconds(2))
    .Subscribe(result =>
    {
        if (!result.IsSucceed)
        {
            Console.WriteLine($"Schema reload failed: {result.Err}");
            return;
        }

        Console.WriteLine($"Reloaded {result.Value!.Count} tags and {result.Value.GroupCount} groups");
    });

ObserveTagDatabaseReload(...) only applies a newly loaded database when validation succeeds. Invalid reloads are emitted as failed results and the last valid client.TagDatabase remains active.

Preview schema changes before applying them

var preview = client.PreviewTagDatabaseDiff("plc-tags.yaml");
if (!preview.IsSucceed)
{
    throw new InvalidOperationException(preview.Err);
}

Console.WriteLine($"Added tags: {preview.Value!.AddedTags.Count}");
Console.WriteLine($"Removed tags: {preview.Value.RemovedTags.Count}");
Console.WriteLine($"Changed tags: {preview.Value.ChangedTags.Count}");

Reactively audit schema changes during hot reload

using var schemaAudit = client
    .ObserveTagDatabaseDiff("plc-tags.yaml", TimeSpan.FromSeconds(2), emitInitial: false)
    .Subscribe(result =>
    {
        if (!result.IsSucceed)
        {
            Console.WriteLine($"Schema diff failed: {result.Err}");
            return;
        }

        var diff = result.Value!;
        Console.WriteLine($"Schema changed: {diff.ChangeCount} semantic changes");
    });

ObserveTagDatabaseDiff(...) emits semantic tag/group changes for each successful reload and keeps the last valid client.TagDatabase when an update is invalid.

Apply rollout policy gates during commissioning

var gatedLoad = client.LoadAndValidateTagDatabase(
    "plc-tags.yaml",
    MitsubishiTagRolloutPolicy.SafeMetadataAndGroups);

if (!gatedLoad.IsSucceed)
{
    throw new InvalidOperationException(gatedLoad.Err);
}

MitsubishiTagRolloutPolicy.SafeMetadataAndGroups allows:

  • metadata-only tag changes
  • tag-group membership/order changes

It rejects:

  • address changes
  • datatype/encoding/length/signedness/byte-order changes
  • added/removed tags or groups

Preview classified changes before applying them

var preview = client.PreviewTagDatabaseDiff(
    "plc-tags.yaml",
    MitsubishiTagRolloutPolicy.SafeMetadataAndGroups);

if (preview.Value is not null)
{
    Console.WriteLine($"Kinds: {preview.Value.ChangeKinds}");
    Console.WriteLine($"Total changes: {preview.Value.ChangeCount}");
}

Reactively enforce rollout policy during hot reload

using var gatedReload = client
    .ObserveTagDatabaseReload(
        "plc-tags.yaml",
        TimeSpan.FromSeconds(2),
        emitInitial: false,
        policy: MitsubishiTagRolloutPolicy.SafeMetadataAndGroups)
    .Subscribe(result =>
    {
        if (!result.IsSucceed)
        {
            Console.WriteLine($"Reload blocked: {result.Err}");
        }
    });

Practical CSV rules for maintenance teams

Recommended conventions:

  • Name: PascalCase or a consistent SCADA/HMI-friendly convention
  • Address: exact Mitsubishi address string with no extra spaces
  • DataType: one of Bit, Word, DWord, Float, String, Int16, UInt16, Int32, UInt32
  • Length: set this for string tags so code can call ReadStringByTagAsync(tagName) without supplying a length each time
  • Encoding: use Ascii unless the PLC text really requires Utf8 or Utf16
  • Signed: set to true for signed integer word/double-word values or signed scaled engineering values
  • ByteOrder: use LittleEndian for the normal two-word Mitsubishi layout and BigEndian only when the external data contract requires it
  • Units: use for UI/reporting metadata such as rpm, °C, or items
  • Description: operator-facing sentence
  • Notes: use for commissioning notes, source document, or unit conversion notes

Example with quoted fields

Name,Address,DataType,Description,Scale,Offset,Notes
LineSpeed,D110,Word,"Main conveyor speed, calculated",0.01,0,"Imported from ""Line-1 IO List"""

Validation behavior

MitsubishiTagDatabase.FromCsv(...) will fail when:

  • there is no header row
  • Name is missing from the header
  • Address is missing from the header
  • a row has an empty required value for Name or Address
  • Scale, Offset, or Length contain invalid numeric values
  • Signed contains an invalid boolean value
  • DataType contains an unsupported value
  • Encoding contains an unsupported value
  • ByteOrder contains an unsupported value

client.ValidateTagDatabase() additionally reports:

  • tag addresses that cannot be parsed for the configured XyNotation
  • string tags that do not define a positive Length
  • tag groups that reference missing tags

The accepted DataType values are exactly:

  • Bit
  • Word
  • DWord
  • Float
  • String
  • Int16
  • UInt16
  • Int32
  • UInt32

The accepted Encoding values are exactly:

  • Ascii
  • Utf8
  • Utf16

The accepted ByteOrder values are exactly:

  • LittleEndian
  • BigEndian

PLC-family-specific usage guidance

This section shows how to think about feature usage by PLC family.

A / AnS legacy paths

Use 1E when the installed Ethernet interface/module exposes A-compatible MC protocol only.

Typical operations:

  • ReadWordsAsync
  • WriteWordsAsync
  • ReadBitsAsync
  • WriteBitsAsync
  • ReadTypeNameAsync
  • LoopbackAsync
  • core remote operations where supported by the target path

Example:

var options = new MitsubishiClientOptions(
    Host: "192.168.0.20",
    Port: 5000,
    FrameType: MitsubishiFrameType.OneE,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Tcp,
    MonitoringTimer: 0x0010,
    CpuType: CpuType.ASeries,
    LegacyPcNumber: 0xFF);

Q / QnA / L / iQ-R / iQ-F / FX5 modern Ethernet

Use 3E as the default unless the integration explicitly needs 4E serial correlation.

Typical operations:

  • all batch operations
  • random read/write
  • block read/write
  • monitor registration/execute
  • type-name read
  • remote control
  • password unlock/lock
  • memory and extend-unit access
  • TCP or UDP depending on endpoint configuration

3E example:

var options = new MitsubishiClientOptions(
    Host: "192.168.0.30",
    Port: 5000,
    FrameType: MitsubishiFrameType.ThreeE,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Tcp,
    Route: MitsubishiRoute.Default,
    MonitoringTimer: 0x0010);

4E example:

var options = new MitsubishiClientOptions(
    Host: "192.168.0.31",
    Port: 5000,
    FrameType: MitsubishiFrameType.FourE,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Tcp,
    Route: MitsubishiRoute.Default,
    MonitoringTimer: 0x0010,
    SerialNumberProvider: () => (ushort)Environment.TickCount);

ASCII endpoint example

var asciiOptions = new MitsubishiClientOptions(
    Host: "192.168.0.40",
    Port: 5000,
    FrameType: MitsubishiFrameType.ThreeE,
    DataCode: CommunicationDataCode.Ascii,
    TransportKind: MitsubishiTransportKind.Tcp,
    Route: MitsubishiRoute.Default,
    MonitoringTimer: 0x0010);

UDP endpoint example

var udpOptions = new MitsubishiClientOptions(
    Host: "192.168.0.50",
    Port: 5000,
    FrameType: MitsubishiFrameType.ThreeE,
    DataCode: CommunicationDataCode.Binary,
    TransportKind: MitsubishiTransportKind.Udp,
    Route: MitsubishiRoute.Default,
    MonitoringTimer: 0x0010);

Serial endpoint example

using System.IO.Ports;

var serialEndpoint = new MitsubishiClientOptions(
    Host: "COM3",
    Port: 0,
    FrameType: MitsubishiFrameType.OneC,
    DataCode: CommunicationDataCode.Ascii,
    TransportKind: MitsubishiTransportKind.Serial,
    Timeout: TimeSpan.FromSeconds(2),
    CpuType: CpuType.Fx3,
    Serial: new MitsubishiSerialOptions(
        PortName: "COM3",
        BaudRate: 9600,
        DataBits: 7,
        Parity: Parity.Even,
        StopBits: StopBits.One,
        Handshake: Handshake.None,
        MessageFormat: MitsubishiSerialMessageFormat.Format1,
        StationNumber: 0x00,
        PcNumber: 0xFF,
        MessageWait: 0x0));

Feature-to-API quick map

Feature API
Default package MitsubishiRx namespace, ReactiveUI.Primitives, SerialPortRx
Reactive bridge package MitsubishiRx.Reactive namespace, ReactiveUI.Primitives.Reactive, SerialPortRx.Reactive
Open transport OpenAsync() / Open()
Close transport CloseAsync() / Close()
Batch word read ReadWordsAsync(address, points)
Batch word write WriteWordsAsync(address, values)
Batch bit read ReadBitsAsync(address, points)
Batch bit write WriteBitsAsync(address, values)
Random word read RandomReadWordsAsync(addresses)
Random word write RandomWriteWordsAsync(values)
Register monitor devices RegisterMonitorAsync(addresses)
Execute monitor ExecuteMonitorAsync()
Block read ReadBlocksAsync(request)
Block write WriteBlocksAsync(request)
Read PLC type ReadTypeNameAsync()
Remote RUN RemoteRunAsync(force, clearMode)
Remote STOP RemoteStopAsync()
Remote PAUSE RemotePauseAsync()
Remote LATCH CLEAR RemoteLatchClearAsync()
Remote RESET RemoteResetAsync()
Unlock UnlockAsync(password)
Lock LockAsync(password)
Clear error ClearErrorAsync()
Loopback LoopbackAsync(data)
Memory read ReadMemoryAsync(command, address, length)
Memory write WriteMemoryAsync(command, address, values)
Raw command execution ExecuteRawAsync(request)
Observe words ObserveWords(...)
Observe bits ObserveBits(...)
Observe heartbeat ObserveWordsHeartbeat(...)
Observe staleness ObserveWordsStale(...)
Triggered latest read ObserveWordsLatest(...)
Observe tag group ObserveTagGroup(...)
Observe tag group heartbeat ObserveTagGroupHeartbeat(...)
Observe tag group staleness ObserveTagGroupStale(...)
Triggered latest tag-group read ObserveTagGroupLatest(...)
Operation logs OperationLogs
Connection states ConnectionStates
Connection stale detection ObserveConnectionHealth(...)
Symbolic word read ReadWordsByTagAsync(tagName, points)
Symbolic bit read ReadBitsByTagAsync(tagName, points)
Symbolic word write WriteWordsByTagAsync(tagName, values)
Symbolic bit write WriteBitsByTagAsync(tagName, values)
Symbolic random word read RandomReadWordsByTagAsync(tagNames)
Symbolic random word write RandomWriteWordsByTagAsync(values)
Symbolic Int16 read ReadInt16ByTagAsync(tagName)
Symbolic Int16 write WriteInt16ByTagAsync(tagName, value)
Symbolic UInt16 read ReadUInt16ByTagAsync(tagName)
Symbolic UInt16 write WriteUInt16ByTagAsync(tagName, value)
Symbolic Int32 read ReadInt32ByTagAsync(tagName)
Symbolic Int32 write WriteInt32ByTagAsync(tagName, value)
Symbolic DWord read ReadDWordByTagAsync(tagName)
Symbolic DWord write WriteDWordByTagAsync(tagName, value)
Symbolic float read ReadFloatByTagAsync(tagName)
Symbolic float write WriteFloatByTagAsync(tagName, value)
Symbolic scaled read ReadScaledDoubleByTagAsync(tagName)
Symbolic scaled write WriteScaledDoubleByTagAsync(tagName, value)
Symbolic string read ReadStringByTagAsync(tagName, wordLength) / ReadStringByTagAsync(tagName)
Symbolic string write WriteStringByTagAsync(tagName, value, wordLength) / WriteStringByTagAsync(tagName, value)
Tag group definition MitsubishiTagGroupDefinition(name, tagNames)
Tag group registration TagDatabase.AddGroup(group)
Tag database validation ValidateTagDatabase()
Tag database load + validate LoadAndValidateTagDatabase(path) / LoadAndValidateTagDatabase(path, policy)
Tag database diff preview PreviewTagDatabaseDiff(path) / PreviewTagDatabaseDiff(path, policy)
Tag database reload stream ObserveTagDatabaseReload(path, pollInterval) / ObserveTagDatabaseReload(path, pollInterval, emitInitial, policy)
Tag database diff stream ObserveTagDatabaseDiff(path, pollInterval) / ObserveTagDatabaseDiff(path, pollInterval, emitInitial, policy)
Tag group snapshot read ReadTagGroupSnapshotAsync(groupName)
Snapshot typed accessor snapshot.GetRequired<T>(tagName)
Tag group write validation ValidateTagGroupWrite(groupName, values)
Partial tag-group write WriteTagGroupValuesAsync(groupName, values)
Full tag-group snapshot write WriteTagGroupSnapshotAsync(snapshot)
Tag database assignment client.TagDatabase = ...
CSV tag import MitsubishiTagDatabase.FromCsv(csvContent)
Schema file load MitsubishiTagDatabase.Load(path)
Schema file save TagDatabase.Save(path)
JSON schema import MitsubishiTagDatabase.FromJson(json)
JSON schema export TagDatabase.ToJson()
YAML schema import MitsubishiTagDatabase.FromYaml(yaml)
YAML schema export TagDatabase.ToYaml()
Generator schema marker [MitsubishiTagClientSchema("""{ ... }""")]
Generated typed client root client.Generated()
Generated typed tag read/write/observe client.Generated().Tags.<Tag>.ReadAsync() / WriteAsync(...) / Observe(...)
Generated typed group read/write/observe client.Generated().Groups.<Group>.ReadAsync() / ReadOptionalAsync() / WriteAsync(...) / Observe(...) / ObserveOptional(...)
Generated snapshot conversion <Group>Snapshot.FromSnapshot(...) / TryFromSnapshot(...) / ToSnapshot()
Generator diagnostics MRTXGEN001 through MRTXGEN011

Serial coverage note

The quick map lists the full public API surface. Serial support covers 1C, 3C, and 4C paths through MitsubishiTransportKind.Serial. Verified serial operations include batch word read via ReadWordsAsync(address, points), batch word write via WriteWordsAsync(address, values), batch bit read via ReadBitsAsync(address, points), batch bit write via WriteBitsAsync(address, values), random word read/write via RandomReadWordsAsync(addresses) / RandomWriteWordsAsync(values), block read/write via ReadBlocksAsync(request) / WriteBlocksAsync(request), monitor registration/execution via RegisterMonitorAsync(addresses) / ExecuteMonitorAsync(), remote control via RemoteRunAsync(force, clearMode), RemoteStopAsync(), RemotePauseAsync(), RemoteLatchClearAsync(), and RemoteResetAsync(), type-name read, loopback, memory / extend-unit access, and raw command execution. For 1C, random, block, and monitor operations are implemented as deterministic client-side compositions over the verified 1C batch read/write path.

Complete API reference

This section is the authoritative quick reference for the public API exposed by MitsubishiRx and MitsubishiRx.Reactive. Earlier sections explain the recommended workflow and provide larger examples; this section gives signatures, return types, and when to use each member.

API conventions

  • All async PLC operations return Task<Responce> or Task<Responce<T>>.
  • Check IsSucceed before using Value.
  • Failure details are available through Err, ErrCode, ErrList, and Exception.
  • Optional CancellationToken parameters cancel the client-side request wait; PLC command support and PLC-side execution semantics remain target dependent.
  • Address-based methods accept Mitsubishi device strings such as D100, M10, X20, W10, or ZR200.
  • Tag-based methods require client.TagDatabase to be assigned first.
  • Ethernet 1E paths have a smaller command set than 3E / 4E. Serial 1C, 3C, and 4C paths cover the public serial API surface; see the serial coverage notes above for the 1C composition behavior.

Package-specific API surface

The runtime PLC, tag, reactive polling, hot stream, write pipeline, protocol, transport, and model APIs are compiled into both packages. Replace the namespace prefix when moving examples between packages.

Area MitsubishiRx MitsubishiRx.Reactive
Package id MitsubishiRx MitsubishiRx.Reactive
Root namespace MitsubishiRx MitsubishiRx.Reactive
Target frameworks net8.0, net9.0, net10.0, net11.0 net8.0, net9.0, net10.0, net11.0
Reactive dependencies ReactiveUI.Primitives, ReactiveUI.Primitives.Async, ReactiveUI.Primitives.Extensions ReactiveUI.Primitives.Reactive, ReactiveUI.Primitives.Extensions.Reactive
Serial dependency SerialPortRx SerialPortRx.Reactive
Scheduler constructor type ReactiveUI.Primitives.Concurrency.ISequencer? System.Reactive.Concurrency.IScheduler?
Latest trigger unit type ReactiveUI.Primitives.RxVoid System.Reactive.Unit
Source generator Included as analyzer and emits MitsubishiRx generated clients Included as analyzer, but generated typed clients currently target the base MitsubishiRx namespace

Package-specific construction examples:

using MitsubishiRx;

await using var client = new MitsubishiRx.MitsubishiRx(options);
using MitsubishiRx.Reactive;

await using var client = new MitsubishiRx.Reactive.MitsubishiRx(options);

For MitsubishiRx.Reactive, prefer the runtime APIs in the tables below for tag reads, group reads, polling, hot observables, and write pipelines. Generated typed clients are documented separately because they currently target the base package namespace.

var result = await client.ReadWordsAsync("D100", 2);
if (!result.IsSucceed)
{
    Console.WriteLine($"PLC read failed: {result.Err} code={result.ErrCode}");
    return;
}

ushort firstWord = result.Value![0];

Client construction and state

API Signature Purpose
Modern constructor MitsubishiRx(MitsubishiClientOptions options, IMitsubishiTransport? transport = null, IScheduler? scheduler = null) Preferred constructor. Pass options and optionally inject a custom transport or scheduler for tests/advanced integrations.
Legacy constructor MitsubishiRx(CpuType cpuType, string ip, int port, int timeout = 1500) Compatibility shortcut for older socket-style code. Prefer MitsubishiClientOptions for new code.
Options MitsubishiClientOptions Options { get; } Effective immutable client options.
TagDatabase MitsubishiTagDatabase? TagDatabase { get; set; } Optional symbolic tag schema used by tag/group/generated APIs.
Connected bool Connected { get; } Current transport connection flag.
ConnectionStates IObservable<MitsubishiConnectionState> ConnectionStates { get; } Reactive connection-state stream.
OperationLogs IObservable<MitsubishiOperationLog> OperationLogs { get; } Request/response log stream for diagnostics and audit.
await using var client = new MitsubishiRx.MitsubishiRx(options);
client.TagDatabase = MitsubishiTagDatabase.Load("plc-tags.yaml");

using var stateSubscription = client.ConnectionStates.Subscribe(state => Console.WriteLine(state));

Connection lifecycle

API Signature Purpose
Open Responce Open() Synchronous open wrapper.
OpenAsync Task<Responce> OpenAsync(CancellationToken cancellationToken = default) Opens TCP/UDP/serial transport.
Close Responce Close() Synchronous close wrapper.
CloseAsync Task<Responce> CloseAsync(CancellationToken cancellationToken = default) Closes the transport.
Dispose void Dispose() Disposes transport, subscriptions, and reactive caches.
DisposeAsync ValueTask DisposeAsync() Async disposal path; preferred with await using.
var open = await client.OpenAsync();
if (!open.IsSucceed)
{
    throw new InvalidOperationException(open.Err);
}

await client.CloseAsync();

Core address-based PLC operations

API Signature Returns Purpose
ReadWordsAsync Task<Responce<ushort[]>> ReadWordsAsync(string address, int points, CancellationToken cancellationToken = default) Word values Batch word read from consecutive devices.
WriteWordsAsync Task<Responce> WriteWordsAsync(string address, IReadOnlyList<ushort> values, CancellationToken cancellationToken = default) Completion Batch word write.
ReadBitsAsync Task<Responce<bool[]>> ReadBitsAsync(string address, int points, CancellationToken cancellationToken = default) Bit values Batch bit read.
WriteBitsAsync Task<Responce> WriteBitsAsync(string address, IReadOnlyList<bool> values, CancellationToken cancellationToken = default) Completion Batch bit write.
RandomReadWordsAsync Task<Responce<ushort[]>> RandomReadWordsAsync(IEnumerable<string> addresses, CancellationToken cancellationToken = default) Word values in request order Sparse word read.
RandomWriteWordsAsync Task<Responce> RandomWriteWordsAsync(IEnumerable<KeyValuePair<string, ushort>> values, CancellationToken cancellationToken = default) Completion Sparse word write.
RegisterMonitorAsync Task<Responce> RegisterMonitorAsync(IEnumerable<string> addresses, CancellationToken cancellationToken = default) Completion Registers devices for later monitor execution.
ExecuteMonitorAsync Task<Responce<byte[]>> ExecuteMonitorAsync(CancellationToken cancellationToken = default) Raw monitor payload Executes the registered monitor.
ReadBlocksAsync Task<Responce<byte[]>> ReadBlocksAsync(MitsubishiBlockRequest request, CancellationToken cancellationToken = default) Raw block payload Reads multiple word/bit blocks.
WriteBlocksAsync Task<Responce> WriteBlocksAsync(MitsubishiBlockRequest request, CancellationToken cancellationToken = default) Completion Writes multiple word/bit blocks.
ReadTypeNameAsync Task<Responce<MitsubishiTypeName>> ReadTypeNameAsync(CancellationToken cancellationToken = default) PLC model information Reads CPU/module type name and code.
RemoteRunAsync Task<Responce> RemoteRunAsync(bool force = true, bool clearMode = false, CancellationToken cancellationToken = default) Completion Issues remote RUN.
RemoteStopAsync Task<Responce> RemoteStopAsync(CancellationToken cancellationToken = default) Completion Issues remote STOP.
RemotePauseAsync Task<Responce> RemotePauseAsync(CancellationToken cancellationToken = default) Completion Issues remote PAUSE.
RemoteLatchClearAsync Task<Responce> RemoteLatchClearAsync(CancellationToken cancellationToken = default) Completion Clears latched device state where supported.
RemoteResetAsync Task<Responce> RemoteResetAsync(CancellationToken cancellationToken = default) Completion Issues remote RESET.
UnlockAsync Task<Responce> UnlockAsync(string password, CancellationToken cancellationToken = default) Completion Remote password unlock.
LockAsync Task<Responce> LockAsync(string password, CancellationToken cancellationToken = default) Completion Remote password lock.
ClearErrorAsync Task<Responce> ClearErrorAsync(CancellationToken cancellationToken = default) Completion Clears error/LED indication where supported.
LoopbackAsync Task<Responce<byte[]>> LoopbackAsync(byte[] data, CancellationToken cancellationToken = default) Echoed payload Link/protocol loopback test.
ReadMemoryAsync Task<Responce<ushort[]>> ReadMemoryAsync(ushort command, ushort address, int length, CancellationToken cancellationToken = default) Word values Memory or intelligent-module buffer read.
WriteMemoryAsync Task<Responce> WriteMemoryAsync(ushort command, ushort address, IReadOnlyList<ushort> values, CancellationToken cancellationToken = default) Completion Memory or intelligent-module buffer write.
ExecuteRawAsync Task<Responce<byte[]>> ExecuteRawAsync(MitsubishiRawCommandRequest request, CancellationToken cancellationToken = default) Raw decoded payload Advanced raw MC/SLMP command execution.
var blocks = new MitsubishiBlockRequest(
    WordBlocks: [new MitsubishiWordBlock(MitsubishiDeviceAddress.Parse("D100"), new ushort[4])],
    BitBlocks: [new MitsubishiBitBlock(MitsubishiDeviceAddress.Parse("M10"), new bool[8])]);

var blockPayload = await client.ReadBlocksAsync(blocks);

Low-level compatibility methods

These methods exist for compatibility with older socket-style consumers. Prefer the typed high-level APIs for new code because they apply frame-specific encoding/decoding and diagnostics consistently.

API Signature Purpose
SendPackage Responce<byte[]> SendPackage(byte[] command, int receiveCount) Send a raw command and expect a fixed receive count.
SendPackageSingle Responce<byte[]> SendPackageSingle(byte[] command) Send one raw command using default receive handling.
SendPackageReliable Responce<byte[]> SendPackageReliable(byte[] command) Send one raw command using the reliable exchange path.

Tag-based APIs

Tag APIs resolve tagName through client.TagDatabase, then delegate to address-based operations or typed conversion helpers.

API Signature Purpose
ReadWordsByTagAsync Task<Responce<ushort[]>> ReadWordsByTagAsync(string tagName, int points, CancellationToken cancellationToken = default) Read raw words from a configured tag address.
ReadBitsByTagAsync Task<Responce<bool[]>> ReadBitsByTagAsync(string tagName, int points, CancellationToken cancellationToken = default) Read bits from a configured tag address.
WriteWordsByTagAsync Task<Responce> WriteWordsByTagAsync(string tagName, IReadOnlyList<ushort> values, CancellationToken cancellationToken = default) Write raw words to a configured tag address.
WriteBitsByTagAsync Task<Responce> WriteBitsByTagAsync(string tagName, IReadOnlyList<bool> values, CancellationToken cancellationToken = default) Write bits to a configured tag address.
RandomReadWordsByTagAsync Task<Responce<ushort[]>> RandomReadWordsByTagAsync(IEnumerable<string> tagNames, CancellationToken cancellationToken = default) Sparse word read by tag names.
RandomWriteWordsByTagAsync Task<Responce> RandomWriteWordsByTagAsync(IEnumerable<KeyValuePair<string, ushort>> values, CancellationToken cancellationToken = default) Sparse word write by tag names.
ReadGeneratedBitTagAsync Task<Responce<bool>> ReadGeneratedBitTagAsync(string tagName, CancellationToken cancellationToken = default) Helper used by generated bit-tag accessors. Usually called through client.Generated().Tags.X.ReadAsync().
WriteGeneratedBitTagAsync Task<Responce> WriteGeneratedBitTagAsync(string tagName, bool value, CancellationToken cancellationToken = default) Helper used by generated bit-tag accessors.
client.TagDatabase = MitsubishiTagDatabase.FromCsv(File.ReadAllText("plc-tags.csv"));

await client.WriteWordsByTagAsync("RecipeNumber", [7]);
var running = await client.ReadBitsByTagAsync("PumpRunning", 1);

Typed tag conversion APIs

API Signature PLC words used Purpose
ReadInt16ByTagAsync Task<Responce<short>> ReadInt16ByTagAsync(string tagName, CancellationToken cancellationToken = default) 1 Signed 16-bit read.
WriteInt16ByTagAsync Task<Responce> WriteInt16ByTagAsync(string tagName, short value, CancellationToken cancellationToken = default) 1 Signed 16-bit write.
ReadUInt16ByTagAsync Task<Responce<ushort>> ReadUInt16ByTagAsync(string tagName, CancellationToken cancellationToken = default) 1 Unsigned 16-bit read.
WriteUInt16ByTagAsync Task<Responce> WriteUInt16ByTagAsync(string tagName, ushort value, CancellationToken cancellationToken = default) 1 Unsigned 16-bit write.
ReadInt32ByTagAsync Task<Responce<int>> ReadInt32ByTagAsync(string tagName, CancellationToken cancellationToken = default) 2 Signed 32-bit read. Honors ByteOrder.
WriteInt32ByTagAsync Task<Responce> WriteInt32ByTagAsync(string tagName, int value, CancellationToken cancellationToken = default) 2 Signed 32-bit write. Honors ByteOrder.
ReadDWordByTagAsync Task<Responce<uint>> ReadDWordByTagAsync(string tagName, CancellationToken cancellationToken = default) 2 Unsigned 32-bit / DWord read.
WriteDWordByTagAsync Task<Responce> WriteDWordByTagAsync(string tagName, uint value, CancellationToken cancellationToken = default) 2 Unsigned 32-bit / DWord write.
ReadFloatByTagAsync Task<Responce<float>> ReadFloatByTagAsync(string tagName, CancellationToken cancellationToken = default) 2 IEEE754 single-precision read.
WriteFloatByTagAsync Task<Responce> WriteFloatByTagAsync(string tagName, float value, CancellationToken cancellationToken = default) 2 IEEE754 single-precision write.
ReadScaledDoubleByTagAsync Task<Responce<double>> ReadScaledDoubleByTagAsync(string tagName, CancellationToken cancellationToken = default) Depends on DataType Engineering value read using (raw * Scale) + Offset.
WriteScaledDoubleByTagAsync Task<Responce> WriteScaledDoubleByTagAsync(string tagName, double value, CancellationToken cancellationToken = default) Depends on DataType Engineering value write using (value - Offset) / Scale.
ReadStringByTagAsync Task<Responce<string>> ReadStringByTagAsync(string tagName, CancellationToken cancellationToken = default) Length metadata Schema-driven string read.
ReadStringByTagAsync Task<Responce<string>> ReadStringByTagAsync(string tagName, int wordLength, CancellationToken cancellationToken = default) wordLength Explicit-length string read.
WriteStringByTagAsync Task<Responce> WriteStringByTagAsync(string tagName, string value, CancellationToken cancellationToken = default) Length metadata Schema-driven string write.
WriteStringByTagAsync Task<Responce> WriteStringByTagAsync(string tagName, string value, int wordLength, CancellationToken cancellationToken = default) wordLength Explicit-length string write.
var total = await client.ReadDWordByTagAsync("TotalCount");
var pressure = await client.ReadFloatByTagAsync("ProcessValue");
var operatorMessage = await client.ReadStringByTagAsync("OperatorMessage");

await client.WriteScaledDoubleByTagAsync("HeadTemp", 22.5d);

Tag database and schema APIs

API Signature Purpose
MitsubishiTagDatabase MitsubishiTagDatabase(IEnumerable<MitsubishiTagDefinition> tags) Creates an in-memory tag database.
Serialization document helpers MitsubishiTagDefinitionDocument.ToModel(), MitsubishiTagDefinitionDocument.FromModel(...), MitsubishiTagGroupDefinitionDocument.ToModel(), MitsubishiTagGroupDefinitionDocument.FromModel(...) DTO mapping helpers used by JSON/YAML persistence; application code normally uses FromJson, FromYaml, ToJson, ToYaml, Load, and Save.
Count int Count { get; } Number of tags.
GroupCount int GroupCount { get; } Number of groups.
Tags IReadOnlyCollection<MitsubishiTagDefinition> Tags { get; } Tag definitions.
Groups IReadOnlyCollection<MitsubishiTagGroupDefinition> Groups { get; } Group definitions.
Add void Add(MitsubishiTagDefinition tag) Adds or replaces a tag after validating metadata.
TryGet bool TryGet(string name, out MitsubishiTagDefinition tag) Case-insensitive optional tag lookup.
GetRequired MitsubishiTagDefinition GetRequired(string name) Case-insensitive required tag lookup.
AddGroup void AddGroup(MitsubishiTagGroupDefinition group) Adds or replaces a group.
TryGetGroup bool TryGetGroup(string name, out MitsubishiTagGroupDefinition group) Optional group lookup.
GetRequiredGroup MitsubishiTagGroupDefinition GetRequiredGroup(string name) Required group lookup.
FromCsv static MitsubishiTagDatabase FromCsv(string csvContent) Loads tag definitions from CSV.
FromJson static MitsubishiTagDatabase FromJson(string json) Loads full schema from JSON.
FromYaml static MitsubishiTagDatabase FromYaml(string yaml) Loads full schema from YAML.
Load static MitsubishiTagDatabase Load(string path) Loads by extension: .csv, .json, .yaml, .yml.
ToJson string ToJson() Serializes full schema to JSON.
ToYaml string ToYaml() Serializes full schema to YAML.
Save void Save(string path) Saves by extension. CSV is tag-only; JSON/YAML preserve groups.
CompareWith MitsubishiTagDatabaseDiff CompareWith(MitsubishiTagDatabase other) Computes semantic schema diff.
var tags = new MitsubishiTagDatabase([
    new MitsubishiTagDefinition("MotorSpeed", "D100", DataType: "Float", Units: "rpm"),
    new MitsubishiTagDefinition("PumpRunning", "M10", DataType: "Bit")
]);

tags.AddGroup(new MitsubishiTagGroupDefinition("Overview", ["MotorSpeed", "PumpRunning"]));
tags.Save("plc-tags.yaml");

Schema validation, reload, diff, and rollout APIs

API Signature Purpose
ValidateTagDatabase Responce ValidateTagDatabase() Validates configured tag addresses, string lengths, and group membership.
LoadAndValidateTagDatabase Responce<MitsubishiTagDatabase> LoadAndValidateTagDatabase(string path) Loads, validates, applies on success using AllowAll.
LoadAndValidateTagDatabase Responce<MitsubishiTagDatabase> LoadAndValidateTagDatabase(string path, MitsubishiTagRolloutPolicy policy) Loads, validates, diff-checks against policy, applies on success.
PreviewTagDatabaseDiff Responce<MitsubishiTagDatabaseDiff> PreviewTagDatabaseDiff(string path) Loads and validates incoming schema, returns semantic diff without applying.
PreviewTagDatabaseDiff Responce<MitsubishiTagDatabaseDiff> PreviewTagDatabaseDiff(string path, MitsubishiTagRolloutPolicy policy) Preview plus rollout policy enforcement.
ObserveTagDatabaseReload IObservable<Responce<MitsubishiTagDatabase>> ObserveTagDatabaseReload(string path, TimeSpan pollInterval, bool emitInitial = true) Polls schema file and emits valid/invalid reload results.
ObserveTagDatabaseReload IObservable<Responce<MitsubishiTagDatabase>> ObserveTagDatabaseReload(string path, TimeSpan pollInterval, bool emitInitial, MitsubishiTagRolloutPolicy policy) Reload stream gated by rollout policy.
ObserveTagDatabaseDiff IObservable<Responce<MitsubishiTagDatabaseDiff>> ObserveTagDatabaseDiff(string path, TimeSpan pollInterval, bool emitInitial = true) Polls schema file and emits semantic diffs.
ObserveTagDatabaseDiff IObservable<Responce<MitsubishiTagDatabaseDiff>> ObserveTagDatabaseDiff(string path, TimeSpan pollInterval, bool emitInitial, MitsubishiTagRolloutPolicy policy) Diff stream gated by rollout policy.

Invalid reload/diff emissions do not replace the active client.TagDatabase.

Tag group APIs

API Signature Purpose
ReadTagGroupSnapshotAsync Task<Responce<MitsubishiTagGroupSnapshot>> ReadTagGroupSnapshotAsync(string groupName, CancellationToken cancellationToken = default) Reads every tag in a group and returns a heterogeneous snapshot.
ValidateTagGroupWrite Responce ValidateTagGroupWrite(string groupName, IReadOnlyDictionary<string, object?> values) Validates provided write values against group membership and tag data types.
WriteTagGroupValuesAsync Task<Responce> WriteTagGroupValuesAsync(string groupName, IReadOnlyDictionary<string, object?> values, CancellationToken cancellationToken = default) Writes only supplied group values in configured group order.
WriteTagGroupSnapshotAsync Task<Responce> WriteTagGroupSnapshotAsync(MitsubishiTagGroupSnapshot snapshot, CancellationToken cancellationToken = default) Writes a complete snapshot back to its group.
Snapshot accessor snapshot.GetRequired<T>(tagName) Gets a typed value or throws when missing/wrong type.
Snapshot optional accessor snapshot.GetOptional<T>(tagName) Gets a typed value or default when missing/wrong type.
var snapshot = await client.ReadTagGroupSnapshotAsync("Overview");
if (snapshot.IsSucceed)
{
    float speed = snapshot.Value!.GetRequired<float>("MotorSpeed");
    bool pump = snapshot.Value.GetRequired<bool>("PumpRunning");
}

Reactive polling APIs

API Signature Purpose
ObserveWords IObservable<Responce<ushort[]>> ObserveWords(string address, int points, TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null, TimeSpan? pollTimeout = null) Polls words.
ObserveBits IObservable<Responce<bool[]>> ObserveBits(string address, int points, TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Polls bits.
ObserveWordsHeartbeat IObservable<Heartbeat<Responce<ushort[]>>> ObserveWordsHeartbeat(string address, int points, TimeSpan pollInterval, TimeSpan heartbeatAfter, TimeSpan? minimumUpdateSpacing = null, TimeSpan? pollTimeout = null) Word polling with heartbeat envelopes.
ObserveWordsStale IObservable<Stale<Responce<ushort[]>>> ObserveWordsStale(string address, int points, TimeSpan pollInterval, TimeSpan staleAfter, TimeSpan? minimumUpdateSpacing = null) Word polling with stale markers.
ObserveWordsLatest IObservable<Responce<ushort[]>> ObserveWordsLatest(string address, int points, IObservable<Unit> trigger) Latest-only triggered read.
ObserveTagGroup IObservable<Responce<MitsubishiTagGroupSnapshot>> ObserveTagGroup(string groupName, TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Polls a tag group.
ObserveTagGroupHeartbeat IObservable<Heartbeat<Responce<MitsubishiTagGroupSnapshot>>> ObserveTagGroupHeartbeat(string groupName, TimeSpan pollInterval, TimeSpan heartbeatAfter, TimeSpan? minimumUpdateSpacing = null) Group polling with heartbeat envelopes.
ObserveTagGroupStale IObservable<Stale<Responce<MitsubishiTagGroupSnapshot>>> ObserveTagGroupStale(string groupName, TimeSpan pollInterval, TimeSpan staleAfter, TimeSpan? minimumUpdateSpacing = null) Group polling with stale markers.
ObserveTagGroupLatest IObservable<Responce<MitsubishiTagGroupSnapshot>> ObserveTagGroupLatest(string groupName, IObservable<Unit> trigger) Latest-only triggered group read.
SampleDiagnostics IObservable<MitsubishiOperationLog> SampleDiagnostics(IObservable<object> trigger) Samples latest operation diagnostics on trigger.
ObserveConnectionHealth IObservable<Stale<MitsubishiConnectionState>> ObserveConnectionHealth(TimeSpan staleAfter) Detects stale connection-state updates.
using var sub = client.ObserveWords("D100", 2, TimeSpan.FromSeconds(1))
    .Subscribe(result => Console.WriteLine(result.IsSucceed ? string.Join(",", result.Value!) : result.Err));

Hot reactive value APIs

Hot reactive APIs share scan work between equivalent subscribers and emit MitsubishiReactiveValue<T> quality envelopes.

API Signature Purpose
ObserveReactiveWords IObservable<MitsubishiReactiveValue<ushort[]>> ObserveReactiveWords(string address, int points, TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Shared hot word scan.
ObserveReactiveTag<T> IObservable<MitsubishiReactiveValue<T>> ObserveReactiveTag<T>(string tagName, TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Shared typed tag projection.
ObserveReactiveTagGroup IObservable<MitsubishiReactiveValue<MitsubishiTagGroupSnapshot>> ObserveReactiveTagGroup(string groupName, TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Shared grouped snapshot scan.
SharedReactiveStream<T> SharedReactiveStream(Func<bool, IObservable<MitsubishiReactiveValue<T>>> streamFactory) with Stream and Dispose() Internal shared-stream primitive exposed by the assembly; application code normally uses the three ObserveReactive... methods instead.

MitsubishiReactiveValue<T> fields: Value, TimestampUtc, Quality, IsHeartbeat, IsStale, Source, Error, ErrorCode, Exception.

MitsubishiReactiveQuality values: Good, Bad, Stale, Heartbeat, Error.

Factory helpers:

API Signature Purpose
MitsubishiReactiveValue.FromResponse static MitsubishiReactiveValue<T> FromResponse<T>(Responce<T> response, DateTimeOffset timestampUtc, string source) Wraps a normal response.
MitsubishiReactiveValue.Heartbeat static MitsubishiReactiveValue<T> Heartbeat<T>(MitsubishiReactiveValue<T> value, DateTimeOffset timestampUtc) Creates a heartbeat envelope.
MitsubishiReactiveValue.Stale static MitsubishiReactiveValue<T> Stale<T>(MitsubishiReactiveValue<T> value, DateTimeOffset timestampUtc) Creates a stale envelope.

Base package example:

using MitsubishiRx;

using var speed = client.ObserveReactiveTag<float>("MotorSpeed", TimeSpan.FromMilliseconds(500))
    .Subscribe(value =>
    {
        if (value.Quality == MitsubishiReactiveQuality.Good)
        {
            Console.WriteLine($"MotorSpeed={value.Value}");
        }
    });

Reactive package example:

using MitsubishiRx.Reactive;

using var overview = client.ObserveReactiveTagGroup("Line1Overview", TimeSpan.FromSeconds(1))
    .Subscribe(value =>
    {
        if (value.Quality == MitsubishiReactiveQuality.Good && value.Value is not null)
        {
            Console.WriteLine(value.Value.GetRequired<float>("MotorSpeed"));
        }
    });

Reactive write pipeline APIs

API Signature Purpose
CreateReactiveWordWritePipeline MitsubishiReactiveWritePipeline<IReadOnlyList<ushort>> CreateReactiveWordWritePipeline(string address, MitsubishiReactiveWriteMode mode, TimeSpan? coalescingWindow = null) Creates a word write pipeline for raw address writes.
CreateReactiveTagWritePipeline<T> MitsubishiReactiveWritePipeline<T> CreateReactiveTagWritePipeline<T>(string tagName, MitsubishiReactiveWriteMode mode, TimeSpan? coalescingWindow = null) Creates a typed tag write pipeline.
MitsubishiReactiveWritePipeline<TPayload>.Post void Post(TPayload payload) Queues/posts a payload according to the configured mode.
MitsubishiReactiveWritePipeline<TPayload>.Results IObservable<MitsubishiReactiveWriteResult> Results { get; } Completion result stream.
MitsubishiReactiveWritePipeline<TPayload>.Mode MitsubishiReactiveWriteMode Mode { get; } Configured behavior.
Dispose void Dispose() Stops the pipeline and releases subscriptions.

MitsubishiReactiveWriteMode values:

Value Behavior
Queued Preserve every posted write in order.
LatestWins Collapse bursts to the latest posted value.
Coalescing Delay within coalescingWindow and write the latest value when the window closes.

MitsubishiReactiveWriteResult fields: Target, TimestampUtc, Mode, Success, Error, ErrorCode, Exception.

using MitsubishiRx.Reactive;

var writes = client.CreateReactiveWordWritePipeline(
    "D100",
    MitsubishiReactiveWriteMode.Queued);

using var results = writes.Results.Subscribe(result =>
    Console.WriteLine($"{result.Target} success={result.Success} error={result.Error}"));

writes.Post([100, 200, 300]);

Generated typed client APIs

MitsubishiRx.Generators is bundled into the MitsubishiRx NuGet package as an analyzer asset at analyzers/dotnet/cs/MitsubishiRx.Generators.dll. Consumer projects reference MitsubishiRx; they normally do not reference MitsubishiRx.Generators directly.

The generator currently emits code under namespace MitsubishiRx and references global::MitsubishiRx.MitsubishiRx, so generated typed clients are base-package APIs. MitsubishiRx.Reactive projects should use the runtime tag and group APIs directly.

Attribute API:

API Shape Purpose
MitsubishiTagClientSchemaAttribute [AttributeUsage(AttributeTargets.Assembly \| AttributeTargets.Class, AllowMultiple = true, Inherited = false)] Marks a compilation with generator schema JSON.
Constructor MitsubishiTagClientSchemaAttribute(string schemaJson) Accepts compile-time schema JSON.
SchemaJson string SchemaJson { get; } Exposes the supplied schema JSON to the generator.

Generated root API:

var generated = client.Generated();
var speed = await generated.Tags.MotorSpeed.ReadAsync();
await generated.Tags.MotorSpeed.WriteAsync(123.4f);

var line = await generated.Groups.Line1.ReadAsync();
await generated.Groups.Line1.WriteAsync(line.Value!);
Generated API Shape Purpose
GeneratedMitsubishiTagClientExtensions.Generated GeneratedMitsubishiTagClient Generated(this global::MitsubishiRx.MitsubishiRx owner) Creates the generated facade over an existing client.
GeneratedMitsubishiTagClient sealed partial class with Tags and Groups Root generated facade.
TagsClient generated.Tags.<SanitizedTagName> Accesses generated per-tag clients.
GroupsClient generated.Groups.<SanitizedGroupName> Accesses generated per-group clients.

Generated tag clients expose:

Generated API Shape Purpose
ReadAsync Task<Responce<T>> ReadAsync(CancellationToken cancellationToken = default) Reads a typed tag value.
WriteAsync Task<Responce> WriteAsync(T value, CancellationToken cancellationToken = default) Writes a typed tag value.
Observe IObservable<MitsubishiReactiveValue<T>> Observe(TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Observes a typed tag.

Generated group clients expose:

Generated API Shape Purpose
ReadAsync Task<Responce<TSnapshot>> ReadAsync(CancellationToken cancellationToken = default) Reads and maps a runtime group snapshot to a generated typed snapshot.
ReadOptionalAsync Task<Responce<TSnapshot?>> ReadOptionalAsync(CancellationToken cancellationToken = default) Returns null when the runtime snapshot is incomplete or type-mismatched.
WriteAsync Task<Responce> WriteAsync(TSnapshot value, CancellationToken cancellationToken = default) Writes a generated typed snapshot back through WriteTagGroupSnapshotAsync.
Observe IObservable<MitsubishiReactiveValue<TSnapshot>> Observe(TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Observes a generated typed group snapshot.
ObserveOptional IObservable<MitsubishiReactiveValue<TSnapshot?>> ObserveOptional(TimeSpan pollInterval, TimeSpan? minimumUpdateSpacing = null) Observes an optional generated group snapshot.

Generated group snapshot helpers expose:

Generated API Shape Purpose
FromSnapshot static TSnapshot FromSnapshot(MitsubishiTagGroupSnapshot snapshot) Required conversion from runtime snapshot to generated record.
TryFromSnapshot static TSnapshot? TryFromSnapshot(MitsubishiTagGroupSnapshot? snapshot) Optional conversion from runtime snapshot to generated record.
ToSnapshot MitsubishiTagGroupSnapshot ToSnapshot() Converts generated record to runtime snapshot for writes.
MapReactive static MitsubishiReactiveValue<TSnapshot> MapReactive(MitsubishiReactiveValue<MitsubishiTagGroupSnapshot> value) Maps reactive runtime snapshots to generated typed envelopes.
MapReactiveOptional static MitsubishiReactiveValue<TSnapshot?> MapReactiveOptional(MitsubishiReactiveValue<MitsubishiTagGroupSnapshot> value) Maps reactive runtime snapshots to optional generated typed envelopes.

Generated dataType mapping:

dataType Generated type Read/write helpers
missing / Word / UInt16 ushort ReadUInt16ByTagAsync / WriteUInt16ByTagAsync
Int16 short ReadInt16ByTagAsync / WriteInt16ByTagAsync
Int32 int ReadInt32ByTagAsync / WriteInt32ByTagAsync
DWord / UInt32 uint ReadDWordByTagAsync / WriteDWordByTagAsync
Float float ReadFloatByTagAsync / WriteFloatByTagAsync
String string ReadStringByTagAsync / WriteStringByTagAsync
Bit bool ReadGeneratedBitTagAsync / WriteGeneratedBitTagAsync

The implementation types behind this feature are MitsubishiTagClientGenerator and MitsubishiTagClientEmitter. The source-generator attribute exposes SchemaJson from MitsubishiTagClientSchemaAttribute; Initialize(...) wires the incremental generator. SchemaModel, TagModel, and GroupModel are generator-side schema parse models. Consumers normally only write the attribute and call generated extension methods, not the generator classes directly.

ID Meaning
MRTXGEN001 Failed to parse or generate schema client source.
MRTXGEN002 Duplicate tag name.
MRTXGEN003 Group references unknown tag name.
MRTXGEN004 Unsupported generated dataType.
MRTXGEN005 Sanitized generated identifier collision.
MRTXGEN006 Empty tag name.
MRTXGEN007 Empty group name.
MRTXGEN008 Group has no member tags.
MRTXGEN009 Duplicate group name.
MRTXGEN010 Empty tag reference inside a group.
MRTXGEN011 Duplicate tag reference inside a group.

Models, records, enums, and constants

Type Members / values Usage
MitsubishiClientOptions Host, Port, FrameType, DataCode, TransportKind, Route, MonitoringTimer, Timeout, CpuType, XyNotation, LegacyPcNumber, SerialNumberProvider, Serial; helper properties ResolvedTimeout, ResolvedRoute, ResolvedSerial; method GetNextSerialNumber() Complete client configuration.
MitsubishiRoute NetworkNumber, StationNumber, ModuleIoNumber, MultidropStationNumber, ExtensionStationNumber; static Default Ethernet route metadata for 3E/4E.
MitsubishiSerialOptions PortName, BaudRate, DataBits, Parity, StopBits, Handshake, MessageFormat, StationNumber, NetworkNumber, PcNumber, RequestDestinationModuleIoNumber, RequestDestinationModuleStationNumber, SelfStationNumber, MessageWait, ReadBufferSize, WriteBufferSize, NewLine; property Route Serial port and serial MC protocol settings.
MitsubishiSerialRoute StationNumber, NetworkNumber, PcNumber, RequestDestinationModuleIoNumber, RequestDestinationModuleStationNumber, SelfStationNumber Derived serial route metadata.
MitsubishiRawCommandRequest Command, Subcommand, Body, Description; property ResolvedBody Raw command request for ExecuteRawAsync.
MitsubishiTransportRequest Payload, ExpectedResponseLength, Description Transport-level request passed to custom transports.
MitsubishiOperationLog TimestampUtc, State, Description, Success, RequestPayload, ResponsePayload, Exception Operation diagnostics.
MitsubishiTypeName ModelName, ModelCode Return model for ReadTypeNameAsync.
MitsubishiDeviceValue Address, Value Random-write word pair.
MitsubishiWordBlock Address, Values Word block descriptor.
MitsubishiBitBlock Address, Values Bit block descriptor.
MitsubishiBlockRequest WordBlocks, BitBlocks; properties ResolvedWordBlocks, ResolvedBitBlocks Multi-block read/write descriptor.
MitsubishiTagDefinition Name, Address, DataType, Description, Scale, Offset, Length, Encoding, Units, Signed, ByteOrder, Notes Symbolic tag schema row.
MitsubishiTagGroupDefinition Name, TagNames, ResolvedTagNames Named group/scan class. ResolvedTagNames gives a non-null ordered member list.
MitsubishiTagGroupSnapshot GroupName, Values, TagNames; GetRequired<T>, GetOptional<T> Typed group result and write payload.
MitsubishiTagChange Name, Previous, Current, ChangeKinds Tag diff item.
MitsubishiTagGroupChange Name, Previous, Current, ChangeKinds Group diff item.
MitsubishiTagDatabaseDiff AddedTags, RemovedTags, ChangedTags, AddedGroups, RemovedGroups, ChangedGroups, ChangeKinds, HasChanges, ChangeCount, static Empty Schema diff result.
MitsubishiReactiveValue<T> Value, TimestampUtc, Quality, IsHeartbeat, IsStale, Source, Error, ErrorCode, Exception; static FromResponse, Heartbeat, Stale Quality envelope emitted by hot reactive APIs and generated observers.
MitsubishiReactiveWritePipeline<TPayload> Mode, Results, Post(TPayload payload), Dispose() Reactive write coordinator for raw word and typed tag writes.
MitsubishiReactiveWriteResult Target, TimestampUtc, Mode, Success, Error, ErrorCode, Exception Result emitted after a reactive write pipeline attempts a write.
Responce IsSucceed, Err, ErrCode, Exception, ErrList, Request, Response, Request2, Response2, TimeConsuming, InitialTime, SetErrInfo, AddErr2List Base response envelope.
Responce<T> Value; constructors from value/base response; SetErrInfo Typed response envelope.

Enums:

Enum Values
CpuType None, ASeries, QnaSeries, QSeries, LSeries, Fx3, Fx5, IQR
MitsubishiFrameType OneE, ThreeE, FourE, OneC, ThreeC, FourC
CommunicationDataCode Binary, Ascii
MitsubishiTransportKind Tcp, Udp, Serial
XyAddressNotation Octal, Hexadecimal
MitsubishiConnectionState Disconnected, Connecting, Connected, Reconnecting, Faulted
MitsubishiSerialMessageFormat Format1, Format4, Format5
DeviceValueKind Bit, Word
DeviceNumberFormat Decimal, Hexadecimal, Octal, XyVariable
MitsubishiReactiveQuality Good, Bad, Stale, Heartbeat, Error
MitsubishiReactiveWriteMode Queued, LatestWins, Coalescing
MitsubishiSchemaChangeKind None, MetadataOnly, AddressChange, DataTypeChange, GroupMembershipChange, StructureChange
MitsubishiTagRolloutPolicy AllowAll, SafeMetadataAndGroups

MitsubishiCommands constants:

Constant Value Command
DeviceRead 0x0401 Batch read
DeviceWrite 0x1401 Batch write
RandomRead 0x0403 Random read
RandomWrite 0x1402 Random write
BlockRead 0x0406 Block read
BlockWrite 0x1406 Block write
EntryMonitorDevice 0x0801 Monitor registration
ExecuteMonitor 0x0802 Execute monitor
ExtendUnitRead 0x0601 Intelligent-module buffer read
ExtendUnitWrite 0x1601 Intelligent-module buffer write
MemoryRead 0x0613 Memory read
MemoryWrite 0x1613 Memory write
ReadTypeName 0x0101 PLC type-name read
RemoteRun 0x1001 Remote RUN
RemoteStop 0x1002 Remote STOP
RemotePause 0x1003 Remote PAUSE
RemoteLatchClear 0x1005 Remote latch clear
RemoteReset 0x1006 Remote RESET
Unlock 0x1630 Remote password unlock
Lock 0x1631 Remote password lock
LoopbackTest 0x0619 Loopback
ClearError 0x1617 Clear error

Device addressing reference

MitsubishiDeviceAddress.Parse(value, xyNotation) validates and normalizes device addresses. MitsubishiDeviceAddress.Metadata exposes supported device families. A parsed address exposes Descriptor, whose MitsubishiDeviceMetadata contains Symbol, BinaryCode, AsciiCode, Kind, and NumberFormat; call GetRadix(xyNotation) when building custom tooling that needs the effective address base.

Device Kind Number format
X Bit Octal or hexadecimal via XyAddressNotation
Y Bit Octal or hexadecimal via XyAddressNotation
M Bit Decimal
L Bit Decimal
B Bit Hexadecimal
D Word Decimal
W Word Hexadecimal
R Word Decimal
ZR Word Hexadecimal
TN Word Decimal
TS Bit Decimal
TC Bit Decimal
CN Word Decimal
CS Bit Decimal
CC Bit Decimal
SM Bit Decimal
SD Word Decimal
var xOctal = MitsubishiDeviceAddress.Parse("X20", XyAddressNotation.Octal);
var xHex = MitsubishiDeviceAddress.Parse("X20", XyAddressNotation.Hexadecimal);
Console.WriteLine($"octal={xOctal.Number}, hex={xHex.Number}");

Advanced protocol and transport extension points

Most applications should use MitsubishiRx high-level APIs. The following public helpers are available for advanced testing, custom tooling, protocol inspection, and custom transports.

API Purpose
IMitsubishiTransport Implement ConnectAsync, DisconnectAsync, ExchangeAsync, IsConnected, Dispose, and DisposeAsync for custom transport backends.
SocketMitsubishiTransport Built-in TCP/UDP transport.
ReactiveSerialMitsubishiTransport Built-in SerialPortRx-backed serial transport.
ReactiveSerialPortAdapter Adapter around SerialPortRx; exposes IsOpen, ReceivedBytes, WrittenBytes, Open, Close, Write, DiscardInBuffer, DiscardOutBuffer, and Dispose.
MitsubishiProtocolEncoding.Encode(...) / Decode(...) Ethernet raw request encoding and response decoding.
MitsubishiProtocolEncoding.GetFixedResponseLength(...) Calculates fixed receive sizes where the frame supports it.
MitsubishiProtocolEncoding.EncodeDeviceBatchRead(...) / EncodeDeviceBatchWrite(...) Ethernet batch device operation builders.
MitsubishiProtocolEncoding.EncodeRandomRead(...) / EncodeRandomWrite(...) Ethernet random operation builders.
MitsubishiProtocolEncoding.EncodeMonitorRegistration(...) / EncodeExecuteMonitor(...) Ethernet monitor builders.
MitsubishiProtocolEncoding.EncodeBlockRead(...) / EncodeBlockWrite(...) Ethernet block operation builders.
MitsubishiProtocolEncoding.EncodeReadTypeName(...), EncodeRemoteOperation(...), EncodeLoopback(...), EncodeRemotePassword(...), EncodeMemoryAccess(...) Ethernet specialized command builders.
MitsubishiSerialProtocolEncoding.EncodeWordReadRequest(...), EncodeWordWriteRequest(...), EncodeBitReadRequest(...), EncodeBitWriteRequest(...) Serial batch operation builders.
MitsubishiSerialProtocolEncoding.EncodeRandomReadRequest(...), EncodeRandomWriteRequest(...), EncodeBlockReadRequest(...), EncodeBlockWriteRequest(...) Serial random/block builders.
MitsubishiSerialProtocolEncoding.EncodeMonitorRegistrationRequest(...), EncodeExecuteMonitorRequest(...), EncodeRemoteOperationRequest(...), EncodeReadTypeNameRequest(...), EncodeLoopbackRequest(...), EncodeMemoryAccessRequest(...), EncodeRawRequest(...) Serial specialized command builders.
MitsubishiSerialProtocolEncoding.Decode(...) Serial response decoding.
MitsubishiSerialProtocolEncoding.IsExpectedFrameComplete(...) Serial frame-completion predicate used by the serial transport.
ResponceExtensions.Fail(...) Converts a response to failed state with error metadata.
ResponceExtensions.ToBaseResponse(...) Converts a typed response into a base Responce.
Mixins.SafeClose(...) Compatibility socket close helper.

Custom transport example:

public sealed class AuditedTransport : IMitsubishiTransport
{
    private readonly IMitsubishiTransport _inner;

    public AuditedTransport(IMitsubishiTransport inner) => _inner = inner;
    public bool IsConnected => _inner.IsConnected;
    public ValueTask ConnectAsync(MitsubishiClientOptions options, CancellationToken cancellationToken = default)
        => _inner.ConnectAsync(options, cancellationToken);
    public ValueTask DisconnectAsync(CancellationToken cancellationToken = default)
        => _inner.DisconnectAsync(cancellationToken);
    public async ValueTask<byte[]> ExchangeAsync(MitsubishiTransportRequest request, CancellationToken cancellationToken = default)
    {
        Console.WriteLine(request.Description);
        return await _inner.ExchangeAsync(request, cancellationToken);
    }
    public void Dispose() => _inner.Dispose();
    public ValueTask DisposeAsync() => _inner.DisposeAsync();
}

await using var client = new MitsubishiRx.MitsubishiRx(options, new AuditedTransport(new SocketMitsubishiTransport()));

Troubleshooting notes

Tag name not found

If tag-based reads fail, verify:

  • client.TagDatabase has been assigned
  • Name matches the CSV/configured value
  • the tag’s Address is a valid Mitsubishi address string

Wrong X / Y values

If X or Y values do not match the PLC documentation:

  • switch XyNotation between Octal and Hexadecimal
  • confirm the expected addressing rule for the installed Ethernet module/path

ASCII vs binary mismatch

If communication succeeds on one endpoint but not another:

  • verify whether the PLC/module is configured for Binary or Ascii
  • verify frame family (OneE, ThreeE, FourE, OneC, ThreeC, FourC)
  • verify TCP vs UDP vs serial configuration on the PLC/module side
  • for serial, also verify MitsubishiSerialMessageFormat (Format1, Format4, Format5) and serial-port settings such as baud rate, parity, stop bits, and handshake

Serial support coverage

Current serial implementation is verified for:

  • reactive SerialPortRx-based transport
  • serial batch word reads through ReadWordsAsync(address, points)
  • serial batch word writes through WriteWordsAsync(address, values)
  • serial batch bit reads through ReadBitsAsync(address, points)
  • serial batch bit writes through WriteBitsAsync(address, values)
  • serial random word reads through RandomReadWordsAsync(addresses) for 1C, 3C, and 4C
  • serial random word writes through RandomWriteWordsAsync(values) for 1C, 3C, and 4C
  • serial block reads through ReadBlocksAsync(request) for 1C, 3C, and 4C
  • serial block writes through WriteBlocksAsync(request) for 1C, 3C, and 4C
  • serial monitor registration through RegisterMonitorAsync(addresses) for 1C, 3C, and 4C
  • serial monitor execution through ExecuteMonitorAsync() for 1C, 3C, and 4C
  • serial remote RUN/STOP/PAUSE/LATCH CLEAR/RESET through RemoteRunAsync(...), RemoteStopAsync(), RemotePauseAsync(), RemoteLatchClearAsync(), and RemoteResetAsync() for 1C, 3C, and 4C
  • serial type-name read through ReadTypeNameAsync() for 1C, tested 3C ASCII, and 4C format 5
  • serial loopback through LoopbackAsync(data) for 1C, tested 3C ASCII, and 4C format 5
  • serial memory and extend-unit access through ReadMemoryAsync(...) / WriteMemoryAsync(...) for 1C, tested 3C ASCII, and 4C format 5
  • raw serial command execution through ExecuteRawAsync(request) for 1C, tested 3C ASCII, and 4C format 5
  • 1C, 3C, and 4C frame selection
  • serial ASCII format 1/4 and 4C binary format 5 decode paths

1C random, block, and monitor operations are implemented by composing the verified 1C batch read/write requests. This keeps legacy computer-link paths usable even when the target does not provide native multi-device command forms.

Remote operations do not execute

Remote operations are target-dependent. Check:

  • CPU mode and permissions
  • Ethernet module settings
  • remote password/lock state
  • target family support and documented operational constraints

License

MIT


MitsubishiRx - Empowering Industrial Automation with Reactive Technology ⚡🏭

Product Compatible and additional computed target framework versions.
.NET 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.  net11.0 is compatible. 
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
3.0.1 49 7/4/2026

ReactiveUI.Primitives- and SerialPortRx-based Mitsubishi PLC support across Ethernet (1E/3E/4E) and serial (1C/3C/4C) MC protocol frames.