697 lines
19 KiB
TypeScript
697 lines
19 KiB
TypeScript
/**
|
|
* 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,
|
|
};
|