A lightweight, microcontroller-oriented C++ library for Mitsubishi SLMP (Seamless Message Protocol). The core client stays buffer-oriented and allocation-free, and an optional high-level layer adds string-address helpers for typed reads, mixed snapshots, and polling.
PlatformIO Registry:
Install from PlatformIO Registry:
lib_deps =
fa-yoshinobu/slmp-connect-cpp-minimal@^0.4.9This library deliberately exposes two layers.
- Core low-level layer in
slmp_minimal.h- fixed caller-owned buffers
- no heap allocation in the core path
- direct control over sync and async request flow
- intended for embedded firmware, RTOS tasks, and size-sensitive builds
- Optional high-level layer in
slmp_high_level.h- string addresses such as
D100,D200:F, andD50.3 - typed helpers, named snapshots, and reusable polling plans
- intended for application code, quick bring-up, and parity with the Python and .NET libraries
- string addresses such as
The goal is not to force one style. The goal is to let firmware stay predictable while still offering a user-friendly surface when convenience matters more than raw control.
- Embedded Optimized: No dynamic memory allocation (
malloc/new). Predictable RAM usage. - Async Support: Built-in state machine for non-blocking communication.
- Optional High-Level API:
readTyped,writeTyped,readNamed,writeNamed, andPoller. - Broad Hardware Support: Board-agnostic design for Wi-Fi and Ethernet (Arduino-compatible).
- Binary 3E/4E: Supports both modern and legacy SLMP frames.
- CI-Ready: Host-side G++ testing and size regression monitoring.
- Cross-Verify Shared Vectors: Host tests regenerate
tests/generated_shared_spec.hfromplc-comm-slmp-cross-verify/specs/sharedso frame/address/device expectations stay aligned with the cross-library parity harness. - Maintained Samples: Small low-level, high-level, and compile-checked host examples.
Two maintained ESP32 examples make the binary-size tradeoff visible on the same board:
examples/esp32_devkitc_low_levelexamples/esp32_devkitc_high_level
Build them with:
pio run -e esp32-devkitc-low-level
pio run -e esp32-devkitc-high-levelThen compare:
.pio/build/esp32-devkitc-low-level/firmware.bin.pio/build/esp32-devkitc-high-level/firmware.bin
Measured on the current reference build:
- low-level sample: Flash
749717bytes, RAM45064bytes - high-level sample: Flash
772181bytes, RAM45184bytes - delta: Flash
+22464bytes, RAM+120bytes
The measurement log is stored in docsrc/validation/reports/ESP32_DEVKITC_SIZE_COMPARISON.md.
The interactive Atom Matrix and W6300 console programs were moved to the companion repository:
This repository now stays focused on the library itself and the smallest maintained samples.
#include <slmp_high_level.h>
#include <slmp_arduino_transport.h>
WiFiClient tcp;
slmp::ArduinoClientTransport transport(tcp);
uint8_t tx_buffer[128];
uint8_t rx_buffer[128];
slmp::SlmpClient plc(transport, tx_buffer, 128, rx_buffer, 128);
void setup() {
constexpr auto family = slmp::highlevel::PlcFamily::IqR;
slmp::highlevel::configureClientForPlcFamily(plc, family);
slmp::TypeNameInfo info = {};
if (!plc.connect("192.168.250.100", 1025)) {
return;
}
plc.readTypeName(info);
}
void loop() {
slmp::highlevel::Snapshot snapshot;
constexpr auto family = slmp::highlevel::PlcFamily::IqR;
if (slmp::highlevel::readNamed(plc, family, {"SM400", "D100", "D200:F", "D50.3"}, snapshot) ==
slmp::Error::Ok) {
// snapshot[0] -> SM400
// snapshot[1] -> D100
// snapshot[2] -> D200:F
// snapshot[3] -> D50.3
}
}#include <slmp_arduino_transport.h>
#include <slmp_minimal.h>
WiFiClient tcp;
slmp::ArduinoClientTransport transport(tcp);
uint8_t tx_buffer[128];
uint8_t rx_buffer[128];
slmp::SlmpClient plc(transport, tx_buffer, sizeof(tx_buffer), rx_buffer, sizeof(rx_buffer));
void setup() {
plc.setFrameType(slmp::FrameType::Frame4E);
plc.setCompatibilityMode(slmp::CompatibilityMode::iQR);
plc.connect("192.168.250.100", 1025);
}
void loop() {
uint16_t word = 0;
plc.readOneWord(slmp::dev::D(slmp::dev::dec(100)), word);
}For application code, the recommended order is:
- Create the fixed-buffer
slmp::SlmpClient. - Set one explicit
PlcFamilythroughconfigureClientForPlcFamily(...). - Connect with
plc.connect(...). - Use
readTyped,writeTyped,readNamed,writeNamed, andPoller. - Drop to
slmp_minimal.honly when you need direct frame-level control, manual async state machines, or specialized embedded integration.
Automatic profile probing is intentionally not part of the current API surface. The high-level helper layer derives fixed frame and compatibility mode defaults from one explicit PlcFamily. Use the PlcFamily overloads of readTyped, writeTyped, readNamed, writeNamed, Poller::compile, parseAddressSpec(), normalizeAddress(), or formatAddressSpec() whenever you need deterministic string-address handling.
The optional helper layer accepts the same user-facing address forms that the Python and .NET libraries use.
| Form | Meaning | Example |
|---|---|---|
D100 |
16-bit unsigned word | one word from D100 |
D100:S |
16-bit signed word | signed value from D100 |
D200:D |
32-bit unsigned dword | D200 + D201 |
D200:L |
32-bit signed dword | signed D200 + D201 |
D300:F |
IEEE-754 float32 | D300 + D301 as float |
D50.3 |
bit inside one word device | bit 3 of D50 |
M1000 |
direct bit device | one logical bit |
Notes:
.bitnotation is valid only for word devices such asD50.3.- Direct bit devices should be addressed directly, for example
M1000,X20, orY1A. B,W,SB,SW,DX, andDYkeep Mitsubishi hexadecimal numbering rules.- High-level string
X/Yaddresses require an explicitPlcFamily. PlcFamily::IqFinterprets stringX/Yin octal. Other supported families use hexadecimal stringX/Y.
The high-level layer lives in slmp_high_level.h.
- It is user-facing and convenience-oriented.
- It uses
std::stringandstd::vector. - The core
slmp_minimal.hclient remains the fixed-buffer, no-allocation transport and protocol layer. - You can compile it out with
SLMP_MINIMAL_ENABLE_HIGH_LEVEL=0when image size matters and the high-level helpers are not used.
The high-level layer also exposes explicit PLC-family device-range helpers. This path does not call ReadTypeName. The caller chooses the family and the helper reads the family-specific SD block once to build a catalog of point_count, inclusive upper_bound, and formatted ranges such as X0000-X1777.
slmp::highlevel::DeviceRangeCatalog catalog;
const slmp::Error err = slmp::highlevel::readDeviceRangeCatalogForPlcFamily(
plc,
slmp::highlevel::PlcFamily::QnU,
catalog);
if (err == slmp::Error::Ok) {
// catalog.entries -> X/Y/M/... with point_count and address_range
}Supported PlcFamily values are IqF, IqR, IqL, MxF, MxR, QCpu, LCpu, QnU, and QnUDV.
slmp::highlevel::Value v = slmp::highlevel::Value::u16Value(321);
slmp::highlevel::writeTyped(plc, "D100", v);
slmp::highlevel::Snapshot updates = {
{"D100", slmp::highlevel::Value::u16Value(321)},
{"D200:F", slmp::highlevel::Value::float32Value(12.5f)},
{"D50.3", slmp::highlevel::Value::bitValue(true)},
};
slmp::highlevel::writeNamed(plc, updates);
std::vector<uint16_t> words;
slmp::highlevel::readWordsChunked(plc, "D1000", 1200, words, 960, true);Chunked helpers are explicit opt-in. Typed helpers, named snapshots, and other logical-value APIs do not silently change one caller-visible value into a different fallback request shape.
char normalized[32] = {};
if (slmp::highlevel::normalizeAddress(" d200:f ", normalized, sizeof(normalized)) == slmp::Error::Ok) {
// normalized -> "D200:F"
}
if (slmp::highlevel::normalizeAddress(" y217 ", slmp::highlevel::PlcFamily::IqF, normalized, sizeof(normalized)) == slmp::Error::Ok) {
// normalized -> "Y217"
}This minimal client focuses on direct device access. Actual availability depends on PLC model, firmware, and access settings.
| Group | Codes | Status | Notes |
|---|---|---|---|
| Bit devices (direct / high-level) | SM, X, Y, M, L, F, V, B, TS, TC, LTS, LTC, STS, STC, LSTS, LSTC, CS, CC, LCS, LCC, SB, DX, DY | Supported | X/Y/B/SB/DX/DY use hexadecimal numbering. Long timer / counter bit families are iQ-R device codes. |
| Word devices (direct / high-level) | SD, D, W, SW, TN, LTN, STN, LSTN, CN, LCN, Z, LZ, R, ZR, RD | Supported | W/SW use hexadecimal numbering. LTN/LSTN also have dedicated decoded helper APIs. |
| Direct device codes that stay excluded from generic direct access | G, HG | Not supported | Use the dedicated module-buffer / extended-device APIs instead of normal direct-device helpers. |
| Extended device access | U\\G, U\\HG, J\\device |
Supported via dedicated APIs | Use readWordsModuleBuf / writeWordsModuleBuf, readBitsModuleBuf / writeBitsModuleBuf, readWordsLinkDirect / writeWordsLinkDirect, or the ExtDeviceSpec random-read helpers. |
Long-family route notes:
LTN,LSTN,LCN, andLZare 32-bit scalar forms in the high-level API.LCNcurrent-value reads and writes use random dword access in the high-level API.LTS,LTC,LSTS, andLSTCstate reads use the long timer 4-word decode helpers.LCSandLCCstate reads use direct bit read.- High-level state writes for
LTS/LTC/LSTS/LSTC/LCS/LCCuse random bit write (0x1402). - Low-level direct bit writes and direct word writes to these long-family logical forms are guarded before transport.
- Edge devices that must read PLC signals with tight RAM/CPU budgets.
- Wi-Fi/Ethernet MCU gateways bridging SLMP data to MQTT/HTTP.
- Firmware projects that want a small Arduino-oriented SLMP core plus an optional high-level helper layer.
Follows the workspace-wide hierarchical documentation policy:
docsrc/user/SETUP_GUIDE.md: installation for Arduino/PlatformIO and hardware setupdocsrc/user/USAGE_GUIDE.md: high-level helpers first, then core sync/async and memory modelexamples/README.md: example selection, including the compile-checked high-level snapshot sampleplc-comm-slmp-cpp-minimal-console-app: companion repository for interactive console sketchesdocsrc/validation/reports/: formal evidence of communication with Mitsubishi hardwaredocsrc/maintainer/DEVELOPER_NOTES.md: metrics, host-testing, and internal design
The generated API reference is driven mainly by comments in the public headers.
src/slmp_minimal.h: low-level client lifecycle, device helpers, sync/async operations, long timer helpers, module-buffer access, link-direct access, memory access, and label APIssrc/slmp_high_level.h: string address parsing, typed values, named snapshots, compiled read plans, chunked reads, andPoller- This README is the overview. The header comments are the per-symbol reference used by the published documentation site.
When adding or changing public APIs, update the header comments in the same change so the generated docs stay aligned with the implementation.
Quality is managed via run_ci.bat.
- Building:
pio run - Static Analysis:
pio check(cppcheck / clang-tidy) - Host Testing: G++ tests for protocol logic.
run_ci.bat
release_check.batDistributed under the MIT License.
