/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Logger, MaybePromise } from "@matter/general"; import { Val } from "@matter/protocol"; import { OccupancySensing } from "@matter/types/clusters/occupancy-sensing"; import { OccupancySensingBehavior } from "./OccupancySensingBehavior.js"; const logger = Logger.get("OccupancySensingServer"); /** * This is the default server implementation of {@link OccupancySensingBehavior}. * * The Matter specification requires the OccupancySensing cluster to support features we do not enable by default. You * should use {@link OccupancySensingServer.with} to specialize the class for the features your implementation supports. * When the OccupancyEvent feature is enabled the OccupancyChanged event is emitted automatically on occupancy change. */ export class OccupancySensingServer extends OccupancySensingBehavior { override initialize(): MaybePromise { if (!Object.values(this.features).some(feature => feature)) { logger.error( `OccupancySensingServer: Since revision 5 of the cluster features need to be set based on the detector type. Currently no features are enabled.`, ); } else if ( !Object.values(this.state.occupancySensorTypeBitmap).some(feature => feature) || this.state.occupancySensorType === undefined ) { // Conveniently fill in existing occupancySensorTypeBitmap and occupancySensorType attributes based on the // features let adjusted = false; if (this.features.passiveInfrared) { this.state.occupancySensorTypeBitmap.pir = true; adjusted = true; } if (this.features.ultrasonic) { this.state.occupancySensorTypeBitmap.ultrasonic = true; adjusted = true; } if (this.features.physicalContact) { this.state.occupancySensorTypeBitmap.physicalContact = true; adjusted = true; } if (adjusted) { if (this.state.occupancySensorTypeBitmap.pir) { if (this.state.occupancySensorTypeBitmap.ultrasonic) { this.state.occupancySensorType = OccupancySensing.OccupancySensorType.PirAndUltrasonic; } else { this.state.occupancySensorType = OccupancySensing.OccupancySensorType.Pir; } } else if (this.state.occupancySensorTypeBitmap.physicalContact) { this.state.occupancySensorType = OccupancySensing.OccupancySensorType.PhysicalContact; } logger.debug( "Sync occupancySensorType to", OccupancySensing.OccupancySensorType[this.state.occupancySensorType], "and occupancySensorTypeBitmap to", this.state.occupancySensorTypeBitmap, "from features", this.features, ); } } // Emit the OccupancyChanged event on occupancy change when the feature is enabled if (this.features.occupancyEvent) { this.reactTo(this.events.occupancy$Changed, this.#emitOccupancyChanged); } else { logger.info( 'OccupancySensingServer: enable the OccupancyEvent feature (e.g. OccupancySensingServer.with("", "OccupancyEvent")) to emit the OccupancyChanged event.', ); } // Clear attributes that are dependent on HoldTime if HoldTime is not set. // TODO - if we instead avoid defaults for attributes with field-conditional conformance, remove this if (this.state.holdTime === undefined) { const state = this.state as unknown as Val.Struct; for (const dep of holdTimeDependencies) { if (state[dep] !== undefined) { state[dep] = undefined; } } } } #emitOccupancyChanged(occupancy: OccupancySensing.Occupancy) { this.events.occupancyChanged?.emit({ occupancy }, this.context); } } const holdTimeDependencies = [ "holdTimeLimits", "pirOccupiedToUnoccupiedDelay", "pirUnoccupiedToOccupiedDelay", "pirUnoccupiedToOccupiedThreshold", "ultrasonicOccupiedToUnoccupiedDelay", "ultrasonicUnoccupiedToOccupiedDelay", "ultrasonicUnoccupiedToOccupiedThreshold", "physicalContactOccupiedToUnoccupiedDelay", "physicalContactUnoccupiedToOccupiedDelay", "physicalContactUnoccupiedToOccupiedThreshold", ];