homebridge
Version:
HomeKit support for the impatient
158 lines • 7.2 kB
JavaScript
/**
* LevelControl Cluster Behavior
*
* Handles brightness/level control for dimmable lights
*/
import { LevelControlServer } from '@matter/main/behaviors/level-control';
import { Status, StatusResponseError } from '@matter/main/types';
import { MatterStatus } from '../errors.js';
import { getRegistryManager } from './EndpointContext.js';
export class HomebridgeLevelControlServer extends LevelControlServer {
/**
* Get the registry for this behavior's endpoint
*/
getRegistry() {
return getRegistryManager(this.endpoint).getRegistry(this.endpoint.id);
}
/**
* Handle moveToLevel command
*/
async moveToLevel(request) {
const endpointId = this.endpoint.id;
const registry = this.getRegistry();
try {
// Execute user handler
await registry.executeHandler(endpointId, 'levelControl', 'moveToLevel', request);
// Only reached if handler succeeded - update Matter state
await super.moveToLevel(request);
// Sync state to cache
registry.syncStateToCache(endpointId, 'levelControl', {
currentLevel: request.level,
});
}
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 set level: ${message}`, Status.Failure);
}
}
/**
* Handle move command
*/
async move(request) {
const endpointId = this.endpoint.id;
const registry = this.getRegistry();
try {
// Execute user handler
await registry.executeHandler(endpointId, 'levelControl', 'move', request);
// Only reached if handler succeeded
await super.move(request);
}
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 level: ${message}`, Status.Failure);
}
}
/**
* Handle step command
*/
async step(request) {
const endpointId = this.endpoint.id;
const registry = this.getRegistry();
try {
// Execute user handler
await registry.executeHandler(endpointId, 'levelControl', 'step', request);
// Only reached if handler succeeded
await super.step(request);
}
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 step level: ${message}`, Status.Failure);
}
}
/**
* Handle stop command
*/
async stop(request) {
const endpointId = this.endpoint.id;
const registry = this.getRegistry();
try {
// Execute user handler
await registry.executeHandler(endpointId, 'levelControl', 'stop', request);
// Only reached if handler succeeded
await super.stop(request);
}
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 level change: ${message}`, Status.Failure);
}
}
/**
* Handle moveToLevelWithOnOff command
*/
async moveToLevelWithOnOff(request) {
const endpointId = this.endpoint.id;
const registry = this.getRegistry();
try {
// Execute user handler
await registry.executeHandler(endpointId, 'levelControl', 'moveToLevelWithOnOff', request);
// Only reached if handler succeeded - update Matter state.
//
// This also turns the OnOff cluster on or off where the device type
// couples them: matter.js does that itself, from inside this command's
// transaction, and its rule is `off` only when the target is minLevel
// (deferred to commit) rather than anything at or below zero.
//
// ⚠️ Do NOT write the OnOff cluster here as well. `endpoint.set()` opens
// a SECOND transaction, and the one running this command already holds
// the lock on `onOff.state` - matter.js then throws
// "Cannot lock <endpoint>.onOff.state synchronously" and the whole
// command fails, after the plugin's handler has already run (#3993).
// HomebridgeOnOffServer reports the coupled change to the cache instead.
await super.moveToLevelWithOnOff(request);
// Sync level state to cache
registry.syncStateToCache(endpointId, 'levelControl', {
currentLevel: request.level,
});
}
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 set level with on/off: ${message}`, Status.Failure);
}
}
}
//# sourceMappingURL=LevelControlBehavior.js.map