Skip to content

SLMP Node-RED API Reference

This page is a user-facing index of the JavaScript SLMP client surface used by the Node-RED nodes. Use the usage guide for flow examples, and this page when you need to find the low-level operation name for a specific SLMP command family.

The main low-level client type is SlmpClient from lib/slmp/client.js.

Construction requires an IPv4 literal or hostname that resolves to IPv4 in host, plus port, transport, a concrete canonical plcProfile, and exactly one complete target or defaultTarget. Timeout is optional with a 3000 ms default. Monitoring timer is optional with a four-second default (16 in 250 ms units), accepts exact integers in 0..65535, and uses explicit 0 for PLC-side indefinite processing wait. It is independent from the local communication timeout. TCP enables keepalive after 30 seconds idle. IPv6 literals and hostnames without an IPv4 result are rejected; the client never selects or falls back to IPv6.

Every runtime target field (network, station, moduleIO, and multidrop) must be a primitive finite safe integer Number within its field range. Decimal or hexadecimal strings, Booleans, boxed Numbers, and coercible objects are not runtime values. The Node-RED editor separately converts saved connection fields and literal Route JSON using the displayed field radix.

remotePassword is optional. Omit it (or use explicit undefined) to disable managed authentication. When present it must be a printable ASCII string with the selected profile's exact length rule: 6–32 characters for iQ-R-family profiles, or exactly 4 for Q/L-family profiles. Null, empty, non-string, and invalid credentials fail during construction. The credential is private client state and is not returned by metadata or serialization.

connect() accepts no options. If managed authentication is configured, every new transport generation is unlocked before its first user command. The removed authentication-bypass option is not part of the public surface; normal, raw, and password request paths cannot skip the lifecycle. Ordinary client operations are admitted in FIFO order and only one wire transaction is active at a time. close() invalidates the active queue generation, rejects active and queued work, and closes the exact transport generation. If the client is idle it first tries to lock an authenticated generation. If work is active or queued, local close takes priority and the PLC lock state must be treated as unknown. In that case close() reports SLMP_OPERATION_OUTCOME_UNKNOWN with reason closed; the local transport is still closed. Overlapping close() calls share one in-flight promise and therefore one generation retirement, at most one managed lock attempt, one transport close, and one success or failure result. Closing state remains active until that shared operation settles, so connect() and normal operations cannot enter between concurrent close callers. A later sequential close is idempotent.

Each activated transaction has one monotonic absolute deadline covering lazy connect, managed unlock, send completion, response framing/correlation, and protocol response decode through the boundary immediately before command-specific result materialization. Partial frames, wrong serials, and foreign routes do not restart it. An explicit connect() has its own connection deadline. A timeout before that boundary retires the current transport generation; no timed-out operation is retried or resent automatically. Once the public Promise has settled with a successful value, write acknowledgement, or PLC end code, a later close() or deadline cannot replace that result. Before publication, an incomplete non-state-changing read is reported as SlmpClosedError. An incomplete state-changing request that may have been sent is instead reported as outcome unknown with reason closed.

The FIFO response phase validates the complete transport identity and the command-specific body shape, including exact lengths, acknowledgement emptiness, bit nibbles, label boundaries, and self-test echo data. A malformed body retires that transport generation before another queued wire request can send; a possibly-sent state change remains outcome-unknown. After validation, pure array/object/string/Buffer construction may run outside the wire FIFO. Public Promises still settle in admission order, and close() is checked before materialization and again before publication.

UDP completion additionally requires both a successful socket.send() callback and a matching complete response. Either may arrive first; a response that arrives first is provisional until send success. Send failure discards it, retires and closes the UDP socket generation, and preserves the normal read-only transport or state-changing outcome-unknown classification. Traffic counters are not updated for a provisional matching response. A socket error releases the detached socket and ignores all later callbacks and messages from that generation. Missing send completion or response expires at the one absolute transaction deadline.

Direct And Random Device Operations

Operation Public API
Direct device read/write readDevices, writeDevices
Random read readRandom
Extended random read readRandomExt
Random word/dword write writeRandomWords
Extended random word/dword write writeRandomWordsExt
Random bit write writeRandomBits
Extended random bit write writeRandomBitsExt
Block read/write readBlock, writeBlock
Type name readTypeName

