@matterbridge/core 3.7.1-dev-20260326-7d15a50 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/behaviors/export.d.ts +1 -0
- package/dist/behaviors/export.js +1 -0
- package/dist/helpers.js +10 -5
- package/dist/matterbridgeBehaviorsServer.d.ts +93 -1
- package/dist/matterbridgeBehaviorsServer.js +452 -1
- package/dist/matterbridgeEndpoint.d.ts +1 -0
- package/dist/matterbridgeEndpoint.js +31 -4
- package/dist/matterbridgeEndpointCommandHandler.d.ts +35 -20
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/helpers.js
CHANGED
|
@@ -74,11 +74,16 @@ export async function addVirtualDevice(aggregatorEndpoint, name, type, callback)
|
|
|
74
74
|
}
|
|
75
75
|
export async function addVirtualDevices(matterbridge, aggregatorEndpoint) {
|
|
76
76
|
if (hasParameter('experimental') && matterbridge.bridgeMode === 'bridge' && aggregatorEndpoint) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
await aggregatorEndpoint.add(
|
|
77
|
+
const lockPin = new MatterbridgeEndpoint(doorLockDevice, { id: 'door_lock_pin' });
|
|
78
|
+
lockPin.createDefaultBridgedDeviceBasicInformationClusterServer('Matterbridge Pin Lock', 'sn_system_lock', 0xfff1, 'Matterbridge', 'Matterbridge Virtual Device', 20000, '2.0.0');
|
|
79
|
+
lockPin.createPinDoorLockClusterServer();
|
|
80
|
+
lockPin.addRequiredClusterServers();
|
|
81
|
+
await aggregatorEndpoint.add(lockPin);
|
|
82
|
+
const lockUserPin = new MatterbridgeEndpoint(doorLockDevice, { id: 'door_lock_user_pin' });
|
|
83
|
+
lockUserPin.createDefaultBridgedDeviceBasicInformationClusterServer('Matterbridge User Pin Lock', 'sn_system_lock', 0xfff1, 'Matterbridge', 'Matterbridge Virtual Device', 20000, '2.0.0');
|
|
84
|
+
lockUserPin.createUserPinDoorLockClusterServer();
|
|
85
|
+
lockUserPin.addRequiredClusterServers();
|
|
86
|
+
await aggregatorEndpoint.add(lockUserPin);
|
|
82
87
|
}
|
|
83
88
|
if (matterbridge.virtualMode !== 'disabled' && matterbridge.bridgeMode === 'bridge' && aggregatorEndpoint) {
|
|
84
89
|
matterbridge.log.notice(`Creating virtual devices for Matterbridge server node...`);
|
|
@@ -38,6 +38,8 @@ import { SmokeCoAlarm } from '@matter/types/clusters/smoke-co-alarm';
|
|
|
38
38
|
import { Thermostat } from '@matter/types/clusters/thermostat';
|
|
39
39
|
import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
|
|
40
40
|
import { WindowCovering } from '@matter/types/clusters/window-covering';
|
|
41
|
+
import { FabricIndex } from '@matter/types/datatype';
|
|
42
|
+
import { Status } from '@matter/types/globals';
|
|
41
43
|
import { AnsiLogger } from 'node-ansi-logger';
|
|
42
44
|
import { CommandHandler } from './matterbridgeEndpointCommandHandler.js';
|
|
43
45
|
export declare class MatterbridgeServer extends Behavior {
|
|
@@ -1431,14 +1433,104 @@ declare const MatterbridgeLiftTiltWindowCoveringServer_base: import("@matter/nod
|
|
|
1431
1433
|
}>, readonly [WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.Tilt, WindowCovering.Feature.PositionAwareTilt]>, readonly [WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.Tilt, WindowCovering.Feature.PositionAwareTilt]>, typeof MatterbridgeWindowCoveringServer, import("@matter/node/behaviors/window-covering").WindowCoveringInterface>;
|
|
1432
1434
|
export declare class MatterbridgeLiftTiltWindowCoveringServer extends MatterbridgeLiftTiltWindowCoveringServer_base {
|
|
1433
1435
|
}
|
|
1434
|
-
declare const MatterbridgeDoorLockServer_base: import("@matter/node").ClusterBehavior.Type<import("@matter/types").
|
|
1436
|
+
declare const MatterbridgeDoorLockServer_base: import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterTypeModifier.WithAlterations<DoorLock.Cluster, import("@matter/types").ClusterTypeModifier.ElementFlagAlterations<{
|
|
1437
|
+
readonly events: {
|
|
1438
|
+
readonly doorLockAlarm: true;
|
|
1439
|
+
readonly lockOperation: true;
|
|
1440
|
+
readonly lockOperationError: true;
|
|
1441
|
+
};
|
|
1442
|
+
readonly commands: {
|
|
1443
|
+
readonly lockDoor: true;
|
|
1444
|
+
readonly unlockDoor: true;
|
|
1445
|
+
readonly unlockWithTimeout: true;
|
|
1446
|
+
};
|
|
1447
|
+
}>>, typeof DoorLockServer, import("@matter/node/behaviors/door-lock").DoorLockInterface>;
|
|
1435
1448
|
export declare class MatterbridgeDoorLockServer extends MatterbridgeDoorLockServer_base {
|
|
1436
1449
|
lockDoor(request: DoorLock.LockDoorRequest): Promise<void>;
|
|
1437
1450
|
unlockDoor(request: DoorLock.UnlockDoorRequest): Promise<void>;
|
|
1451
|
+
unlockWithTimeout(request: DoorLock.UnlockWithTimeoutRequest): Promise<void>;
|
|
1452
|
+
}
|
|
1453
|
+
declare const MatterbridgePinDoorLockServer_base: import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterTypeModifier.WithAlterations<import("@matter/types").ClusterComposer.WithFeatures<DoorLock.Cluster, readonly [DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess]>, import("@matter/types").ClusterTypeModifier.ElementFlagAlterations<{
|
|
1454
|
+
readonly events: {
|
|
1455
|
+
readonly doorLockAlarm: true;
|
|
1456
|
+
readonly lockOperation: true;
|
|
1457
|
+
readonly lockOperationError: true;
|
|
1458
|
+
};
|
|
1459
|
+
readonly commands: {
|
|
1460
|
+
readonly lockDoor: true;
|
|
1461
|
+
readonly unlockDoor: true;
|
|
1462
|
+
readonly unlockWithTimeout: true;
|
|
1463
|
+
readonly setUserStatus: true;
|
|
1464
|
+
readonly getUserStatus: true;
|
|
1465
|
+
readonly setUserType: true;
|
|
1466
|
+
readonly getUserType: true;
|
|
1467
|
+
};
|
|
1468
|
+
}>>, import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterComposer.WithFeatures<DoorLock.Cluster, readonly [DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess]>, typeof DoorLockServer, import("@matter/node/behaviors/door-lock").DoorLockInterface>, import("@matter/node/behaviors/door-lock").DoorLockInterface>;
|
|
1469
|
+
export declare class MatterbridgePinDoorLockServer extends MatterbridgePinDoorLockServer_base {
|
|
1470
|
+
lockDoor(request: DoorLock.LockDoorRequest): Promise<void>;
|
|
1471
|
+
unlockDoor(request: DoorLock.UnlockDoorRequest): Promise<void>;
|
|
1472
|
+
unlockWithTimeout(request: DoorLock.UnlockWithTimeoutRequest): Promise<void>;
|
|
1438
1473
|
setPinCode(request: DoorLock.SetPinCodeRequest): Promise<void>;
|
|
1439
1474
|
getPinCode(request: DoorLock.GetPinCodeRequest): Promise<DoorLock.GetPinCodeResponse>;
|
|
1440
1475
|
clearPinCode(request: DoorLock.ClearPinCodeRequest): Promise<void>;
|
|
1441
1476
|
clearAllPinCodes(): Promise<void>;
|
|
1477
|
+
setUserStatus(request: DoorLock.SetUserStatusRequest): Promise<void>;
|
|
1478
|
+
getUserStatus(request: DoorLock.GetUserStatusRequest): Promise<DoorLock.GetUserStatusResponse>;
|
|
1479
|
+
setUserType(request: DoorLock.SetUserTypeRequest): Promise<void>;
|
|
1480
|
+
getUserType(request: DoorLock.GetUserTypeRequest): Promise<DoorLock.GetUserTypeResponse>;
|
|
1481
|
+
}
|
|
1482
|
+
declare const MatterbridgeUserPinDoorLockServer_base: import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterTypeModifier.WithAlterations<import("@matter/types").ClusterComposer.WithFeatures<DoorLock.Cluster, readonly [DoorLock.Feature.User, DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess]>, import("@matter/types").ClusterTypeModifier.ElementFlagAlterations<{
|
|
1483
|
+
readonly events: {
|
|
1484
|
+
readonly doorLockAlarm: true;
|
|
1485
|
+
readonly lockOperation: true;
|
|
1486
|
+
readonly lockOperationError: true;
|
|
1487
|
+
};
|
|
1488
|
+
readonly commands: {
|
|
1489
|
+
readonly lockDoor: true;
|
|
1490
|
+
readonly unlockDoor: true;
|
|
1491
|
+
readonly unlockWithTimeout: true;
|
|
1492
|
+
};
|
|
1493
|
+
}>>, import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterComposer.WithFeatures<DoorLock.Cluster, readonly [DoorLock.Feature.User, DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess]>, typeof DoorLockServer, import("@matter/node/behaviors/door-lock").DoorLockInterface>, import("@matter/node/behaviors/door-lock").DoorLockInterface>;
|
|
1494
|
+
export declare class MatterbridgeUserPinDoorLockServer extends MatterbridgeUserPinDoorLockServer_base {
|
|
1495
|
+
protected internal: MatterbridgeUserPinDoorLockServer.Internal;
|
|
1496
|
+
private getAccessingFabricIndex;
|
|
1497
|
+
private findStoredCredential;
|
|
1498
|
+
private getStoredCredentialStateDebug;
|
|
1499
|
+
private logStoredCredentialState;
|
|
1500
|
+
private hasMatchingPinCredential;
|
|
1501
|
+
private validateRemotePinCode;
|
|
1502
|
+
private getNextOccupiedCredentialIndex;
|
|
1503
|
+
private upsertStoredCredential;
|
|
1504
|
+
private clearStoredCredential;
|
|
1505
|
+
lockDoor(request: DoorLock.LockDoorRequest): Promise<void>;
|
|
1506
|
+
unlockDoor(request: DoorLock.UnlockDoorRequest): Promise<void>;
|
|
1507
|
+
unlockWithTimeout(request: DoorLock.UnlockWithTimeoutRequest): Promise<void>;
|
|
1508
|
+
setUser(request: DoorLock.SetUserRequest): Promise<void>;
|
|
1509
|
+
getUser(request: DoorLock.GetUserRequest): Promise<DoorLock.GetUserResponse>;
|
|
1510
|
+
clearUser(request: DoorLock.ClearUserRequest): Promise<void>;
|
|
1511
|
+
setCredential(request: DoorLock.SetCredentialRequest): Promise<DoorLock.SetCredentialResponse>;
|
|
1512
|
+
getCredentialStatus(request: DoorLock.GetCredentialStatusRequest): Promise<DoorLock.GetCredentialStatusResponse>;
|
|
1513
|
+
clearCredential(request: DoorLock.ClearCredentialRequest): Promise<void>;
|
|
1514
|
+
}
|
|
1515
|
+
export declare namespace MatterbridgeUserPinDoorLockServer {
|
|
1516
|
+
type StoredCredential = DoorLock.Credential & {
|
|
1517
|
+
credentialData: Uint8Array;
|
|
1518
|
+
};
|
|
1519
|
+
type StoredUser = {
|
|
1520
|
+
userIndex: number;
|
|
1521
|
+
userName: string | null;
|
|
1522
|
+
userUniqueId: number | null;
|
|
1523
|
+
userStatus: DoorLock.UserStatus | null;
|
|
1524
|
+
userType: DoorLock.UserType | null;
|
|
1525
|
+
credentialRule: DoorLock.CredentialRule | null;
|
|
1526
|
+
credentials: StoredCredential[] | null;
|
|
1527
|
+
creatorFabricIndex: FabricIndex | null;
|
|
1528
|
+
lastModifiedFabricIndex: FabricIndex | null;
|
|
1529
|
+
nextUserIndex: number | null;
|
|
1530
|
+
};
|
|
1531
|
+
class Internal {
|
|
1532
|
+
users: StoredUser[];
|
|
1533
|
+
}
|
|
1442
1534
|
}
|
|
1443
1535
|
declare const MatterbridgeFanControlServer_base: import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterComposer.WithFeatures<FanControl.Cluster, readonly [FanControl.Feature.Auto, FanControl.Feature.Step]>, typeof FanControlServer, import("@matter/node/behaviors/fan-control").FanControlInterface>;
|
|
1444
1536
|
export declare class MatterbridgeFanControlServer extends MatterbridgeFanControlServer_base {
|
|
@@ -32,7 +32,11 @@ import { SmokeCoAlarm } from '@matter/types/clusters/smoke-co-alarm';
|
|
|
32
32
|
import { Thermostat } from '@matter/types/clusters/thermostat';
|
|
33
33
|
import { ValveConfigurationAndControl } from '@matter/types/clusters/valve-configuration-and-control';
|
|
34
34
|
import { WindowCovering } from '@matter/types/clusters/window-covering';
|
|
35
|
+
import { StatusResponse } from '@matter/types/common';
|
|
36
|
+
import { FabricIndex } from '@matter/types/datatype';
|
|
37
|
+
import { Status } from '@matter/types/globals';
|
|
35
38
|
import { getEnumDescription } from '@matterbridge/utils';
|
|
39
|
+
import { debugStringify } from 'node-ansi-logger';
|
|
36
40
|
export class MatterbridgeServer extends Behavior {
|
|
37
41
|
static id = 'matterbridge';
|
|
38
42
|
initialize() {
|
|
@@ -343,7 +347,54 @@ export class MatterbridgeLiftWindowCoveringServer extends MatterbridgeWindowCove
|
|
|
343
347
|
}
|
|
344
348
|
export class MatterbridgeLiftTiltWindowCoveringServer extends MatterbridgeWindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift, WindowCovering.Feature.Tilt, WindowCovering.Feature.PositionAwareTilt) {
|
|
345
349
|
}
|
|
346
|
-
export class MatterbridgeDoorLockServer extends DoorLockServer.
|
|
350
|
+
export class MatterbridgeDoorLockServer extends DoorLockServer.enable({
|
|
351
|
+
events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true },
|
|
352
|
+
commands: { lockDoor: true, unlockDoor: true, unlockWithTimeout: true },
|
|
353
|
+
}) {
|
|
354
|
+
async lockDoor(request) {
|
|
355
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
356
|
+
device.log.info(`Locking door (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
357
|
+
await device.commandHandler.executeHandler('DoorLock.lockDoor', {
|
|
358
|
+
command: 'lockDoor',
|
|
359
|
+
request,
|
|
360
|
+
cluster: DoorLockServer.id,
|
|
361
|
+
attributes: this.state,
|
|
362
|
+
endpoint: this.endpoint,
|
|
363
|
+
});
|
|
364
|
+
device.log.debug(`MatterbridgeDoorLockServer: lockDoor called`);
|
|
365
|
+
await super.lockDoor(request);
|
|
366
|
+
}
|
|
367
|
+
async unlockDoor(request) {
|
|
368
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
369
|
+
device.log.info(`Unlocking door (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
370
|
+
await device.commandHandler.executeHandler('DoorLock.unlockDoor', {
|
|
371
|
+
command: 'unlockDoor',
|
|
372
|
+
request,
|
|
373
|
+
cluster: DoorLockServer.id,
|
|
374
|
+
attributes: this.state,
|
|
375
|
+
endpoint: this.endpoint,
|
|
376
|
+
});
|
|
377
|
+
device.log.debug(`MatterbridgeDoorLockServer: unlockDoor called`);
|
|
378
|
+
await super.unlockDoor(request);
|
|
379
|
+
}
|
|
380
|
+
async unlockWithTimeout(request) {
|
|
381
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
382
|
+
device.log.info(`Unlocking door with timeout ${request.timeout} seconds (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
383
|
+
await device.commandHandler.executeHandler('DoorLock.unlockWithTimeout', {
|
|
384
|
+
command: 'unlockWithTimeout',
|
|
385
|
+
request,
|
|
386
|
+
cluster: DoorLockServer.id,
|
|
387
|
+
attributes: this.state,
|
|
388
|
+
endpoint: this.endpoint,
|
|
389
|
+
});
|
|
390
|
+
device.log.debug(`MatterbridgeDoorLockServer: unlockWithTimeout called`);
|
|
391
|
+
this.state.lockState = DoorLock.LockState.Unlocked;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
export class MatterbridgePinDoorLockServer extends DoorLockServer.with(DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess).enable({
|
|
395
|
+
events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true },
|
|
396
|
+
commands: { lockDoor: true, unlockDoor: true, unlockWithTimeout: true, setUserStatus: true, getUserStatus: true, setUserType: true, getUserType: true },
|
|
397
|
+
}) {
|
|
347
398
|
async lockDoor(request) {
|
|
348
399
|
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
349
400
|
device.log.info(`Locking door with pincode ${request.pinCode ? '0x' + Buffer.from(request.pinCode).toString('hex') : 'N/A'} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
@@ -370,6 +421,19 @@ export class MatterbridgeDoorLockServer extends DoorLockServer.with(DoorLock.Fea
|
|
|
370
421
|
device.log.debug(`MatterbridgeDoorLockServer: unlockDoor called`);
|
|
371
422
|
await super.unlockDoor(request);
|
|
372
423
|
}
|
|
424
|
+
async unlockWithTimeout(request) {
|
|
425
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
426
|
+
device.log.info(`Unlocking door with pincode ${request.pinCode ? '0x' + Buffer.from(request.pinCode).toString('hex') : 'N/A'} timeout ${request.timeout} seconds (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
427
|
+
await device.commandHandler.executeHandler('DoorLock.unlockWithTimeout', {
|
|
428
|
+
command: 'unlockWithTimeout',
|
|
429
|
+
request,
|
|
430
|
+
cluster: DoorLockServer.id,
|
|
431
|
+
attributes: this.state,
|
|
432
|
+
endpoint: this.endpoint,
|
|
433
|
+
});
|
|
434
|
+
device.log.debug(`MatterbridgeDoorLockServer: unlockWithTimeout called`);
|
|
435
|
+
this.state.lockState = DoorLock.LockState.Unlocked;
|
|
436
|
+
}
|
|
373
437
|
async setPinCode(request) {
|
|
374
438
|
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
375
439
|
device.log.info(`Setting pin code ${request.pin ? '0x' + Buffer.from(request.pin).toString('hex') : 'N/A'} for user ${request.userId} type ${getEnumDescription(DoorLock.UserType, request.userType)} status ${getEnumDescription(DoorLock.UserStatus, request.userStatus)} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
@@ -423,7 +487,394 @@ export class MatterbridgeDoorLockServer extends DoorLockServer.with(DoorLock.Fea
|
|
|
423
487
|
});
|
|
424
488
|
device.log.debug('MatterbridgeDoorLockServer: clearAllPinCodes called');
|
|
425
489
|
}
|
|
490
|
+
async setUserStatus(request) {
|
|
491
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
492
|
+
device.log.info(`Setting user status for user ${request.userId} to ${getEnumDescription(DoorLock.UserStatus, request.userStatus)} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
493
|
+
await device.commandHandler.executeHandler('DoorLock.setUserStatus', {
|
|
494
|
+
command: 'setUserStatus',
|
|
495
|
+
request,
|
|
496
|
+
cluster: DoorLockServer.id,
|
|
497
|
+
attributes: this.state,
|
|
498
|
+
endpoint: this.endpoint,
|
|
499
|
+
});
|
|
500
|
+
device.log.debug(`MatterbridgeDoorLockServer: setUserStatus called for user ${request.userId}`);
|
|
501
|
+
}
|
|
502
|
+
async getUserStatus(request) {
|
|
503
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
504
|
+
device.log.info(`Getting user status for user ${request.userId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
505
|
+
await device.commandHandler.executeHandler('DoorLock.getUserStatus', {
|
|
506
|
+
command: 'getUserStatus',
|
|
507
|
+
request,
|
|
508
|
+
cluster: DoorLockServer.id,
|
|
509
|
+
attributes: this.state,
|
|
510
|
+
endpoint: this.endpoint,
|
|
511
|
+
});
|
|
512
|
+
device.log.debug(`MatterbridgeDoorLockServer: getUserStatus called for user ${request.userId}`);
|
|
513
|
+
return {
|
|
514
|
+
userId: request.userId,
|
|
515
|
+
userStatus: DoorLock.UserStatus.Available,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
async setUserType(request) {
|
|
519
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
520
|
+
device.log.info(`Setting user type for user ${request.userId} to ${getEnumDescription(DoorLock.UserType, request.userType)} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
521
|
+
await device.commandHandler.executeHandler('DoorLock.setUserType', {
|
|
522
|
+
command: 'setUserType',
|
|
523
|
+
request,
|
|
524
|
+
cluster: DoorLockServer.id,
|
|
525
|
+
attributes: this.state,
|
|
526
|
+
endpoint: this.endpoint,
|
|
527
|
+
});
|
|
528
|
+
device.log.debug(`MatterbridgeDoorLockServer: setUserType called for user ${request.userId}`);
|
|
529
|
+
}
|
|
530
|
+
async getUserType(request) {
|
|
531
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
532
|
+
device.log.info(`Getting user type for user ${request.userId} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
533
|
+
await device.commandHandler.executeHandler('DoorLock.getUserType', {
|
|
534
|
+
command: 'getUserType',
|
|
535
|
+
request,
|
|
536
|
+
cluster: DoorLockServer.id,
|
|
537
|
+
attributes: this.state,
|
|
538
|
+
endpoint: this.endpoint,
|
|
539
|
+
});
|
|
540
|
+
device.log.debug(`MatterbridgeDoorLockServer: getUserType called for user ${request.userId}`);
|
|
541
|
+
return {
|
|
542
|
+
userId: request.userId,
|
|
543
|
+
userType: DoorLock.UserType.UnrestrictedUser,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
426
546
|
}
|
|
547
|
+
export class MatterbridgeUserPinDoorLockServer extends DoorLockServer.with(DoorLock.Feature.User, DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess).enable({
|
|
548
|
+
events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true },
|
|
549
|
+
commands: { lockDoor: true, unlockDoor: true, unlockWithTimeout: true },
|
|
550
|
+
}) {
|
|
551
|
+
getAccessingFabricIndex() {
|
|
552
|
+
let fabricIndex;
|
|
553
|
+
try {
|
|
554
|
+
fabricIndex = this.context.fabric;
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
if (fabricIndex === undefined || fabricIndex === FabricIndex.NO_FABRIC) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
return fabricIndex;
|
|
563
|
+
}
|
|
564
|
+
findStoredCredential(credential) {
|
|
565
|
+
for (const user of this.internal.users) {
|
|
566
|
+
for (const storedCredential of user.credentials ?? []) {
|
|
567
|
+
if (storedCredential.credentialType === credential.credentialType && storedCredential.credentialIndex === credential.credentialIndex) {
|
|
568
|
+
return { user, storedCredential };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
getStoredCredentialStateDebug() {
|
|
575
|
+
if (this.internal.users.length === 0) {
|
|
576
|
+
return 'no users';
|
|
577
|
+
}
|
|
578
|
+
return this.internal.users
|
|
579
|
+
.map((user) => {
|
|
580
|
+
const credentials = user.credentials
|
|
581
|
+
?.map((credential) => `${getEnumDescription(DoorLock.CredentialType, credential.credentialType)}:${credential.credentialIndex}=0x${Buffer.from(credential.credentialData).toString('hex')}`)
|
|
582
|
+
.join(', ') ?? 'none';
|
|
583
|
+
return `user ${user.userIndex} [${credentials}]`;
|
|
584
|
+
})
|
|
585
|
+
.join('; ');
|
|
586
|
+
}
|
|
587
|
+
logStoredCredentialState(reason) {
|
|
588
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
589
|
+
device.log.debug(`MatterbridgeDoorLockServer: ${reason}; stored credentials: ${this.getStoredCredentialStateDebug()}`);
|
|
590
|
+
}
|
|
591
|
+
hasMatchingPinCredential(pinCode) {
|
|
592
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
593
|
+
device.log.debug(`MatterbridgeDoorLockServer: checking remote PIN 0x${Buffer.from(pinCode).toString('hex')} against ${this.internal.users.length} user(s)`);
|
|
594
|
+
for (const user of this.internal.users) {
|
|
595
|
+
for (const storedCredential of user.credentials ?? []) {
|
|
596
|
+
if (storedCredential.credentialType !== DoorLock.CredentialType.Pin) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (Buffer.from(storedCredential.credentialData).equals(Buffer.from(pinCode))) {
|
|
600
|
+
device.log.debug(`MatterbridgeDoorLockServer: remote PIN matched userIndex ${user.userIndex} credentialIndex ${storedCredential.credentialIndex}`);
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
device.log.debug(`MatterbridgeDoorLockServer: remote PIN 0x${Buffer.from(pinCode).toString('hex')} did not match any stored PIN credential`);
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
validateRemotePinCode(pinCode) {
|
|
609
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
610
|
+
if (!this.state.requirePinForRemoteOperation) {
|
|
611
|
+
device.log.debug('MatterbridgeDoorLockServer: skipping remote PIN validation because requirePinForRemoteOperation is false');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (pinCode === undefined) {
|
|
615
|
+
device.log.debug('MatterbridgeDoorLockServer: rejecting remote operation because the request did not include a PIN');
|
|
616
|
+
this.logStoredCredentialState('remote PIN validation failed');
|
|
617
|
+
throw new StatusResponse.FailureError('Missing or invalid PIN code for remote operation');
|
|
618
|
+
}
|
|
619
|
+
device.log.debug(`MatterbridgeDoorLockServer: validating remote PIN 0x${Buffer.from(pinCode).toString('hex')}`);
|
|
620
|
+
if (pinCode === undefined || !this.hasMatchingPinCredential(pinCode)) {
|
|
621
|
+
this.logStoredCredentialState('remote PIN validation failed');
|
|
622
|
+
throw new StatusResponse.FailureError('Missing or invalid PIN code for remote operation');
|
|
623
|
+
}
|
|
624
|
+
device.log.debug(`MatterbridgeDoorLockServer: accepted remote PIN 0x${Buffer.from(pinCode).toString('hex')}`);
|
|
625
|
+
}
|
|
626
|
+
getNextOccupiedCredentialIndex(credential) {
|
|
627
|
+
let nextCredentialIndex = null;
|
|
628
|
+
for (const user of this.internal.users) {
|
|
629
|
+
for (const storedCredential of user.credentials ?? []) {
|
|
630
|
+
if (storedCredential.credentialType !== credential.credentialType || storedCredential.credentialIndex <= credential.credentialIndex) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (nextCredentialIndex === null || storedCredential.credentialIndex < nextCredentialIndex) {
|
|
634
|
+
nextCredentialIndex = storedCredential.credentialIndex;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return nextCredentialIndex;
|
|
639
|
+
}
|
|
640
|
+
upsertStoredCredential(userIndex, credential, credentialData) {
|
|
641
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
642
|
+
if (userIndex === null) {
|
|
643
|
+
device.log.debug(`MatterbridgeDoorLockServer: not storing credentialType ${getEnumDescription(DoorLock.CredentialType, credential.credentialType)} credentialIndex ${credential.credentialIndex} because userIndex is null`);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const user = this.internal.users.find((storedUser) => storedUser.userIndex === userIndex);
|
|
647
|
+
if (!user) {
|
|
648
|
+
device.log.debug(`MatterbridgeDoorLockServer: not storing credentialType ${getEnumDescription(DoorLock.CredentialType, credential.credentialType)} credentialIndex ${credential.credentialIndex} because userIndex ${userIndex} was not found`);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
let removedCredentials = 0;
|
|
652
|
+
for (const storedUser of this.internal.users) {
|
|
653
|
+
const nextCredentials = storedUser.credentials?.filter((storedCredential) => storedCredential.credentialType !== credential.credentialType || storedCredential.credentialIndex !== credential.credentialIndex) ?? null;
|
|
654
|
+
removedCredentials += (storedUser.credentials?.length ?? 0) - (nextCredentials?.length ?? 0);
|
|
655
|
+
storedUser.credentials = nextCredentials && nextCredentials.length > 0 ? nextCredentials : null;
|
|
656
|
+
}
|
|
657
|
+
if (!user.credentials) {
|
|
658
|
+
user.credentials = [];
|
|
659
|
+
}
|
|
660
|
+
user.credentials.push({
|
|
661
|
+
credentialType: credential.credentialType,
|
|
662
|
+
credentialIndex: credential.credentialIndex,
|
|
663
|
+
credentialData: Uint8Array.from(credentialData),
|
|
664
|
+
});
|
|
665
|
+
device.log.debug(`MatterbridgeDoorLockServer: stored credentialType ${getEnumDescription(DoorLock.CredentialType, credential.credentialType)} credentialIndex ${credential.credentialIndex} for userIndex ${userIndex} (removed ${removedCredentials} replaced credential(s))`);
|
|
666
|
+
this.logStoredCredentialState('credential stored');
|
|
667
|
+
}
|
|
668
|
+
clearStoredCredential(credential) {
|
|
669
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
670
|
+
for (const user of this.internal.users) {
|
|
671
|
+
if (credential === null) {
|
|
672
|
+
user.credentials = null;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const nextCredentials = user.credentials?.filter((storedCredential) => storedCredential.credentialType !== credential.credentialType || storedCredential.credentialIndex !== credential.credentialIndex) ?? null;
|
|
676
|
+
user.credentials = nextCredentials && nextCredentials.length > 0 ? nextCredentials : null;
|
|
677
|
+
}
|
|
678
|
+
device.log.debug(`MatterbridgeDoorLockServer: cleared ${credential ? `${getEnumDescription(DoorLock.CredentialType, credential.credentialType)} credentialIndex ${credential.credentialIndex}` : 'all credentials'} from internal state`);
|
|
679
|
+
this.logStoredCredentialState('credential cleared');
|
|
680
|
+
}
|
|
681
|
+
async lockDoor(request) {
|
|
682
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
683
|
+
device.log.info(`Locking door with pincode ${request.pinCode ? '0x' + Buffer.from(request.pinCode).toString('hex') : 'N/A'} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
684
|
+
this.validateRemotePinCode(request.pinCode);
|
|
685
|
+
device.log.debug(`MatterbridgeDoorLockServer: remote lockDoor PIN validation completed`);
|
|
686
|
+
await device.commandHandler.executeHandler('DoorLock.lockDoor', {
|
|
687
|
+
command: 'lockDoor',
|
|
688
|
+
request,
|
|
689
|
+
cluster: DoorLockServer.id,
|
|
690
|
+
attributes: this.state,
|
|
691
|
+
endpoint: this.endpoint,
|
|
692
|
+
});
|
|
693
|
+
device.log.debug(`MatterbridgeDoorLockServer: lockDoor called`);
|
|
694
|
+
await super.lockDoor(request);
|
|
695
|
+
}
|
|
696
|
+
async unlockDoor(request) {
|
|
697
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
698
|
+
device.log.info(`Unlocking door with pincode ${request.pinCode ? '0x' + Buffer.from(request.pinCode).toString('hex') : 'N/A'} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
699
|
+
this.validateRemotePinCode(request.pinCode);
|
|
700
|
+
device.log.debug(`MatterbridgeDoorLockServer: remote unlockDoor PIN validation completed`);
|
|
701
|
+
await device.commandHandler.executeHandler('DoorLock.unlockDoor', {
|
|
702
|
+
command: 'unlockDoor',
|
|
703
|
+
request,
|
|
704
|
+
cluster: DoorLockServer.id,
|
|
705
|
+
attributes: this.state,
|
|
706
|
+
endpoint: this.endpoint,
|
|
707
|
+
});
|
|
708
|
+
device.log.debug(`MatterbridgeDoorLockServer: unlockDoor called`);
|
|
709
|
+
await super.unlockDoor(request);
|
|
710
|
+
}
|
|
711
|
+
async unlockWithTimeout(request) {
|
|
712
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
713
|
+
device.log.info(`Unlocking door with pincode ${request.pinCode ? '0x' + Buffer.from(request.pinCode).toString('hex') : 'N/A'} timeout ${request.timeout} seconds (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
714
|
+
this.validateRemotePinCode(request.pinCode);
|
|
715
|
+
device.log.debug(`MatterbridgeDoorLockServer: remote unlockWithTimeout PIN validation completed`);
|
|
716
|
+
await device.commandHandler.executeHandler('DoorLock.unlockWithTimeout', {
|
|
717
|
+
command: 'unlockWithTimeout',
|
|
718
|
+
request,
|
|
719
|
+
cluster: DoorLockServer.id,
|
|
720
|
+
attributes: this.state,
|
|
721
|
+
endpoint: this.endpoint,
|
|
722
|
+
});
|
|
723
|
+
device.log.debug(`MatterbridgeDoorLockServer: unlockWithTimeout called`);
|
|
724
|
+
this.state.lockState = DoorLock.LockState.Unlocked;
|
|
725
|
+
}
|
|
726
|
+
async setUser(request) {
|
|
727
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
728
|
+
const accessingFabricIndex = this.getAccessingFabricIndex();
|
|
729
|
+
device.log.info(`Setting user operationType ${getEnumDescription(DoorLock.DataOperationType, request.operationType)} userIndex ${request.userIndex} userName ${request.userName ?? 'null'} userUniqueId ${request.userUniqueId ?? 'null'} userStatus ${getEnumDescription(DoorLock.UserStatus, request.userStatus, { fallback: 'null' })} userType ${getEnumDescription(DoorLock.UserType, request.userType, { fallback: 'null' })} credentialRule ${getEnumDescription(DoorLock.CredentialRule, request.credentialRule, { fallback: 'null' })} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
730
|
+
device.log.debug(`MatterbridgeDoorLockServer: setUser accessingFabricIndex ${accessingFabricIndex ?? 'null'}`);
|
|
731
|
+
await device.commandHandler.executeHandler('DoorLock.setUser', {
|
|
732
|
+
command: 'setUser',
|
|
733
|
+
request,
|
|
734
|
+
cluster: DoorLockServer.id,
|
|
735
|
+
attributes: this.state,
|
|
736
|
+
endpoint: this.endpoint,
|
|
737
|
+
});
|
|
738
|
+
const user = this.internal.users.find((user) => user.userIndex === request.userIndex);
|
|
739
|
+
device.log.debug(`MatterbridgeDoorLockServer: setUser called for userIndex ${request.userIndex} (${user ? 'existing user ' + debugStringify(user) : 'new user'})`);
|
|
740
|
+
if (!user && request.operationType === DoorLock.DataOperationType.Add) {
|
|
741
|
+
this.internal.users.push({
|
|
742
|
+
userIndex: request.userIndex,
|
|
743
|
+
userName: request.userName,
|
|
744
|
+
userUniqueId: request.userUniqueId,
|
|
745
|
+
userStatus: request.userStatus,
|
|
746
|
+
userType: request.userType,
|
|
747
|
+
credentialRule: request.credentialRule,
|
|
748
|
+
credentials: null,
|
|
749
|
+
creatorFabricIndex: accessingFabricIndex,
|
|
750
|
+
lastModifiedFabricIndex: accessingFabricIndex,
|
|
751
|
+
nextUserIndex: null,
|
|
752
|
+
});
|
|
753
|
+
device.log.debug(`MatterbridgeDoorLockServer: added userIndex ${request.userIndex} (total users: ${this.internal.users.length}) to internal state: ${debugStringify(this.internal.users.find((user) => user.userIndex === request.userIndex))}`);
|
|
754
|
+
this.logStoredCredentialState('user added');
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
this.logStoredCredentialState(`setUser completed for userIndex ${request.userIndex} without adding a new internal user`);
|
|
758
|
+
}
|
|
759
|
+
async getUser(request) {
|
|
760
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
761
|
+
device.log.info(`Getting userIndex ${request.userIndex} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
762
|
+
await device.commandHandler.executeHandler('DoorLock.getUser', {
|
|
763
|
+
command: 'getUser',
|
|
764
|
+
request,
|
|
765
|
+
cluster: DoorLockServer.id,
|
|
766
|
+
attributes: this.state,
|
|
767
|
+
endpoint: this.endpoint,
|
|
768
|
+
});
|
|
769
|
+
const user = this.internal.users.find((user) => user.userIndex === request.userIndex);
|
|
770
|
+
device.log.debug(`MatterbridgeDoorLockServer: getUser called for userIndex ${request.userIndex} (total users: ${this.internal.users.length}) (${user ? 'existing user: ' + debugStringify(user) : 'new user'})`);
|
|
771
|
+
this.logStoredCredentialState(`getUser returning state for userIndex ${request.userIndex}`);
|
|
772
|
+
if (user) {
|
|
773
|
+
return {
|
|
774
|
+
...user,
|
|
775
|
+
credentials: user.credentials?.map(({ credentialType, credentialIndex }) => ({ credentialType, credentialIndex })) ?? null,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
return {
|
|
779
|
+
userIndex: request.userIndex,
|
|
780
|
+
userName: null,
|
|
781
|
+
userUniqueId: null,
|
|
782
|
+
userStatus: null,
|
|
783
|
+
userType: null,
|
|
784
|
+
credentialRule: null,
|
|
785
|
+
credentials: null,
|
|
786
|
+
creatorFabricIndex: null,
|
|
787
|
+
lastModifiedFabricIndex: null,
|
|
788
|
+
nextUserIndex: null,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
async clearUser(request) {
|
|
792
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
793
|
+
device.log.info(`Clearing userIndex ${request.userIndex} ${request.userIndex === 0xfffe ? '(all users)' : ''} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
794
|
+
await device.commandHandler.executeHandler('DoorLock.clearUser', {
|
|
795
|
+
command: 'clearUser',
|
|
796
|
+
request,
|
|
797
|
+
cluster: DoorLockServer.id,
|
|
798
|
+
attributes: this.state,
|
|
799
|
+
endpoint: this.endpoint,
|
|
800
|
+
});
|
|
801
|
+
device.log.debug(`MatterbridgeDoorLockServer: clearUser called for userIndex ${request.userIndex}`);
|
|
802
|
+
this.logStoredCredentialState(`clearUser completed for userIndex ${request.userIndex}`);
|
|
803
|
+
}
|
|
804
|
+
async setCredential(request) {
|
|
805
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
806
|
+
const accessingFabricIndex = this.getAccessingFabricIndex();
|
|
807
|
+
device.log.info(`Setting credential operationType ${getEnumDescription(DoorLock.DataOperationType, request.operationType)} credentialType ${getEnumDescription(DoorLock.CredentialType, request.credential.credentialType)} credentialIndex ${request.credential.credentialIndex} credentialData ${Buffer.from(request.credentialData).toString('hex') ? '0x' + Buffer.from(request.credentialData).toString('hex') : '0x'} userIndex ${request.userIndex ?? 'null'} userStatus ${getEnumDescription(DoorLock.UserStatus, request.userStatus, { fallback: 'null' })} userType ${getEnumDescription(DoorLock.UserType, request.userType, { fallback: 'null' })} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
808
|
+
await device.commandHandler.executeHandler('DoorLock.setCredential', {
|
|
809
|
+
command: 'setCredential',
|
|
810
|
+
request,
|
|
811
|
+
cluster: DoorLockServer.id,
|
|
812
|
+
attributes: this.state,
|
|
813
|
+
endpoint: this.endpoint,
|
|
814
|
+
});
|
|
815
|
+
const user = this.internal.users.find((user) => user.userIndex === request.userIndex);
|
|
816
|
+
const existingCredential = this.findStoredCredential(request.credential);
|
|
817
|
+
device.log.debug(`MatterbridgeDoorLockServer: setCredential pre-update lookup for credentialIndex ${request.credential.credentialIndex} (${existingCredential ? 'existing credential found' : 'no existing credential'})`);
|
|
818
|
+
device.log.debug(`MatterbridgeDoorLockServer: setCredential called for credentialIndex ${request.credential.credentialIndex}`);
|
|
819
|
+
if (user && (request.operationType === DoorLock.DataOperationType.Add || request.operationType === DoorLock.DataOperationType.Modify)) {
|
|
820
|
+
this.upsertStoredCredential(request.userIndex, request.credential, request.credentialData);
|
|
821
|
+
user.lastModifiedFabricIndex = accessingFabricIndex;
|
|
822
|
+
device.log.debug(`MatterbridgeDoorLockServer: setCredential updated lastModifiedFabricIndex for userIndex ${user.userIndex} to ${accessingFabricIndex ?? 'null'}`);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
device.log.debug(`MatterbridgeDoorLockServer: setCredential did not update internal state for credentialIndex ${request.credential.credentialIndex} (user ${request.userIndex ?? 'null'} not found or operation not handled)`);
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
status: Status.Success,
|
|
829
|
+
userIndex: request.userIndex,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
async getCredentialStatus(request) {
|
|
833
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
834
|
+
device.log.info(`Getting credential status for credentialType ${getEnumDescription(DoorLock.CredentialType, request.credential.credentialType)} credentialIndex ${request.credential.credentialIndex} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
835
|
+
await device.commandHandler.executeHandler('DoorLock.getCredentialStatus', {
|
|
836
|
+
command: 'getCredentialStatus',
|
|
837
|
+
request,
|
|
838
|
+
cluster: DoorLockServer.id,
|
|
839
|
+
attributes: this.state,
|
|
840
|
+
endpoint: this.endpoint,
|
|
841
|
+
});
|
|
842
|
+
const credentialRecord = this.findStoredCredential(request.credential);
|
|
843
|
+
const nextCredentialIndex = this.getNextOccupiedCredentialIndex(request.credential);
|
|
844
|
+
device.log.debug(`MatterbridgeDoorLockServer: getCredentialStatus called`);
|
|
845
|
+
device.log.debug(`MatterbridgeDoorLockServer: getCredentialStatus result for credentialIndex ${request.credential.credentialIndex} (${credentialRecord ? `userIndex ${credentialRecord.user.userIndex} credentialData 0x${Buffer.from(credentialRecord.storedCredential.credentialData).toString('hex')}` : 'credential missing'}, nextCredentialIndex ${nextCredentialIndex ?? 'null'})`);
|
|
846
|
+
return {
|
|
847
|
+
credentialExists: credentialRecord !== null,
|
|
848
|
+
userIndex: credentialRecord?.user.userIndex ?? null,
|
|
849
|
+
creatorFabricIndex: credentialRecord?.user.creatorFabricIndex ?? null,
|
|
850
|
+
lastModifiedFabricIndex: credentialRecord?.user.lastModifiedFabricIndex ?? null,
|
|
851
|
+
nextCredentialIndex,
|
|
852
|
+
credentialData: credentialRecord?.storedCredential.credentialData ?? null,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
async clearCredential(request) {
|
|
856
|
+
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
857
|
+
device.log.info(`Clearing credentialType ${request.credential ? getEnumDescription(DoorLock.CredentialType, request.credential.credentialType) : 'null'} credentialIndex ${request.credential ? request.credential.credentialIndex : 'null'} ${request.credential === null ? '(all credentials)' : ''} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
|
|
858
|
+
await device.commandHandler.executeHandler('DoorLock.clearCredential', {
|
|
859
|
+
command: 'clearCredential',
|
|
860
|
+
request,
|
|
861
|
+
cluster: DoorLockServer.id,
|
|
862
|
+
attributes: this.state,
|
|
863
|
+
endpoint: this.endpoint,
|
|
864
|
+
});
|
|
865
|
+
this.clearStoredCredential(request.credential);
|
|
866
|
+
device.log.debug('MatterbridgeDoorLockServer: clearCredential called');
|
|
867
|
+
this.logStoredCredentialState(`clearCredential completed for ${request.credential
|
|
868
|
+
? `${getEnumDescription(DoorLock.CredentialType, request.credential.credentialType)} credentialIndex ${request.credential.credentialIndex}`
|
|
869
|
+
: 'all credentials'}`);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
(function (MatterbridgeUserPinDoorLockServer) {
|
|
873
|
+
class Internal {
|
|
874
|
+
users = [];
|
|
875
|
+
}
|
|
876
|
+
MatterbridgeUserPinDoorLockServer.Internal = Internal;
|
|
877
|
+
})(MatterbridgeUserPinDoorLockServer || (MatterbridgeUserPinDoorLockServer = {}));
|
|
427
878
|
export class MatterbridgeFanControlServer extends FanControlServer.with(FanControl.Feature.Auto, FanControl.Feature.Step) {
|
|
428
879
|
async step(request) {
|
|
429
880
|
const device = this.endpoint.stateOf(MatterbridgeServer);
|
|
@@ -173,6 +173,7 @@ export declare class MatterbridgeEndpoint extends Endpoint {
|
|
|
173
173
|
createDefaultActivatedCarbonFilterMonitoringClusterServer(condition?: number, changeIndication?: ResourceMonitoring.ChangeIndication, inPlaceIndicator?: boolean | undefined, lastChangedTime?: number | null | undefined, replacementProductList?: ResourceMonitoring.ReplacementProduct[]): this;
|
|
174
174
|
createDefaultDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
|
|
175
175
|
createPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
|
|
176
|
+
createUserPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
|
|
176
177
|
createDefaultModeSelectClusterServer(description: string, supportedModes: ModeSelect.ModeOption[], currentMode?: number, startUpMode?: number): this;
|
|
177
178
|
createDefaultValveConfigurationAndControlClusterServer(valveState?: ValveConfigurationAndControl.ValveState, valveLevel?: number): this;
|
|
178
179
|
createDefaultPumpConfigurationAndControlClusterServer(pumpMode?: PumpConfigurationAndControl.OperationMode): this;
|
|
@@ -62,7 +62,7 @@ import { VendorId } from '@matter/types/datatype';
|
|
|
62
62
|
import { inspectError } from '@matterbridge/utils/error';
|
|
63
63
|
import { isValidNumber, isValidObject, isValidString } from '@matterbridge/utils/validate';
|
|
64
64
|
import { AnsiLogger, CYAN, db, debugStringify, hk, or, YELLOW, zb } from 'node-ansi-logger';
|
|
65
|
-
import { MatterbridgeActivatedCarbonFilterMonitoringServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeColorControlServer, MatterbridgeDeviceEnergyManagementModeServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeDoorLockServer, MatterbridgeFanControlServer, MatterbridgeHepaFilterMonitoringServer, MatterbridgeIdentifyServer, MatterbridgeLevelControlServer, MatterbridgeModeSelectServer, MatterbridgeOnOffServer, MatterbridgeOperationalStateServer, MatterbridgePowerSourceServer, MatterbridgeServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeSwitchServer, MatterbridgeThermostatServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeWindowCoveringServer, } from './matterbridgeBehaviorsServer.js';
|
|
65
|
+
import { MatterbridgeActivatedCarbonFilterMonitoringServer, MatterbridgeBooleanStateConfigurationServer, MatterbridgeColorControlServer, MatterbridgeDeviceEnergyManagementModeServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeDoorLockServer, MatterbridgeFanControlServer, MatterbridgeHepaFilterMonitoringServer, MatterbridgeIdentifyServer, MatterbridgeLevelControlServer, MatterbridgeModeSelectServer, MatterbridgeOnOffServer, MatterbridgeOperationalStateServer, MatterbridgePinDoorLockServer, MatterbridgePowerSourceServer, MatterbridgeServer, MatterbridgeSmokeCoAlarmServer, MatterbridgeSwitchServer, MatterbridgeThermostatServer, MatterbridgeUserPinDoorLockServer, MatterbridgeValveConfigurationAndControlServer, MatterbridgeWindowCoveringServer, } from './matterbridgeBehaviorsServer.js';
|
|
66
66
|
import { CommandHandler } from './matterbridgeEndpointCommandHandler.js';
|
|
67
67
|
import { addClusterServers, addFixedLabel, addOptionalClusterServers, addRequiredClusterServers, addUserLabel, checkNotLatinCharacters, createUniqueId, featuresFor, generateUniqueId, getApparentElectricalPowerMeasurementClusterServer, getAttribute, getAttributeId, getBehavior, getBehaviourTypesFromClusterClientIds, getBehaviourTypesFromClusterServerIds, getCluster, getClusterId, getDefaultDeviceEnergyManagementClusterServer, getDefaultDeviceEnergyManagementModeClusterServer, getDefaultElectricalEnergyMeasurementClusterServer, getDefaultElectricalPowerMeasurementClusterServer, getDefaultFlowMeasurementClusterServer, getDefaultIlluminanceMeasurementClusterServer, getDefaultOccupancySensingClusterServer, getDefaultOperationalStateClusterServer, getDefaultPowerSourceBatteryClusterServer, getDefaultPowerSourceRechargeableBatteryClusterServer, getDefaultPowerSourceReplaceableBatteryClusterServer, getDefaultPowerSourceWiredClusterServer, getDefaultPressureMeasurementClusterServer, getDefaultRelativeHumidityMeasurementClusterServer, getDefaultTemperatureMeasurementClusterServer, invokeBehaviorCommand, lowercaseFirstLetter, setAttribute, setCluster, subscribeAttribute, triggerEvent, updateAttribute, } from './matterbridgeEndpointHelpers.js';
|
|
68
68
|
const MATTERBRIDGE_ENDPOINT_BRAND = Symbol('MatterbridgeEndpoint.brand');
|
|
@@ -1048,7 +1048,10 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
1048
1048
|
return this;
|
|
1049
1049
|
}
|
|
1050
1050
|
createDefaultDoorLockClusterServer(lockState = DoorLock.LockState.Locked, lockType = DoorLock.LockType.DeadBolt) {
|
|
1051
|
-
this.behaviors.require(MatterbridgeDoorLockServer.
|
|
1051
|
+
this.behaviors.require(MatterbridgeDoorLockServer.enable({
|
|
1052
|
+
events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true },
|
|
1053
|
+
commands: { lockDoor: true, unlockDoor: true, unlockWithTimeout: true },
|
|
1054
|
+
}), {
|
|
1052
1055
|
lockState,
|
|
1053
1056
|
lockType,
|
|
1054
1057
|
actuatorEnabled: true,
|
|
@@ -1059,8 +1062,9 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
1059
1062
|
return this;
|
|
1060
1063
|
}
|
|
1061
1064
|
createPinDoorLockClusterServer(lockState = DoorLock.LockState.Locked, lockType = DoorLock.LockType.DeadBolt) {
|
|
1062
|
-
this.behaviors.require(
|
|
1065
|
+
this.behaviors.require(MatterbridgePinDoorLockServer.with(DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess).enable({
|
|
1063
1066
|
events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true },
|
|
1067
|
+
commands: { lockDoor: true, unlockDoor: true, unlockWithTimeout: true, setUserStatus: true, getUserStatus: true, setUserType: true, getUserType: true },
|
|
1064
1068
|
}), {
|
|
1065
1069
|
lockState,
|
|
1066
1070
|
lockType,
|
|
@@ -1069,12 +1073,35 @@ export class MatterbridgeEndpoint extends Endpoint {
|
|
|
1069
1073
|
supportedOperatingModes: { normal: false, vacation: true, privacy: true, noRemoteLockUnlock: false, passage: true, alwaysSet: 2047 },
|
|
1070
1074
|
autoRelockTime: 0,
|
|
1071
1075
|
numberOfPinUsersSupported: 10,
|
|
1072
|
-
minPinCodeLength: 4,
|
|
1073
1076
|
maxPinCodeLength: 10,
|
|
1077
|
+
minPinCodeLength: 4,
|
|
1078
|
+
wrongCodeEntryLimit: 5,
|
|
1079
|
+
userCodeTemporaryDisableTime: 60,
|
|
1074
1080
|
sendPinOverTheAir: true,
|
|
1075
1081
|
requirePinForRemoteOperation: true,
|
|
1082
|
+
});
|
|
1083
|
+
return this;
|
|
1084
|
+
}
|
|
1085
|
+
createUserPinDoorLockClusterServer(lockState = DoorLock.LockState.Locked, lockType = DoorLock.LockType.DeadBolt) {
|
|
1086
|
+
this.behaviors.require(MatterbridgeUserPinDoorLockServer.with(DoorLock.Feature.User, DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess).enable({
|
|
1087
|
+
events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true },
|
|
1088
|
+
commands: { lockDoor: true, unlockDoor: true, unlockWithTimeout: true },
|
|
1089
|
+
}), {
|
|
1090
|
+
lockState,
|
|
1091
|
+
lockType,
|
|
1092
|
+
actuatorEnabled: true,
|
|
1093
|
+
operatingMode: DoorLock.OperatingMode.Normal,
|
|
1094
|
+
supportedOperatingModes: { normal: false, vacation: true, privacy: true, noRemoteLockUnlock: false, passage: true, alwaysSet: 2047 },
|
|
1095
|
+
autoRelockTime: 0,
|
|
1096
|
+
numberOfPinUsersSupported: 10,
|
|
1097
|
+
maxPinCodeLength: 10,
|
|
1098
|
+
minPinCodeLength: 4,
|
|
1076
1099
|
wrongCodeEntryLimit: 5,
|
|
1077
1100
|
userCodeTemporaryDisableTime: 60,
|
|
1101
|
+
requirePinForRemoteOperation: true,
|
|
1102
|
+
numberOfTotalUsersSupported: 10,
|
|
1103
|
+
credentialRulesSupport: { single: true },
|
|
1104
|
+
numberOfCredentialsSupportedPerUser: 10,
|
|
1078
1105
|
});
|
|
1079
1106
|
return this;
|
|
1080
1107
|
}
|
|
@@ -71,16 +71,6 @@ export interface MatterbridgeEndpointCommands {
|
|
|
71
71
|
setTarget: HandlerFunction;
|
|
72
72
|
lockDoor: HandlerFunction;
|
|
73
73
|
unlockDoor: HandlerFunction;
|
|
74
|
-
setPinCode: HandlerFunction;
|
|
75
|
-
getPinCode: HandlerFunction;
|
|
76
|
-
clearPinCode: HandlerFunction;
|
|
77
|
-
clearAllPinCodes: HandlerFunction;
|
|
78
|
-
setUser: HandlerFunction;
|
|
79
|
-
getUser: HandlerFunction;
|
|
80
|
-
clearUser: HandlerFunction;
|
|
81
|
-
setCredential: HandlerFunction;
|
|
82
|
-
getCredentialStatus: HandlerFunction;
|
|
83
|
-
clearCredential: HandlerFunction;
|
|
84
74
|
setpointRaiseLower: HandlerFunction;
|
|
85
75
|
setActivePresetRequest: HandlerFunction;
|
|
86
76
|
step: HandlerFunction;
|
|
@@ -393,16 +383,6 @@ export type CommandHandlerDataMap = {
|
|
|
393
383
|
};
|
|
394
384
|
'lockDoor': CommandHandlerData<'DoorLock.lockDoor'>;
|
|
395
385
|
'unlockDoor': CommandHandlerData<'DoorLock.unlockDoor'>;
|
|
396
|
-
'setPinCode': CommandHandlerData<'DoorLock.setPinCode'>;
|
|
397
|
-
'getPinCode': CommandHandlerData<'DoorLock.getPinCode'>;
|
|
398
|
-
'clearPinCode': CommandHandlerData<'DoorLock.clearPinCode'>;
|
|
399
|
-
'clearAllPinCodes': CommandHandlerData<'DoorLock.clearAllPinCodes'>;
|
|
400
|
-
'setUser': CommandHandlerData<'DoorLock.setUser'>;
|
|
401
|
-
'getUser': CommandHandlerData<'DoorLock.getUser'>;
|
|
402
|
-
'clearUser': CommandHandlerData<'DoorLock.clearUser'>;
|
|
403
|
-
'setCredential': CommandHandlerData<'DoorLock.setCredential'>;
|
|
404
|
-
'getCredentialStatus': CommandHandlerData<'DoorLock.getCredentialStatus'>;
|
|
405
|
-
'clearCredential': CommandHandlerData<'DoorLock.clearCredential'>;
|
|
406
386
|
'DoorLock.lockDoor': {
|
|
407
387
|
command: 'lockDoor';
|
|
408
388
|
request: DoorLock.LockDoorRequest;
|
|
@@ -417,6 +397,13 @@ export type CommandHandlerDataMap = {
|
|
|
417
397
|
attributes: ClusterAttributeValues<(typeof DoorLock.Complete)['attributes']>;
|
|
418
398
|
endpoint: MatterbridgeEndpoint;
|
|
419
399
|
};
|
|
400
|
+
'DoorLock.unlockWithTimeout': {
|
|
401
|
+
command: 'unlockWithTimeout';
|
|
402
|
+
request: DoorLock.UnlockWithTimeoutRequest;
|
|
403
|
+
cluster: 'doorLock';
|
|
404
|
+
attributes: ClusterAttributeValues<(typeof DoorLock.Complete)['attributes']>;
|
|
405
|
+
endpoint: MatterbridgeEndpoint;
|
|
406
|
+
};
|
|
420
407
|
'DoorLock.setPinCode': {
|
|
421
408
|
command: 'setPinCode';
|
|
422
409
|
request: DoorLock.SetPinCodeRequest;
|
|
@@ -445,6 +432,34 @@ export type CommandHandlerDataMap = {
|
|
|
445
432
|
attributes: ClusterAttributeValues<(typeof DoorLock.Complete)['attributes']>;
|
|
446
433
|
endpoint: MatterbridgeEndpoint;
|
|
447
434
|
};
|
|
435
|
+
'DoorLock.setUserStatus': {
|
|
436
|
+
command: 'setUserStatus';
|
|
437
|
+
request: DoorLock.SetUserStatusRequest;
|
|
438
|
+
cluster: 'doorLock';
|
|
439
|
+
attributes: ClusterAttributeValues<(typeof DoorLock.Complete)['attributes']>;
|
|
440
|
+
endpoint: MatterbridgeEndpoint;
|
|
441
|
+
};
|
|
442
|
+
'DoorLock.getUserStatus': {
|
|
443
|
+
command: 'getUserStatus';
|
|
444
|
+
request: DoorLock.GetUserStatusRequest;
|
|
445
|
+
cluster: 'doorLock';
|
|
446
|
+
attributes: ClusterAttributeValues<(typeof DoorLock.Complete)['attributes']>;
|
|
447
|
+
endpoint: MatterbridgeEndpoint;
|
|
448
|
+
};
|
|
449
|
+
'DoorLock.setUserType': {
|
|
450
|
+
command: 'setUserType';
|
|
451
|
+
request: DoorLock.SetUserTypeRequest;
|
|
452
|
+
cluster: 'doorLock';
|
|
453
|
+
attributes: ClusterAttributeValues<(typeof DoorLock.Complete)['attributes']>;
|
|
454
|
+
endpoint: MatterbridgeEndpoint;
|
|
455
|
+
};
|
|
456
|
+
'DoorLock.getUserType': {
|
|
457
|
+
command: 'getUserType';
|
|
458
|
+
request: DoorLock.GetUserTypeRequest;
|
|
459
|
+
cluster: 'doorLock';
|
|
460
|
+
attributes: ClusterAttributeValues<(typeof DoorLock.Complete)['attributes']>;
|
|
461
|
+
endpoint: MatterbridgeEndpoint;
|
|
462
|
+
};
|
|
448
463
|
'DoorLock.setUser': {
|
|
449
464
|
command: 'setUser';
|
|
450
465
|
request: DoorLock.SetUserRequest;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matterbridge/core",
|
|
3
|
-
"version": "3.7.1
|
|
3
|
+
"version": "3.7.1",
|
|
4
4
|
"description": "Matterbridge core library",
|
|
5
5
|
"author": "https://github.com/Luligu",
|
|
6
6
|
"homepage": "https://matterbridge.io/",
|
|
@@ -122,10 +122,10 @@
|
|
|
122
122
|
],
|
|
123
123
|
"dependencies": {
|
|
124
124
|
"@matter/main": "0.16.10",
|
|
125
|
-
"@matterbridge/dgram": "3.7.1
|
|
126
|
-
"@matterbridge/thread": "3.7.1
|
|
127
|
-
"@matterbridge/types": "3.7.1
|
|
128
|
-
"@matterbridge/utils": "3.7.1
|
|
125
|
+
"@matterbridge/dgram": "3.7.1",
|
|
126
|
+
"@matterbridge/thread": "3.7.1",
|
|
127
|
+
"@matterbridge/types": "3.7.1",
|
|
128
|
+
"@matterbridge/utils": "3.7.1",
|
|
129
129
|
"express": "5.2.1",
|
|
130
130
|
"multer": "2.1.1",
|
|
131
131
|
"node-ansi-logger": "3.2.0",
|