feat(devicemanager): Introduce a UniversalDevice architecture with composable Feature system; add extensive new device/protocol support and discovery/refactors
This commit is contained in:
346
ts/interfaces/feature.interfaces.ts
Normal file
346
ts/interfaces/feature.interfaces.ts
Normal file
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Feature Type Definitions
|
||||
* Features are composable capabilities that can be attached to devices
|
||||
*/
|
||||
|
||||
import type { IRetryOptions } from './index.js';
|
||||
|
||||
// ============================================================================
|
||||
// Feature Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* All supported feature types
|
||||
*/
|
||||
export type TFeatureType =
|
||||
| 'scan' // Can scan documents (eSCL, SANE)
|
||||
| 'print' // Can print documents (IPP, JetDirect)
|
||||
| 'fax' // Can send/receive fax
|
||||
| 'copy' // Can copy (scan + print combined)
|
||||
| 'playback' // Can play media (audio/video)
|
||||
| 'volume' // Has volume control
|
||||
| 'power' // Has power status (UPS, smart plug)
|
||||
| 'snmp' // SNMP queryable
|
||||
| 'dlna-render' // DLNA renderer
|
||||
| 'dlna-serve' // DLNA server (content provider)
|
||||
;
|
||||
|
||||
/**
|
||||
* Feature connection state
|
||||
*/
|
||||
export type TFeatureState = 'disconnected' | 'connecting' | 'connected' | 'error';
|
||||
|
||||
// ============================================================================
|
||||
// Base Feature Interface
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Base interface for all features
|
||||
*/
|
||||
export interface IFeature {
|
||||
/** Feature type identifier */
|
||||
readonly type: TFeatureType;
|
||||
/** Protocol used by this feature */
|
||||
readonly protocol: string;
|
||||
/** Current feature state */
|
||||
readonly state: TFeatureState;
|
||||
/** Port used by this feature (may differ from device port) */
|
||||
readonly port: number;
|
||||
/** Connect to the feature endpoint */
|
||||
connect(): Promise<void>;
|
||||
/** Disconnect from the feature endpoint */
|
||||
disconnect(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feature info for serialization
|
||||
*/
|
||||
export interface IFeatureInfo {
|
||||
type: TFeatureType;
|
||||
protocol: string;
|
||||
port: number;
|
||||
state: TFeatureState;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Scan Feature
|
||||
// ============================================================================
|
||||
|
||||
export type TScanProtocol = 'escl' | 'sane' | 'wia';
|
||||
export type TScanFormat = 'png' | 'jpeg' | 'pdf' | 'tiff';
|
||||
export type TColorMode = 'color' | 'grayscale' | 'blackwhite';
|
||||
export type TScanSource = 'flatbed' | 'adf' | 'adf-duplex';
|
||||
|
||||
export interface IScanCapabilities {
|
||||
resolutions: number[];
|
||||
formats: TScanFormat[];
|
||||
colorModes: TColorMode[];
|
||||
sources: TScanSource[];
|
||||
maxWidth: number; // mm
|
||||
maxHeight: number; // mm
|
||||
minWidth: number; // mm
|
||||
minHeight: number; // mm
|
||||
}
|
||||
|
||||
export interface IScanArea {
|
||||
x: number; // X offset in mm
|
||||
y: number; // Y offset in mm
|
||||
width: number; // Width in mm
|
||||
height: number; // Height in mm
|
||||
}
|
||||
|
||||
export interface IScanOptions {
|
||||
resolution?: number;
|
||||
format?: TScanFormat;
|
||||
colorMode?: TColorMode;
|
||||
source?: TScanSource;
|
||||
area?: IScanArea;
|
||||
intent?: 'document' | 'photo' | 'preview';
|
||||
quality?: number; // 1-100 for JPEG
|
||||
}
|
||||
|
||||
export interface IScanResult {
|
||||
data: Buffer;
|
||||
format: TScanFormat;
|
||||
width: number;
|
||||
height: number;
|
||||
resolution: number;
|
||||
colorMode: TColorMode;
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
export interface IScanFeatureInfo extends IFeatureInfo {
|
||||
type: 'scan';
|
||||
protocol: TScanProtocol;
|
||||
supportedFormats: TScanFormat[];
|
||||
supportedResolutions: number[];
|
||||
supportedColorModes: TColorMode[];
|
||||
supportedSources: TScanSource[];
|
||||
hasAdf: boolean;
|
||||
hasDuplex: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Print Feature
|
||||
// ============================================================================
|
||||
|
||||
export type TPrintProtocol = 'ipp' | 'jetdirect' | 'lpd';
|
||||
export type TPrintSides = 'one-sided' | 'two-sided-long-edge' | 'two-sided-short-edge';
|
||||
export type TPrintQuality = 'draft' | 'normal' | 'high';
|
||||
export type TPrintColorMode = 'color' | 'monochrome';
|
||||
|
||||
export interface IPrintCapabilities {
|
||||
colorSupported: boolean;
|
||||
duplexSupported: boolean;
|
||||
mediaSizes: string[];
|
||||
mediaTypes: string[];
|
||||
resolutions: number[];
|
||||
maxCopies: number;
|
||||
sidesSupported: TPrintSides[];
|
||||
qualitySupported: TPrintQuality[];
|
||||
}
|
||||
|
||||
export interface IPrintOptions {
|
||||
copies?: number;
|
||||
mediaSize?: string;
|
||||
mediaType?: string;
|
||||
sides?: TPrintSides;
|
||||
quality?: TPrintQuality;
|
||||
colorMode?: TPrintColorMode;
|
||||
jobName?: string;
|
||||
}
|
||||
|
||||
export interface IPrintJob {
|
||||
id: number;
|
||||
name: string;
|
||||
state: 'pending' | 'processing' | 'completed' | 'canceled' | 'aborted';
|
||||
stateReason?: string;
|
||||
createdAt: Date;
|
||||
completedAt?: Date;
|
||||
pagesPrinted?: number;
|
||||
pagesTotal?: number;
|
||||
}
|
||||
|
||||
export interface IPrintFeatureInfo extends IFeatureInfo {
|
||||
type: 'print';
|
||||
protocol: TPrintProtocol;
|
||||
supportsColor: boolean;
|
||||
supportsDuplex: boolean;
|
||||
supportedMediaSizes: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Playback Feature
|
||||
// ============================================================================
|
||||
|
||||
export type TPlaybackProtocol = 'sonos' | 'airplay' | 'chromecast' | 'dlna';
|
||||
export type TPlaybackState = 'playing' | 'paused' | 'stopped' | 'buffering' | 'unknown';
|
||||
|
||||
export interface ITrackInfo {
|
||||
title?: string;
|
||||
artist?: string;
|
||||
album?: string;
|
||||
albumArtUri?: string;
|
||||
duration?: number; // seconds
|
||||
uri?: string;
|
||||
}
|
||||
|
||||
export interface IPlaybackStatus {
|
||||
state: TPlaybackState;
|
||||
position: number; // seconds
|
||||
duration: number; // seconds
|
||||
track?: ITrackInfo;
|
||||
}
|
||||
|
||||
export interface IPlaybackFeatureInfo extends IFeatureInfo {
|
||||
type: 'playback';
|
||||
protocol: TPlaybackProtocol;
|
||||
supportsQueue: boolean;
|
||||
supportsSeek: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Volume Feature
|
||||
// ============================================================================
|
||||
|
||||
export interface IVolumeFeatureInfo extends IFeatureInfo {
|
||||
type: 'volume';
|
||||
minVolume: number;
|
||||
maxVolume: number;
|
||||
volumeStep: number;
|
||||
supportsMute: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Power Feature (UPS, Smart Plugs)
|
||||
// ============================================================================
|
||||
|
||||
export type TPowerProtocol = 'nut' | 'snmp' | 'smart-plug';
|
||||
export type TPowerStatus = 'online' | 'onbattery' | 'lowbattery' | 'charging' | 'discharging' | 'bypass' | 'offline' | 'error' | 'unknown';
|
||||
|
||||
export interface IBatteryInfo {
|
||||
charge: number; // 0-100%
|
||||
runtime: number; // seconds remaining
|
||||
voltage?: number; // volts
|
||||
temperature?: number; // celsius
|
||||
health?: 'good' | 'weak' | 'replace';
|
||||
}
|
||||
|
||||
export interface IPowerInfo {
|
||||
inputVoltage?: number;
|
||||
outputVoltage?: number;
|
||||
inputFrequency?: number;
|
||||
outputFrequency?: number;
|
||||
load?: number; // 0-100%
|
||||
power?: number; // watts
|
||||
}
|
||||
|
||||
export interface IPowerFeatureInfo extends IFeatureInfo {
|
||||
type: 'power';
|
||||
protocol: TPowerProtocol;
|
||||
hasBattery: boolean;
|
||||
supportsShutdown: boolean;
|
||||
supportsTest: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SNMP Feature
|
||||
// ============================================================================
|
||||
|
||||
export type TSnmpVersion = 'v1' | 'v2c' | 'v3';
|
||||
|
||||
export interface ISnmpVarbind {
|
||||
oid: string;
|
||||
type: number;
|
||||
value: unknown;
|
||||
}
|
||||
|
||||
export interface ISnmpFeatureInfo extends IFeatureInfo {
|
||||
type: 'snmp';
|
||||
version: TSnmpVersion;
|
||||
community?: string; // for v1/v2c
|
||||
sysDescr?: string;
|
||||
sysName?: string;
|
||||
sysLocation?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DLNA Render Feature
|
||||
// ============================================================================
|
||||
|
||||
export interface IDlnaTransportInfo {
|
||||
state: 'STOPPED' | 'PLAYING' | 'PAUSED' | 'TRANSITIONING' | 'NO_MEDIA_PRESENT';
|
||||
status: string;
|
||||
speed: string;
|
||||
}
|
||||
|
||||
export interface IDlnaPositionInfo {
|
||||
track: number;
|
||||
trackDuration: string;
|
||||
trackMetaData?: string;
|
||||
trackUri?: string;
|
||||
relTime: string;
|
||||
absTime: string;
|
||||
}
|
||||
|
||||
export interface IDlnaRenderFeatureInfo extends IFeatureInfo {
|
||||
type: 'dlna-render';
|
||||
udn: string;
|
||||
friendlyName: string;
|
||||
supportsVolume: boolean;
|
||||
supportedProtocols: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DLNA Server Feature
|
||||
// ============================================================================
|
||||
|
||||
export interface IDlnaContentItem {
|
||||
id: string;
|
||||
parentId: string;
|
||||
title: string;
|
||||
class: string;
|
||||
restricted: boolean;
|
||||
uri?: string;
|
||||
albumArtUri?: string;
|
||||
duration?: string;
|
||||
size?: number;
|
||||
isContainer: boolean;
|
||||
childCount?: number;
|
||||
}
|
||||
|
||||
export interface IDlnaBrowseResult {
|
||||
items: IDlnaContentItem[];
|
||||
numberReturned: number;
|
||||
totalMatches: number;
|
||||
updateId: number;
|
||||
}
|
||||
|
||||
export interface IDlnaServeFeatureInfo extends IFeatureInfo {
|
||||
type: 'dlna-serve';
|
||||
udn: string;
|
||||
friendlyName: string;
|
||||
contentCount?: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Discovery Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Feature discovered during network scan or mDNS/SSDP
|
||||
*/
|
||||
export interface IDiscoveredFeature {
|
||||
type: TFeatureType;
|
||||
protocol: string;
|
||||
port: number;
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a feature instance
|
||||
*/
|
||||
export interface IFeatureOptions {
|
||||
retryOptions?: IRetryOptions;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export type TConnectionState = 'disconnected' | 'connecting' | 'connected' | 'er
|
||||
// ============================================================================
|
||||
|
||||
export type TScannerProtocol = 'sane' | 'escl';
|
||||
export type TScanFormat = 'png' | 'jpeg' | 'pdf';
|
||||
export type TScanFormat = 'png' | 'jpeg' | 'pdf' | 'tiff';
|
||||
export type TColorMode = 'color' | 'grayscale' | 'blackwhite';
|
||||
export type TScanSource = 'flatbed' | 'adf' | 'adf-duplex';
|
||||
|
||||
@@ -179,7 +179,7 @@ export interface IDiscoveredDevice {
|
||||
id: string;
|
||||
name: string;
|
||||
type: TDeviceType;
|
||||
protocol: TScannerProtocol | 'ipp';
|
||||
protocol: string; // 'escl' | 'sane' | 'ipp' | 'airplay' | 'sonos' | 'chromecast' | etc.
|
||||
address: string;
|
||||
port: number;
|
||||
txtRecords: Record<string, string>;
|
||||
@@ -321,7 +321,7 @@ export interface INetworkScanOptions {
|
||||
concurrency?: number;
|
||||
/** Timeout per probe in milliseconds (default: 2000) */
|
||||
timeout?: number;
|
||||
/** Ports to probe (default: [80, 443, 631, 6566, 9100]) */
|
||||
/** Ports to probe (default: [80, 443, 631, 6566, 9100, 7000, 1400, 8009]) */
|
||||
ports?: number[];
|
||||
/** Check for eSCL scanners (default: true) */
|
||||
probeEscl?: boolean;
|
||||
@@ -329,11 +329,17 @@ export interface INetworkScanOptions {
|
||||
probeIpp?: boolean;
|
||||
/** Check for SANE scanners (default: true) */
|
||||
probeSane?: boolean;
|
||||
/** Check for AirPlay speakers (default: true) */
|
||||
probeAirplay?: boolean;
|
||||
/** Check for Sonos speakers (default: true) */
|
||||
probeSonos?: boolean;
|
||||
/** Check for Chromecast devices (default: true) */
|
||||
probeChromecast?: boolean;
|
||||
}
|
||||
|
||||
export interface INetworkScanDevice {
|
||||
type: 'scanner' | 'printer';
|
||||
protocol: 'escl' | 'sane' | 'ipp' | 'jetdirect';
|
||||
type: 'scanner' | 'printer' | 'speaker';
|
||||
protocol: 'escl' | 'sane' | 'ipp' | 'jetdirect' | 'airplay' | 'sonos' | 'chromecast';
|
||||
port: number;
|
||||
name?: string;
|
||||
model?: string;
|
||||
@@ -364,3 +370,9 @@ export type TNetworkScannerEvents = {
|
||||
'error': (error: Error) => void;
|
||||
'cancelled': () => void;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Feature Types (Universal Device Architecture)
|
||||
// ============================================================================
|
||||
|
||||
export * from './feature.interfaces.js';
|
||||
|
||||
Reference in New Issue
Block a user