Extended random APIs use the 008x subcommands. Use qualified device notation such as U1\G0, U3E0\HG0, or J2\SW10 where the route requires it. Raw extension fields are not public. When index or indirect modification is needed, wrap the address in new SlmpExtendedDevice(address, modification) with SlmpIndexZ, SlmpIndexLz, or SlmpIndirect. Extended write tuples are exact [device, value] pairs; the device may be a qualified string or the typed wrapper.

The current Node-RED low-level client does not expose separate extended direct device helpers. Use the extended random APIs for routed random access.

readDevices and writeDevices require a Boolean bitUnit. With bitUnit: true, the device must be a canonical bit device. Explicit bitUnit: false access may still read or write a packed 16-bit word through a bit-device family. Random bit entries and Block bitBlocks require bit devices; Block wordBlocks require word devices. Random and block writes reject duplicate or overlapping destinations. Every bit write value is a native JavaScript Boolean; numeric and string spellings such as 0, 1, "ON", and "OFF" are rejected before transport. Every object-form DeviceRef used by direct, typed, random, block, or monitor helpers must carry the exact client plcProfile; a missing or different identity is rejected before serial allocation or transport.

Structured device operations must also fit their complete consumed span in the selected wire device-number field. The exact protocol boundaries are 24 bits for Q/L and 32 bits for ordinary iQ-R entries. A link-direct J-qualified entry always uses the 24-bit Q/L device specification, including on an iQ-R client. A normal word or bit consumes one device number, an ordinary DWord or float consumes two word-device numbers, and a packed bit-device word or Block bit point consumes 16 bit-device numbers; a low-level packed bit-device DWord consumes 32. Native long devices keep their command-specific stride. Direct, Random, Monitor registration, Block, typed, and named helpers reject a crossing span before request framing, serial allocation, connection, or traffic accounting. This is a wire-format check only; profile-catalog practical device ranges are not used as pre-transport address guards. Random word/DWord write overlap checks use the same ordinary, packed-bit, and native long-device widths, so overlapping destinations are rejected without rejecting valid adjacent native DWords.

readNamed emits exactly one Random Read request or rejects the complete plan before transport. Counted word values, strings, DWord arrays, and packable bit entries are expanded into Random Read entries and deduplicated within the selected profile's one-request limit. That expansion and each result index are compiled once into a private immutable plan reused by send and result mapping; the plan is not exposed for caller mutation. A long-timer route that requires Direct Read is never selected implicitly; use readTyped or an explicit long-timer helper. writeNamed also must fit exactly one request and rejects the complete update before I/O otherwise.

For a fixed repeated read, call prepareReadNamed(client, addresses, options) once and retain the returned opaque plan. await plan.execute({ signal }) reuses the owned request payload and result indexes while still assigning a new serial, entering the normal FIFO, enforcing a new absolute deadline, and checking the current client lifecycle on every execution. An aborted active execution retires its transport generation so a late response cannot satisfy a later request. Call plan.dispose() when the plan is no longer needed. Disposal rejects future executions and releases the retained client, payload, and decode plan references; an execution that was already admitted completes normally.

The prepared plan is bound to the exact SlmpClient and its profile, frame, compatibility, and target signature. It may be reused after an explicit close/reopen of that same unchanged client, but not with another client or after a configuration-signature change. compileReadPlan remains the inspectable structural address-expansion API; it is not a send-ready client-bound plan. readNamed remains the one-shot convenience API and does not accept a prepared plan in place of addresses. The Node-RED read node applies the same API internally with a one-entry exact-match cache, including for dynamic addresses and targets.

Numeric high-level writes for U, S, D, L, and F accept primitive JavaScript Numbers only. Numeric strings, boxed Numbers, BigInt, Booleans, null, arrays, and coercible objects fail before queue admission. Accepted integers must retain the existing exact wire range, and F must remain finite after Float32 conversion. Scalar and counted-array forms use the same policy; STR remains string-only and BIT remains Boolean-only.

High-level helper options may supply request-scoped values such as target. The helper-generated device lists, value lists, block lists, point counts, and bitUnit route are authoritative and cannot be replaced through that options object.

Specialized Operations

