UNPKG

modbus-connect

Version:

Modbus RTU over Web Serial and Node.js SerialPort

1,289 lines 82.7 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_async_mutex = require("async-mutex"); var import_logger = __toESM(require("./logger.js")); var import_errors = require("./errors.js"); function hasTransportProperty(obj) { return typeof obj === "object" && obj !== null && "transport" in obj; } function hasFlushMethod(obj) { return typeof obj === "object" && obj !== null && "flush" in obj && typeof obj.flush === "function"; } class TaskController { id; resourceId; priority; name; fn; interval; onData; onError; onStart; onStop; onFinish; onBeforeEach; onRetry; shouldRun; onSuccess; onFailure; maxRetries; backoffDelay; taskTimeout; stopped; paused; loopRunning; executionInProgress; stats; transportMutex; logger; pollingManager; constructor(options, pollingManager) { const { id, resourceId, priority = 0, interval, fn, onData, onError, onStart, onStop, onFinish, onBeforeEach, onRetry, shouldRun, onSuccess, onFailure, name = null, maxRetries = 3, backoffDelay = 1e3, taskTimeout = 5e3 } = options; this.id = id; this.resourceId = resourceId; this.priority = priority; this.name = name; this.fn = Array.isArray(fn) ? fn : [fn]; this.interval = interval; this.onData = onData; this.onError = onError; this.onStart = onStart; this.onStop = onStop; this.onFinish = onFinish; this.onBeforeEach = onBeforeEach; this.onRetry = onRetry; this.shouldRun = shouldRun; this.onSuccess = onSuccess; this.onFailure = onFailure; this.maxRetries = maxRetries; this.backoffDelay = backoffDelay; this.taskTimeout = taskTimeout; this.stopped = true; this.paused = false; this.loopRunning = false; this.executionInProgress = false; this.pollingManager = pollingManager; this.stats = { totalRuns: 0, totalErrors: 0, lastError: null, lastResult: null, lastRunTime: null, retries: 0, successes: 0, failures: 0 }; this.transportMutex = new import_async_mutex.Mutex(); this.logger = pollingManager.loggerInstance.createLogger("TaskController"); this.logger.setLevel("error"); this.logger.debug("TaskController created", { id, resourceId: resourceId || void 0, priority, interval, maxRetries, backoffDelay, taskTimeout }); } /** * Запускает задачу. */ async start() { if (!this.stopped) { this.logger.debug("Task already running"); return; } this.stopped = false; this.loopRunning = true; this.logger.info("Task started", { id: this.id }); this.onStart?.(); if (this.resourceId) { this.scheduleRun(); } else { this._runLoop(); } } /** * Останавливает задачу. */ stop() { if (this.stopped) { this.logger.debug("Task already stopped", { id: this.id }); return; } this.stopped = true; this.loopRunning = false; this.logger.info("Task stopped", { id: this.id }); this.onStop?.(); } /** * Ставит задачу на паузу. */ pause() { if (this.paused) { this.logger.debug("Task already paused", { id: this.id }); return; } this.paused = true; this.logger.info("Task paused", { id: this.id }); } /** * Возобновляет задачу. */ resume() { if (!this.stopped && this.paused) { this.paused = false; this.logger.info("Task resumed", { id: this.id }); this.scheduleRun(); } else { this.logger.debug("Cannot resume task - not paused or stopped", { id: this.id }); } } /** * Планирует запуск задачи. */ scheduleRun() { if (this.stopped) { this.logger.debug("Cannot schedule run - task is stopped", { id: this.id }); return; } if (this.resourceId && this.pollingManager.queues.has(this.resourceId)) { const queue = this.pollingManager.queues.get(this.resourceId); queue?.markTaskReady(this); } else if (!this.resourceId) { if (this.stopped) { this.start(); } else if (!this.loopRunning) { this.loopRunning = true; this._runLoop(); } } } /** * Выполняет задачу один раз. * @returns {Promise<void>} */ async executeOnce() { if (this.stopped || this.paused) { this.logger.debug("Cannot execute - task is stopped or paused", { id: this.id }); return; } if (this.shouldRun && this.shouldRun() === false) { this.logger.debug("Task should not run according to shouldRun function", { id: this.id }); this._scheduleNextRun(); return; } this.onBeforeEach?.(); this.executionInProgress = true; this.stats.totalRuns++; this.logger.debug("Executing task once", { id: this.id }); const release = await this.transportMutex.acquire(); try { const firstFunction = this.fn[0]; if (firstFunction && typeof firstFunction === "function") { const result = firstFunction(); if (result && hasTransportProperty(result) && result.transport) { if (hasFlushMethod(result.transport)) { try { await result.transport.flush(); this.logger.debug("Transport flushed successfully", { id: this.id }); } catch (flushErr) { const error = flushErr instanceof Error ? flushErr : new import_errors.PollingManagerError(String(flushErr)); this.logger.warn("Flush failed", { id: this.id, error: error.message }); } } } } let success = false; const results = []; for (let fnIndex = 0; fnIndex < this.fn.length; fnIndex++) { let retryCount = 0; let result = null; let fnSuccess = false; while (!this.stopped && retryCount <= this.maxRetries) { if (this.paused) { this.logger.debug("Task paused during execution", { id: this.id }); this.executionInProgress = false; return; } try { const fnResult = this.fn[fnIndex]; if (typeof fnResult !== "function") { throw new import_errors.PollingManagerError( `Task ${this.id} fn at index ${fnIndex} is not a function` ); } const promiseResult = fnResult(); if (!(promiseResult instanceof Promise)) { throw new import_errors.PollingManagerError( `Task ${this.id} fn ${fnIndex} did not return a Promise` ); } result = await this._withTimeout(promiseResult, this.taskTimeout); fnSuccess = true; this.stats.successes++; this.stats.lastError = null; break; } catch (err) { const error = err instanceof Error ? err : new import_errors.PollingManagerError(String(err)); if (error instanceof import_errors.ModbusTimeoutError) { this.logger.error("Modbus timeout error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusCRCError) { this.logger.error("Modbus CRC error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusParityError) { this.logger.error("Modbus parity error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusNoiseError) { this.logger.error("Modbus noise error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusFramingError) { this.logger.error("Modbus framing error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusOverrunError) { this.logger.error("Modbus overrun error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusCollisionError) { this.logger.error("Modbus collision error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusConfigError) { this.logger.error("Modbus config error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusBaudRateError) { this.logger.error("Modbus baud rate error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusSyncError) { this.logger.error("Modbus sync error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusFrameBoundaryError) { this.logger.error("Modbus frame boundary error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusLRCError) { this.logger.error("Modbus LRC error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusChecksumError) { this.logger.error("Modbus checksum error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusDataConversionError) { this.logger.error("Modbus data conversion error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusBufferOverflowError) { this.logger.error("Modbus buffer overflow error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusBufferUnderrunError) { this.logger.error("Modbus buffer underrun error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusMemoryError) { this.logger.error("Modbus memory error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusStackOverflowError) { this.logger.error("Modbus stack overflow error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusResponseError) { this.logger.error("Modbus response error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidAddressError) { this.logger.error("Modbus invalid address error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidFunctionCodeError) { this.logger.error("Modbus invalid function code error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidQuantityError) { this.logger.error("Modbus invalid quantity error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusIllegalDataAddressError) { this.logger.error("Modbus illegal data address error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusIllegalDataValueError) { this.logger.error("Modbus illegal data value error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusSlaveBusyError) { this.logger.error("Modbus slave busy error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusAcknowledgeError) { this.logger.error("Modbus acknowledge error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusSlaveDeviceFailureError) { this.logger.error("Modbus slave device failure error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusMalformedFrameError) { this.logger.error("Modbus malformed frame error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidFrameLengthError) { this.logger.error("Modbus invalid frame length error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidTransactionIdError) { this.logger.error("Modbus invalid transaction ID error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusUnexpectedFunctionCodeError) { this.logger.error("Modbus unexpected function code error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusConnectionRefusedError) { this.logger.error("Modbus connection refused error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusConnectionTimeoutError) { this.logger.error("Modbus connection timeout error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusNotConnectedError) { this.logger.error("Modbus not connected error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusAlreadyConnectedError) { this.logger.error("Modbus already connected error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInsufficientDataError) { this.logger.error("Modbus insufficient data error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusGatewayPathUnavailableError) { this.logger.error("Modbus gateway path unavailable error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusGatewayTargetDeviceError) { this.logger.error("Modbus gateway target device error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidStartingAddressError) { this.logger.error("Modbus invalid starting address error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusMemoryParityError) { this.logger.error("Modbus memory parity error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusBroadcastError) { this.logger.error("Modbus broadcast error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusGatewayBusyError) { this.logger.error("Modbus gateway busy error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusDataOverrunError) { this.logger.error("Modbus data overrun error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusTooManyEmptyReadsError) { this.logger.error("Modbus too many empty reads error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInterFrameTimeoutError) { this.logger.error("Modbus inter-frame timeout error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusSilentIntervalError) { this.logger.error("Modbus silent interval error", { id: this.id, error: error.message }); } retryCount++; this.stats.totalErrors++; this.stats.retries++; this.stats.lastError = error; this.onRetry?.(error, fnIndex, retryCount); const isFlushedError = error instanceof import_errors.ModbusFlushError; let backoffDelay = this.backoffDelay; if (isFlushedError) { this.logger.debug("Flush error detected, resetting backoff", { id: this.id }); backoffDelay = this.backoffDelay; } const delay = isFlushedError ? Math.min(50, backoffDelay) : backoffDelay * Math.pow(2, retryCount - 1); if (retryCount > this.maxRetries) { this.stats.failures++; this.onFailure?.(error); this.onError?.(error, fnIndex, retryCount); this.logger.warn("Max retries exhausted for fn[" + fnIndex + "]", { id: this.id, fnIndex, retryCount, error: error.message }); } else { this.logger.debug("Retrying fn[" + fnIndex + "] with delay", { id: this.id, delay, retryCount }); await this._sleep(delay); if (this.stopped) { this.executionInProgress = false; return; } } } } results.push(result); success = success || fnSuccess; } this.stats.lastResult = results; this.stats.lastRunTime = Date.now(); if (results.length > 0 && results.some((r) => r !== null && r !== void 0)) { this.onData?.(results); this.logger.debug("Data callback executed", { id: this.id, resultsCount: results.length }); } else { this.logger.warn("Skipping onData - all results invalid", { id: this.id, results: "invalid" }); } this.onFinish?.(success, results); this.logger.info("Task execution completed", { id: this.id, success, resultsCount: results.length }); this.pollingManager.loggerInstance.flush(); } finally { release(); this.executionInProgress = false; } this._scheduleNextRun(); } /** * Планирует следующий запуск задачи. * @private */ _scheduleNextRun() { if (!this.stopped && this.resourceId) { setTimeout(() => { if (!this.stopped) { this.logger.debug("Scheduling next run (queued)", { id: this.id }); this.scheduleRun(); } }, this.interval); } else if (!this.stopped && !this.resourceId) { setTimeout(() => { if (!this.stopped && this.loopRunning) { this.logger.debug("Scheduling next run (loop)", { id: this.id }); this._runLoop(); } }, this.interval); } } /** * Проверяет, запущена ли задача. */ isRunning() { return !this.stopped; } /** * Проверяет, приостановлена ли задача. */ isPaused() { return this.paused; } /** * Устанавливает интервал задачи. */ setInterval(ms) { this.interval = ms; this.logger.info("Interval updated", { id: this.id, interval: ms }); } /** * Возвращает состояние задачи. */ getState() { return { stopped: this.stopped, paused: this.paused, running: this.loopRunning, inProgress: this.executionInProgress }; } /** * Возвращает статистику задачи. */ getStats() { return { ...this.stats }; } /** * Оригинальный цикл выполнения задачи (для задач без resourceId). * @private */ async _runLoop() { let backoffDelay = this.backoffDelay; this.logger.info("Starting run loop", { id: this.id }); while (this.loopRunning && !this.stopped) { if (this.paused) { this.logger.debug("Task paused in loop", { id: this.id }); await this._sleep(this.interval); continue; } if (this.shouldRun && this.shouldRun() === false) { this.logger.debug("Task should not run according to shouldRun function", { id: this.id }); await this._sleep(this.interval); continue; } this.onBeforeEach?.(); this.executionInProgress = true; this.stats.totalRuns++; const release = await this.transportMutex.acquire(); try { const firstFunction = this.fn[0]; if (firstFunction && typeof firstFunction === "function") { const result = firstFunction(); if (result && hasTransportProperty(result) && result.transport) { if (hasFlushMethod(result.transport)) { try { await result.transport.flush(); this.logger.debug("Transport flushed successfully", { id: this.id }); } catch (flushErr) { const error = flushErr instanceof Error ? flushErr : new import_errors.PollingManagerError(String(flushErr)); this.logger.warn("Flush failed", { id: this.id, error: error.message }); } } } } let success = false; const results = []; for (let fnIndex = 0; fnIndex < this.fn.length; fnIndex++) { let retryCount = 0; let result = null; let fnSuccess = false; while (this.loopRunning && !this.stopped && retryCount <= this.maxRetries) { try { const fnResult = this.fn[fnIndex]; if (typeof fnResult !== "function") { throw new import_errors.PollingManagerError( `Task ${this.id} fn at index ${fnIndex} is not a function` ); } const promiseResult = fnResult(); if (!(promiseResult instanceof Promise)) { throw new import_errors.PollingManagerError( `Task ${this.id} fn ${fnIndex} did not return a Promise` ); } result = await this._withTimeout(promiseResult, this.taskTimeout); fnSuccess = true; this.stats.successes++; this.stats.lastError = null; backoffDelay = this.backoffDelay; break; } catch (err) { const error = err instanceof Error ? err : new import_errors.PollingManagerError(String(err)); if (error instanceof import_errors.ModbusTimeoutError) { this.logger.error("Modbus timeout error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusCRCError) { this.logger.error("Modbus CRC error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusParityError) { this.logger.error("Modbus parity error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusNoiseError) { this.logger.error("Modbus noise error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusFramingError) { this.logger.error("Modbus framing error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusOverrunError) { this.logger.error("Modbus overrun error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusCollisionError) { this.logger.error("Modbus collision error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusConfigError) { this.logger.error("Modbus config error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusBaudRateError) { this.logger.error("Modbus baud rate error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusSyncError) { this.logger.error("Modbus sync error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusFrameBoundaryError) { this.logger.error("Modbus frame boundary error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusLRCError) { this.logger.error("Modbus LRC error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusChecksumError) { this.logger.error("Modbus checksum error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusDataConversionError) { this.logger.error("Modbus data conversion error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusBufferOverflowError) { this.logger.error("Modbus buffer overflow error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusBufferUnderrunError) { this.logger.error("Modbus buffer underrun error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusMemoryError) { this.logger.error("Modbus memory error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusStackOverflowError) { this.logger.error("Modbus stack overflow error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusResponseError) { this.logger.error("Modbus response error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidAddressError) { this.logger.error("Modbus invalid address error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidFunctionCodeError) { this.logger.error("Modbus invalid function code error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidQuantityError) { this.logger.error("Modbus invalid quantity error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusIllegalDataAddressError) { this.logger.error("Modbus illegal data address error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusIllegalDataValueError) { this.logger.error("Modbus illegal data value error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusSlaveBusyError) { this.logger.error("Modbus slave busy error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusAcknowledgeError) { this.logger.error("Modbus acknowledge error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusSlaveDeviceFailureError) { this.logger.error("Modbus slave device failure error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusMalformedFrameError) { this.logger.error("Modbus malformed frame error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidFrameLengthError) { this.logger.error("Modbus invalid frame length error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidTransactionIdError) { this.logger.error("Modbus invalid transaction ID error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusUnexpectedFunctionCodeError) { this.logger.error("Modbus unexpected function code error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusConnectionRefusedError) { this.logger.error("Modbus connection refused error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusConnectionTimeoutError) { this.logger.error("Modbus connection timeout error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusNotConnectedError) { this.logger.error("Modbus not connected error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusAlreadyConnectedError) { this.logger.error("Modbus already connected error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInsufficientDataError) { this.logger.error("Modbus insufficient data error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusGatewayPathUnavailableError) { this.logger.error("Modbus gateway path unavailable error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusGatewayTargetDeviceError) { this.logger.error("Modbus gateway target device error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInvalidStartingAddressError) { this.logger.error("Modbus invalid starting address error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusMemoryParityError) { this.logger.error("Modbus memory parity error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusBroadcastError) { this.logger.error("Modbus broadcast error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusGatewayBusyError) { this.logger.error("Modbus gateway busy error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusDataOverrunError) { this.logger.error("Modbus data overrun error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusTooManyEmptyReadsError) { this.logger.error("Modbus too many empty reads error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusInterFrameTimeoutError) { this.logger.error("Modbus inter-frame timeout error", { id: this.id, error: error.message }); } else if (error instanceof import_errors.ModbusSilentIntervalError) { this.logger.error("Modbus silent interval error", { id: this.id, error: error.message }); } retryCount++; this.stats.totalErrors++; this.stats.retries++; this.stats.lastError = error; this.onRetry?.(error, fnIndex, retryCount); const isFlushedError = error instanceof import_errors.ModbusFlushError; if (isFlushedError) { this.logger.debug("Flush error detected, resetting backoff", { id: this.id }); backoffDelay = this.backoffDelay; } const delay = isFlushedError ? Math.min(50, backoffDelay) : backoffDelay * Math.pow(2, retryCount - 1); if (retryCount > this.maxRetries) { this.stats.failures++; this.onFailure?.(error); this.onError?.(error, fnIndex, retryCount); this.logger.warn("Max retries exhausted for fn[" + fnIndex + "]", { id: this.id, fnIndex, retryCount, error: error.message }); } else { this.logger.debug("Retrying fn[" + fnIndex + "] with delay", { id: this.id, delay, retryCount }); await this._sleep(delay); } } } results.push(result); success = success || fnSuccess; } this.stats.lastResult = results; this.stats.lastRunTime = Date.now(); if (results.length > 0 && results.some((r) => r !== null && r !== void 0)) { this.onData?.(results); this.logger.debug("Data callback executed", { id: this.id, resultsCount: results.length }); } else { this.logger.warn("Skipping onData - all results invalid", { id: this.id, results: "invalid" }); } this.onFinish?.(success, results); this.logger.info("Task execution completed", { id: this.id, success, resultsCount: results.length }); this.pollingManager.loggerInstance.flush(); } finally { release(); this.executionInProgress = false; } await this._sleep(this.interval); } this.loopRunning = false; this.logger.debug("Run loop finished", { id: this.id }); } /** * Sleeps for given amount of milliseconds. * @param {number} ms * @returns {Promise} * @private */ _sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Wraps a promise with a timeout. * @param {Promise} promise * @param {number} timeout * @returns {Promise} * @private */ _withTimeout(promise, timeout) { return new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new import_errors.ModbusTimeoutError("Task timed out")), timeout); promise.then((result) => { clearTimeout(timer); resolve(result); }).catch((err) => { clearTimeout(timer); reject(err); }); }); } } class TaskQueue { resourceId; pollingManager; queuedOrProcessingTasks; taskQueue; mutex; processing; logger; constructor(resourceId, pollingManager, taskSet, loggerInstance) { this.resourceId = resourceId; this.pollingManager = pollingManager; this.queuedOrProcessingTasks = taskSet; this.taskQueue = []; this.mutex = new import_async_mutex.Mutex(); this.processing = false; this.logger = loggerInstance.createLogger("TaskQueue"); this.logger.setLevel("error"); } /** * Добавляет задачу в очередь на обработку. * @param {TaskController} taskController */ enqueue(taskController) { if (!taskController.stopped && taskController.resourceId === this.resourceId) { const taskKey = `${this.resourceId}:${taskController.id}`; if (!this.queuedOrProcessingTasks.has(taskKey)) { this.queuedOrProcessingTasks.add(taskKey); this.taskQueue.push(taskController.id); this.logger.debug("Task enqueued", { taskId: taskController.id }); this._processNext(); } else { this.logger.debug("Task already queued", { taskId: taskController.id }); } } } /** * Удаляет задачу из очереди. * @param {string} taskId */ removeTask(taskId) { const taskKey = `${this.resourceId}:${taskId}`; this.queuedOrProcessingTasks.delete(taskKey); this.taskQueue = this.taskQueue.filter((id) => id !== taskId); this.logger.debug("Task removed from queue", { taskId }); } /** * Проверяет, пуста ли очередь. * @returns {boolean} */ isEmpty() { return this.taskQueue.length === 0; } /** * Очищает очередь. */ clear() { for (const taskId of this.taskQueue) { const taskKey = `${this.resourceId}:${taskId}`; this.queuedOrProcessingTasks.delete(taskKey); } this.taskQueue = []; this.logger.debug("Queue cleared", { resourceId: this.resourceId }); } /** * Сообщает очереди, что задача готова к следующему запуску. * @param {TaskController} taskController */ markTaskReady(taskController) { if (!taskController.stopped && taskController.resourceId === this.resourceId) { const taskKey = `${this.resourceId}:${taskController.id}`; if (!this.queuedOrProcessingTasks.has(taskKey)) { this.queuedOrProcessingTasks.add(taskKey); this.taskQueue.push(taskController.id); this.logger.debug("Task marked as ready", { taskId: taskController.id }); this._processNext(); } } } /** * Обрабатывает очередь задач. * @private */ async _processNext() { if (this.processing || this.taskQueue.length === 0) { if (this.taskQueue.length === 0) { this.logger.debug("Queue is empty", { resourceId: this.resourceId }); } return; } this.processing = true; this.logger.debug("Acquiring mutex for task processing", { resourceId: this.resourceId }); const release = await this.mutex.acquire(); let taskKey = null; try { const taskId = this.taskQueue.shift(); if (!taskId) return; taskKey = `${this.resourceId}:${taskId}`; this.logger.debug("Processing task", { taskId }); const taskController = this.pollingManager.tasks.get(taskId); if (!taskController || taskController.stopped) { this.logger.debug("Task is stopped or does not exist", { taskId }); this.queuedOrProcessingTasks.delete(taskKey); if (this.taskQueue.length > 0) { setTimeout(() => { this.processing = false; this._processNext(); }, 0); } return; } await this.pollingManager._executeQueuedTask(taskController); this.logger.debug("Task executed successfully", { taskId }); } catch (error) { const err = error instanceof Error ? error : new import_errors.PollingManagerError(String(error)); if (err instanceof import_errors.ModbusTimeoutError) { this.logger.error("Modbus timeout error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusCRCError) { this.logger.error("Modbus CRC error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusParityError) { this.logger.error("Modbus parity error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusNoiseError) { this.logger.error("Modbus noise error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusFramingError) { this.logger.error("Modbus framing error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusOverrunError) { this.logger.error("Modbus overrun error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusCollisionError) { this.logger.error("Modbus collision error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusConfigError) { this.logger.error("Modbus config error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusBaudRateError) { this.logger.error("Modbus baud rate error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusSyncError) { this.logger.error("Modbus sync error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusFrameBoundaryError) { this.logger.error("Modbus frame boundary error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusLRCError) { this.logger.error("Modbus LRC error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusChecksumError) { this.logger.error("Modbus checksum error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusDataConversionError) { this.logger.error("Modbus data conversion error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusBufferOverflowError) { this.logger.error("Modbus buffer overflow error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusBufferUnderrunError) { this.logger.error("Modbus buffer underrun error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusMemoryError) { this.logger.error("Modbus memory error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusStackOverflowError) { this.logger.error("Modbus stack overflow error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusResponseError) { this.logger.error("Modbus response error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusInvalidAddressError) { this.logger.error("Modbus invalid address error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusInvalidFunctionCodeError) { this.logger.error("Modbus invalid function code error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusInvalidQuantityError) { this.logger.error("Modbus invalid quantity error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusIllegalDataAddressError) { this.logger.error("Modbus illegal data address error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusIllegalDataValueError) { this.logger.error("Modbus illegal data value error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusSlaveBusyError) { this.logger.error("Modbus slave busy error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusAcknowledgeError) { this.logger.error("Modbus acknowledge error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusSlaveDeviceFailureError) { this.logger.error("Modbus slave device failure error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusMalformedFrameError) { this.logger.error("Modbus malformed frame error in queue processing", { resourceId: this.resourceId, error: err.message }); } else if (err instanceof import_errors.ModbusInvalidFrameLengthError) { this.logger.error("Modbus invalid frame length error in queue processing", { resourceId: this.resourceId, error: