UNPKG

node-fxplc

Version:

Node.js library for low-level Mitsubishi FX (MELSEC) PLC framed protocol communication

83 lines (82 loc) 3.54 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TransportTCP = void 0; // TransportTCP.js - TCP transport implementation using BufferAccumulator for framed reads. const net_1 = __importDefault(require("net")); const errors_js_1 = require("./errors.js"); const transport_buffer_js_1 = require("./transport-buffer.js"); class TransportTCP { /** * @param {object} opts * @param {string} opts.host PLC gateway / adapter host * @param {number} opts.port TCP port * @param {number} [opts.timeout=1000] Per-operation read timeout (ms) * @param {number} [opts.flushDelay=1000] Delay after connect before enabling reads (allow device flush) */ constructor({ host, port, timeout = 1000, flushDelay = 1000 }) { this.host = host; this.port = port; this.timeout = timeout; this.flushDelay = flushDelay; this._socket = null; this._connecting = null; this._reconnect = false; this._reconnectDelay = 1000; this._bufferAcc = new transport_buffer_js_1.BufferAccumulator(); } /** Enable / disable simple reconnect loop */ setReconnect(enabled, delayMs = 1000) { this._reconnect = enabled; this._reconnectDelay = delayMs; } /** Establish connection (idempotent) */ async connect() { if (this._socket) return; if (this._connecting) return this._connecting; this._connecting = new Promise((resolve, reject) => { const s = net_1.default.createConnection({ host: this.host, port: this.port }); let done = false; const fail = (e) => { if (done) return; done = true; s.destroy(); this._socket = null; this._connecting = null; reject(e); }; s.once('error', fail); s.setTimeout(this.timeout, () => fail(new errors_js_1.NoResponseError('Connect timeout'))); s.once('connect', () => { setTimeout(() => { s.setTimeout(0); s.off('error', fail); s.on('data', d => this._onData(d)); s.on('close', () => { if (this._reconnect) { this._socket = null; setTimeout(() => this.connect().catch(() => { }), this._reconnectDelay); } }); this._socket = s; this._connecting = null; done = true; resolve(); }, this.flushDelay); }); }); return this._connecting; } _onData(d) { this._bufferAcc.push(d); } async _ensure() { if (!this._socket) await this.connect(); } /** Write raw frame bytes */ async write(data) { await this._ensure(); if (!this._socket) throw new errors_js_1.NotConnectedError(); this._socket.write(data); } /** Read exact byte count (delegates to accumulator) */ async read(size) { await this._ensure(); try { return await this._bufferAcc.read(size, this.timeout); } catch { throw new errors_js_1.NoResponseError(); } } /** Close socket and reject pending reads */ close() { if (this._socket) { this._socket.destroy(); this._socket = null; } this._bufferAcc.close(); this._connecting = null; } } exports.TransportTCP = TransportTCP;