SLMP Protocol for Rust
Async Rust implementation of the SLMP library, based on the plc-comm-slmp-dotnet
implementation and aligned with the shared plc-comm-slmp-cross-verify harness.
The crate focuses on Binary 3E / 4E SLMP over TCP and UDP and keeps the same operation meaning as the existing Python, .NET, C++, Node-RED, and Rust verification clients.
What This Repo Contains
- async Rust library crate:
src/ cross-verifywrapper binary:src/bin/slmp_verify_client.rs- runnable examples:
examples/ - address and usage guides:
docs/ - minimal
napi-rsworkspace member for future Node packaging:crates/slmp-node
Current Scope
- raw device access: word, bit, dword, float32
- random read/write
- block read/write
- extended-device read/write
- memory read/write
- extend-unit read/write
- remote operations and self-test
- high-level typed helpers
- high-level named read/write and polling helpers
- live device-range catalog via user-selected PLC family plus family
SDregisters slmp_verify_clientwrapper forplc-comm-slmp-cross-verify- minimal
napi-rsNode binding scaffold incrates/slmp-node
PLC Family Defaults
SlmpConnectionOptions::new(host, family) applies the default SLMP frame and
compatibility profile for the selected PLC family. PLC IO Checker iOS and
Android store the selected model label only and rely on this mapping instead of
persisting frame or compatibility settings.
| PLC family | Default frame | Default compatibility |
|---|---|---|
iQ-R, iQ-L, MX-R, MX-F |
Frame4E |
Iqr |
iQ-F |
Frame3E |
Legacy |
QCPU, LCPU, QnU, QnUDV |
Frame3E |
Legacy |
RD is a real MELSEC device on the live iQ-L target that was checked, but it is
profile-sensitive: legacy word subcommand 0x0000 returned 0xC05B, while Iqr
word subcommand 0x0002 read RD0 and RD524287 successfully and rejected
RD524288 with range error 0x4031. Use the iQ-L family defaults or set
compatibility_mode = SlmpCompatibilityMode::Iqr explicitly when probing RD
on iQ-L class PLCs.
For iQ-F and legacy PLC families, do not select Frame4E; use the selected
PLC family defaults instead of probing alternate profiles.
Installation
Install from crates.io:
The public package name is plc-comm-slmp-rust, and the library import path is
plc_comm_slmp.
Requires Rust 1.85 or newer.
Cargo.toml:
[]
= "0.1.9"
= { = "1", = ["rt-multi-thread", "macros"] }
Quick Start
Raw Client Usage
use ;
async
Recommended High-Level Usage
use ;
async
Typed Access
use ;
# async
Runnable Examples
The repository includes examples that compile as part of the crate and can be run directly against a PLC or mock server.
raw_read_write
Low-level word read plus optional write/read-back.
SLMP_HOST=192.168.250.100 \
SLMP_PORT=1025 \
SLMP_PLC_FAMILY=iq-r \
Enable writes explicitly:
SLMP_ENABLE_WRITES=1 \
SLMP_WRITE_ADDRESS=D600 \
SLMP_WRITE_VALUES=111,222 \
named_helpers
Named snapshot, typed decoding, optional write_named, and one poll_named
tick.
SLMP_HOST=192.168.250.100 \
SLMP_PLC_FAMILY=iq-f \
SLMP_NAMED_ADDRESSES='D100,D200:F,D50.3,LTN10:D,LTS10' \
advanced_operations
Safe read-heavy sample that covers type-name, random read, block read, extended device read, and self-test loopback.
SLMP_HOST=192.168.250.100 \
SLMP_PLC_FAMILY=iq-r \
SLMP_RANDOM_WORDS='D100,R10' \
SLMP_RANDOM_DWORDS='D200,LTN10' \
SLMP_EXT_DEVICE='J1\W10' \
device_range_catalog
Reads the family-specific SD window for a user-selected PLC family and prints
points plus formatted address ranges such as X0000-X2FFF.
SLMP_HOST=192.168.250.100 \
SLMP_PORT=1025 \
SLMP_PLC_FAMILY=iq-f \
device_matrix_compare
Real-PLC regression sample that writes the same address through multiple command paths and checks that read-back stays aligned.
- bit devices:
write_bits,write_random_bits,write_typed,write_named, rawrequest - word devices:
write_words,write_random_words,write_typed,write_named, rawrequest - 32-bit devices:
write_dwords,write_random_words,write_typed,write_named, rawrequest J1\\...devices: extended helper APIs plus rawrequest
SLMP_HOST=192.168.250.100 \
SLMP_PORT=1025 \
SLMP_PLC_FAMILY=iq-r \
This example exits non-zero when command paths for the same address disagree.
Focus on a subset while debugging:
SLMP_COMPARE_ONLY='LTS10,LTC10,LCS10,LCC10,LTN10,LSTN10' \
extended_device_coverage
Real-PLC sweep for Extended Specification devices. It reads every
SLMP_EXT_DEVICES x SLMP_EXT_POINTS x SLMP_EXT_DIRECT_MEMORIES candidate,
optionally writes and restores with SLMP_EXT_WRITE_CHECK=1, and writes a
Markdown report with OK/NG kept visible.
SLMP_HOST=192.168.250.100 \
SLMP_PORT=1025 \
SLMP_PLC_FAMILY=iq-r \
SLMP_EXT_DEVICES='U3E0\G10' \
SLMP_EXT_POINTS='1,2' \
SLMP_EXT_WRITE_CHECK=1 \
device_range_sample_compare
Real-PLC range sample that reads the live device-range catalog, then tests up to
10 addresses for each supported device: start, end, middle, and additional
distributed points. Each point is read, written with two values, verified, and
restored. Normal bit devices also compare contiguous read_bits results with
DeviceReadBlock packed bit-word values.
SLMP_HOST=192.168.250.100 \
SLMP_PORT=1025 \
SLMP_PLC_FAMILY=iq-l \
Focus on a subset while debugging:
SLMP_SAMPLE_ONLY='D,M,RD' \
route_validation_compare
Real-PLC route sample aligned with the more advanced plc-comm-slmp-dotnet
coverage. It compares block, random, typed, LZ, and range-error routes so that
helper APIs keep resolving to the intended SLMP commands.
SLMP_HOST=192.168.250.100 \
SLMP_PORT=1025 \
SLMP_PLC_FAMILY=iq-l \
Use SLMP_PORT=1027 SLMP_TRANSPORT=udp for the iQ-L UDP path.
The shared environment variables for these examples are documented in
docs/RECIPES.md.
For live PLC-dependent device limits resolved from a user-selected PLC family
plus family SD registers, see docs/DEVICE_RANGES.md.
Public API Surface
Main exports:
SlmpConnectionOptionsSlmpClientSlmpAddressread_type_name/read_device_range_catalog/read_device_range_catalog_for_familyread_typed/write_typedwrite_bit_in_wordread_named/write_namedpoll_namedrun_device_range_sample_comparerun_route_validation_compareread_words_single_request/read_dwords_single_requestread_words_chunked/read_dwords_chunkedwrite_words_single_request/write_dwords_single_requestwrite_words_chunked/write_dwords_chunked
Important model types:
SlmpDeviceAddressSlmpQualifiedDeviceAddressSlmpTargetAddressSlmpExtensionSpecSlmpTypeNameInfoSlmpDeviceRangeCatalog,SlmpDeviceRangeEntry,SlmpDeviceRangeFamilySlmpRandomReadResultSlmpBlockRead,SlmpBlockWrite,SlmpBlockReadResultSlmpLongTimerResultSlmpValue
Supported Address Forms
High-level helpers are intended to cover these forms first.
- plain word devices:
D100,R50,ZR0,TN0,CN0 - plain bit devices:
M1000,X20,Y20,B10 - typed suffixes:
D100:S,D200:D,D300:L,D400:F - bit-in-word form:
D50.3 - long current-value forms:
LTN10:D,LSTN20:D,LCN30:D - extended devices:
J1\\SW0,U3\\G100,U1\\HG0
.bit notation is only valid for word devices. Address bit devices directly.
See also:
Choosing the Right API
- Use raw device methods when you need exact SLMP request control.
- Use
read_typedandwrite_typedwhen one address maps to one scalar value. - Use
read_namedandwrite_namedwhen your application needs a snapshot with mixed dtypes and bit-in-word decoding. - Use
poll_namedfor a lightweight periodic stream. - Use
read_randomandread_blockwhen you want to keep request counts low. - Use the extended-device methods for
J...andU...paths. read_namedandwrite_namedcurrently target plain device addresses, notJ...orU...qualified addresses.
Long-Family Behavior
The Rust implementation follows the same normalized behavior as the other libraries:
LTN,LSTN,LCN, andLZdefault to 32-bit readsLCNhigh-level reads and writes use random dword access (0x0403/0x1402)LTS,LTC,LSTS,LSTC,LCS, andLCCare state readsLCSandLCCreads use direct bit read throughread_typed/read_namedLCSandLCCare rejected forRead Random (0x0403),Read Block (0x0406),Write Block (0x1406), andEntry Monitor Device (0x0801)- direct bit reads/writes (
0x0401/0x1401) and Read Random (0x0403) forLTS,LTC,LSTS, andLSTCare rejected by the Rust client API; use helper APIs, random bit write (0x1402), or 4-word block reads fromLTN/LSTN - direct bit writes (
0x1401) forLCSandLCCare also rejected; usewrite_typed/write_namedso random bit write (0x1402) is selected - direct dword reads for
LTN,LSTN,LCN, andLZare rejected; use helper APIs, random dword high-level access, or explicit 4-word block reads where supported
Route guard note: keep low-level direct bit routes guarded for
LTS/LTC/LSTS/LSTC and direct bit writes guarded for LCS/LCC.
High-level writes must continue to resolve to random bit write (0x1402).
That behavior is intentional and is enforced through
plc-comm-slmp-cross-verify.
Cross-Verify
This repo is designed to participate in:
plc-comm-slmp-cross-verify/specs/sharedpython verify.py --clients rust- full parity runs with Python, .NET, C++, and Node-RED
- live PLC verification through the same saved baseline/profile flow
The wrapper binary used by the harness is:
Development
Format and test:
Run the Rust tests:
Check the Node binding scaffold:
Run Rust-only parity through the canonical harness:
Run full parity:
Run live PLC verification with the validated R120 profile:
Node Binding
crates/slmp-node is currently a thin napi-rs scaffold. It is not yet the
main delivery path. The current purpose is to keep the Rust workspace ready for
future Node package work without redesigning the crate layout later.
License
Distributed under the MIT License.