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