Files
lmxopcua/docs/research/drivers/melsec-slmp.md
Joseph Doherty 8fc147d8d4
v2-ci / build (push) Successful in 3m14s
v2-ci / unit-tests (push) Failing after 9m13s
docs: driver-expansion program — 8 research reports + 7 design docs, parallel-reviewed
Adds the driver-expansion program design (umbrella: universal Discover-backed
browser + MTConnect, MQTT/Sparkplug B, BACnet/IP, SQL poll, Omron, Modbus RTU;
MELSEC deferred) plus the per-driver research reports.

All docs went through a 7-agent parallel review against the codebase before
this commit. Highlights fixed in review:

- universal browser: FOCAS FixedTree fills post-connect -> UntilStable settle
  + FixedTree.Enabled patch; MQTT reconciled to bespoke (was contradicting the
  program doc's SupportsOnlineDiscovery=false verdict)
- modbus-rtu: SerialPort.ReadTimeout doesn't bound async BaseStream reads ->
  linked-CTS per-op deadline (R2-01 class); BCL enum reuse would leak
  System.IO.Ports into Contracts
- bacnet: DiscoveryRediscoverPolicy enum name; UDP 47808 contention; live
  suite rewritten around unicast Who-Is + BBMD (broadcast doesn't cross VMs)
- sql-poll: real tier registration via DriverFactoryRegistry.Register;
  blackhole gate must not docker-pause the shared central SQL Server
- mqtt: Sparkplug v3.0 STATE topic form; first-in-repo proto codegen noted
- omron: host hardcodes isIdempotent:false today (retry seam unshipped);
  v1 scopes UDTs to dotted-leaf access
- mtconnect: SecurityClassification.ViewOnly; factory ParseEnum<T> pattern
- program doc: both valid enum-serialization patterns; IRediscoverable is
  change-signal-gated; RTU P2 adds System.IO.Ports; label is host-side
2026-07-15 16:40:36 -04:00

409 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mitsubishi MELSEC SLMP / MC-Protocol driver — research
**Status:** research spike (no code). Recommendation is to build a native
**SLMP / MC-protocol** Equipment-kind driver, hand-rolling the 3E-binary frame,
patterned on the existing S7 (byte-oriented binary TCP) and Modbus (polling,
pre-declared-tag, no-browse) drivers.
**Scope note vs. `docs/v2/mitsubishi.md`:** that document catalogues reaching
MELSEC *over Modbus TCP* via bolt-on Ethernet modules (QJ71MT91, RJ71EN71 MODBUS
slave mode, FX5U built-in MODBUS, FX3U-ENET-P502…). This driver is the **native
alternative** that talks the PLC's own protocol and supersedes that lossy path.
The two can coexist — the Modbus path stays valid for sites that only enabled the
MODBUS slave — but SLMP is the strictly-better option wherever the CPU's native
Ethernet/MC endpoint is reachable.
---
## 1. Protocol summary + the gap it closes
### SLMP vs. MC 3E / 4E / 1E
**MC protocol** (MELSEC Communication protocol) is Mitsubishi's long-standing
client/server request-response protocol for reading and writing CPU device
memory over Ethernet (and serial). **SLMP** (SeamLess Message Protocol) is the
modern superset: SLMP's 3E and 4E frames are *bit-for-bit identical* to the
"QnA-compatible 3E/4E" MC-protocol frames, so an SLMP client talks to any
MC-protocol server and vice-versa. For our purposes **SLMP ≡ MC-protocol 3E/4E**;
we implement one framer and cover both product lines.
Frame families:
| Frame | Subheader (req/resp) | Use | Notes |
|---|---|---|---|
| **3E** | `5000` / `D000` | **Primary target.** Q / L / iQ-R / iQ-F / FX5 Ethernet | Stateless; no serial number. Simplest and most universal. |
| **4E** | `5400…0000` / `D400…0000` | iQ-R / iQ-F, when request/response correlation is wanted | 3E **plus** a 2-byte serial number echoed in the response — lets a client pipeline and match replies. Superset of 3E. |
| **1E** | `00`/`01`/`02`/`03` cmd bytes; `80`+cmd resp | Legacy A-series and some FX | Different, older layout. Only needed for very old CPUs. |
Each frame carries a **binary** or **ASCII** encoding. Binary is half the bytes
and what every modern deployment uses; ASCII exists mainly for text-only serial
links. We implement **3E binary** first, structure the framer so **4E binary**
is a thin superset, and treat 1E + ASCII as out-of-scope v1 (documented fallback).
### 3E binary request layout (the frame we hand-roll)
Reading `D200`, 1 word (from Mitsubishi *SLMP Reference Manual* SH-080956ENG and
the FA-Support worked example [3][4]):
```
50 00 Subheader (request 3E)
00 Network No. (0x00 = own/local network)
FF PC No. (0xFF = local/host CPU)
FF 03 Request dest module I/O (0x03FF = own CPU)
00 Request dest module station
0C 00 Request data length (little-endian; bytes that follow this field)
10 00 Monitoring timer (0x0010 = 250 ms units; 0 = wait forever)
01 04 Command (0x0401 Batch Read, little-endian on wire)
00 00 Subcommand (0x0000 = word units; 0x0001 = bit units)
C8 00 00 Head device number (200, 3 bytes little-endian)
A8 Device code (0xA8 = D, data register)
01 00 Device point count (1 word, little-endian)
```
Response: `D000` subheader, the routing echo, a data length, a **2-byte end code**
(`0000` = success; non-zero = error, e.g. `C051` device-count over range,
`4031` wrong device, `C059`/`C05C` command/subcommand error), then the payload.
Key commands:
| Command | Subcmd | Meaning | Max points |
|---|---|---|---|
| `0401` | `0000` | Batch Read, **word** units | 960 words (3E) |
| `0401` | `0001` | Batch Read, **bit** units | 7168 bits |
| `1401` | `0000` / `0001` | Batch Write, word / bit | 960 / 7168 |
| `0403` | `0000` | **Random Read** (scattered word/dword addresses) | 192 points |
| `1402` | `0000` | Random Write (scattered) | ~160 points |
| `0406` | — | Read block (multiple blocks) | — |
| `1001` / `1002` | — | Remote STOP / RUN | — |
| `0101` | — | Read CPU model name (cheap connectivity probe) | — |
### Device codes (3E binary, 1-byte code)
Bit-vs-word classification is the crux of the addressing model. Non-exhaustive;
**verify the full table against SH-080956ENG before coding** — these are the
common ones:
| Device | Code (hex) | Kind | Number base in engineering tools |
|---|---|---|---|
| X input | `9C` | **bit** | **hex** (Q/L/iQ-R), octal (FX/iQ-F) |
| Y output | `9D` | **bit** | **hex** (Q/L/iQ-R), octal (FX/iQ-F) |
| M internal relay | `90` | bit | decimal |
| L latch relay | `92` | bit | decimal |
| F annunciator | `93` | bit | decimal |
| B link relay | `A0` | **bit** | **hex** |
| SM special relay | `91` | bit | decimal |
| D data register | `A8` | **word** | decimal |
| W link register | `B4` | **word** | **hex** |
| R file register | `AF` | word | decimal |
| ZR extended file reg | `B0` | word | **hex** |
| SD special register | `A9` | word | decimal |
| TN timer current | `C2` | word | decimal |
| CN counter current | `C5` | word | decimal |
| TS/CS timer/counter contact | `C1`/`C4` | bit | decimal |
### The gap SLMP closes over the Modbus-TCP path
`docs/v2/mitsubishi.md` documents, in detail, why the Modbus path is lossy. SLMP
removes each of those failure modes:
1. **No per-site "Modbus Device Assignment" block.** SLMP addresses the CPU's
device memory directly by `<device code, number>` (e.g. `D200`, `M100`).
There is *no* 16-entry assignment table to configure in GX Works and no
"two sites with the same module expose different maps" problem — the biggest
single source of Modbus-path fragility disappears.
2. **X/Y reachable natively** as their own bit devices, not shoehorned into a
second non-zero coil bank (Modbus default maps X/Y at offset 8192+). The
hex-vs-octal number-base trap **remains** (it is a CPU convention, not a
transport artifact) — see §2/§3 — but there is no *second* Modbus-offset
translation layered on top.
3. **Full device coverage.** L, F, B, W, R, ZR, SM/SD, timers/counters are all
directly addressable. The Modbus path can only see whatever the engineer
chose to expose in the assignment block.
4. **No FC caps / sub-spec quirks.** No "QJ71MT91 doesn't support FC16", no
125-register FC03 ceiling, no odd-coil-byte truncation. SLMP batch read is
960 words in one PDU vs. Modbus' 125.
5. **Random read/write** of scattered addresses in one round-trip (`0403`/`1402`)
— impossible in Modbus without one PDU per contiguous run.
6. **Word-order (CDAB) is still a per-tag concern** (§3) — 32-bit values still
span two consecutive words low-word-first — but this is now *our* decode
choice, not something filtered through a module's fixed behavior.
### .NET library options — LICENSE analysis
| Option | License | .NET / maturity | Verdict |
|---|---|---|---|
| **HslCommunication** (`MelsecMcNet`) | **Commercial / NOT free** — "公对公签订合同", company-to-company contract + VAT invoice; source only with paid license [5][6] | net35+; very mature, widely used | **BLOCKER — do not use.** Requires a signed commercial contract. Rules it out for this repo. |
| **McProtocol** (SecondShiftEngineer) | **LGPL-3.0** [7] | netstandard2.0 (loads on net10); MC1E/3E/4E; last updated **2018** | LGPL is usable when consumed as an unmodified dynamic library, but it is a **copyleft** dependency to vet with legal, and it is **7+ years stale**. Useful as a *reference*, weak as a *dependency*. |
| **McpX** | **MIT** [8] | net7/8/9 + netstandard, cross-platform; TCP/UDP, 3E/4E binary+ASCII, batch + random + monitor + remote password; first release 2025, actively developed (v0.7.0 Jun 2026), ~53★, single maintainer | **MIT is clean.** The most license-friendly library and feature-complete, but young + one-maintainer + pre-1.0 → supply-chain/bus-factor risk for a production driver. |
| **`s-pms/melsec_mc_net`** | **MIT** [9] | **C** (not .NET) — Windows/Linux; 3E binary+ASCII, batch + typed read/write; full device-code table | Not consumable from .NET, but an **excellent MIT reference** for a hand-roll (device codes, framing, transaction serialization). |
| **libslmp / libslmp2** (Neucrede) | open-source C/C++ | C/C++ | Reference only, not .NET. |
**Recommendation: hand-roll the 3E-binary framer.** Rationale:
- The frame is small and fully specified (SH-080956ENG); the existing **S7 driver
already proves this repo hand-rolls byte-oriented binary TCP** with a clean
`IS7Plc` seam and a fake for tests. SLMP is *simpler* than S7's PDU negotiation.
- The only clean-licence library (McpX, MIT) is young/one-maintainer/pre-1.0 —
taking it as a hard dependency in a production OT server is more risk than a
~600-line framer we own and test.
- HslCommunication (the mature option) is a hard **commercial-licence blocker**.
- Keep McpX and the MIT `melsec_mc_net`/`libslmp` sources as **cross-check
references** for the framer + device-code table.
The repo already carries a **head-start**: `MelsecAddress` +`MelsecFamily` in
`ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing` encode the hex-vs-octal X/Y family
logic (written for the Modbus path). That logic ports directly into the new
driver's addressing project.
---
## 2. Capability mapping
Same capability-interface set as Modbus/S7 (`IDriver, ITagDiscovery, IReadable,
IWritable, ISubscribable, IHostConnectivityProbe`). This is a **full read/write**
PLC driver.
| Capability | SLMP mapping |
|---|---|
| **Connect** (`IDriver.InitializeAsync`) | TCP client to CPU Ethernet port (default **502** is Modbus; SLMP default is engineer-configured, commonly `1025`/`5007` or a user-set port; **UDP optional** — SLMP supports both; start TCP-only like Modbus). Open socket, optionally issue `0101` Read-CPU-model as a connect assertion. Reconnect/keepalive/idle-disconnect knobs mirror `ModbusDriverOptions`. |
| **Read** (`IReadable`) | **Batch Read `0401`** for contiguous runs (word subcmd `0000` for D/W/R/ZR/TN/CN; bit subcmd `0001` for M/X/Y/B/L). **Random Read `0403`** to coalesce scattered addresses into one PDU. A read planner (like Modbus' block coalescing / `MaxReadGap`) groups tags by device code into batch reads, splitting at the 960-word PDU cap. |
| **Write** (`IWritable`) | **Batch Write `1401`** (word/bit) for runs; **Random Write `1402`** for scattered. Bit-write to M/Y honored; word-write to D/W/R. Write-through gated by the standard `WriteOperate` node authz + `NodeWriteRouter` like every other protocol driver (the `EquipmentTagRefResolver<TDef>` pattern). |
| **Subscribe** (`ISubscribable`) | **Poll-based**, exactly like Modbus/S7 — **SLMP has no native push/unsolicited path** for general device polling. Reuse the shared polling overlay engine (the same `ISubscribable` polling helper Modbus uses). (SLMP *does* have a "device monitor register" `0801`/monitor `0802` mechanism, but it is a stateful convenience, not a change-push; poll is the right model.) |
| **Discover** (`ITagDiscovery`) | **Offline / config-driven.** SLMP exposes **no on-wire symbol table** — the driver returns exactly the pre-declared tags from `SlmpDriverOptions.Tags`, identical to Modbus. No online enumeration. |
| **Probe** (`IHostConnectivityProbe`) | Cheap `0101` Read-CPU-model, or a 1-word `0401` read of a known device, on an interval; raise `OnHostStatusChanged` on transitions. |
| **Alarms / History** | None native. (A future scripted-alarm layer works the same as for any polling driver.) |
### Data-type mapping (SLMP words/bits → OPC UA)
| OPC UA type | SLMP encoding | Notes |
|---|---|---|
| Boolean | 1 bit device (M/X/Y/B/L) via bit-subcmd; or 1 bit of a word device | Bit-in-word needs a `bitIndex` like Modbus `BitInRegister`. |
| Int16 / UInt16 | 1 word device | Native width. |
| Int32 / UInt32 | 2 consecutive words | **word order** matters — MELSEC native is low-word-first (`CDAB` when viewed as a word pair). Per-tag `wordOrder` knob. |
| Float (Single) | 2 words | Same word-order concern. |
| Int64 / UInt64 / Double | 4 words | Same. |
| String | N words, 2 ASCII chars/word | Byte-order-within-word knob (like Modbus `StringByteOrder`); MELSEC packs low byte = first char in some setups. |
| DateTime | vendor-specific packing | v1: skip or map from a documented word layout. |
| Array | `ValueRank=1`, `arrayLength` × element-words, one batch read | Cap at 960-word PDU; auto-chunk. |
**Addressing model = device code + number + base.** A tag names a *device code*
(`D`,`M`,`X`,`Y`,`W`,`B`,`R`,`ZR`,…), a *device number*, and — critically — the
number's **base**: X/Y/B/W/ZR are **hex** on Q/L/iQ-R and X/Y are **octal** on
FX/iQ-F; D/M/L/F/R/timers/counters are **decimal** everywhere. The driver must
preserve the engineering-tool base the operator typed (the `MelsecFamily` enum
already models this). Word-vs-bit is a property of the device code and selects
the batch-read subcommand.
---
## 3. TagConfig JSON shape
Mirrors `ModbusTagDefinition` / `ModbusEquipmentTagParser` (leading-`{` marks an
equipment-tag TagConfig blob; strict enum reads reject typos → `BadNodeIdUnknown`).
Proposed per-tag fields:
| Field | Type | Meaning |
|---|---|---|
| `device` | enum string | Device code: `D`,`M`,`X`,`Y`,`W`,`B`,`R`,`ZR`,`L`,`F`,`SM`,`SD`,`TN`,`CN`,… |
| `number` | string | Device number **as the operator types it in GX Works** (kept as string to preserve hex/octal). |
| `numberBase` | enum | `Decimal` / `Hex` / `Octal` — defaulted from the driver-level `MelsecFamily`, overridable per tag. |
| `dataType` | enum | `Boolean,Int16,UInt16,Int32,UInt32,Int64,UInt64,Float,Double,String`. |
| `bitIndex` | int 015 | For a Boolean read from a bit of a word device (omit for true bit devices). |
| `wordOrder` | enum | `ABCD` / `CDAB` — 32/64-bit word order (MELSEC native = `CDAB`, low word first). Default `CDAB`. |
| `stringLength` | int | ASCII chars for `String` (2 per word). |
| `stringByteOrder` | enum | High-byte-first vs low-byte-first within a word. |
| `arrayLength` | int | `isArray && arrayLength>=1` → OPC UA array. |
| `writable` | bool | Defaults true; node authz is the real gate. |
**Example** — a 32-bit float production count at `D200` (hex-family Q CPU, native
CDAB word order), and a bit alarm at `M100`:
```json
{
"device": "D",
"number": "200",
"numberBase": "Decimal",
"dataType": "Float",
"wordOrder": "CDAB",
"writable": false
}
```
```json
{
"device": "M",
"number": "100",
"numberBase": "Decimal",
"dataType": "Boolean"
}
```
```json
{
"device": "X",
"number": "1A",
"numberBase": "Hex",
"dataType": "Boolean",
"_comment": "Q-series X1A = physical input 26 decimal; hex base preserved from GX Works"
}
```
Driver-level `SlmpDriverOptions` mirrors `ModbusDriverOptions`: `Host`, `Port`,
`Frame` (`ThreeE`/`FourE`), `Encoding` (`Binary`), `NetworkNo`/`PcNo`/`DestModuleIo`/
`DestStation` routing bytes (defaults `0x00/0xFF/0x03FF/0x00` = local CPU),
`MonitoringTimer`, `Family` (`Q_L_iQR`/`F_iQF`), `Timeout`, reconnect/keepalive/
idle knobs, `MaxPointsPerRead` (≤960), a read-coalescing gap budget, and the
pre-declared `Tags` list.
---
## 4. BROWSEABILITY VERDICT — **NO**
**Definitively not browseable. No `*.Browser` project is warranted.**
SLMP/MC-protocol exposes a **flat, typed device-memory space** (`D`, `M`, `X`,
`Y`, `W`, `R`, …) addressed purely by `<device code, number>`. There is **no
on-wire symbol table, no tag directory, and no metadata service** in the
protocol. The commands enumerated in the SLMP Reference Manual are memory
read/write, remote CPU control, and self-test — none returns "what tags exist."
This is structurally identical to Modbus and S7, both of which are (correctly)
non-browseable in this repo: the address space is a raw memory map, not a
discoverable namespace. The symbolic tag names live only in the GX Works project
file on the engineer's PC, never on the wire.
**Searched-for exceptions, none qualifying:**
- **CPU model / capability reads** (`0101`, self-test `0619`) return device
*types and counts*, not a symbol list — useful for a connect assertion, not
browse.
- **Device monitor register** (`0801`/`0802`) is a client-side convenience for
re-reading a *previously specified* set — the client supplies the addresses;
the CPU never volunteers them.
- **Label/tag communication** (iQ-R "device/label access via SLMP" with a name
string) exists in newer firmware but requires the client to *already know* the
global-label name and only resolves a name the engineer defined — it is a
by-name read, still **not an enumeration**. Not a browse source.
- **GX Works project (`.gx3`) / CSV label export** could seed tags *offline*, but
that is a file-import feature, not an on-wire `IDriverBrowser` session, and is
out of scope here.
Verdict: **NO browser.** Tags are authored via the pre-declared list + the typed
tag editor (§7), exactly like Modbus/S7. In `TagConfigEditorMap` the driver gets
a typed editor but **no** `IDriverBrowser`/address-picker.
---
## 5. Test-fixture strategy
Follow the repo's established **hand-rolled TCP stub** pattern (S7's `IS7Plc`
fake, FOCAS's mock, Modbus' transport seam). SLMP is a request/response binary
protocol over TCP, so a deterministic stub is straightforward and CI-friendly.
**Recommended layers:**
1. **Unit — framer round-trip tests** (no socket). Encode/decode 3E-binary
request/response byte arrays against golden vectors taken from SH-080956ENG
and the MIT `melsec_mc_net`/McpX examples. This is where the device-code table,
hex/octal number parsing, word-order (CDAB), and end-code handling get pinned.
Port the existing `MelsecAddress` address tests.
2. **In-process fake `ISlmpClient`** (mirrors `FakeFocasClient` / S7's fake) for
driver-level read/write/subscribe/discover behavior without a network.
3. **Integration — a hand-rolled SLMP server stub** (a `TcpListener` that decodes
3E-binary requests and serves a seeded device-memory dictionary), packaged as a
Docker fixture under `tests/.../Docker/` with the `project=lmxopcua` label and
an env-gated skip (like `FocasSimFixture`'s `localhost:PORT` probe). This gives
real-socket read/write round-trips, batch-read chunking, and reconnect tests
deterministically. **This is the primary integration path** — write the stub;
don't depend on vendor tooling in CI.
4. **Optional real-target gates (not in CI):**
- **GX Works3 / GX Works2 simulator (GX Simulator3)** exposes an SLMP-capable
virtual CPU but is **Windows-only, licensed, GUI-driven** — usable for a
manual bring-up gate on a Windows box, not for automated CI (same posture as
the AVEVA/mxaccessgw live gates).
- **Open-source sims:** community MC/SLMP server sims exist (e.g. Go
`moge800/gomcprotocol`, Node `plcpeople/mcprotocol` has a server mode,
`libslmp`/`libmelcli` samples). Any could back a container, but a
repo-owned .NET stub is lower-maintenance and matches house style.
- A real FX5U / iQ-R on the bench is the final acceptance gate (a `LiveIntegration`
env-gated suite, mirroring the historian live gate).
Reuse `docs/v2/mitsubishi.md`'s `Mitsubishi_<model>_<behavior>` test-naming
convention for the behavioral cases (CDAB word order, hex X20 = 32, octal X20 = 16,
960-word batch cap, end-code `C051` on over-range).
---
## 6. Effort / risk / phasing
**Overall effort: moderate** — comparable to the S7 driver. The framer is small;
the complexity budget is almost entirely in the **addressing model** (device
codes × bit/word × hex/octal/decimal base × CDAB word order), which is exactly
where MELSEC drivers go wrong. Front-load it.
**Top risks:**
1. **Addressing correctness** (highest). The hex/octal/decimal base split per
device family, plus CDAB word order for 32/64-bit values, is the #1 real-world
bug source (per `docs/v2/mitsubishi.md`). Mitigation: exhaustive framer/address
unit tests with golden vectors *before* any driver wiring; reuse `MelsecAddress`.
2. **Library/licensing** — resolved by hand-rolling (avoids the HslCommunication
commercial blocker and the McpX bus-factor risk), but it means we own the
protocol correctness. Cross-check against the MIT `melsec_mc_net` + McpX + the
SLMP Reference Manual.
3. **Frame/port/family fragmentation** — 3E vs 4E, binary vs ASCII, TCP vs UDP,
Q/L/iQ-R hex vs FX/iQ-F octal, engineer-chosen port. Mitigation: ship **3E
binary / TCP** only in v1, structure the framer so 4E is a superset, document
the rest as fallbacks (same discipline as S7).
**Phasing:**
- **Phase 0 — Addressing + framer (no I/O).** New `…Driver.Slmp.Addressing`
project (device codes, base parsing, word order — port `MelsecAddress`) and a
3E-binary encoder/decoder with golden-vector unit tests. `…Driver.Slmp.Contracts`
with `SlmpDriverOptions` + `SlmpTagDefinition` + `SlmpEquipmentTagParser`.
- **Phase 1 — Read path.** `SlmpDriver : IDriver, ITagDiscovery, IReadable,
IHostConnectivityProbe` over an `ISlmpClient` TCP seam (S7-style), with a fake
+ the Docker stub server. Batch Read `0401` + read planner/coalescing + all
scalar/array/string/word-order decoding.
- **Phase 2 — Write + Subscribe.** Add `IWritable` (Batch Write `1401`, Random
Write `1402`; write-through via the `EquipmentTagRefResolver<TDef>` +
`NodeWriteRouter` pattern) and `ISubscribable` on the shared polling overlay.
- **Phase 3 — AdminUI typed tag editor.** `SlmpTagConfigEditor` +
`SlmpTagConfigModel` (FromJson/ToJson/Validate), registered in
`TagConfigEditorMap` + `TagConfigValidator`. Driver-edit page + `IDriverProbe`
(with the `JsonStringEnumConverter` fix from the driver enum-serialization
memory). **No** `IDriverBrowser` (§4).
- **Phase 4 — Random read/write coalescing + 4E frame + live gate.** `0403`/`1402`
optimization, optional 4E, and an env-gated `LiveIntegration` suite against a
real FX5U/iQ-R or GX Simulator3.
---
## References
1. Mitsubishi Electric, *SLMP Reference Manual* (SH-080956ENG) —
https://dl.mitsubishielectric.com/dl/fa/document/manual/plc/sh080956eng/sh080956engl.pdf
2. Mitsubishi Electric, *MELSEC iQ-F FX5 User's Manual (SLMP)* (JY997D56001) —
https://dl.mitsubishielectric.com/dl/fa/document/manual/plcf/jy997d56001/jy997d56001k.pdf
3. Inductive Automation, *Understanding Mitsubishi PLCs* (3E frame, D-register
word order) —
https://support.inductiveautomation.com/hc/en-us/articles/16517576753165-Understanding-Mitsubishi-PLCs
4. FA Support Me, *PLC and PC communication via SLMP Protocol* (3E-binary worked
example) —
https://www.fasupportme.com/portal/en/kb/articles/plc-and-pc-communication-via-slmp-protocol
5. dathlin/HslCommunication (GitHub) — "Not free open source" —
https://github.com/dathlin/hslcommunication
6. HslCommunication commercial-licence page —
http://www.hslcommunication.cn/Cooperation
7. McProtocol NuGet (LGPL-3.0, MC1E/3E/4E, last updated 2018) —
https://www.nuget.org/packages/McProtocol/
8. McpX NuGet / repo (MIT, .NET 7/8/9, 3E/4E binary+ASCII, batch+random) —
https://libraries.io/nuget/McpX
9. s-pms/melsec_mc_net (GitHub, MIT, C reference impl, full device-code table) —
https://github.com/s-pms/melsec_mc_net
10. Neucrede/libslmp2 (open-source C/C++ SLMP library, reference) —
https://github.com/Neucrede/libslmp2
11. In-repo: `docs/v2/mitsubishi.md` (MELSEC-over-Modbus quirks this driver
supersedes) and `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing/MelsecAddress.cs`
(hex/octal family logic to port).