Table of Contents

SLMP .NET User Guide

Asynchronous SLMP (MC Protocol) client library for .NET 9.0 targeting Mitsubishi PLC families.

Getting Started

Installation

Add a project reference to src/PlcComm.Slmp/PlcComm.Slmp.csproj, or reference the compiled DLL in your .NET 9.0 project.

SlmpClient.OpenAndConnectAsync auto-detects the frame type (3E/4E) and compatibility mode (iQ-R/Legacy), then returns a connected QueuedSlmpClient.

using PlcComm.Slmp;

await using var client = await SlmpClient.OpenAndConnectAsync("192.168.1.10", port: 1025);

// Read 1 word from D100
var words = await client.ReadWordsRawAsync(new SlmpDeviceAddress(SlmpDeviceCode.D, 100), 1);
Console.WriteLine($"D100 = {words[0]}");

Port guide:

Port Usage
1025 iQ-R / iQ-F built-in Ethernet (default)
5000 GX Works3 / GX Simulator3
5007 Q/L series built-in Ethernet

Manual Connection

When you already know the frame type and compatibility mode:

using var client = new SlmpClient("192.168.1.10", 1025, SlmpTransportMode.Tcp);
client.FrameType = SlmpFrameType.Frame4E;
client.CompatibilityMode = SlmpCompatibilityMode.Iqr;
await client.OpenAsync();

Reading and Writing Devices

String-based Device Parsing

Use SlmpDeviceParser.Parse to convert address strings to SlmpDeviceAddress:

var addr = SlmpDeviceParser.Parse("D100");   // D register 100
var addr2 = SlmpDeviceParser.Parse("M0");    // Relay 0
var addr3 = SlmpDeviceParser.Parse("X1A");   // Input X1A (hex)

Word / DWord Read

// Raw word read (up to 960 words per request)
ushort[] raw = await client.ReadWordsRawAsync(SlmpDeviceParser.Parse("D0"), 10);

// Chunked read — auto-splits if count > 960
ushort[] all = await client.ReadWordsAsync(SlmpDeviceParser.Parse("D0"), 2000, allowSplit: true);

// DWord (32-bit) read
uint[] dwords = await client.ReadDWordsAsync(SlmpDeviceParser.Parse("D0"), 100, allowSplit: true);

Word / DWord Write

// Write single word
await client.WriteWordsAsync(SlmpDeviceParser.Parse("D100"), [1234]);

// Write multiple words
await client.WriteWordsAsync(SlmpDeviceParser.Parse("D100"), [10, 20, 30]);

Typed Read / Write

Use extension methods for automatic type conversion (float32, signed int, etc.).

dtype .NET Type Size
"U" ushort 1 word
"S" short (signed 16-bit) 1 word
"D" uint (unsigned 32-bit) 2 words
"L" int (signed 32-bit) 2 words
"F" float 2 words
using PlcComm.Slmp;

var addr = SlmpDeviceParser.Parse("D100");

// Read float32 from D100
float f = (float)await client.ReadTypedAsync(addr, "F");

// Read signed 32-bit from D200
int i = (int)await client.ReadTypedAsync(SlmpDeviceParser.Parse("D200"), "L");

// Write float32 to D100
await client.WriteTypedAsync(addr, "F", 3.14f);

// Read-modify-write single bit within a word
await client.WriteBitInWordAsync(SlmpDeviceParser.Parse("D300"), bitIndex: 3, value: true);

Named-Device Read

Read multiple devices in one call using address strings with optional type suffixes.

Address notation:

Format Meaning
"D100" D100 as ushort
"D100:F" D100 as float32
"D100:S" D100 as signed 16-bit
"D100:L" D100–D101 as signed 32-bit
"D100.3" Bit 3 of D100 (bool)
var result = await client.ReadNamedAsync(["D100", "D101:F", "D102:S", "D0.3"]);
// result["D100"]  → ushort 42
// result["D101:F"] → float 3.14
// result["D102:S"] → short -1
// result["D0.3"]  → bool true

