@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.
@@ -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 lock = new MatterbridgeEndpoint(doorLockDevice, { id: 'system_lock' });
78
- lock.createDefaultBridgedDeviceBasicInformationClusterServer('Matterbridge System Lock', 'sn_system_lock', 0xfff1, 'Matterbridge', 'Matterbridge Virtual Device', 20000, '2.0.0');
79
- lock.createPinDoorLockClusterServer();
80
- lock.addRequiredClusterServers();
81
- await aggregatorEndpoint.add(lock);
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").ClusterComposer.WithFeatures<DoorLock.Cluster, readonly [DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess]>, typeof DoorLockServer, import("@matter/node/behaviors/door-lock").DoorLockInterface>;
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.with(DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess) {
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.with().enable({ events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true } }), {
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(MatterbridgeDoorLockServer.with(DoorLock.Feature.PinCredential, DoorLock.Feature.CredentialOverTheAirAccess).enable({
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-dev-20260326-7d15a50",
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-dev-20260326-7d15a50",
126
- "@matterbridge/thread": "3.7.1-dev-20260326-7d15a50",
127
- "@matterbridge/types": "3.7.1-dev-20260326-7d15a50",
128
- "@matterbridge/utils": "3.7.1-dev-20260326-7d15a50",
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",