@pmouli/isy-matter-server
Version:
Service to expose an ISY device as a Matter Border router
101 lines (83 loc) • 4.23 kB
text/typescript
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);
}
}