Polling

PollAsync continuously yields snapshots at a fixed interval until the cancellation token fires.

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

await foreach (var snapshot in client.PollAsync(
    ["D100", "D101:F", "M0.0"],
    interval: TimeSpan.FromSeconds(1),
    ct: cts.Token))
{
    Console.WriteLine($"D100={snapshot["D100"]}, D101:F={snapshot["D101:F"]}");
}

Thread-Safe Access (QueuedSlmpClient)

SlmpClient is not thread-safe. For concurrent access from multiple tasks, wrap it in QueuedSlmpClient, which serializes all operations with an internal semaphore.

OpenAndConnectAsync already returns a QueuedSlmpClient.

var inner = new SlmpClient("192.168.1.10", 1025);
await using var client = new QueuedSlmpClient(inner);
await client.OpenAsync();

// Safe to call from multiple tasks simultaneously
var results = await Task.WhenAll(
    client.ReadWordsRawAsync(SlmpDeviceParser.Parse("D100"), 1),
    client.ReadWordsRawAsync(SlmpDeviceParser.Parse("D200"), 1)
);

Supported Devices

Actual availability depends on PLC model and firmware.

Bit Devices

Accessed via ReadBitsAsync() / WriteBitsAsync().

Symbol Device Name Address Base Notes
SM Special relay Decimal
X Input relay Hex
Y Output relay Hex
M Internal relay Decimal
L Latch relay Decimal
F Annunciator Decimal
V Edge relay Decimal
B Link relay Hex
SB Link special relay Hex
DX Direct input Hex
DY Direct output Hex
TS Timer contact Decimal
TC Timer coil Decimal
STS Retentive timer contact Decimal
STC Retentive timer coil Decimal
CS Counter contact Decimal
CC Counter coil Decimal
LTS Long timer contact Decimal iQ-R
LTC Long timer coil Decimal iQ-R
LSTS Long retentive timer contact Decimal iQ-R
LSTC Long retentive timer coil Decimal iQ-R
LCS Long counter contact Decimal iQ-R
LCC Long counter coil Decimal iQ-R

S (step relay) is present in the device code table but is intentionally disabled.

Word Devices

Accessed via ReadWordsRawAsync() / WriteWordsAsync().

Symbol Device Name Address Base Notes
SD Special register Decimal
D Data register Decimal
W Link register Hex
SW Link special register Hex
TN Timer current value Decimal
STN Retentive timer current value Decimal
CN Counter current value Decimal
Z Index register Decimal
LZ Long index register Decimal iQ-R
R File register Decimal
ZR File register (extended) Decimal
RD Refresh data register Decimal
LTN Long timer current value Decimal iQ-R; prefer ReadLongTimerAsync()
LSTN Long retentive timer current value Decimal iQ-R; prefer ReadLongRetentiveTimerAsync()
LCN Long counter current value Decimal iQ-R

Long Timer / Retentive Timer Helpers (iQ-R)

var results = await client.ReadLongTimerAsync(headNo: 0, points: 4);
foreach (var r in results)
    Console.WriteLine($"current={r.CurrentValue} set={r.SetValue} contact={r.Contact} coil={r.Coil}");
Method Reads Returns
ReadLongTimerAsync(headNo, points) LTN SlmpLongTimerResult[] with CurrentValue, SetValue, Contact (LTS), Coil (LTC)
ReadLongRetentiveTimerAsync(headNo, points) LSTN SlmpLongTimerResult[] for LST
ReadLtcStatesAsync(headNo, points) LTN → LTC coil bool[]
ReadLtsStatesAsync(headNo, points) LTN → LTS contact bool[]
ReadLstcStatesAsync(headNo, points) LSTN → LSTC coil bool[]
ReadLstsStatesAsync(headNo, points) LSTN → LSTS contact bool[]

