import * as plugins from './plugins.js'; import { MdnsDiscovery, SERVICE_TYPES } from './discovery/discovery.classes.mdns.js'; import { NetworkScanner } from './discovery/discovery.classes.networkscanner.js'; import { SsdpDiscovery, SSDP_SERVICE_TYPES, type ISsdpDevice } from './discovery/discovery.classes.ssdp.js'; import { Scanner } from './scanner/scanner.classes.scanner.js'; import { Printer } from './printer/printer.classes.printer.js'; import { SnmpDevice, type ISnmpDeviceInfo } from './snmp/snmp.classes.snmpdevice.js'; import { UpsDevice, type IUpsDeviceInfo, type TUpsProtocol } from './ups/ups.classes.upsdevice.js'; import { DlnaRenderer, type IDlnaRendererInfo } from './dlna/dlna.classes.renderer.js'; import { DlnaServer, type IDlnaServerInfo } from './dlna/dlna.classes.server.js'; import { Speaker, type ISpeakerInfo } from './speaker/speaker.classes.speaker.js'; import { SonosSpeaker, type ISonosSpeakerInfo } from './speaker/speaker.classes.sonos.js'; import { AirPlaySpeaker, type IAirPlaySpeakerInfo } from './speaker/speaker.classes.airplay.js'; import { ChromecastSpeaker, type IChromecastSpeakerInfo } from './speaker/speaker.classes.chromecast.js'; import type { IDeviceManagerOptions, IDiscoveredDevice, IRetryOptions, TDeviceManagerEvents, INetworkScanOptions, INetworkScanResult, TDeviceType, TFeatureType, } from './interfaces/index.js'; // Universal Device & Features import { UniversalDevice } from './device/device.classes.device.js'; import type { Feature } from './features/index.js'; /** * Default device manager options */ const DEFAULT_OPTIONS: Required = { autoDiscovery: true, discoveryTimeout: 10000, enableRetry: true, maxRetries: 5, retryBaseDelay: 1000, }; /** * Main Device Manager class for discovering and managing network devices */ export class DeviceManager extends plugins.events.EventEmitter { private mdnsDiscovery: MdnsDiscovery; private ssdpDiscovery: SsdpDiscovery; private _networkScanner: NetworkScanner | null = null; // Device collections private scanners: Map = new Map(); private printers: Map = new Map(); private snmpDevices: Map = new Map(); private upsDevices: Map = new Map(); private dlnaRenderers: Map = new Map(); private dlnaServers: Map = new Map(); private sonosSpeakers: Map = new Map(); private airplaySpeakers: Map = new Map(); private chromecastSpeakers: Map = new Map(); // Universal devices (new architecture) private universalDevices: Map = new Map(); private options: Required; private retryOptions: IRetryOptions; constructor(options?: IDeviceManagerOptions) { super(); this.options = { ...DEFAULT_OPTIONS, ...options }; this.retryOptions = { maxRetries: this.options.maxRetries, baseDelay: this.options.retryBaseDelay, maxDelay: 16000, multiplier: 2, jitter: true, }; this.mdnsDiscovery = new MdnsDiscovery({ timeout: this.options.discoveryTimeout, }); this.ssdpDiscovery = new SsdpDiscovery(); this.setupDiscoveryEvents(); this.setupSsdpDiscoveryEvents(); } /** * Setup event forwarding from mDNS discovery service */ private setupDiscoveryEvents(): void { this.mdnsDiscovery.on('device:found', (device: IDiscoveredDevice) => { this.handleMdnsDeviceFound(device); }); this.mdnsDiscovery.on('device:lost', (device: IDiscoveredDevice) => { this.handleDeviceLost(device); }); this.mdnsDiscovery.on('started', () => { this.emit('discovery:started'); }); this.mdnsDiscovery.on('stopped', () => { this.emit('discovery:stopped'); }); } /** * Setup event forwarding from SSDP discovery service */ private setupSsdpDiscoveryEvents(): void { this.ssdpDiscovery.on('device:found', (device: ISsdpDevice) => { this.handleSsdpDeviceFound(device); }); this.ssdpDiscovery.on('started', () => { this.emit('ssdp:started'); }); this.ssdpDiscovery.on('stopped', () => { this.emit('ssdp:stopped'); }); } /** * Handle newly discovered mDNS device */ private handleMdnsDeviceFound(device: IDiscoveredDevice): void { if (device.type === 'scanner') { // Create Scanner instance const scanner = Scanner.fromDiscovery( { id: device.id, name: device.name, address: device.address, port: device.port, protocol: device.protocol as 'sane' | 'escl', txtRecords: device.txtRecords, }, this.options.enableRetry ? this.retryOptions : undefined ); this.scanners.set(device.id, scanner); this.emit('scanner:found', scanner.getScannerInfo()); } else if (device.type === 'printer') { // Create Printer instance const printer = Printer.fromDiscovery( { id: device.id, name: device.name, address: device.address, port: device.port, txtRecords: device.txtRecords, }, this.options.enableRetry ? this.retryOptions : undefined ); this.printers.set(device.id, printer); this.emit('printer:found', printer.getPrinterInfo()); } else if (device.type === 'speaker') { // Create appropriate speaker instance based on protocol this.handleMdnsSpeakerFound(device); } } /** * Handle mDNS speaker discovery */ private handleMdnsSpeakerFound(device: IDiscoveredDevice): void { const protocol = device.protocol; const txt = device.txtRecords || {}; if (protocol === 'sonos') { // Sonos speaker const speaker = SonosSpeaker.fromDiscovery( { id: device.id, name: device.name, address: device.address, port: device.port || 1400, roomName: txt['room'] || device.name, modelName: txt['model'] || txt['md'], }, this.options.enableRetry ? this.retryOptions : undefined ); this.sonosSpeakers.set(device.id, speaker); this.emit('speaker:found', speaker.getSpeakerInfo()); } else if (protocol === 'airplay') { // AirPlay speaker (HomePod, Apple TV, etc.) const features = txt['features'] ? parseInt(txt['features'], 16) : 0; const speaker = AirPlaySpeaker.fromDiscovery( { id: device.id, name: device.name, address: device.address, port: device.port || 7000, roomName: txt['room'] || device.name, modelName: txt['model'] || txt['am'], features, deviceId: txt['deviceid'] || txt['pk'], }, this.options.enableRetry ? this.retryOptions : undefined ); this.airplaySpeakers.set(device.id, speaker); this.emit('speaker:found', speaker.getSpeakerInfo()); } else if (protocol === 'chromecast') { // Chromecast / Google Cast const speaker = ChromecastSpeaker.fromDiscovery( { id: device.id, name: txt['fn'] || device.name, address: device.address, port: device.port || 8009, friendlyName: txt['fn'] || device.name, modelName: txt['md'], }, this.options.enableRetry ? this.retryOptions : undefined ); this.chromecastSpeakers.set(device.id, speaker); this.emit('speaker:found', speaker.getSpeakerInfo()); } } /** * Handle newly discovered SSDP/UPnP device */ private handleSsdpDeviceFound(device: ISsdpDevice): void { if (!device.description) { return; } const serviceType = device.serviceType; const deviceType = device.description.deviceType; // Check for DLNA Media Renderer if (serviceType.includes('MediaRenderer') || deviceType.includes('MediaRenderer')) { const renderer = DlnaRenderer.fromSsdpDevice(device, this.retryOptions); if (renderer) { this.dlnaRenderers.set(renderer.id, renderer); this.emit('dlna:renderer:found', renderer.getDeviceInfo()); } } // Check for DLNA Media Server if (serviceType.includes('MediaServer') || deviceType.includes('MediaServer')) { const server = DlnaServer.fromSsdpDevice(device, this.retryOptions); if (server) { this.dlnaServers.set(server.id, server); this.emit('dlna:server:found', server.getDeviceInfo()); } } // Check for Sonos ZonePlayer if (serviceType.includes('ZonePlayer') || deviceType.includes('ZonePlayer')) { const speaker = SonosSpeaker.fromDiscovery( { id: `sonos:${device.usn}`, name: device.description.friendlyName, address: device.address, port: 1400, roomName: device.description.friendlyName, modelName: device.description.modelName, }, this.retryOptions ); this.sonosSpeakers.set(speaker.id, speaker); this.emit('speaker:found', speaker.getSpeakerInfo()); } } /** * Handle lost device */ private handleDeviceLost(device: IDiscoveredDevice): void { if (device.type === 'scanner') { const scanner = this.scanners.get(device.id); if (scanner) { // Disconnect if connected if (scanner.isConnected) { scanner.disconnect().catch(() => {}); } this.scanners.delete(device.id); this.emit('scanner:lost', device.id); } } else if (device.type === 'printer') { const printer = this.printers.get(device.id); if (printer) { // Disconnect if connected if (printer.isConnected) { printer.disconnect().catch(() => {}); } this.printers.delete(device.id); this.emit('printer:lost', device.id); } } } /** * Start device discovery (mDNS and SSDP) */ public async startDiscovery(): Promise { await Promise.all([ this.mdnsDiscovery.start(), this.ssdpDiscovery.start(), ]); } /** * Stop device discovery */ public async stopDiscovery(): Promise { await Promise.all([ this.mdnsDiscovery.stop(), this.ssdpDiscovery.stop(), ]); } /** * Check if discovery is running */ public get isDiscovering(): boolean { return this.mdnsDiscovery.running || this.ssdpDiscovery.isRunning; } /** * Get all discovered scanners */ public getScanners(): Scanner[] { return Array.from(this.scanners.values()); } /** * Get all discovered printers */ public getPrinters(): Printer[] { return Array.from(this.printers.values()); } /** * Get all discovered SNMP devices */ public getSnmpDevices(): SnmpDevice[] { return Array.from(this.snmpDevices.values()); } /** * Get all discovered UPS devices */ public getUpsDevices(): UpsDevice[] { return Array.from(this.upsDevices.values()); } /** * Get all DLNA media renderers */ public getDlnaRenderers(): DlnaRenderer[] { return Array.from(this.dlnaRenderers.values()); } /** * Get all DLNA media servers */ public getDlnaServers(): DlnaServer[] { return Array.from(this.dlnaServers.values()); } /** * Get all speakers (all protocols) */ public getSpeakers(): Speaker[] { return [ ...this.getSonosSpeakers(), ...this.getAirPlaySpeakers(), ...this.getChromecastSpeakers(), ]; } /** * Get all Sonos speakers */ public getSonosSpeakers(): SonosSpeaker[] { return Array.from(this.sonosSpeakers.values()); } /** * Get all AirPlay speakers */ public getAirPlaySpeakers(): AirPlaySpeaker[] { return Array.from(this.airplaySpeakers.values()); } /** * Get all Chromecast speakers */ public getChromecastSpeakers(): ChromecastSpeaker[] { return Array.from(this.chromecastSpeakers.values()); } /** * Get scanner by ID */ public getScanner(id: string): Scanner | undefined { return this.scanners.get(id); } /** * Get printer by ID */ public getPrinter(id: string): Printer | undefined { return this.printers.get(id); } /** * Get SNMP device by ID */ public getSnmpDevice(id: string): SnmpDevice | undefined { return this.snmpDevices.get(id); } /** * Get UPS device by ID */ public getUpsDevice(id: string): UpsDevice | undefined { return this.upsDevices.get(id); } /** * Get DLNA renderer by ID */ public getDlnaRenderer(id: string): DlnaRenderer | undefined { return this.dlnaRenderers.get(id); } /** * Get DLNA server by ID */ public getDlnaServer(id: string): DlnaServer | undefined { return this.dlnaServers.get(id); } /** * Get speaker by ID (any protocol) */ public getSpeaker(id: string): Speaker | undefined { return this.sonosSpeakers.get(id) ?? this.airplaySpeakers.get(id) ?? this.chromecastSpeakers.get(id); } /** * Get all devices (all types) */ public getAllDevices(): (Scanner | Printer | SnmpDevice | UpsDevice | DlnaRenderer | DlnaServer | Speaker)[] { return [ ...this.getScanners(), ...this.getPrinters(), ...this.getSnmpDevices(), ...this.getUpsDevices(), ...this.getDlnaRenderers(), ...this.getDlnaServers(), ...this.getSpeakers(), ]; } /** * Get devices by type */ public getDevicesByType(type: TDeviceType): (Scanner | Printer | SnmpDevice | UpsDevice | DlnaRenderer | DlnaServer | Speaker)[] { switch (type) { case 'scanner': return this.getScanners(); case 'printer': return this.getPrinters(); case 'snmp': return this.getSnmpDevices(); case 'ups': return this.getUpsDevices(); case 'dlna-renderer': return this.getDlnaRenderers(); case 'dlna-server': return this.getDlnaServers(); case 'speaker': return this.getSpeakers(); default: return []; } } /** * Get device by ID (any type) */ public getDeviceById(id: string): Scanner | Printer | SnmpDevice | UpsDevice | DlnaRenderer | DlnaServer | Speaker | undefined { return this.scanners.get(id) ?? this.printers.get(id) ?? this.snmpDevices.get(id) ?? this.upsDevices.get(id) ?? this.dlnaRenderers.get(id) ?? this.dlnaServers.get(id) ?? this.getSpeaker(id); } /** * Add a scanner manually (without discovery) */ public async addScanner( address: string, port: number, protocol: 'escl' | 'sane' = 'escl', name?: string ): Promise { const id = `manual:${protocol}:${address}:${port}`; // Check if already exists if (this.scanners.has(id)) { return this.scanners.get(id)!; } const scanner = Scanner.fromDiscovery( { id, name: name ?? `Scanner at ${address}`, address, port, protocol, txtRecords: {}, }, this.options.enableRetry ? this.retryOptions : undefined ); // Try to connect to validate await scanner.connect(); this.scanners.set(id, scanner); this.emit('scanner:found', scanner.getScannerInfo()); return scanner; } /** * Add a printer manually (without discovery) */ public async addPrinter( address: string, port: number = 631, name?: string, ippPath?: string ): Promise { const id = `manual:ipp:${address}:${port}`; // Check if already exists if (this.printers.has(id)) { return this.printers.get(id)!; } const printer = Printer.fromDiscovery( { id, name: name ?? `Printer at ${address}`, address, port, txtRecords: ippPath ? { rp: ippPath } : {}, }, this.options.enableRetry ? this.retryOptions : undefined ); // Try to connect to validate await printer.connect(); this.printers.set(id, printer); this.emit('printer:found', printer.getPrinterInfo()); return printer; } /** * Get the NetworkScanner instance for advanced control */ public get networkScanner(): NetworkScanner { if (!this._networkScanner) { this._networkScanner = new NetworkScanner(); this.setupNetworkScannerEvents(); } return this._networkScanner; } /** * Setup event forwarding from network scanner */ private setupNetworkScannerEvents(): void { if (!this._networkScanner) return; this._networkScanner.on('device:found', (result: INetworkScanResult) => { this.emit('network:device:found', result); }); this._networkScanner.on('progress', (progress) => { this.emit('network:progress', progress); }); this._networkScanner.on('complete', (results) => { this.emit('network:complete', results); }); this._networkScanner.on('error', (error) => { this.emit('error', error); }); } /** * Scan a network range for devices (IP-based, not mDNS) * Found devices are automatically added to the device manager */ public async scanNetwork( options: INetworkScanOptions ): Promise<{ scanners: Scanner[]; printers: Printer[]; speakers: Speaker[] }> { const results = await this.networkScanner.scan(options); const foundScanners: Scanner[] = []; const foundPrinters: Printer[] = []; const foundSpeakers: Speaker[] = []; for (const result of results) { for (const device of result.devices) { try { if (device.type === 'scanner') { if (device.protocol === 'escl') { const scanner = await this.addScanner( result.address, device.port, 'escl', device.name ); foundScanners.push(scanner); } else if (device.protocol === 'sane') { const scanner = await this.addScanner( result.address, device.port, 'sane', device.name ); foundScanners.push(scanner); } } else if (device.type === 'printer') { if (device.protocol === 'ipp') { const printer = await this.addPrinter( result.address, device.port, device.name ); foundPrinters.push(printer); } // JetDirect printers don't have a protocol handler yet } else if (device.type === 'speaker') { if (device.protocol === 'airplay') { const speaker = await this.addAirPlaySpeaker( result.address, { name: device.name, port: device.port } ); foundSpeakers.push(speaker); } else if (device.protocol === 'sonos') { const speaker = await this.addSonosSpeaker( result.address, { name: device.name, port: device.port } ); foundSpeakers.push(speaker); } else if (device.protocol === 'chromecast') { const speaker = await this.addChromecastSpeaker( result.address, { name: device.name, port: device.port } ); foundSpeakers.push(speaker); } } } catch (error) { // Device could not be added (connection failed, etc.) this.emit('error', error instanceof Error ? error : new Error(String(error))); } } } return { scanners: foundScanners, printers: foundPrinters, speakers: foundSpeakers }; } /** * Cancel an ongoing network scan */ public async cancelNetworkScan(): Promise { if (this._networkScanner) { await this._networkScanner.cancel(); } } /** * Check if a network scan is in progress */ public get isNetworkScanning(): boolean { return this._networkScanner?.isScanning ?? false; } /** * Remove a device */ public async removeDevice(id: string): Promise { const scanner = this.scanners.get(id); if (scanner) { if (scanner.isConnected) { await scanner.disconnect(); } this.scanners.delete(id); this.emit('scanner:lost', id); return true; } const printer = this.printers.get(id); if (printer) { if (printer.isConnected) { await printer.disconnect(); } this.printers.delete(id); this.emit('printer:lost', id); return true; } return false; } /** * Disconnect all devices */ public async disconnectAll(): Promise { const disconnectPromises: Promise[] = []; // Disconnect all device types const allDevices = this.getAllDevices(); for (const device of allDevices) { if (device.isConnected) { disconnectPromises.push(device.disconnect().catch(() => {})); } } await Promise.all(disconnectPromises); } /** * Stop discovery and disconnect all devices */ public async shutdown(): Promise { await this.stopDiscovery(); await this.disconnectAll(); // Clear all device collections this.scanners.clear(); this.printers.clear(); this.snmpDevices.clear(); this.upsDevices.clear(); this.dlnaRenderers.clear(); this.dlnaServers.clear(); this.sonosSpeakers.clear(); this.airplaySpeakers.clear(); this.chromecastSpeakers.clear(); this.universalDevices.clear(); } /** * Refresh status of all devices */ public async refreshAllStatus(): Promise { const refreshPromises: Promise[] = []; const allDevices = this.getAllDevices(); for (const device of allDevices) { if (device.isConnected) { refreshPromises.push( device.refreshStatus().catch((error) => { this.emit('error', error); }) ); } } await Promise.all(refreshPromises); } // ============================================================================ // Manual Device Addition Methods // ============================================================================ /** * Add an SNMP device manually */ public async addSnmpDevice( address: string, port: number = 161, options?: { name?: string; community?: string } ): Promise { const id = `manual:snmp:${address}:${port}`; if (this.snmpDevices.has(id)) { return this.snmpDevices.get(id)!; } const device = SnmpDevice.fromDiscovery( { id, name: options?.name ?? `SNMP Device at ${address}`, address, port, community: options?.community ?? 'public', }, this.retryOptions ); await device.connect(); this.snmpDevices.set(id, device); this.emit('snmp:found', device.getDeviceInfo()); return device; } /** * Add a UPS device manually */ public async addUpsDevice( address: string, protocol: TUpsProtocol, options?: { name?: string; port?: number; upsName?: string; community?: string } ): Promise { const port = options?.port ?? (protocol === 'nut' ? 3493 : 161); const id = `manual:ups:${address}:${port}`; if (this.upsDevices.has(id)) { return this.upsDevices.get(id)!; } const device = UpsDevice.fromDiscovery( { id, name: options?.name ?? `UPS at ${address}`, address, port, protocol, upsName: options?.upsName, community: options?.community, }, this.retryOptions ); await device.connect(); this.upsDevices.set(id, device); this.emit('ups:found', device.getDeviceInfo()); return device; } /** * Add a Sonos speaker manually */ public async addSonosSpeaker( address: string, options?: { name?: string; port?: number } ): Promise { const port = options?.port ?? 1400; const id = `manual:sonos:${address}:${port}`; if (this.sonosSpeakers.has(id)) { return this.sonosSpeakers.get(id)!; } const speaker = SonosSpeaker.fromDiscovery( { id, name: options?.name ?? `Sonos at ${address}`, address, port, }, this.retryOptions ); await speaker.connect(); this.sonosSpeakers.set(id, speaker); this.emit('speaker:found', speaker.getSpeakerInfo()); return speaker; } /** * Add an AirPlay speaker manually */ public async addAirPlaySpeaker( address: string, options?: { name?: string; port?: number } ): Promise { const port = options?.port ?? 7000; const id = `manual:airplay:${address}:${port}`; if (this.airplaySpeakers.has(id)) { return this.airplaySpeakers.get(id)!; } const speaker = AirPlaySpeaker.fromDiscovery( { id, name: options?.name ?? `AirPlay at ${address}`, address, port, }, this.retryOptions ); await speaker.connect(); this.airplaySpeakers.set(id, speaker); this.emit('speaker:found', speaker.getSpeakerInfo()); return speaker; } /** * Add a Chromecast speaker manually */ public async addChromecastSpeaker( address: string, options?: { name?: string; port?: number } ): Promise { const port = options?.port ?? 8009; const id = `manual:chromecast:${address}:${port}`; if (this.chromecastSpeakers.has(id)) { return this.chromecastSpeakers.get(id)!; } const speaker = ChromecastSpeaker.fromDiscovery( { id, name: options?.name ?? `Chromecast at ${address}`, address, port, }, this.retryOptions ); await speaker.connect(); this.chromecastSpeakers.set(id, speaker); this.emit('speaker:found', speaker.getSpeakerInfo()); return speaker; } // ============================================================================ // Universal Device & Feature-Based API (New Architecture) // ============================================================================ /** * Get all universal devices */ public getUniversalDevices(): UniversalDevice[] { return Array.from(this.universalDevices.values()); } /** * Get a universal device by ID */ public getUniversalDevice(id: string): UniversalDevice | undefined { return this.universalDevices.get(id); } /** * Get a universal device by address */ public getUniversalDeviceByAddress(address: string): UniversalDevice | undefined { for (const device of this.universalDevices.values()) { if (device.address === address) { return device; } } return undefined; } /** * Get all universal devices that have a specific feature * @param featureType The feature type to search for */ public getDevicesWithFeature(featureType: TFeatureType): UniversalDevice[] { return this.getUniversalDevices().filter(device => device.hasFeature(featureType)); } /** * Get all universal devices that have ALL specified features * @param featureTypes Array of feature types - device must have ALL of them */ public getDevicesWithFeatures(featureTypes: TFeatureType[]): UniversalDevice[] { return this.getUniversalDevices().filter(device => device.hasFeatures(featureTypes)); } /** * Get all universal devices that have ANY of the specified features * @param featureTypes Array of feature types - device must have at least one */ public getDevicesWithAnyFeature(featureTypes: TFeatureType[]): UniversalDevice[] { return this.getUniversalDevices().filter(device => featureTypes.some(type => device.hasFeature(type)) ); } /** * Add a universal device */ public addUniversalDevice(device: UniversalDevice): void { const existingDevice = this.universalDevices.get(device.id); if (existingDevice) { // Merge features into existing device for (const feature of device.getFeatures()) { if (!existingDevice.hasFeature(feature.type)) { existingDevice.addFeature(feature); } } } else { this.universalDevices.set(device.id, device); this.emit('universal:device:found', device); } } /** * Add a feature to an existing universal device */ public addFeatureToDevice(deviceId: string, feature: Feature): boolean { const device = this.universalDevices.get(deviceId); if (device) { device.addFeature(feature); this.emit('feature:added', { device, feature }); return true; } return false; } /** * Remove a feature from a universal device */ public async removeFeatureFromDevice(deviceId: string, featureType: TFeatureType): Promise { const device = this.universalDevices.get(deviceId); if (device) { const removed = await device.removeFeature(featureType); if (removed) { this.emit('feature:removed', { device, featureType }); } return removed; } return false; } /** * Remove a universal device */ public async removeUniversalDevice(id: string): Promise { const device = this.universalDevices.get(id); if (device) { await device.disconnect(); this.universalDevices.delete(id); this.emit('universal:device:lost', id); return true; } return false; } /** * Get feature from a device by type */ public getFeatureFromDevice(deviceId: string, featureType: TFeatureType): T | undefined { const device = this.universalDevices.get(deviceId); return device?.getFeature(featureType); } } // Export all classes and types export { // Discovery MdnsDiscovery, NetworkScanner, SsdpDiscovery, SERVICE_TYPES, SSDP_SERVICE_TYPES, // Scanner & Printer Scanner, Printer, // SNMP SnmpDevice, // UPS UpsDevice, // DLNA DlnaRenderer, DlnaServer, // Speakers Speaker, SonosSpeaker, AirPlaySpeaker, ChromecastSpeaker, // Universal Device (new architecture) UniversalDevice, };