modbus-connect
Version:
Modbus RTU over Web Serial and Node.js SerialPort
1,289 lines • 82.7 kB
JavaScript
"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: