/** * 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']; } // ============================================================================ // Smart Home Factories // ============================================================================ import { SwitchFeature, type ISwitchFeatureOptions } from '../features/feature.switch.js'; import { SensorFeature, type ISensorFeatureOptions } from '../features/feature.sensor.js'; import { LightFeature, type ILightFeatureOptions } from '../features/feature.light.js'; import { CoverFeature, type ICoverFeatureOptions } from '../features/feature.cover.js'; import { LockFeature, type ILockFeatureOptions } from '../features/feature.lock.js'; import { FanFeature, type IFanFeatureOptions } from '../features/feature.fan.js'; import { ClimateFeature, type IClimateFeatureOptions } from '../features/feature.climate.js'; import { CameraFeature, type ICameraFeatureOptions } from '../features/feature.camera.js'; import type { TSwitchProtocol, TSensorProtocol, TLightProtocol, TCoverProtocol, TLockProtocol, TFanProtocol, TClimateProtocol, TCameraProtocol, ISwitchProtocolClient, ISensorProtocolClient, ILightProtocolClient, ICoverProtocolClient, ILockProtocolClient, IFanProtocolClient, IClimateProtocolClient, ICameraProtocolClient, ILightCapabilities, ICoverCapabilities, IFanCapabilities, IClimateCapabilities, ICameraCapabilities, TSensorDeviceClass, TSensorStateClass, TCoverDeviceClass, } from '../interfaces/smarthome.interfaces.js'; // Smart Switch Factory export interface ISmartSwitchDiscoveryInfo { id: string; name: string; address: string; port: number; entityId: string; protocol: TSwitchProtocol; protocolClient: ISwitchProtocolClient; deviceClass?: 'outlet' | 'switch'; } export function createSmartSwitch( info: ISmartSwitchDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); (device as { id: string }).id = info.id; const switchFeature = new SwitchFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, entityId: info.entityId, protocolClient: info.protocolClient, deviceClass: info.deviceClass, }); device.addFeature(switchFeature); return device; } // Smart Sensor Factory export interface ISmartSensorDiscoveryInfo { id: string; name: string; address: string; port: number; entityId: string; protocol: TSensorProtocol; protocolClient: ISensorProtocolClient; deviceClass?: TSensorDeviceClass; stateClass?: TSensorStateClass; unit?: string; } export function createSmartSensor( info: ISmartSensorDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); (device as { id: string }).id = info.id; const sensorFeature = new SensorFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, entityId: info.entityId, protocolClient: info.protocolClient, deviceClass: info.deviceClass, stateClass: info.stateClass, unit: info.unit, }); device.addFeature(sensorFeature); return device; } // Smart Light Factory export interface ISmartLightDiscoveryInfo { id: string; name: string; address: string; port: number; entityId: string; protocol: TLightProtocol; protocolClient: ILightProtocolClient; capabilities?: Partial; } export function createSmartLight( info: ISmartLightDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); (device as { id: string }).id = info.id; const lightFeature = new LightFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, entityId: info.entityId, protocolClient: info.protocolClient, capabilities: info.capabilities, }); device.addFeature(lightFeature); return device; } // Smart Cover Factory export interface ISmartCoverDiscoveryInfo { id: string; name: string; address: string; port: number; entityId: string; protocol: TCoverProtocol; protocolClient: ICoverProtocolClient; deviceClass?: TCoverDeviceClass; capabilities?: Partial; } export function createSmartCover( info: ISmartCoverDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); (device as { id: string }).id = info.id; const coverFeature = new CoverFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, entityId: info.entityId, protocolClient: info.protocolClient, deviceClass: info.deviceClass, capabilities: info.capabilities, }); device.addFeature(coverFeature); return device; } // Smart Lock Factory export interface ISmartLockDiscoveryInfo { id: string; name: string; address: string; port: number; entityId: string; protocol: TLockProtocol; protocolClient: ILockProtocolClient; supportsOpen?: boolean; } export function createSmartLock( info: ISmartLockDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); (device as { id: string }).id = info.id; const lockFeature = new LockFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, entityId: info.entityId, protocolClient: info.protocolClient, supportsOpen: info.supportsOpen, }); device.addFeature(lockFeature); return device; } // Smart Fan Factory export interface ISmartFanDiscoveryInfo { id: string; name: string; address: string; port: number; entityId: string; protocol: TFanProtocol; protocolClient: IFanProtocolClient; capabilities?: Partial; } export function createSmartFan( info: ISmartFanDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); (device as { id: string }).id = info.id; const fanFeature = new FanFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, entityId: info.entityId, protocolClient: info.protocolClient, capabilities: info.capabilities, }); device.addFeature(fanFeature); return device; } // Smart Climate Factory export interface ISmartClimateDiscoveryInfo { id: string; name: string; address: string; port: number; entityId: string; protocol: TClimateProtocol; protocolClient: IClimateProtocolClient; capabilities?: Partial; } export function createSmartClimate( info: ISmartClimateDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); (device as { id: string }).id = info.id; const climateFeature = new ClimateFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, entityId: info.entityId, protocolClient: info.protocolClient, capabilities: info.capabilities, }); device.addFeature(climateFeature); return device; } // Smart Camera Factory export interface ISmartCameraDiscoveryInfo { id: string; name: string; address: string; port: number; entityId: string; protocol: TCameraProtocol; protocolClient: ICameraProtocolClient; capabilities?: Partial; } export function createSmartCamera( info: ISmartCameraDiscoveryInfo, retryOptions?: IRetryOptions ): UniversalDevice { const device = new UniversalDevice(info.address, info.port, { name: info.name, retryOptions, }); (device as { id: string }).id = info.id; const cameraFeature = new CameraFeature(device.getDeviceReference(), info.port, { protocol: info.protocol, entityId: info.entityId, protocolClient: info.protocolClient, capabilities: info.capabilities, }); device.addFeature(cameraFeature); return device; } // ============================================================================ // Exports // ============================================================================ export { // Re-export device and feature types for convenience UniversalDevice, ScanFeature, PrintFeature, PlaybackFeature, VolumeFeature, PowerFeature, SnmpFeature, // Smart home features SwitchFeature, SensorFeature, LightFeature, CoverFeature, LockFeature, FanFeature, ClimateFeature, CameraFeature, };