MitsubishiRx.Reactive
3.0.1
dotnet add package MitsubishiRx.Reactive --version 3.0.1
NuGet\Install-Package MitsubishiRx.Reactive -Version 3.0.1
<PackageReference Include="MitsubishiRx.Reactive" Version="3.0.1" />
<PackageVersion Include="MitsubishiRx.Reactive" Version="3.0.1" />
<PackageReference Include="MitsubishiRx.Reactive" />
paket add MitsubishiRx.Reactive --version 3.0.1
#r "nuget: MitsubishiRx.Reactive, 3.0.1"
#:package MitsubishiRx.Reactive@3.0.1
#addin nuget:?package=MitsubishiRx.Reactive&version=3.0.1
#tool nuget:?package=MitsubishiRx.Reactive&version=3.0.1
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:
DisconnectedConnectingConnectedReconnectingFaulted
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
Minternal relaysXinputsYoutputsL,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:
QueuedLatestWinsCoalescing
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
MitsubishiTagDefinitionMitsubishiTagDatabaseMitsubishiTagGroupDefinitionMitsubishiTagGroupSnapshotMitsubishiRx.TagDatabaseReadWordsByTagAsync(...)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:
WordDWordFloat
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 DataTypeis validated when tags are added or imported from CSV- supported
DataTypevalues are:BitWordDWordFloatString
DataTypematching 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, andAsciiwherever 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 });
Recommended usage model
- 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
MitsubishiTagGroupDefinitionscan 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:
NameAddress
- 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, andLengthvalues should use invariant-culture formatting Signedshould betrueorfalse- do not include engineering units inside
ScaleorOffset Addressmust contain a valid Mitsubishi device address string usable by the client- if
DataTypeis supplied it must be one of:BitWordDWordFloatStringInt16UInt16Int32UInt32
- if
Encodingis supplied it must be one of:AsciiUtf8Utf16
- if
ByteOrderis supplied it must be one of:LittleEndianBigEndian
DataType,Encoding, andByteOrdermatching is case-insensitive when imported, but stored in canonical form- use one logical PLC item per CSV row
- keep
Nameunique 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 conventionAddress: exact Mitsubishi address string with no extra spacesDataType: one ofBit,Word,DWord,Float,String,Int16,UInt16,Int32,UInt32Length: set this for string tags so code can callReadStringByTagAsync(tagName)without supplying a length each timeEncoding: useAsciiunless the PLC text really requiresUtf8orUtf16Signed: set totruefor signed integer word/double-word values or signed scaled engineering valuesByteOrder: useLittleEndianfor the normal two-word Mitsubishi layout andBigEndianonly when the external data contract requires itUnits: use for UI/reporting metadata such asrpm,°C, oritemsDescription: operator-facing sentenceNotes: 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
Nameis missing from the headerAddressis missing from the header- a row has an empty required value for
NameorAddress Scale,Offset, orLengthcontain invalid numeric valuesSignedcontains an invalid boolean valueDataTypecontains an unsupported valueEncodingcontains an unsupported valueByteOrdercontains 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:
BitWordDWordFloatStringInt16UInt16Int32UInt32
The accepted Encoding values are exactly:
AsciiUtf8Utf16
The accepted ByteOrder values are exactly:
LittleEndianBigEndian
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:
ReadWordsAsyncWriteWordsAsyncReadBitsAsyncWriteBitsAsyncReadTypeNameAsyncLoopbackAsync- 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>orTask<Responce<T>>. - Check
IsSucceedbefore usingValue. - Failure details are available through
Err,ErrCode,ErrList, andException. - Optional
CancellationTokenparameters 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, orZR200. - Tag-based methods require
client.TagDatabaseto be assigned first. - Ethernet
1Epaths have a smaller command set than3E/4E. Serial1C,3C, and4Cpaths 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.TagDatabasehas been assignedNamematches the CSV/configured value- the tag’s
Addressis a valid Mitsubishi address string
Wrong X / Y values
If X or Y values do not match the PLC documentation:
- switch
XyNotationbetweenOctalandHexadecimal - 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
BinaryorAscii - 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)for1C,3C, and4C - serial random word writes through
RandomWriteWordsAsync(values)for1C,3C, and4C - serial block reads through
ReadBlocksAsync(request)for1C,3C, and4C - serial block writes through
WriteBlocksAsync(request)for1C,3C, and4C - serial monitor registration through
RegisterMonitorAsync(addresses)for1C,3C, and4C - serial monitor execution through
ExecuteMonitorAsync()for1C,3C, and4C - serial remote RUN/STOP/PAUSE/LATCH CLEAR/RESET through
RemoteRunAsync(...),RemoteStopAsync(),RemotePauseAsync(),RemoteLatchClearAsync(), andRemoteResetAsync()for1C,3C, and4C - serial type-name read through
ReadTypeNameAsync()for1C, tested3CASCII, and4Cformat 5 - serial loopback through
LoopbackAsync(data)for1C, tested3CASCII, and4Cformat 5 - serial memory and extend-unit access through
ReadMemoryAsync(...)/WriteMemoryAsync(...)for1C, tested3CASCII, and4Cformat 5 - raw serial command execution through
ExecuteRawAsync(request)for1C, tested3CASCII, and4Cformat 5 1C,3C, and4Cframe 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 | Versions 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. |
-
net10.0
- ReactiveUI.Primitives.Extensions.Reactive (>= 6.0.0)
- ReactiveUI.Primitives.Reactive (>= 6.0.0)
- SerialPortRx.Reactive (>= 5.0.20)
- YamlDotNet (>= 18.1.0)
-
net11.0
- ReactiveUI.Primitives.Extensions.Reactive (>= 6.0.0)
- ReactiveUI.Primitives.Reactive (>= 6.0.0)
- SerialPortRx.Reactive (>= 5.0.20)
- YamlDotNet (>= 18.1.0)
-
net8.0
- ReactiveUI.Primitives.Extensions.Reactive (>= 6.0.0)
- ReactiveUI.Primitives.Reactive (>= 6.0.0)
- SerialPortRx.Reactive (>= 5.0.20)
- YamlDotNet (>= 18.1.0)
-
net9.0
- ReactiveUI.Primitives.Extensions.Reactive (>= 6.0.0)
- ReactiveUI.Primitives.Reactive (>= 6.0.0)
- SerialPortRx.Reactive (>= 5.0.20)
- YamlDotNet (>= 18.1.0)
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.