import { S7Client } from '../client.js'; import { PollPoint, ChangeRecord, ReadPoint, ChangeHandler, ErrorHandler } from './types.js'; import { normalize, typeSize } from './codec.js'; import { groupByDb, byteRange } from './batch.js'; import { decode } from './codec.js'; export class PollingRunner { private _points: PollPoint[] = []; private _cache = new Map(); private _polling = false; private _pollingActive = false; private _timer: ReturnType | null = null; private _interval = 1000; private _onChange: ChangeHandler | null = null; private _onError: ErrorHandler | null = null; constructor(private client: S7Client) {} setPollPoints(points: PollPoint[]): void { this._points = points.map(p => normalize(p)); } setPollInterval(ms: number): void { this._interval = ms; } onChange(handler: ChangeHandler): void { this._onChange = handler; } onError(handler: ErrorHandler): void { this._onError = handler; } get pollingActive(): boolean { return this._pollingActive; } startPolling(): void { if (this._pollingActive && this._timer) return; if (!this._points.length) { throw new Error('Cannot startPolling: no poll points configured. Call setPollPoints() first.'); } this._pollingActive = true; this._startTimer(); } stopPolling(): void { this._pollingActive = false; if (this._timer) { clearInterval(this._timer); this._timer = null; } } clearCache(): void { this._cache.clear(); } handleDisconnect(): void { if (this._timer) { clearInterval(this._timer); this._timer = null; } } private _startTimer(): void { if (this._timer) clearInterval(this._timer); this._timer = setInterval(() => this._poll().catch(() => {}), this._interval); this._poll().catch(() => {}); } private async _poll(): Promise { if (this._polling || !this._pollingActive) return; this._polling = true; const snapshot = [...this._points]; if (!snapshot.length) { this._polling = false; return; } try { const values = await this._batchRead(snapshot as ReadPoint[]); const changes: ChangeRecord[] = []; for (const p of snapshot) { const cur = values.get(p.address); const old = this._cache.get(p.address); if (!this._cache.has(p.address) || old !== cur) { changes.push({ address: p.address, dataType: p.dataType, oldValue: old, newValue: cur, name: p.name }); } this._cache.set(p.address, cur); } if (changes.length > 0) { this._onChange?.(changes); } } catch (err) { this._onError?.(err); throw err; } finally { this._polling = false; } } private async _batchRead(points: ReadPoint[]): Promise> { const result = new Map(); if (!points.length) return result; for (const [dbNumber, items] of groupByDb(points)) { const { start, size } = byteRange(items, p => typeSize(p.dataType, p.stringLen)); const buf = await this.client.DBRead(dbNumber, start, size) as Buffer; for (const { addr, item } of items) { result.set(item.address, decode(buf, { ...addr, byteOffset: addr.byteOffset - start }, item.dataType)); } } return result; } }