Operation Public API
Memory command words memoryReadWords, memoryWriteWords
Extend-unit command bytes extendUnitReadBytes, extendUnitWriteBytes
Extend-unit command words extendUnitReadWords, extendUnitWriteWords
Monitor registration/cycle registerMonitorDevices, registerMonitorDevicesExt, runMonitorCycle
Label array access readArrayLabels, writeArrayLabels
Label random access readRandomLabels, writeRandomLabels
Remote CPU control remoteRun, remoteStop, remotePause, remoteLatchClear, remoteReset
Remote password remotePasswordUnlock, remotePasswordLock
CPU operation state readCpuOperationState
Self-test loopback selfTestLoopback
Clear PLC error clearError

Array label unitSpecification is 0 for a logical bit count and 1 for a logical byte count. Both forms occupy whole two-byte wire units: bit counts use ceil(arrayDataLength / 16) * 2 bytes and byte counts use ceil(arrayDataLength / 2) * 2 bytes. The logical length must be positive, and writeArrayLabels requires the exact padded buffer length. Random label read and write data lengths must also be positive and even. Read responses must match the requested count and, for array labels, each requested unit and logical length; malformed or trailing data raises SlmpError.

Remote RUN is remoteRun({ force, clearMode }), where force is Boolean and clearMode is one of RemoteClearMode.NO_CLEAR, RemoteClearMode.CLEAR_EXCEPT_LATCH, or RemoteClearMode.CLEAR_ALL. Remote PAUSE is remotePause({ force }). Both fields are required. Remote RESET accepts no subcommand or response-wait override.

remotePasswordUnlock and remotePasswordLock are explicit low-level commands for a client constructed without managed remotePassword. They are rejected on a managed client so a manual lock cannot make its connection-generation state incorrect. Managed clients use only automatic connect/close authentication.

Monitor registration and each runMonitorCycle call are separate one-request operations. The cycle requires explicit registered Word and DWord counts. It does not auto-register, retry, or infer them; the PLC defines the error when a cycle is requested before registration. selfTestLoopback accepts a 1–960 byte Buffer containing only ASCII 0-9/A-F and verifies declared length, actual length, and exact echo. clearError always uses the fixed empty payload.

High-Level Helpers

Operation Public API
Address parsing and formatting parseDevice, deviceToString, normalizeAddress, parseAddress, formatParsedAddress
Extended-device model SlmpExtendedDevice, SlmpIndexZ, SlmpIndexLz, SlmpIndirect
Typed values readTyped, writeTyped
Named one-request reads and writes compileReadPlan, prepareReadNamed, readNamed, writeNamed
Bit-in-word write writeBitInWord (direct or qualified U/J Extended Device route)

writeBitInWord validates and snapshots the complete operation before it enters the client queue, including both request routes and capacity. It then holds one ordinary-client FIFO turn and one absolute procedure deadline while it sends one word read followed by one word write. The write is still sent when the requested bit already has the desired value. This prevents another operation on the same client from interleaving between those requests, but it is not an atomic PLC operation: another connection or PLC program logic can change the word in the race window, and the read and write can occur in different PLC scans. A failure after the write may have been sent is outcome-unknown. The helper never retries automatically; verify PLC state before deciding whether to issue a new operation. Direct words use Direct Read/Write. Qualified U module-buffer and J link-direct words use Extended Random Read/Write, with the exact same qualified route in both requests. Unsupported profile/route combinations fail before the read.

All public address-to-number and number-to-address helpers require the canonical plcProfile. parseDevice returns an immutable semantic object that contains that profile. Passing the object to a client configured for another profile is rejected before serial allocation, counters, or transport. This exact match also applies inside direct, random, block, monitor-registration, and unit-specific word/bit collections.

The supported dtype vocabulary is BIT, U, S, D, L, F, and STR. Compatibility spellings :I, :STRING, and DSTR... are not accepted. :BIT is valid only for canonical bit devices. Numeric and string dtypes are valid only for canonical word devices; use the .0 through .F selector for a semantic bit inside a word device. This semantic rule is separate from explicit low-level packed word-unit access to a bit-device family.

