/** * Device Factory Functions * Create UniversalDevice instances with appropriate features */ import { UniversalDevice, type IDeviceCreateOptions } from '../device/device.classes.device.js'; import { ScanFeature, type IScanFeatureOptions } from '../features/feature.scan.js'; import { PrintFeature, type IPrintFeatureOptions } from '../features/feature.print.js'; import { PlaybackFeature, type IPlaybackFeatureOptions } from '../features/feature.playback.js'; import { VolumeFeature, type IVolumeFeatureOptions } from '../features/feature.volume.js'; import { PowerFeature, type IPowerFeatureOptions } from '../features/feature.power.js'; import { SnmpFeature, type ISnmpFeatureOptions } from '../features/feature.snmp.js'; import type { TScannerProtocol, TScanFormat, TColorMode, TScanSource, IRetryOptions, } from '../interfaces/index.js'; import type { TPrintProtocol } from '../interfaces/feature.interfaces.js'; // ============================================================================ // Scanner Factory // ============================================================================ export interface IScannerDiscoveryInfo { id: string; name: string; address: string; port: number; protocol: TScannerProtocol | 'ipp'; txtRecords: Record; } /** * Create a scanner device (UniversalDevice with ScanFeature) */ export function createScanner( info: IScannerDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const protocol = info.protocol === 'ipp' ? 'escl' : info.protocol; const isSecure = info.txtRecords['TLS'] === '1' || (protocol === 'escl' && info.port === 443); // Parse capabilities from TXT records const formats = parseScanFormats(info.txtRecords); const resolutions = parseScanResolutions(info.txtRecords); const colorModes = parseScanColorModes(info.txtRecords); const sources = parseScanSources(info.txtRecords); const device = new UniversalDevice(info.address, info.port, { name: info.name, manufacturer: info.txtRecords['usb_MFG'] || info.txtRecords['mfg'], model: info.txtRecords['usb_MDL'] || info.txtRecords['mdl'] || info.txtRecords['ty'], retryOptions, }); // Override the generated ID with discovery ID (device as { id: string }).id = info.id; // Add scan feature const scanFeature = new ScanFeature(device.getDeviceReference(), info.port, { protocol: protocol as 'escl' | 'sane', secure: isSecure, supportedFormats: formats, supportedResolutions: resolutions, supportedColorModes: colorModes, supportedSources: sources, hasAdf: sources.includes('adf') || sources.includes('adf-duplex'), hasDuplex: sources.includes('adf-duplex'), }); device.addFeature(scanFeature); return device; } // ============================================================================ // Printer Factory // ============================================================================ export interface IPrinterDiscoveryInfo { id: string; name: string; address: string; port: number; txtRecords: Record; } /** * Create a printer device (UniversalDevice with PrintFeature) */ export function createPrinter( info: IPrinterDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const ippPath = info.txtRecords['rp'] || info.txtRecords['rfo'] || '/ipp/print'; const uri = `ipp://${info.address}:${info.port}${ippPath.startsWith('/') ? '' : '/'}${ippPath}`; const device = new UniversalDevice(info.address, info.port, { name: info.name, manufacturer: info.txtRecords['usb_MFG'] || info.txtRecords['mfg'], model: info.txtRecords['usb_MDL'] || info.txtRecords['mdl'] || info.txtRecords['ty'], retryOptions, }); // Override the generated ID with discovery ID (device as { id: string }).id = info.id; // Add print feature const printFeature = new PrintFeature(device.getDeviceReference(), info.port, { protocol: 'ipp', uri, supportsColor: info.txtRecords['Color'] === 'T' || info.txtRecords['color'] === 'true', supportsDuplex: info.txtRecords['Duplex'] === 'T' || info.txtRecords['duplex'] === 'true', }); device.addFeature(printFeature); return device; } // ============================================================================ // SNMP Device Factory // ============================================================================ export interface ISnmpDiscoveryInfo { id: string; name: string; address: string; port: number; community?: string; } /** * Create an SNMP device (UniversalDevice with SnmpFeature) */ export function createSnmpDevice( info: ISnmpDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); // Override the generated ID with discovery ID (device as { id: string }).id = info.id; // Add SNMP feature const snmpFeature = new SnmpFeature(device.getDeviceReference(), info.port, { community: info.community ?? 'public', }); device.addFeature(snmpFeature); return device; } // ============================================================================ // UPS Device Factory // ============================================================================ export interface IUpsDiscoveryInfo { id: string; name: string; address: string; port: number; protocol: 'nut' | 'snmp'; upsName?: string; community?: string; } /** * Create a UPS device (UniversalDevice with PowerFeature) */ export function createUpsDevice( info: IUpsDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); // Override the generated ID with discovery ID (device as { id: string }).id = info.id; // Add power feature const powerFeature = new PowerFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, upsName: info.upsName, community: info.community, }); device.addFeature(powerFeature); return device; } // ============================================================================ // Speaker Factory // ============================================================================ export interface ISpeakerDiscoveryInfo { id: string; name: string; address: string; port: number; protocol: 'sonos' | 'airplay' | 'chromecast' | 'dlna'; roomName?: string; modelName?: string; features?: number; // AirPlay feature flags deviceId?: string; friendlyName?: string; } /** * Create a speaker device (UniversalDevice with PlaybackFeature and VolumeFeature) */ export function createSpeaker( info: ISpeakerDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, model: info.modelName, retryOptions, }); // Override the generated ID with discovery ID (device as { id: string }).id = info.id; // Add playback feature const playbackFeature = new PlaybackFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, supportsQueue: info.protocol === 'sonos', supportsSeek: info.protocol !== 'airplay', }); device.addFeature(playbackFeature); // Add volume feature const volumeFeature = new VolumeFeature(device.getDeviceReference(), info.port, { volumeProtocol: info.protocol, minVolume: 0, maxVolume: 100, supportsMute: true, }); device.addFeature(volumeFeature); return device; } // ============================================================================ // DLNA Factory // ============================================================================ export interface IDlnaRendererDiscoveryInfo { id: string; name: string; address: string; port: number; controlUrl: string; friendlyName: string; modelName?: string; manufacturer?: string; } /** * Create a DLNA renderer device (UniversalDevice with PlaybackFeature) */ export function createDlnaRenderer( info: IDlnaRendererDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.friendlyName || info.name, manufacturer: info.manufacturer, model: info.modelName, retryOptions, }); // Override the generated ID with discovery ID (device as { id: string }).id = info.id; // Add playback feature for DLNA const playbackFeature = new PlaybackFeature(device.getDeviceReference(), info.port, { protocol: 'dlna', supportsQueue: false, supportsSeek: true, }); device.addFeature(playbackFeature); // Add volume feature const volumeFeature = new VolumeFeature(device.getDeviceReference(), info.port, { volumeProtocol: 'dlna', minVolume: 0, maxVolume: 100, supportsMute: true, }); device.addFeature(volumeFeature); return device; } // ============================================================================ // Parsing Helpers // ============================================================================ function parseScanFormats(txtRecords: Record): TScanFormat[] { const formats: TScanFormat[] = []; const pdl = txtRecords['pdl'] || txtRecords['DocumentFormat'] || ''; if (pdl.includes('jpeg') || pdl.includes('jpg')) formats.push('jpeg'); if (pdl.includes('png')) formats.push('png'); if (pdl.includes('pdf')) formats.push('pdf'); if (pdl.includes('tiff')) formats.push('tiff'); return formats.length > 0 ? formats : ['jpeg', 'png']; } function parseScanResolutions(txtRecords: Record): number[] { const rs = txtRecords['rs'] || ''; const parts = rs.split(',').map((s) => parseInt(s.trim())).filter((n) => !isNaN(n) && n > 0); return parts.length > 0 ? parts : [75, 150, 300, 600]; } function parseScanColorModes(txtRecords: Record): TColorMode[] { const cs = txtRecords['cs'] || txtRecords['ColorSpace'] || ''; const modes: TColorMode[] = []; if (cs.includes('color') || cs.includes('RGB')) modes.push('color'); if (cs.includes('gray') || cs.includes('grayscale')) modes.push('grayscale'); if (cs.includes('binary') || cs.includes('bw')) modes.push('blackwhite'); return modes.length > 0 ? modes : ['color', 'grayscale']; } function parseScanSources(txtRecords: Record): TScanSource[] { const is = txtRecords['is'] || txtRecords['InputSource'] || ''; const sources: TScanSource[] = []; if (is.includes('platen') || is.includes('flatbed') || is === '') { sources.push('flatbed'); } if (is.includes('adf') || is.includes('feeder')) { sources.push('adf'); } if (is.includes('duplex')) { sources.push('adf-duplex'); } return sources.length > 0 ? sources : ['flatbed']; } // ============================================================================ // Exports // ============================================================================ export { // Re-export device and feature types for convenience UniversalDevice, ScanFeature, PrintFeature, PlaybackFeature, VolumeFeature, PowerFeature, SnmpFeature, };