UNPKG

homebridge

Version:
137 lines 7.33 kB
/** * Thermostat Cluster Behavior * * Handles thermostat commands for heating and cooling systems */ import { ThermostatServer } from '@matter/main/behaviors/thermostat'; import { Status, StatusResponseError } from '@matter/main/types'; import { MatterStatus } from '../errors.js'; import { getRegistryManager } from './EndpointContext.js'; /** * Custom Thermostat Server that calls plugin handlers */ export class HomebridgeThermostatServer extends ThermostatServer { /** * Get the registry for this behavior's endpoint */ getRegistry() { return getRegistryManager(this.endpoint).getRegistry(this.endpoint.id); } initialize() { super.initialize(); // React to systemMode attribute changes (off, heat, cool, auto, etc.) this.reactTo(this.events.systemMode$Changed, this.#handleSystemModeChange, { offline: true }); // React to occupiedHeatingSetpoint attribute changes (target heating temperature) // Using 'as any' because these events are feature-dependent (Heating/Cooling features) // and may not be present in the base events type at compile time const events = this.events; if (events.occupiedHeatingSetpoint$Changing) { this.reactTo(events.occupiedHeatingSetpoint$Changing, this.#handleOccupiedHeatingSetpointChanging, { offline: true }); } // React to occupiedCoolingSetpoint attribute changes (target cooling temperature) if (events.occupiedCoolingSetpoint$Changing) { this.reactTo(events.occupiedCoolingSetpoint$Changing, this.#handleOccupiedCoolingSetpointChanging, { offline: true }); } } async #handleSystemModeChange(value, oldValue) { const endpointId = this.endpoint.id; const registry = this.getRegistry(); try { // Execute user handler await registry.executeHandler(endpointId, 'thermostat', 'systemModeChange', { systemMode: value, oldSystemMode: oldValue }); // Sync state to cache registry.syncStateToCache(endpointId, 'thermostat', { systemMode: value }); } 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 change system mode: ${message}`, Status.Failure); } } async #handleOccupiedHeatingSetpointChanging(value) { const endpointId = this.endpoint.id; const registry = this.getRegistry(); // Using 'as any' because occupiedHeatingSetpoint is feature-dependent (Heating feature) const oldValue = this.state.occupiedHeatingSetpoint; try { // Execute user handler await registry.executeHandler(endpointId, 'thermostat', 'occupiedHeatingSetpointChange', { occupiedHeatingSetpoint: value, oldOccupiedHeatingSetpoint: oldValue }); // Sync state to cache registry.syncStateToCache(endpointId, 'thermostat', { occupiedHeatingSetpoint: value }); } 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 change heating setpoint: ${message}`, Status.Failure); } } async #handleOccupiedCoolingSetpointChanging(value) { const endpointId = this.endpoint.id; const registry = this.getRegistry(); // Using 'as any' because occupiedCoolingSetpoint is feature-dependent (Cooling feature) const oldValue = this.state.occupiedCoolingSetpoint; try { // Execute user handler await registry.executeHandler(endpointId, 'thermostat', 'occupiedCoolingSetpointChange', { occupiedCoolingSetpoint: value, oldOccupiedCoolingSetpoint: oldValue }); // Sync state to cache registry.syncStateToCache(endpointId, 'thermostat', { occupiedCoolingSetpoint: value }); } 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 change cooling setpoint: ${message}`, Status.Failure); } } async setpointRaiseLower(request) { const endpointId = this.endpoint.id; const registry = this.getRegistry(); try { // Execute user handler await registry.executeHandler(endpointId, 'thermostat', 'setpointRaiseLower', request); // Only reached if handler succeeded - update Matter state await super.setpointRaiseLower(request); // Sync thermostat setpoints to cache // Using 'as any' because these properties are feature-dependent (Heating/Cooling features) // and may not be present in the base state type at compile time const currentState = this.state; const stateUpdate = {}; if (currentState.occupiedCoolingSetpoint !== undefined) { stateUpdate.occupiedCoolingSetpoint = currentState.occupiedCoolingSetpoint; } if (currentState.occupiedHeatingSetpoint !== undefined) { stateUpdate.occupiedHeatingSetpoint = currentState.occupiedHeatingSetpoint; } registry.syncStateToCache(endpointId, 'thermostat', stateUpdate); } 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 adjust setpoint: ${message}`, Status.Failure); } } } //# sourceMappingURL=ThermostatBehavior.js.map