Public address counts are positive safe integers. parseAddress() accepts a complete ASCII-decimal suffix from 1 through Number.MAX_SAFE_INTEGER with no sign, whitespace, fraction, exponent, non-ASCII digit, or trailing text and preserves the exact Number. formatParsedAddress() accepts a count on a hand-built object only when hasCount is true and count is a primitive positive safe-integer Number; it performs no coercion. Syntax/safe-integer validation precedes and is separate from each command/profile point limit.

Numeric fields in runtime extension objects also require primitive finite safe integer Numbers. An explicitly present null is invalid; only an omitted optional field selects its documented default. String qualification in Jn\... and Un\... device syntax is unchanged; it is parsed as address syntax rather than runtime numeric coercion.

The raw request API requires command, subcommand, and an explicit byte payload. Known commands have a fixed read-only/state-changing classification. Unknown vendor commands are conservatively state-changing unless the caller supplies the Boolean stateChanging: false assertion. Request series and 4E serial are not public options; both are derived or assigned by the client. PLC errors expose the numeric end code, stable slmp_end_code_xxxx key, and structured error information, not localized manual-derived messages.

When a non-zero response contains structured error information, its network, station, module I/O, multidrop, command, and subcommand must match the active wire request. A mismatch raises a malformed SlmpError, closes the supplying transport generation, and is not a definitive PLC error. A possibly sent state-changing operation is instead SlmpOperationOutcomeUnknownError with reason malformed-response. Bytes after a matching nine-byte prefix remain available as additional PLC error data. Standard acknowledgement-only APIs also require an empty data body after end code zero; unexpected data is the same malformed/outcome-unknown failure. rawCommand() is excluded and continues to return arbitrary successful response data.

Timeout and lifecycle errors are machine-readable: SlmpTimeoutError (SLMP_TIMEOUT), SlmpClosedError (SLMP_CLOSED), and SlmpNotConnectedError (SLMP_NOT_CONNECTED). If a state-changing request may have been sent before timeout, close, or transport failure, the result is SlmpOperationOutcomeUnknownError (SLMP_OPERATION_OUTCOME_UNKNOWN). Its reason is timeout, closed, transport, or malformed-response, and cause retains the original error. Do not automatically retry outcome-unknown operations; first verify PLC state.

TCP command payloads are limited to 65,529 bytes. UDP command payloads are limited to 65,492 bytes for 3E and 65,488 bytes for 4E so the complete frame fits one datagram. Single-request command builders reject oversized inputs before transport or serial allocation and never truncate or split them. Label builders additionally enforce their aggregate payload size; their largest protocol-representable even payload is 65,528 bytes. readNamed never splits a plan: its expanded Random Read entry count must fit the selected profile limit.

Profile Selection

availablePlcProfiles() returns a new array of canonical, connection-selectable profile IDs. It excludes Unspecified and the base-only melsec:qcpu profile; modifying the returned array does not change the library's profile registry.

profileDescriptors() returns canonical name, display name, connection availability, and base-profile metadata for every profile. The base-only melsec:qcpu entry is included with connectable: false; the editor filters that entry from connection selections.

Target Module I/O Constants

ModuleIONo provides named request-header module I/O numbers for multi-CPU and routed CPU targets. Use these values in the route object's moduleIO field; omitted route targets still use the own-station route 0x03FF.

Constant Value
ModuleIONo.CONTROL_SYSTEM_CPU 0x03D0
ModuleIONo.STANDBY_SYSTEM_CPU 0x03D1
ModuleIONo.SYSTEM_A_CPU 0x03D2
ModuleIONo.SYSTEM_B_CPU 0x03D3
ModuleIONo.MULTIPLE_CPU_1 .. ModuleIONo.MULTIPLE_CPU_4 0x03E0 .. 0x03E3
ModuleIONo.REMOTE_HEAD_1 / ModuleIONo.REMOTE_HEAD_2 0x03E0 / 0x03E1
ModuleIONo.CONTROL_SYSTEM_REMOTE_HEAD / ModuleIONo.STANDBY_SYSTEM_REMOTE_HEAD 0x03D0 / 0x03D1
ModuleIONo.OWN_STATION 0x03FF

Traffic Statistics

SlmpClient.trafficStats() returns a frozen { requestCount, txBytes, rxBytes } snapshot. Counters are cumulative for the client lifetime and are not reset by close or reconnect.