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.
Quick Connect (Recommended)
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.
Link Direct Device (CC-Link IE)
// 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 |