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-fxplcOptional (for debug logging):
npm install debugEnable logs by setting environment variable (PowerShell):
$env:DEBUG = "fxplc:*"; node yourscript.mjsThis 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 codeconst { FXPLCClient, TransportTCP, TransportSerial } = require('node-fxplc');
// ...your codeBoth 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 disconnectSerial 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.