Files
devicemanager/ts/features/feature.lock.ts

224 lines
5.4 KiB
TypeScript
Raw Normal View History

/**
* Lock Feature
* Provides control for smart locks
*/
import { Feature, type TDeviceReference } from './feature.abstract.js';
import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
import type {
TLockProtocol,
TLockState,
ILockCapabilities,
ILockStateInfo,
ILockFeatureInfo,
ILockProtocolClient,
} from '../interfaces/smarthome.interfaces.js';
/**
* Options for creating a LockFeature
*/
export interface ILockFeatureOptions extends IFeatureOptions {
/** Protocol type */
protocol: TLockProtocol;
/** Entity ID (for Home Assistant) */
entityId: string;
/** Protocol client for controlling the lock */
protocolClient: ILockProtocolClient;
/** Whether the lock supports physical open */
supportsOpen?: boolean;
}
/**
* Lock Feature - control for smart locks
*
* Protocol-agnostic: works with Home Assistant, MQTT, August, Yale, etc.
*
* @example
* ```typescript
* const lock = device.getFeature<LockFeature>('lock');
* if (lock) {
* await lock.lock();
* console.log(`Lock is ${lock.isLocked ? 'locked' : 'unlocked'}`);
* }
* ```
*/
export class LockFeature extends Feature {
public readonly type = 'lock' as const;
public readonly protocol: TLockProtocol;
/** Entity ID (e.g., "lock.front_door") */
public readonly entityId: string;
/** Capabilities */
public readonly capabilities: ILockCapabilities;
/** Current state */
protected _lockState: TLockState = 'unknown';
protected _isLocked: boolean = false;
/** Protocol client for controlling the lock */
private protocolClient: ILockProtocolClient;
constructor(
device: TDeviceReference,
port: number,
options: ILockFeatureOptions
) {
super(device, port, options);
this.protocol = options.protocol;
this.entityId = options.entityId;
this.protocolClient = options.protocolClient;
this.capabilities = {
supportsOpen: options.supportsOpen ?? false,
};
}
// ============================================================================
// Properties
// ============================================================================
/**
* Get current lock state (cached)
*/
public get lockState(): TLockState {
return this._lockState;
}
/**
* Check if locked (cached)
*/
public get isLocked(): boolean {
return this._isLocked;
}
/**
* Check if unlocked
*/
public get isUnlocked(): boolean {
return this._lockState === 'unlocked';
}
/**
* Check if currently locking
*/
public get isLocking(): boolean {
return this._lockState === 'locking';
}
/**
* Check if currently unlocking
*/
public get isUnlocking(): boolean {
return this._lockState === 'unlocking';
}
/**
* Check if jammed
*/
public get isJammed(): boolean {
return this._lockState === 'jammed';
}
// ============================================================================
// Connection
// ============================================================================
protected async doConnect(): Promise<void> {
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
}
// ============================================================================
// Lock Control
// ============================================================================
/**
* Lock the lock
*/
public async lock(): Promise<void> {
await this.protocolClient.lock(this.entityId);
this._lockState = 'locking';
this.emit('state:changed', this.getState());
}
/**
* Unlock the lock
*/
public async unlock(): Promise<void> {
await this.protocolClient.unlock(this.entityId);
this._lockState = 'unlocking';
this.emit('state:changed', this.getState());
}
/**
* Open the lock (physically open the door if supported)
*/
public async open(): Promise<void> {
if (!this.capabilities.supportsOpen) {
throw new Error('Lock does not support physical open');
}
await this.protocolClient.open(this.entityId);
this._lockState = 'unlocked';
this._isLocked = false;
this.emit('state:changed', this.getState());
}
/**
* Get current state as object
*/
public getState(): ILockStateInfo {
return {
state: this._lockState,
isLocked: this._isLocked,
};
}
/**
* Refresh state from the device
*/
public async refreshState(): Promise<ILockStateInfo> {
const state = await this.protocolClient.getState(this.entityId);
this.updateStateInternal(state);
return state;
}
/**
* Update state from external source
*/
public updateState(state: ILockStateInfo): void {
this.updateStateInternal(state);
this.emit('state:changed', state);
}
/**
* Internal state update
*/
private updateStateInternal(state: ILockStateInfo): void {
this._lockState = state.state;
this._isLocked = state.isLocked;
}
// ============================================================================
// Feature Info
// ============================================================================
public getFeatureInfo(): ILockFeatureInfo {
return {
...this.getBaseFeatureInfo(),
type: 'lock',
protocol: this.protocol,
capabilities: this.capabilities,
currentState: this.getState(),
};
}
}