See also: Release Notes
Node.js library for communication with Mitsubishi FX PLCs using low-level MELSEC FX series serial protocol.
Note: it is not the same as Non-Protocol Communication (or D8120) as described in FX Series Programmable Controllers manuals.
This is a Node.js port of a Python library.
lib/
— Library source codetest/
— Tests and example scripts
Pre-release. Core client API + TCP/Serial transports work for basic read/write, but advanced resilience and integration testing are still in progress.
See ROADMAP.md
for detailed progress & planned items. Contributions / hardware test feedback are welcome.
npm install node-fxplc
Optional (for debug logging):
npm install debug
Enable logs by setting environment variable (PowerShell):
$env:DEBUG = "fxplc:*"; node yourscript.mjs
This package supports both ESM (import
) and CommonJS (require
) consumers. Use the appropriate syntax for your project type:
import { FXPLCClient, TransportTCP, TransportSerial } from 'node-fxplc';
// ...your code
const { FXPLCClient, TransportTCP, TransportSerial } = require('node-fxplc');
// ...your code
Both entrypoints provide the same API and typings. See below for detailed usage examples.
- Low-level MELSEC FX serial frame protocol (hex payload + checksum)
- Read / write single bit & forced coil (ON/OFF)
- Read / write signed word and generic numeric types via converters
- Batch read/write (auto-coalesces consecutive addresses)
- Raw byte read/write access for advanced use
- TCP & Serial transports with buffer accumulation & timeouts
- Simple retry + operation-level timeout
- Event emitter (error / connect / disconnect)
This is NOT the "Non-Protocol Communication" (D8120) mode. It targets the classic hex framed ASCII protocol (FX0N/FX1N style). Serial defaults: 7 data bits, even parity, 1 stop bit (7E1), commonly 9600 baud. Verify your PLC parameters.
Compatibility note: The protocol layer is electrical-transport agnostic; it should also work with many FX-compatible clone PLCs exposing an RS422 port, provided they implement the same framed command set. Use a proper RS422↔USB (or RS422↔RS232) converter and match serial parameters (7E1, baud rate). Timing and response behaviors may vary between clones—enable retries if needed and report any incompatibilities.
- Bit areas (e.g. M, X, Y) via
readBit
/writeBit
- Data registers (e.g. D) via numeric read/write helpers
Parsing is handled by
RegisterDef.parse('D100')
. Unsupported / unknown areas will throw until implemented.
Exposed error types (see errors.js
):
NoResponseError
– timeout or no replyResponseMalformedError
– bad checksum / frame inconsistencyNotSupportedCommandError
– PLC replied NAKNotConnectedError
– transport not open Use instanceof checks for granular handling.
Client option timeoutMs
controls per-operation timeout. Retry policy via retry: { count, delayMs }
(default 1 attempt). Example:
const plc = new FXPLCClient(transport, { retry: { count: 3, delayMs: 150 }, timeoutMs: 2500 });
Enable simple reconnect:
const tcp = new TransportTCP({ host: '127.0.0.1', port: 5000 });
tcp.setReconnect(true, 2000); // every 2s after disconnect
Serial transport currently has no auto-reopen loop (planned).
import { FXPLCClient, TransportTCP } from 'node-fxplc';
const transport = new TransportTCP({ host: '127.0.0.1', port: 5000 });
await transport.connect();
const plc = new FXPLCClient(transport);
const bit = await plc.readBit('M0');
console.log('Bit M0:', bit);
const vals = await plc.batchRead(['D100','D101']);
await plc.writeBit('M10', true);
plc.close();
plc.readBit('M0', (err, bit) => {
if (err) return console.error('Error:', err);
console.log('Bit M0:', bit);
});
plc.batchRead(['D100','D101'], (err, vals) => {
if (!err) console.log('Values:', vals);
});
plc.on('error', err => console.error('PLC Error:', err));
plc.on('connect', () => console.log('Connected!'));
plc.on('disconnect', () => console.log('Disconnected!'));
try {
await plc.readBit('Z999'); // invalid register
} catch(e) {
console.error('Input error:', e.message);
}
await plc.batchWrite(['D100','D101'], [123,456]);
import { FXPLCClient, TransportSerial } from 'node-fxplc';
const transport = new TransportSerial({ path: 'COM3', baudRate: 9600, timeout: 1500 });
const plc = new FXPLCClient(transport, { debug: true });
const bit = await plc.readBit('M0');
console.log('M0 =', bit);
plc.close();
Install debug
and set DEBUG=fxplc:*
env variable. Logs show TX/RX hex frames.
Released under the MIT License. See the LICENSE
file for full text.
Next focus: transport robustness, CLI, integration tests, richer docs.
Note: Serial transport is functional but minimally tested; use caution in production scenarios.