Skip to content

feat(plc4j/slmp): add read-only SLMP (MELSEC 3E) driver#2640

Merged
sruehl merged 19 commits into
apache:developfrom
LivingLikeKrillin:feature/slmp-driver
Jul 14, 2026
Merged

feat(plc4j/slmp): add read-only SLMP (MELSEC 3E) driver#2640
sruehl merged 19 commits into
apache:developfrom
LivingLikeKrillin:feature/slmp-driver

Conversation

@LivingLikeKrillin

Copy link
Copy Markdown
Contributor

Second increment of the SLMP roadmap in #2585, building on the merged protocol module (#2597): the Java driver (plc4j/drivers/slmp) on the new SPI.

What's in it

  • slmp:tcp://{host}:{port} driver (default port 5007), read-only v0: Batch Read (0x0401), word units
  • Word devices D / R (decimal) and W (hex, optional 0x prefix); data types WORD (default), INT, UINT, DINT, UDINT, REAL; addresses like D350:DINT, R200:REAL[4], W0x1A:WORD[10] (each of these exact forms is unit-tested)
  • Conservative single-frame ceiling of 960 words per request — oversized requests are rejected, there is no splitting optimizer yet
  • Config: monitoring-timer (written into each 3E frame, default 0 = wait infinitely) and request-timeout (client-side, default 5000 ms)
  • Correlation design: the 3E frame carries no transaction id, so the connection runs a single in-flight request (maxConcurrentRequests = 1) with one pending-response slot. A timeout surfaces as REMOTE_ERROR for that tag only; other failures as INTERNAL_ERROR; one tag's failure never fails the whole read (partial-failure isolation). The mis-attribution caveat for late responses after a timeout is documented at the slot — 3E simply cannot prevent it, so a timed-out read should be treated as unreliable by the caller.
  • Housekeeping: website protocol page + nav entry + feature-matrix rows, RELEASE_NOTES entry, POM on the repo-wide Maven 4.1.0 model. I also registered the driver in plc4j-driver-all, following the majority of drivers — but noticed UMAS isn't registered there; happy to drop the registration if you'd rather stage it the same way

Validation

  • The ParserSerializer vectors from Add SLMP (MELSEC Communication 3E) protocol module (read-only wire layer) #2597 (worked examples of the public Mitsubishi reference manual SH-080008) are now actually executed by this module — 5/5 round-trip byte-identically through the generated code, closing the gap flagged in the protocol PR
  • End-to-end DriverTestsuite IT (API read → codec → response → PlcValue)
  • Failure-path unit tests over a scripted transport (modeled on ModbusRtuConnectionRequestChainTest): timeout isolation, transport loss mid-flight, send failure, unsolicited frame. Writing these exposed a real bug during development — the partial-failure mapping sat behind allOf(...).thenApply(...), which never runs on exceptional completion, so one failed tag failed the entire read; fixed with handle() and now pinned by the tests
  • Interop against an independently developed third-party 3E implementation over a live TCP socket: batch reads of D/W devices, including a multi-word value produced by the other implementation's encoder and decoded by this driver — confirming low-word-first byte order across implementations. The exercise surfaced two deviations from SH-080008 on the other side: a response-subheader mismatch observed on the wire (their responses had to be corrected locally before they would parse, since this driver discriminates responses on the spec's 0xD000 subheader), and an R-device-code difference found while reading their source (avoided in the test by sticking to D/W). The throwaway harness is not part of this PR; logs and details available on request
  • JaCoCo: 86% line coverage on the hand-written classes from a clean build (module gate: 70%)
  • Module verify (tests + JaCoCo gate) green with the branch rebased onto current develop; Apache RAT at module level: 32/32 files approved, 0 unknown

Bench validation on real MELSEC hardware would be very welcome — the driver is read-only, so pointing it at a test PLC is side-effect-free.

Next (not in this PR)


Developed with AI assistance; the wire behavior is verified against SH-080008's worked examples and cross-checked against an independent third-party implementation as noted above.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Add SlmpParserSerializerTest (extends ParserSerializerTestsuiteRunner) so the
3E frame reference vectors in ParserSerializerTestsuite.xml are actually
exercised instead of sitting unused, and wire in the protocols-slmp test-jar
so the testsuite resource is on the driver test classpath.

Correct the deviceCode enum fields in the testsuite from the shorthand
<deviceCode>D</deviceCode> to the canonical <SlmpDeviceCode .../> element form
so the vectors round-trip through parse/serialize.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
- Add SLMP protocol documentation page to the website and nav
- Add release-notes entry for the new driver
- Register the driver in the plc4j-driver-all bundle
- Migrate the module POM to the Maven 4.1.0 model used repo-wide

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
CompletableFuture.allOf completes exceptionally when any tag future
fails, and thenApply never runs on exceptional completion - so the
per-tag error mapping (timeout -> REMOTE_ERROR, anything else ->
INTERNAL_ERROR) was unreachable and a single failed tag failed the
entire read request, contrary to the documented partial-failure
isolation. Assemble the response in handle() instead.

