UNPKG

homebridge

Version:
117 lines 5.21 kB
/** * ClosureControl Cluster Behavior * * Handles move/stop commands for closures - garage doors, gates and similar. */ import { ClosureControlServer } from '@matter/main/behaviors/closure-control'; import { ClosureControl } from '@matter/main/clusters/closure-control'; import { Status, StatusResponseError } from '@matter/main/types'; import { MatterStatus } from '../errors.js'; import { getRegistryManager } from './EndpointContext.js'; /** * Default ClosureControl Server. * * matter.js's base ClosureControlServer implements none of the commands - * invoking one throws NotImplementedError - so a closure without plugin * handlers would advertise moveTo/stop and then fail every one. This default * reflects the command in the cluster state instead, which is enough for the * endpoint to behave sanely on its own. * * The move is reported as complete immediately. A plugin that knows better * (a garage door takes ~15 seconds) should update `overallCurrentState` from * its own device reports; the point here is only that the target is recorded * and the closure never looks stuck. */ export class DefaultClosureControlServer extends ClosureControlServer { async moveTo(request) { const target = { ...(this.state.overallTargetState ?? {}), ...(request.position !== undefined ? { position: request.position } : {}), ...(request.latch !== undefined ? { latch: request.latch } : {}), ...(request.speed !== undefined ? { speed: request.speed } : {}), }; this.state.overallTargetState = target; this.state.overallCurrentState = target; this.state.mainState = ClosureControl.MainState.Stopped; } async stop() { // Stopping leaves the closure wherever it is, so the target becomes the // current position rather than the other way round. this.state.overallTargetState = this.state.overallCurrentState; this.state.mainState = ClosureControl.MainState.Stopped; } } /** * Custom ClosureControl Server that calls plugin handlers. * * Extends the default server so `super.*()` records the state (the matter.js * base would throw NotImplementedError). */ export class HomebridgeClosureControlServer extends DefaultClosureControlServer { /** * Get the registry for this behavior's endpoint */ getRegistry() { return getRegistryManager(this.endpoint).getRegistry(this.endpoint.id); } /** * Handle 'moveTo' command */ async moveTo(request) { const endpointId = this.endpoint.id; const registry = this.getRegistry(); try { // Execute user handler await registry.executeHandler(endpointId, 'closureControl', 'moveTo', request); // Only reached if handler succeeded - update Matter state via super await super.moveTo(request); // Sync state to cache registry.syncStateToCache(endpointId, 'closureControl', { overallTargetState: this.state.overallTargetState, overallCurrentState: this.state.overallCurrentState, mainState: this.state.mainState, }); } catch (error) { // If user handler already threw a StatusResponseError, propagate it as-is // This sends a proper Matter protocol error response to the controller if (MatterStatus.isMatterProtocolError(error)) { throw error; } // For other errors, wrap in appropriate StatusResponseError // This prevents the endpoint from crashing and keeps the device online const message = error instanceof Error ? error.message : String(error); throw new StatusResponseError(`Failed to move closure: ${message}`, Status.Failure); } } /** * Handle 'stop' command */ async stop() { const endpointId = this.endpoint.id; const registry = this.getRegistry(); try { // Execute user handler await registry.executeHandler(endpointId, 'closureControl', 'stop'); // Only reached if handler succeeded - update Matter state via super await super.stop(); // Sync state to cache registry.syncStateToCache(endpointId, 'closureControl', { overallTargetState: this.state.overallTargetState, mainState: this.state.mainState, }); } catch (error) { // If user handler already threw a StatusResponseError, propagate it as-is // This sends a proper Matter protocol error response to the controller if (MatterStatus.isMatterProtocolError(error)) { throw error; } // For other errors, wrap in appropriate StatusResponseError // This prevents the endpoint from crashing and keeps the device online const message = error instanceof Error ? error.message : String(error); throw new StatusResponseError(`Failed to stop closure: ${message}`, Status.Failure); } } } //# sourceMappingURL=ClosureControlBehavior.js.map