feat(smarthome): add smart home features and Home Assistant integration (WebSocket protocol, discovery, factories, interfaces)

This commit is contained in:
2026-01-09 16:20:54 +00:00
parent 7bcec69658
commit 38a6e5c250
23 changed files with 4786 additions and 5 deletions

View File

@@ -0,0 +1,202 @@
/**
* Sensor Feature
* Provides read-only state for sensors (temperature, humidity, power, etc.)
*/
import { Feature, type TDeviceReference } from './feature.abstract.js';
import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
import type {
TSensorProtocol,
TSensorDeviceClass,
TSensorStateClass,
ISensorCapabilities,
ISensorState,
ISensorFeatureInfo,
ISensorProtocolClient,
} from '../interfaces/smarthome.interfaces.js';
/**
* Options for creating a SensorFeature
*/
export interface ISensorFeatureOptions extends IFeatureOptions {
/** Protocol type */
protocol: TSensorProtocol;
/** Entity ID (for Home Assistant) */
entityId: string;
/** Protocol client for reading sensor state */
protocolClient: ISensorProtocolClient;
/** Device class (temperature, humidity, etc.) */
deviceClass?: TSensorDeviceClass;
/** State class (measurement, total, etc.) */
stateClass?: TSensorStateClass;
/** Unit of measurement */
unit?: string;
/** Precision (decimal places) */
precision?: number;
}
/**
* Sensor Feature - read-only state values
*
* Protocol-agnostic: works with Home Assistant, MQTT, SNMP, etc.
*
* @example
* ```typescript
* const sensor = device.getFeature<SensorFeature>('sensor');
* if (sensor) {
* await sensor.refreshState();
* console.log(`Temperature: ${sensor.value} ${sensor.unit}`);
* }
* ```
*/
export class SensorFeature extends Feature {
public readonly type = 'sensor' as const;
public readonly protocol: TSensorProtocol;
/** Entity ID (e.g., "sensor.living_room_temperature") */
public readonly entityId: string;
/** Capabilities */
public readonly capabilities: ISensorCapabilities;
/** Current state */
protected _value: string | number | boolean = '';
protected _numericValue?: number;
protected _unit?: string;
protected _lastUpdated: Date = new Date();
/** Protocol client for reading sensor state */
private protocolClient: ISensorProtocolClient;
constructor(
device: TDeviceReference,
port: number,
options: ISensorFeatureOptions
) {
super(device, port, options);
this.protocol = options.protocol;
this.entityId = options.entityId;
this.protocolClient = options.protocolClient;
this.capabilities = {
deviceClass: options.deviceClass,
stateClass: options.stateClass,
unit: options.unit,
precision: options.precision,
};
this._unit = options.unit;
}
// ============================================================================
// Properties
// ============================================================================
/**
* Get current value (cached)
*/
public get value(): string | number | boolean {
return this._value;
}
/**
* Get numeric value if available (cached)
*/
public get numericValue(): number | undefined {
return this._numericValue;
}
/**
* Get unit of measurement
*/
public get unit(): string | undefined {
return this._unit;
}
/**
* Get device class
*/
public get deviceClass(): TSensorDeviceClass | undefined {
return this.capabilities.deviceClass;
}
/**
* Get state class
*/
public get stateClass(): TSensorStateClass | undefined {
return this.capabilities.stateClass;
}
/**
* Get last updated timestamp
*/
public get lastUpdated(): Date {
return this._lastUpdated;
}
// ============================================================================
// Connection
// ============================================================================
protected async doConnect(): Promise<void> {
// Fetch initial state
try {
const state = await this.protocolClient.getState(this.entityId);
this.updateStateInternal(state);
} catch {
// Ignore errors fetching initial state
}
}
protected async doDisconnect(): Promise<void> {
// Nothing to disconnect
}
// ============================================================================
// Sensor Reading
// ============================================================================
/**
* Refresh state from the device
*/
public async refreshState(): Promise<ISensorState> {
const state = await this.protocolClient.getState(this.entityId);
this.updateStateInternal(state);
return state;
}
/**
* Update state from external source (e.g., state change event)
*/
public updateState(state: ISensorState): void {
this.updateStateInternal(state);
this.emit('state:changed', state);
}
/**
* Internal state update
*/
private updateStateInternal(state: ISensorState): void {
this._value = state.value;
this._numericValue = state.numericValue;
this._unit = state.unit || this._unit;
this._lastUpdated = state.lastUpdated;
}
// ============================================================================
// Feature Info
// ============================================================================
public getFeatureInfo(): ISensorFeatureInfo {
return {
...this.getBaseFeatureInfo(),
type: 'sensor',
protocol: this.protocol,
capabilities: this.capabilities,
currentState: {
value: this._value,
numericValue: this._numericValue,
unit: this._unit,
lastUpdated: this._lastUpdated,
},
};
}
}