Long timer / retentive timer set values (LT, LST) can only be read via these helpers, not as direct device codes.

Module Buffer Access (Intelligent Module)

var values = await client.ReadWordsExtendedAsync(SlmpQualifiedDeviceParser.Parse(@"U3\G100"), 4, default);
await client.WriteWordsExtendedAsync(SlmpQualifiedDeviceParser.Parse(@"U3\G100"), [1, 2, 3, 4], default);

U□ is the slot number in hex (e.g. U3, U3E0). Direct G / HG access without U□\ prefix is not supported.

// Word read/write (subcommand 0x0080)
var words = await client.ReadWordsExtendedAsync(SlmpQualifiedDeviceParser.Parse(@"J2\SW10"), 1, default);
await client.WriteWordsExtendedAsync(SlmpQualifiedDeviceParser.Parse(@"J1\SW14"), [2], default);

// Bit read/write (subcommand 0x0081, 16-point units)
var bits = await client.ReadBitsExtendedAsync(SlmpQualifiedDeviceParser.Parse(@"J1\X10"), 16, default);
await client.WriteBitsExtendedAsync(SlmpQualifiedDeviceParser.Parse(@"J1\X11"), [true], default);

Known limitations:

Device End Code Note
J1\B0 (B device) 0x4031 Not supported on CC-Link IE; GOT returns the same error

Other Station Routing (他局指定)

By default, requests target the directly connected PLC (self station). To route to another station, set TargetAddress on the client.

// Other station: Network 1, Station 1
client.TargetAddress = new SlmpTargetAddress(Network: 0x01, Station: 0x01);

// Full specification (network, station, moduleIo, multidrop)
client.TargetAddress = new SlmpTargetAddress(0x01, 0x01, 0x03FF, 0x00);

SlmpTargetAddress fields:

Field Default Description
Network 0x00 Network number (0x00 = local network)
Station 0xFF Station number (0xFF = control CPU of self station)
ModuleIo 0x03FF Module I/O No.
Multidrop 0x00 Multidrop station No. (0x00 = no multidrop)

Common ModuleIo values:

Value Description
0x03FF Own station / control CPU (default)
0x03E0 Multiple CPU No.1 / Remote head No.1
0x03E1 Multiple CPU No.2 / Remote head No.2
0x03E2 Multiple CPU No.3
0x03E3 Multiple CPU No.4
0x03D0 Control system CPU (redundant)
0x03D1 Standby system CPU (redundant)

SlmpTargetParser.ParseNamed shorthand:

Format Example Description
"SELF" "SELF" Own station, control CPU
"SELF-CPU1" .. "SELF-CPU4" "SELF-CPU2" Own station, Multiple CPU No.
"NWx-STy" "NW1-ST3" Network x, Station y
"NAME,NW,ST,IO,MD" "PLC2,1,3,0x03FF,0" Full specification

Error Handling

Exception Condition
SlmpError PLC returned a non-zero end code, or profile detection failed
TimeoutException Response not received within client.Timeout
SocketException Network error or connection refused
try
{
    var words = await client.ReadWordsRawAsync(SlmpDeviceParser.Parse("D100"), 1);
}
catch (SlmpError ex) when (ex.EndCode is not null)
{
    Console.WriteLine($"PLC error: 0x{ex.EndCode:X4}");
}
catch (SocketException)
{
    Console.WriteLine("Network error — check IP address and PLC Ethernet settings.");
}

Common end codes:

End Code Cause
0xC059 Device out of range for this PLC model
0x4004 Invalid frame format (check FrameType / CompatibilityMode)

Troubleshooting

Symptom Cause Fix
SlmpError "Could not detect PLC profile" Auto-detect failed Specify FrameType and CompatibilityMode manually
SocketException connection refused Wrong port Check GX Works3 SLMP port configuration
TimeoutException Wrong IP or route down Confirm with ping
Data looks wrong Wrong CompatibilityMode iQ-R series uses Iqr; Q/L series uses Legacy