/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { MaybePromise, Observable, Seconds, Time, Timer } from "@matter/general"; import { hasRemoteActor } from "@matter/protocol"; import { Identify } from "@matter/types/clusters/identify"; import { IdentifyBehavior } from "./IdentifyBehavior.js"; /** * This is the default server implementation of {@link IdentifyBehavior}. * * This implementation includes all features of {@link Identify.Cluster} and implements all mandatory commands. You should use * {@link IdentifyServer.with} to specialize the class for the features your implementation supports. Alternatively * you can extend this class and override the methods you need to change or add mandatory commands. * * Beside the standard state values the following additional state values are available: * * `isIdentifying` - Indicates if the device is currently identifying. * * Beside the standard events the following additional events are available: * * `startIdentifying` - Emitted when the device starts identifying. Use it to start your own identifying logic. This is mandatory. * * `stopIdentifying` - Emitted when the device stops identifying. This is mandatory. * * `effectTriggered` - Emitted when an effect should be triggered. Use it to trigger the effect. Depending on the device type this is mandatory! */ export class IdentifyServer extends IdentifyBehavior { declare protected internal: IdentifyServer.Internal; declare readonly state: IdentifyServer.State; declare readonly events: IdentifyServer.Events; override initialize(): MaybePromise { if (this.state.identifyType === undefined) { this.state.identifyType = Identify.IdentifyType.None; } this.events.identifyTime$Changed.quiet.config = { shouldEmit: (newValue, oldValue, context) => hasRemoteActor(context) || ((oldValue === 0 || newValue === 0) && newValue !== oldValue) ? "now" : false, suppressionEnabled: false, }; // TODO - identifyTime should become virtual attribute with timer to update isIdentifying // Enable I/2.4 once this is done this.internal.identifyTimer = Time.getPeriodicTimer( "Identify time update", Seconds.one, this.callback(this.#identifyTick, { lock: true }), ); // So whenever the attribute OR the identify command was invoked we react to it. this.reactTo(this.events.identifyTime$Changed, this.#identifyTimeChangedHandler); } #startIdentifying() { if (!this.internal.identifyTimer?.isRunning) { this.internal.identifyTimer?.start(); this.state.isIdentifying = true; this.events.startIdentifying.emit(); } } #stopIdentifying() { if (this.internal.identifyTimer?.isRunning) { this.internal.identifyTimer?.stop(); this.state.isIdentifying = false; this.events.stopIdentifying.emit(); } } #identifyTimeChangedHandler() { if (this.state.identifyTime === 0) { this.#stopIdentifying(); } else { this.#startIdentifying(); } } override async [Symbol.asyncDispose]() { this.#stopIdentifying(); await super[Symbol.asyncDispose]?.(); } #identifyTick() { let time = (this.state.identifyTime ?? 0) - 1; if (time <= 0) { time = 0; } this.state.identifyTime = time; } override identify({ identifyTime }: Identify.IdentifyRequest): MaybePromise { this.state.identifyTime = identifyTime; } override triggerEffect(effect: Identify.TriggerEffectRequest): MaybePromise { this.events.effectTriggered.emit(effect); } } export namespace IdentifyServer { export class Internal { identifyTimer?: Timer; } export class State extends IdentifyBehavior.State { isIdentifying: boolean = false; } export class Events extends IdentifyBehavior.Events { startIdentifying = Observable(); stopIdentifying = Observable(); effectTriggered = Observable<[effect: Identify.TriggerEffectRequest]>(); } }