UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

101 lines (83 loc) 4.23 kB
import { MatterFlowError } from '@matter/general'; import { AdministratorCommissioning, GeneralCommissioning } from '@matter/main/clusters'; import type { ServerNode } from '@matter/node'; import { AdministratorCommissioningServer, GeneralCommissioningServer } from '@matter/node/behaviors'; import { DeviceCommissioner, FabricManager, SessionManager, type CommissioningMode, type SecureSession } from '@matter/protocol'; import { ServerNodeFailsafeContext } from './ServerNodeFailsafeContext.js'; const SuccessResponse = { errorCode: GeneralCommissioning.CommissioningError.Ok, debugText: '' }; //TODO: add devices after commissioning export const SuccessResponseWithCommissioningMode = (mode: CommissioningMode) => ({ errorCode: GeneralCommissioning.CommissioningError.Ok, debugText: '', commissioningMode: mode }); export class SlowGeneralCommissioningBehavior extends GeneralCommissioningServer { override initialize() { const bci = this.state.basicCommissioningInfo; if (bci.failSafeExpiryLengthSeconds === undefined) { // One minute bci.failSafeExpiryLengthSeconds = 60; } if (bci.maxCumulativeFailsafeSeconds === undefined) { // 5 minutes, recommended by spec bci.maxCumulativeFailsafeSeconds = 900; } this.state.breadcrumb = 0; const sessionManager = this.env.get(SessionManager); this.reactTo(sessionManager.sessions.added, this.#handleAddedPaseSessions); } /** As required by Commissioning Flows any new PASE session needs to arm the failsafe for 60s. */ async #handleAddedPaseSessions(session: SecureSession) { if ( !session.isPase || // Only PASE sessions session.fabric !== undefined // That does not have an assigned fabric (can never happen in real usecases) ) { return; } await this.#armFailSafe({ breadcrumb: this.state.breadcrumb, expiryLengthSeconds: this.state.basicCommissioningInfo.failSafeExpiryLengthSeconds }, session); } async #armFailSafe({ breadcrumb, expiryLengthSeconds }: GeneralCommissioning.ArmFailSafeRequest, session: SecureSession) { const commissioner = this.env.get(DeviceCommissioner); try { // If the fail-safe timer is not currently armed, the commissioning window is open, and the command was // received over a CASE session, the command SHALL leave the current fail-safe state unchanged and // immediately respond with an ArmFailSafeResponse containing an ErrorCode value of BusyWithOtherAdmin. This // is done to allow commissioners, which use PASE connections, the opportunity to use the failsafe during // the relatively short commissioning window. if (!commissioner.isFailsafeArmed && this.agent.get(AdministratorCommissioningServer).state.windowStatus !== AdministratorCommissioning.CommissioningWindowStatus.WindowNotOpen && !session.isPase) { throw new MatterFlowError('Failed to arm failsafe using CASE while commissioning window is opened.'); } if (commissioner.isFailsafeArmed) { await commissioner.failsafeContext.extend(session.fabric, expiryLengthSeconds); } else { // If ExpiryLengthSeconds is 0 and the fail-safe timer was not armed, then this command invocation SHALL lead // to a success response with no side effect against the fail-safe context. if (expiryLengthSeconds === 0) return SuccessResponse; await commissioner.beginTimed( new ServerNodeFailsafeContext(this.endpoint as ServerNode, { fabrics: this.env.get(FabricManager), sessions: this.env.get(SessionManager), expiryLengthSeconds, maxCumulativeFailsafeSeconds: this.state.basicCommissioningInfo.maxCumulativeFailsafeSeconds, associatedFabric: session.fabric }) ); } if (commissioner.isFailsafeArmed) { // If failsafe is armed after the command, set breadcrumb (not when expired) this.state.breadcrumb = breadcrumb; } } catch (error) { MatterFlowError.accept(error); //logger.debug(`Error while arming failSafe timer`, error); return { errorCode: GeneralCommissioning.CommissioningError.BusyWithOtherAdmin, debugText: error.message }; } return SuccessResponse; } override armFailSafe(request: GeneralCommissioning.ArmFailSafeRequest) { return this.#armFailSafe(request, this.session); } }