Files
devicemanager/ts/factories/index.ts

697 lines
19 KiB
TypeScript
Raw Normal View History

/**
* 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<string, string>;
}
/**
* 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<string, string>;
}
/**
* 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<string, string>): 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<string, string>): 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<string, string>): 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<string, string>): 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<ILightCapabilities>;
}
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<ICoverCapabilities>;
}
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<IFanCapabilities>;
}
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<IClimateCapabilities>;
}
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<ICameraCapabilities>;
}
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,
};