UNPKG

modbus-connect

Version:

Modbus RTU over Web Serial and Node.js SerialPort

327 lines (274 loc) 8.14 kB
// polling-manager.js class PollingManager { constructor() { this.tasks = new Map(); } addTask(options) { const { id } = options; if (!id) throw new Error('Polling task must have an "id".'); if (this.tasks.has(id)) { throw new Error(`Polling task with id "${id}" already exists.`); } const controller = new TaskController(options); this.tasks.set(id, controller); if (options.immediate) { controller.start(); } } updateTask(id, newOptions) { if (!this.tasks.has(id)) { throw new Error(`Polling task with id "${id}" does not exist.`); } this.removeTask(id); this.addTask({ id, ...newOptions }); } removeTask(id) { const task = this.tasks.get(id); if (task) { task.stop(); this.tasks.delete(id); } } restartTask(id) { const task = this.tasks.get(id); if (task) { task.stop(); task.start(); } } startTask(id) { this.tasks.get(id)?.start(); } stopTask(id) { this.tasks.get(id)?.stop(); } pauseTask(id) { this.tasks.get(id)?.pause(); } resumeTask(id) { this.tasks.get(id)?.resume(); } setTaskInterval(id, interval) { this.tasks.get(id)?.setInterval(interval); } isTaskRunning(id) { return this.tasks.get(id)?.isRunning() ?? false; } isTaskPaused(id) { return this.tasks.get(id)?.isPaused() ?? false; } getTaskState(id) { return this.tasks.get(id)?.getState() ?? null; } getTaskStats(id) { return this.tasks.get(id)?.getStats() ?? null; } hasTask(id) { return this.tasks.has(id); } getTaskIds() { return Array.from(this.tasks.keys()); } clearAll() { for (const task of this.tasks.values()) { task.stop(); } this.tasks.clear(); } restartAllTasks() { for (const task of this.tasks.values()) { task.stop(); task.start(); } } pauseAllTasks() { for (const task of this.tasks.values()) { task.pause(); } } resumeAllTasks() { for (const task of this.tasks.values()) { task.resume(); } } startAllTasks() { for (const task of this.tasks.values()) { task.start(); } } stopAllTasks() { for (const task of this.tasks.values()) { task.stop(); } } getAllTaskStats() { const stats = {}; for (const [id, task] of this.tasks.entries()) { stats[id] = task.getStats(); } return stats; } } class TaskController { constructor({ id, interval, fn, onData, onError, onStart, onStop, onFinish, onBeforeEach, onRetry, shouldRun, onSuccess, onFailure, name = null, immediate = false, maxRetries = 0, backoffDelay = 0, taskTimeout = 2000 }) { this.id = id; 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.stats = { totalRuns: 0, totalErrors: 0, lastError: null, lastResult: null, lastRunTime: null, retries: 0, successes: 0, failures: 0 }; } async start() { if (!this.stopped) return; this.stopped = false; this.loopRunning = true; this.onStart?.(); this._runLoop(); } stop() { if (this.stopped) return; this.stopped = true; this.loopRunning = false; this.onStop?.(); } pause() { this.paused = true; } resume() { if (!this.stopped && this.paused) { this.paused = false; } } isRunning() { return !this.stopped; } isPaused() { return this.paused; } setInterval(ms) { this.interval = ms; } getState() { return { stopped: this.stopped, paused: this.paused, running: this.loopRunning, inProgress: this.executionInProgress }; } getStats() { return { ...this.stats }; } async _runLoop() { while (!this.stopped) { if (this.paused) { await this._sleep(this.interval); continue; } if (this.shouldRun && this.shouldRun() === false) { await this._sleep(this.interval); continue; } this.onBeforeEach?.(); this.executionInProgress = true; this.stats.totalRuns++; let success = false; let results = []; for (let fnIndex = 0; fnIndex < this.fn.length; fnIndex++) { let retryCount = 0; let result = null; while (!this.stopped && retryCount <= this.maxRetries) { try { result = await this._withTimeout(this.fn[fnIndex](), this.taskTimeout); this.stats.successes++; this.stats.lastError = null; success = true; break; } catch (err) { retryCount++; this.stats.totalErrors++; this.stats.retries++; this.stats.lastError = err; this.onRetry?.(err, fnIndex, retryCount); if (retryCount > this.maxRetries) { this.stats.failures++; this.onFailure?.(err); this.onError?.(err, fnIndex, retryCount); } else if (this.backoffDelay > 0) { await this._sleep(this.backoffDelay); } } } results.push(result); } this.stats.lastResult = results; this.stats.lastRunTime = Date.now(); this.executionInProgress = false; this.onData?.(...results); this.onFinish?.(success, results); await this._sleep(this.interval); } } _sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } _withTimeout(promise, timeout) { return new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error('Task timed out')), timeout); promise .then(result => { clearTimeout(timer); resolve(result); }) .catch(err => { clearTimeout(timer); reject(err); }); }); } } module.exports = PollingManager