import * as plugins from '../plugins.js'; import type { IDeviceInfo, TDeviceType, TDeviceStatus, TConnectionState, IRetryOptions, } from '../interfaces/index.js'; import { withRetry } from '../helpers/helpers.retry.js'; /** * Abstract base class for all devices (scanners, printers) */ export abstract class Device extends plugins.events.EventEmitter { public readonly id: string; public readonly name: string; public readonly type: TDeviceType; public readonly address: string; public readonly port: number; protected _status: TDeviceStatus = 'unknown'; protected _connectionState: TConnectionState = 'disconnected'; protected _lastError: Error | null = null; public manufacturer?: string; public model?: string; public serialNumber?: string; public firmwareVersion?: string; protected retryOptions: IRetryOptions; constructor(info: IDeviceInfo, retryOptions?: IRetryOptions) { super(); this.id = info.id; this.name = info.name; this.type = info.type; this.address = info.address; this.port = info.port; this._status = info.status; this.manufacturer = info.manufacturer; this.model = info.model; this.serialNumber = info.serialNumber; this.firmwareVersion = info.firmwareVersion; this.retryOptions = retryOptions ?? { maxRetries: 5, baseDelay: 1000, maxDelay: 16000, multiplier: 2, jitter: true, }; } /** * Get current device status */ public get status(): TDeviceStatus { return this._status; } /** * Get current connection state */ public get connectionState(): TConnectionState { return this._connectionState; } /** * Get last error if any */ public get lastError(): Error | null { return this._lastError; } /** * Check if device is connected */ public get isConnected(): boolean { return this._connectionState === 'connected'; } /** * Update device status */ protected setStatus(status: TDeviceStatus): void { if (this._status !== status) { const oldStatus = this._status; this._status = status; this.emit('status:changed', { oldStatus, newStatus: status }); } } /** * Update connection state */ protected setConnectionState(state: TConnectionState): void { if (this._connectionState !== state) { const oldState = this._connectionState; this._connectionState = state; this.emit('connection:changed', { oldState, newState: state }); } } /** * Set error state */ protected setError(error: Error): void { this._lastError = error; this.setStatus('error'); this.emit('error', error); } /** * Clear error state */ protected clearError(): void { this._lastError = null; if (this._status === 'error') { this.setStatus('online'); } } /** * Execute an operation with retry logic */ protected async withRetry(fn: () => Promise): Promise { return withRetry(fn, this.retryOptions); } /** * Connect to the device */ public async connect(): Promise { if (this.isConnected) { return; } this.setConnectionState('connecting'); this.clearError(); try { await this.withRetry(() => this.doConnect()); this.setConnectionState('connected'); this.setStatus('online'); } catch (error) { this.setConnectionState('error'); this.setError(error instanceof Error ? error : new Error(String(error))); throw error; } } /** * Disconnect from the device */ public async disconnect(): Promise { if (this._connectionState === 'disconnected') { return; } try { await this.doDisconnect(); } finally { this.setConnectionState('disconnected'); } } /** * Get device info as plain object */ public getInfo(): IDeviceInfo { return { id: this.id, name: this.name, type: this.type, address: this.address, port: this.port, status: this._status, manufacturer: this.manufacturer, model: this.model, serialNumber: this.serialNumber, firmwareVersion: this.firmwareVersion, }; } /** * Implementation-specific connect logic * Override in subclasses */ protected abstract doConnect(): Promise; /** * Implementation-specific disconnect logic * Override in subclasses */ protected abstract doDisconnect(): Promise; /** * Refresh device status * Override in subclasses */ public abstract refreshStatus(): Promise; }