Add SlmpConnectionFailurePathTest (modeled on
ModbusRtuConnectionRequestChainTest) covering timeout isolation,
transport loss, send failure and unsolicited frames.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
The docs advertise W0x1A:WORD[10]; the 0x prefix, datatype and quantity
were each tested separately but never combined in one address.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Language table: Java marked work-in-progress (read-only v0).
Feature table: read single/multiple green, everything else red.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
Align the website wording with the code comment: 960 is this driver's
conservative per-request ceiling, not asserted as the device maximum.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
The timeout cleanup ran pendingResponse = null unconditionally on the
delayer thread. CompletableFuture dependents complete LIFO, so the
throttle releases the next request before that cleanup runs; in that
window the successor installs its own slot, which the cleanup then
wiped - dropping the successor's genuine response as unsolicited and
spuriously timing it out as well. Hold the slot in an AtomicReference
and compare-and-clear against the owning future; claims in
handleIncomingMessage/failPending use getAndSet.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
- order the AtomicReference import per ASCII convention
- state the LIFO dependent ordering as OpenJDK behavior, and note the
  compare-and-clear is correct regardless of it
- log when a late response is dropped against an already-completed
  request, matching the unsolicited-frame log

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an initial read-only SLMP (Mitsubishi MELSEC 3E, binary over TCP) driver for Plc4J, along with documentation and integration into the build/release artifacts. This introduces tag parsing/decoding for common word devices and wires the driver into the existing SPI + testsuite infrastructure.

Changes:

  • Introduces plc4j-driver-slmp with a 3E binary message codec, connection implementation (single in-flight correlation), tag parsing, and word-based datatype decoding.
  • Adds unit + integration tests (parser/serializer vectors, DriverTestsuite IT, failure-path correlation tests).
  • Updates website protocol docs/nav, release notes, and registers the driver in plc4j-driver-all.

Reviewed changes

Copilot reviewed 26 out of 37 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
website/asciidoc/modules/users/pages/protocols/slmp.adoc New SLMP protocol/driver user documentation (connection string, tag formats, supported types/devices).
website/asciidoc/modules/users/pages/protocols/index.adoc Adds SLMP rows to protocol/feature matrices.
website/asciidoc/modules/users/nav.adoc Adds SLMP to user docs navigation.
RELEASE_NOTES Announces initial SLMP Java driver feature.
plc4j/drivers/slmp/** New SLMP driver module: driver/connection/codec/tag + generated readwrite model + tests/resources.
plc4j/drivers/all/pom.xml Adds SLMP driver as a runtime dependency of the “all drivers” artifact.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…out inputs

Harden three edge inputs surfaced by review, so bad values fail with a
clear, typed error instead of a raw exception or silent wire corruption:

- SlmpTag: wrap the quantity parse in try/catch so a value past
  Integer.MAX_VALUE (e.g. D0:WORD[999999999999]) raises
  PlcInvalidTagException, consistent with the device-number parse in the
  same method, rather than a raw NumberFormatException.
- SlmpConnection: validate config at connect time. monitoring-timer is
  serialized as an unsigned 16-bit 3E field, so reject values outside
  [0, 65535] before they can be truncated on the wire; request-timeout
  feeds CompletableFuture.orTimeout, so reject non-positive values that
  would time out every request immediately. Validation lives in onConnect
  because the configuration is populated by direct field injection, which
  bypasses the setters.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 37 changed files in this pull request and generated 2 comments.

Comment thread plc4j/drivers/slmp/src/main/java/org/apache/plc4x/java/slmp/tag/SlmpTag.java Outdated

@sruehl sruehl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see review comments above

Two parse-time guards in SlmpTag.of(), matching the existing
fail-with-PlcInvalidTagException-at-parse philosophy rather than
leaking a low-level serialization error later:

- Device numbers are a 24-bit unsigned field in the 3E frame; values
  above 0xFFFFFF (e.g. D16777216) now raise PlcInvalidTagException
  instead of failing during serialization with a BufferException.
- numberOfPoints is computed as (long) quantity * wordsPerElement so a
  large int-range quantity (e.g. 2147483647 with a 2-word type) can no
  longer overflow negative and slip past the MAX_POINTS ceiling.

Covered by SlmpTagTest.rejectsDeviceNumberExceeding24Bit and
rejectsQuantityWhoseWordCountOverflowsInt.

Signed-off-by: Jooyoung Jung <livinglikekrillin@gmail.com>
@LivingLikeKrillin
LivingLikeKrillin requested a review from sruehl July 14, 2026 09:46
@sruehl
sruehl merged commit 7b90aef into apache:develop Jul 14, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants