/** * 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('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 { // 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 { // Nothing to disconnect } // ============================================================================ // Sensor Reading // ============================================================================ /** * Refresh state from the device */ public async refreshState(): Promise { 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, }, }; } }