UNPKG

modbus-connect

Version:

Modbus RTU over Web Serial and Node.js SerialPort

1,250 lines 76 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var import_logger = __toESM(require("../logger.js")); var import_constants = require("../constants/constants.js"); var import_errors = require("../errors.js"); var import_crc = require("../utils/crc.js"); class SlaveEmulator { slaveAddress; coils; discreteInputs; holdingRegisters; inputRegisters; exceptions; _infinityTasks; loggerEnabled; logger; connected; constructor(slaveAddress = 1, options = {}) { if (typeof slaveAddress !== "number" || slaveAddress < 0 || slaveAddress > 247) { throw new import_errors.ModbusInvalidAddressError(slaveAddress); } this.slaveAddress = slaveAddress; this.coils = /* @__PURE__ */ new Map(); this.discreteInputs = /* @__PURE__ */ new Map(); this.holdingRegisters = /* @__PURE__ */ new Map(); this.inputRegisters = /* @__PURE__ */ new Map(); this.exceptions = /* @__PURE__ */ new Map(); this._infinityTasks = /* @__PURE__ */ new Map(); this.loggerEnabled = !!options.loggerEnabled; const loggerInstance = new import_logger.default(); this.logger = loggerInstance.createLogger("SlaveEmulator"); if (!this.loggerEnabled) { this.logger.setLevel("error"); } else { this.logger.setLevel("info"); } this.connected = false; } enableLogger() { if (!this.loggerEnabled) { this.loggerEnabled = true; this.logger.setLevel("info"); } } disableLogger() { if (this.loggerEnabled) { this.loggerEnabled = false; this.logger.setLevel("error"); } } async connect() { this.logger.info("Connecting to emulator...", { slaveAddress: this.slaveAddress }); this.connected = true; this.logger.info("Connected", { slaveAddress: this.slaveAddress }); } async disconnect() { this.logger.info("Disconnecting from emulator...", { slaveAddress: this.slaveAddress }); this.connected = false; this.logger.info("Disconnected", { slaveAddress: this.slaveAddress }); } _validateAddress(address) { if (typeof address !== "number" || address < 0 || address > 65535) { throw new import_errors.ModbusInvalidAddressError(address); } } _validateQuantity(quantity, max = 125) { if (typeof quantity !== "number" || quantity <= 0 || quantity > max) { throw new import_errors.ModbusInvalidQuantityError(quantity, 1, max); } } _validateValue(value, isRegister = false) { if (isRegister) { if (typeof value !== "number") { throw new import_errors.ModbusIllegalDataValueError(String(value), "number between 0 and 65535"); } if (value < 0 || value > 65535) { throw new import_errors.ModbusIllegalDataValueError(value, "between 0 and 65535"); } } else { if (typeof value !== "boolean") { throw new import_errors.ModbusIllegalDataValueError(String(value), "boolean"); } } } infinityChange(params) { const { typeRegister, register, range, interval } = params; if (!typeRegister || typeof register !== "number" || !Array.isArray(range) || range.length !== 2) { throw new import_errors.ModbusDataConversionError(params, "valid InfinityChangeParams"); } if (typeof interval !== "number" || interval <= 0) { throw new import_errors.ModbusDataConversionError(interval, "positive number"); } const key = `${typeRegister}:${register}`; this.stopInfinityChange({ typeRegister, register }); const [min, max] = range; const setters = { Holding: (addr, val) => this.setHoldingRegister(addr, val), Input: (addr, val) => this.setInputRegister(addr, val), Coil: (addr, val) => this.setCoil(addr, val), Discrete: (addr, val) => this.setDiscreteInput(addr, val) }; const setter = setters[typeRegister]; if (!setter) { throw new import_errors.ModbusDataConversionError(typeRegister, "valid register type"); } if (min > max) { throw new import_errors.ModbusDataConversionError(range, "valid range (min <= max)"); } const intervalId = setInterval(() => { try { const value = typeRegister === "Holding" || typeRegister === "Input" ? Math.floor(Math.random() * (max - min + 1)) + min : Math.random() < 0.5; setter(register, value); this.logger.debug("Infinity change updated", { typeRegister, register, value, slaveAddress: this.slaveAddress }); } catch (error) { if (error instanceof import_errors.ModbusExceptionError) { this.logger.error("Modbus exception in infinity change task", { error: error.message, functionCode: error.functionCode, exceptionCode: error.exceptionCode, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidAddressError) { this.logger.error("Invalid address in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidQuantityError) { this.logger.error("Invalid quantity in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusIllegalDataValueError) { this.logger.error("Illegal data value in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusIllegalDataAddressError) { this.logger.error("Illegal data address in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusSlaveDeviceFailureError) { this.logger.error("Slave device failure in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusCRCError) { this.logger.error("CRC error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusResponseError) { this.logger.error("Response error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusTimeoutError) { this.logger.error("Timeout error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusFlushError) { this.logger.error("Flush error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusDataConversionError) { this.logger.error("Data conversion error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusBufferOverflowError) { this.logger.error("Buffer overflow error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusBufferUnderrunError) { this.logger.error("Buffer underrun error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusMemoryError) { this.logger.error("Memory error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusStackOverflowError) { this.logger.error("Stack overflow error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidFunctionCodeError) { this.logger.error("Invalid function code error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusSlaveBusyError) { this.logger.error("Slave busy error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusAcknowledgeError) { this.logger.error("Acknowledge error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusMemoryParityError) { this.logger.error("Memory parity error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusGatewayPathUnavailableError) { this.logger.error("Gateway path unavailable error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusGatewayTargetDeviceError) { this.logger.error("Gateway target device error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusGatewayBusyError) { this.logger.error("Gateway busy error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusDataOverrunError) { this.logger.error("Data overrun error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusBroadcastError) { this.logger.error("Broadcast error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusConfigError) { this.logger.error("Config error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusBaudRateError) { this.logger.error("Baud rate error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusSyncError) { this.logger.error("Sync error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusFrameBoundaryError) { this.logger.error("Frame boundary error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusLRCError) { this.logger.error("LRC error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusChecksumError) { this.logger.error("Checksum error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusParityError) { this.logger.error("Parity error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusNoiseError) { this.logger.error("Noise error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusFramingError) { this.logger.error("Framing error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusOverrunError) { this.logger.error("Overrun error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusCollisionError) { this.logger.error("Collision error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusTooManyEmptyReadsError) { this.logger.error("Too many empty reads error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInterFrameTimeoutError) { this.logger.error("Inter-frame timeout error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusSilentIntervalError) { this.logger.error("Silent interval error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidStartingAddressError) { this.logger.error("Invalid starting address error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusMalformedFrameError) { this.logger.error("Malformed frame error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidFrameLengthError) { this.logger.error("Invalid frame length error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidTransactionIdError) { this.logger.error("Invalid transaction ID error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusUnexpectedFunctionCodeError) { this.logger.error("Unexpected function code error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusConnectionRefusedError) { this.logger.error("Connection refused error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusConnectionTimeoutError) { this.logger.error("Connection timeout error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusNotConnectedError) { this.logger.error("Not connected error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusAlreadyConnectedError) { this.logger.error("Already connected error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInsufficientDataError) { this.logger.error("Insufficient data error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } else { this.logger.error("Error in infinity change task", { error: error.message, typeRegister, register, slaveAddress: this.slaveAddress }); } } }, interval); this._infinityTasks.set(key, intervalId); this.logger.info("Infinity change started", { typeRegister, register, interval, slaveAddress: this.slaveAddress }); } stopInfinityChange(params) { const { typeRegister, register } = params; const key = `${typeRegister}:${register}`; if (this._infinityTasks.has(key)) { const intervalId = this._infinityTasks.get(key); if (intervalId) { clearInterval(intervalId); } this._infinityTasks.delete(key); this.logger.debug("Infinity change stopped", { typeRegister, register, slaveAddress: this.slaveAddress }); } } setException(functionCode, address, exceptionCode) { this._validateAddress(address); this.exceptions.set(`${functionCode}_${address}`, exceptionCode); this.logger.info( `Exception set: functionCode=0x${functionCode.toString(16)}, address=${address}, exceptionCode=0x${exceptionCode.toString(16)}`, { functionCode: `0x${functionCode.toString(16)}`, address, exceptionCode, slaveAddress: this.slaveAddress } ); } _checkException(functionCode, address) { this._validateAddress(address); const key = `${functionCode}_${address}`; if (this.exceptions.has(key)) { const exCode = this.exceptions.get(key); this.logger.warn( `Throwing exception for function 0x${functionCode.toString(16)} at address ${address}: code 0x${exCode.toString(16)}`, { functionCode: `0x${functionCode.toString(16)}`, address, exceptionCode: exCode, slaveAddress: this.slaveAddress } ); throw new import_errors.ModbusExceptionError(functionCode, exCode); } } addRegisters(definitions) { if (!definitions || typeof definitions !== "object") { throw new import_errors.ModbusDataConversionError(definitions, "valid RegisterDefinitions object"); } const stats = { coils: 0, discrete: 0, holding: 0, input: 0 }; try { if (Array.isArray(definitions.coils)) { for (const { start, value } of definitions.coils) { this.setCoil(start, value); stats.coils++; } } if (Array.isArray(definitions.discrete)) { for (const { start, value } of definitions.discrete) { this.setDiscreteInput(start, value); stats.discrete++; } } if (Array.isArray(definitions.holding)) { for (const { start, value } of definitions.holding) { this.setHoldingRegister(start, value); stats.holding++; } } if (Array.isArray(definitions.input)) { for (const { start, value } of definitions.input) { this.setInputRegister(start, value); stats.input++; } } this.logger.info("Registers added successfully", { ...stats, slaveAddress: this.slaveAddress }); } catch (error) { if (error instanceof import_errors.ModbusExceptionError) { this.logger.error("Modbus exception adding registers", { error: error.message, functionCode: error.functionCode, exceptionCode: error.exceptionCode, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidAddressError) { this.logger.error("Invalid address adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidQuantityError) { this.logger.error("Invalid quantity adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusIllegalDataValueError) { this.logger.error("Illegal data value adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusIllegalDataAddressError) { this.logger.error("Illegal data address adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusSlaveDeviceFailureError) { this.logger.error("Slave device failure adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusCRCError) { this.logger.error("CRC error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusResponseError) { this.logger.error("Response error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusTimeoutError) { this.logger.error("Timeout error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusFlushError) { this.logger.error("Flush error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusDataConversionError) { this.logger.error("Data conversion error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusBufferOverflowError) { this.logger.error("Buffer overflow error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusBufferUnderrunError) { this.logger.error("Buffer underrun error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusMemoryError) { this.logger.error("Memory error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusStackOverflowError) { this.logger.error("Stack overflow error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidFunctionCodeError) { this.logger.error("Invalid function code error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusSlaveBusyError) { this.logger.error("Slave busy error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusAcknowledgeError) { this.logger.error("Acknowledge error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusMemoryParityError) { this.logger.error("Memory parity error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusGatewayPathUnavailableError) { this.logger.error("Gateway path unavailable error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusGatewayTargetDeviceError) { this.logger.error("Gateway target device error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusGatewayBusyError) { this.logger.error("Gateway busy error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusDataOverrunError) { this.logger.error("Data overrun error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusBroadcastError) { this.logger.error("Broadcast error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusConfigError) { this.logger.error("Config error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusBaudRateError) { this.logger.error("Baud rate error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusSyncError) { this.logger.error("Sync error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusFrameBoundaryError) { this.logger.error("Frame boundary error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusLRCError) { this.logger.error("LRC error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusChecksumError) { this.logger.error("Checksum error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusParityError) { this.logger.error("Parity error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusNoiseError) { this.logger.error("Noise error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusFramingError) { this.logger.error("Framing error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusOverrunError) { this.logger.error("Overrun error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusCollisionError) { this.logger.error("Collision error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusTooManyEmptyReadsError) { this.logger.error("Too many empty reads error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInterFrameTimeoutError) { this.logger.error("Inter-frame timeout error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusSilentIntervalError) { this.logger.error("Silent interval error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidStartingAddressError) { this.logger.error("Invalid starting address error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusMalformedFrameError) { this.logger.error("Malformed frame error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidFrameLengthError) { this.logger.error("Invalid frame length error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInvalidTransactionIdError) { this.logger.error("Invalid transaction ID error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusUnexpectedFunctionCodeError) { this.logger.error("Unexpected function code error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusConnectionRefusedError) { this.logger.error("Connection refused error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusConnectionTimeoutError) { this.logger.error("Connection timeout error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusNotConnectedError) { this.logger.error("Not connected error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusAlreadyConnectedError) { this.logger.error("Already connected error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else if (error instanceof import_errors.ModbusInsufficientDataError) { this.logger.error("Insufficient data error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } else { this.logger.error("Error adding registers", { error: error.message, definitions: JSON.stringify(definitions), slaveAddress: this.slaveAddress }); } throw error; } } setCoil(address, value) { this._validateAddress(address); this._validateValue(value, false); this.coils.set(address, !!value); this.logger.debug("Coil set", { address, value: !!value, slaveAddress: this.slaveAddress }); } getCoil(address) { this._validateAddress(address); return this.coils.get(address) || false; } readCoils(startAddress, quantity) { this._validateAddress(startAddress); this._validateQuantity(quantity, 2e3); if (startAddress + quantity > 65536) { throw new import_errors.ModbusIllegalDataAddressError(startAddress, quantity); } this.logger.info("readCoils", { startAddress, quantity, slaveAddress: this.slaveAddress }); for (let addr = startAddress; addr < startAddress + quantity; addr++) { this._checkException(import_constants.ModbusFunctionCode.READ_COILS, addr); } const result = []; for (let i = 0; i < quantity; i++) { result.push(this.getCoil(startAddress + i)); } return result; } writeSingleCoil(address, value) { this._validateAddress(address); this._validateValue(value, false); this._checkException(import_constants.ModbusFunctionCode.WRITE_SINGLE_COIL, address); this.setCoil(address, value); this.logger.info("writeSingleCoil", { address, value: !!value, slaveAddress: this.slaveAddress }); } writeMultipleCoils(startAddress, values) { this._validateAddress(startAddress); this._validateQuantity(values.length, 1968); if (!Array.isArray(values)) { throw new import_errors.ModbusDataConversionError(values, "array"); } if (startAddress + values.length > 65536) { throw new import_errors.ModbusIllegalDataAddressError(startAddress, values.length); } values.forEach((val, idx) => { this._validateValue(val, false); this._checkException(import_constants.ModbusFunctionCode.WRITE_MULTIPLE_COILS, startAddress + idx); }); values.forEach((val, idx) => { this.setCoil(startAddress + idx, val); }); this.logger.info("writeMultipleCoils", { startAddress, values: values.length, slaveAddress: this.slaveAddress }); } setDiscreteInput(address, value) { this._validateAddress(address); this._validateValue(value, false); this.discreteInputs.set(address, !!value); this.logger.debug("Discrete Input set", { address, value: !!value, slaveAddress: this.slaveAddress }); } getDiscreteInput(address) { this._validateAddress(address); return this.discreteInputs.get(address) || false; } readDiscreteInputs(startAddress, quantity) { this._validateAddress(startAddress); this._validateQuantity(quantity, 2e3); if (startAddress + quantity > 65536) { throw new import_errors.ModbusIllegalDataAddressError(startAddress, quantity); } this.logger.info("readDiscreteInputs", { startAddress, quantity, slaveAddress: this.slaveAddress }); for (let addr = startAddress; addr < startAddress + quantity; addr++) { this._checkException(import_constants.ModbusFunctionCode.READ_DISCRETE_INPUTS, addr); } const result = []; for (let i = 0; i < quantity; i++) { result.push(this.getDiscreteInput(startAddress + i)); } return result; } setHoldingRegister(address, value) { this._validateAddress(address); this._validateValue(value, true); const maskedValue = value & 65535; this.holdingRegisters.set(address, maskedValue); this.logger.debug("Holding Register set", { address, value: maskedValue, slaveAddress: this.slaveAddress }); } getHoldingRegister(address) { this._validateAddress(address); return this.holdingRegisters.get(address) || 0; } readHoldingRegisters(startAddress, quantity) { this._validateAddress(startAddress); this._validateQuantity(quantity, 125); if (startAddress + quantity > 65536) { throw new import_errors.ModbusIllegalDataAddressError(startAddress, quantity); } this.logger.info("readHoldingRegisters", { startAddress, quantity, slaveAddress: this.slaveAddress }); for (let addr = startAddress; addr < startAddress + quantity; addr++) { this._checkException(import_constants.ModbusFunctionCode.READ_HOLDING_REGISTERS, addr); } const result = []; for (let i = 0; i < quantity; i++) { result.push(this.getHoldingRegister(startAddress + i)); } return result; } writeSingleRegister(address, value) { this._validateAddress(address); this._validateValue(value, true); this._checkException(import_constants.ModbusFunctionCode.WRITE_SINGLE_REGISTER, address); this.setHoldingRegister(address, value); this.logger.info("writeSingleRegister", { address, value, slaveAddress: this.slaveAddress }); } writeMultipleRegisters(startAddress, values) { this._validateAddress(startAddress); this._validateQuantity(values.length, 123); if (!Array.isArray(values)) { throw new import_errors.ModbusDataConversionError(values, "array"); } if (startAddress + values.length > 65536) { throw new import_errors.ModbusIllegalDataAddressError(startAddress, values.length); } values.forEach((val, idx) => { this._validateValue(val, true); this._checkException(import_constants.ModbusFunctionCode.WRITE_MULTIPLE_REGISTERS, startAddress + idx); }); values.forEach((val, idx) => { this.setHoldingRegister(startAddress + idx, val); }); this.logger.info("writeMultipleRegisters", { startAddress, values: values.length, slaveAddress: this.slaveAddress }); } setInputRegister(address, value) { this._validateAddress(address); this._validateValue(value, true); const maskedValue = value & 65535; this.inputRegisters.set(address, maskedValue); this.logger.debug("Input Register set", { address, value: maskedValue, slaveAddress: this.slaveAddress }); } getInputRegister(address) { this._validateAddress(address); return this.inputRegisters.get(address) || 0; } readInputRegisters(startAddress, quantity) { this._validateAddress(startAddress); this._validateQuantity(quantity, 125); if (startAddress + quantity > 65536) { throw new import_errors.ModbusIllegalDataAddressError(startAddress, quantity); } this.logger.info("readInputRegisters", { startAddress, quantity, slaveAddress: this.slaveAddress }); for (let addr = startAddress; addr < startAddress + quantity; addr++) { this._checkException(import_constants.ModbusFunctionCode.READ_INPUT_REGISTERS, addr); } const result = []; for (let i = 0; i < quantity; i++) { result.push(this.getInputRegister(startAddress + i)); } return result; } // --- Прямые методы (без RTU) --- readHolding(start, quantity) { this._validateAddress(start); this._validateQuantity(quantity, 125); if (start + quantity > 65536) { throw new import_errors.ModbusIllegalDataAddressError(start, quantity); } const result = []; for (let i = 0; i < quantity; i++) { const addr = start + i; this._checkException(import_constants.ModbusFunctionCode.READ_HOLDING_REGISTERS, addr); result.push(this.getHoldingRegister(addr)); } return result; } readInput(start, quantity) { this._validateAddress(start); this._validateQuantity(quantity, 125); if (start + quantity > 65536) { throw new import_errors.ModbusIllegalDataAddressError(start, quantity); } const result = []; for (let i = 0; i < quantity; i++) { const addr = start + i; this._checkException(import_constants.ModbusFunctionCode.READ_INPUT_REGISTERS, addr); result.push(this.getInputRegister(addr)); } return result; } // --- Методы диагностики и мониторинга --- getRegisterStats() { return { coils: this.coils.size, discreteInputs: this.discreteInputs.size, holdingRegisters: this.holdingRegisters.size, inputRegisters: this.inputRegisters.size, exceptions: this.exceptions.size, infinityTasks: this._infinityTasks.size }; } getRegisterDump() { return { coils: Object.fromEntries(this.coils), discreteInputs: Object.fromEntries(this.discreteInputs), holdingRegisters: Object.fromEntries(this.holdingRegisters), inputRegisters: Object.fromEntries(this.inputRegisters) }; } getInfinityTasks() { return Array.from(this._infinityTasks.keys()); } clearAllRegisters() { this.coils.clear(); this.discreteInputs.clear(); this.holdingRegisters.clear(); this.inputRegisters.clear(); this.logger.info("All registers cleared", { slaveAddress: this.slaveAddress }); } clearExceptions() { this.exceptions.clear(); this.logger.info("All exceptions cleared", { slaveAddress: this.slaveAddress }); } clearInfinityTasks() { for (const intervalId of this._infinityTasks.values()) { clearInterval(intervalId); } this._infinityTasks.clear(); this.logger.info("All infinity tasks cleared", { slaveAddress: this.slaveAddress }); } // --- Graceful shutdown --- async destroy() { this.logger.info("Destroying SlaveEmulator", { slaveAddress: this.slaveAddress }); this.clearInfinityTasks(); if (this.connected) { await this.disconnect(); } this.clearAllRegisters(); this.clearExceptions(); this.logger.info("SlaveEmulator destroyed", { slaveAddress: this.slaveAddress }); } // --- Modbus RTU Frame handler --- handleRequest(buffer) { try { if (!this.connected) { this.logger.warn("Received request but emulator not connected", { slaveAddress: this.slaveAddress }); return null; } if (!(buffer instanceof Uint8Array)) { throw new import_errors.ModbusDataConversionError(buffer, "Uint8Array"); } if (buffer.length < 5) { throw new import_errors.ModbusResponseError("Invalid Modbus RTU frame: too short"); } const crcReceived = (buffer[buffer.length - 2] | buffer[buffer.length - 1] << 8) & 65535; const dataForCrc = buffer.subarray(0, buffer.length - 2); const crcCalculatedBuffer = (0, import_crc.crc16Modbus)(dataForCrc); if (crcCalculatedBuffer.length < 2) { throw new import_errors.ModbusCRCError("crc16Modbus returned invalid buffer length"); } const crcCalculated = crcCalculatedBuffer[0] << 8 | crcCalculatedBuffer[1]; if (crcReceived !== crcCalculated) { this.logger.warn("CRC mismatch", { received: `0x${crcReceived.toString(16)}`, calculated: `0x${crcCalculated.toString(16)}`, frame: Buffer.from(buffer).toString("hex"), slaveAddress: this.slaveAddress }); throw new import_errors.ModbusCRCError("CRC mismatch detected"); } const slaveAddr = buffer[0]; if (slaveAddr !== this.slaveAddress && slaveAddr !== 0) { this.logger.debug("Frame ignored - wrong slave address", { targetSlave: slaveAddr, thisSlave: this.slaveAddress }); return null; } const functionCode = buffer[1]; const data = buffer.subarray(2, buffer.length - 2); this.logger.info("Modbus request received", { slaveAddress: slaveAddr, functionCode: `0x${functionCode.toString(16)}`, data: Buffer.from(data).toString("hex"), dataLength: data.length }); return this._processFunctionCode(functionCode, data, slaveAddr); } catch (error) { if (error instanceof import_errors.ModbusExceptionError) { this.logger.error("Modbus exception processing request", { error: error.message, functionCode: error.functionCode, exceptionCode: error.exceptionCode, slaveAddress: this.slaveAddress }); return this._createExceptionResponse(error.functionCode, error.exceptionCode); } else if (error instanceof import_errors.ModbusInvalidAddressError) { this.logger.error("Invalid address processing request", { error: error.message, slaveAddress: this.slaveAddress }); return this._createExceptionResponse( buffer?.[1] || 0, import_constants.ModbusExceptionCode.ILLEGAL_DATA_ADDRESS ); } else if (error instanceof import_errors.ModbusInvalidQuantityError) { this.logger.error("Invalid quantity processing request", { error: error.message, slaveAddress: this.slaveAddress }); return this._createExceptionResponse( buffer?.[1] || 0, import_constants.ModbusExceptionCode.ILLEGAL_DATA_VALUE ); } else if (error instanceof import_errors.ModbusIllegalDataValueError) { this.logger.error("Illegal data value process