feat(smarthome): add smart home features and Home Assistant integration (WebSocket protocol, discovery, factories, interfaces)
This commit is contained in:
170
ts/features/feature.switch.ts
Normal file
170
ts/features/feature.switch.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user