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,170 @@
/**
* Switch Feature
* Provides binary on/off control for smart switches, outlets, etc.
*/
import { Feature, type TDeviceReference } from './feature.abstract.js';
import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
import type {
TSwitchProtocol,
ISwitchCapabilities,
ISwitchState,
ISwitchFeatureInfo,
ISwitchProtocolClient,
} from '../interfaces/smarthome.interfaces.js';
/**
* Options for creating a SwitchFeature
*/
export interface ISwitchFeatureOptions extends IFeatureOptions {
/** Protocol type */
protocol: TSwitchProtocol;
/** Entity ID (for Home Assistant) */
entityId: string;
/** Protocol client for controlling the switch */
protocolClient: ISwitchProtocolClient;
/** Device class */
deviceClass?: 'outlet' | 'switch';
}
/**
* Switch Feature - binary on/off control
*
* Protocol-agnostic: works with Home Assistant, MQTT, Tasmota, Tuya, etc.
*
* @example
* ```typescript
* const sw = device.getFeature<SwitchFeature>('switch');
* if (sw) {
* await sw.turnOn();
* await sw.toggle();
* console.log(`Switch is ${sw.isOn ? 'on' : 'off'}`);
* }
* ```
*/
export class SwitchFeature extends Feature {
public readonly type = 'switch' as const;
public readonly protocol: TSwitchProtocol;
/** Entity ID (e.g., "switch.living_room") */
public readonly entityId: string;
/** Capabilities */
public readonly capabilities: ISwitchCapabilities;
/** Current state */
protected _isOn: boolean = false;
/** Protocol client for controlling the switch */
private protocolClient: ISwitchProtocolClient;
constructor(
device: TDeviceReference,
port: number,
options: ISwitchFeatureOptions
) {
super(device, port, options);
this.protocol = options.protocol;
this.entityId = options.entityId;
this.protocolClient = options.protocolClient;
this.capabilities = {
deviceClass: options.deviceClass,
};
}
// ============================================================================
// Properties
// ============================================================================
/**
* Get current on/off state (cached)
*/
public get isOn(): boolean {
return this._isOn;
}
// ============================================================================
// Connection
// ============================================================================
protected async doConnect(): Promise<void> {
// Fetch initial state
try {
const state = await this.protocolClient.getState(this.entityId);
this._isOn = state.isOn;
} catch {
// Ignore errors fetching initial state
}
}
protected async doDisconnect(): Promise<void> {
// Nothing to disconnect
}
// ============================================================================
// Switch Control
// ============================================================================
/**
* Turn on the switch
*/
public async turnOn(): Promise<void> {
await this.protocolClient.turnOn(this.entityId);
this._isOn = true;
this.emit('state:changed', { isOn: true });
}
/**
* Turn off the switch
*/
public async turnOff(): Promise<void> {
await this.protocolClient.turnOff(this.entityId);
this._isOn = false;
this.emit('state:changed', { isOn: false });
}
/**
* Toggle the switch
*/
public async toggle(): Promise<void> {
await this.protocolClient.toggle(this.entityId);
this._isOn = !this._isOn;
this.emit('state:changed', { isOn: this._isOn });
}
/**
* Refresh state from the device
*/
public async refreshState(): Promise<ISwitchState> {
const state = await this.protocolClient.getState(this.entityId);
this._isOn = state.isOn;
return state;
}
/**
* Update state from external source (e.g., state change event)
*/
public updateState(state: ISwitchState): void {
const changed = this._isOn !== state.isOn;
this._isOn = state.isOn;
if (changed) {
this.emit('state:changed', state);
}
}
// ============================================================================
// Feature Info
// ============================================================================
public getFeatureInfo(): ISwitchFeatureInfo {
return {
...this.getBaseFeatureInfo(),
type: 'switch',
protocol: this.protocol,
capabilities: this.capabilities,
currentState: {
isOn: this._isOn,
},
};
}
}