@matterbridge/core 3.6.0-dev-20260302-e3a7eb0 → 3.6.0-dev-20260303-2f99eca

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.
@@ -8,10 +8,10 @@ import path from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { inspect } from 'node:util';
10
10
  import { Crypto, Environment, LogFormat as MatterLogFormat, Logger, LogLevel as MatterLogLevel, StorageService, UINT16_MAX, UINT32_MAX, } from '@matter/general';
11
- import { Endpoint, ServerNode } from '@matter/node';
12
- import { BasicInformationServer } from '@matter/node/behaviors/basic-information';
11
+ import { Endpoint, NetworkClient, ServerNode } from '@matter/node';
12
+ import { BasicInformationClient, BasicInformationServer } from '@matter/node/behaviors/basic-information';
13
13
  import { AggregatorEndpoint } from '@matter/node/endpoints';
14
- import { PaseClient } from '@matter/protocol';
14
+ import { PaseClient, Read, Subscribe } from '@matter/protocol';
15
15
  import { DeviceTypeId, VendorId } from '@matter/types/datatype';
16
16
  import { BroadcastServer } from '@matterbridge/thread';
17
17
  import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from '@matterbridge/types';
@@ -1532,26 +1532,82 @@ export class Matterbridge extends EventEmitter {
1532
1532
  reachable: true,
1533
1533
  },
1534
1534
  });
1535
- this.log.info(`Starting matter commissioning controller: ${this.controllerNode.peers.size} peer nodes...`);
1535
+ this.log.info(`Loaded matter commissioning controller with ${this.controllerNode.peers.size} peer nodes.`);
1536
+ const requestedNodeId = getIntParameter('node');
1537
+ const nodeLabel = requestedNodeId !== undefined ? `Node ${requestedNodeId}` : undefined;
1538
+ const peerId = requestedNodeId !== undefined ? `peer${requestedNodeId}` : undefined;
1536
1539
  if (hasParameter('pair')) {
1537
1540
  let options;
1538
1541
  if (hasParameter('pairingcode')) {
1539
1542
  const pairingCode = getParameter('pairingcode');
1540
- this.log.info(`Pairing device with pairingcode: ${pairingCode}`);
1543
+ this.log.info(`Pairing device ${nodeLabel} with pairingcode: ${pairingCode}`);
1541
1544
  const pairingData = ManualPairingCodeCodec.decode(pairingCode);
1542
- this.log.info(`Data extracted from pairing code: ${Logger.toJSON(pairingData)}`);
1543
- options = { id: 'device', pairingCode };
1545
+ this.log.info(`- passcode: ${pairingData.passcode}`);
1546
+ this.log.info(`- shortDiscriminator: ${pairingData.shortDiscriminator ?? 'n/a (should never be n/a, short discriminator is encoded in the manual pairing code)'} `);
1547
+ this.log.info(`- longDiscriminator: ${pairingData.discriminator ?? 'n/a (manual pairing code encodes short discriminator only)'}`);
1548
+ this.log.info(`- vendorId: ${pairingData.vendorId ?? 'n/a'}`);
1549
+ this.log.info(`- productId: ${pairingData.productId ?? 'n/a'}`);
1550
+ options = {
1551
+ id: peerId,
1552
+ pairingCode,
1553
+ ...(pairingData.shortDiscriminator !== undefined ? { shortDiscriminator: pairingData.shortDiscriminator } : {}),
1554
+ ...(pairingData.vendorId !== undefined ? { vendorId: pairingData.vendorId } : {}),
1555
+ ...(pairingData.productId !== undefined ? { productId: pairingData.productId } : {}),
1556
+ };
1544
1557
  }
1545
1558
  else {
1546
1559
  const discriminator = getIntParameter('discriminator');
1547
1560
  const passcode = getIntParameter('passcode') ?? 0;
1548
- options = { id: 'device', discriminator, passcode };
1561
+ this.log.info(`Pairing device ${nodeLabel} with discriminator: ${discriminator}, passcode: ${passcode}`);
1562
+ options = { id: peerId, discriminator, passcode };
1563
+ }
1564
+ this.log.notice(`Commissioning node ${nodeLabel} with options: ${debugStringify(options)}`);
1565
+ try {
1566
+ const commissionedNode = await this.controllerNode.peers.commission(options);
1567
+ this.log.notice(`Commissioning successfully done for node ${commissionedNode.id}`);
1568
+ }
1569
+ catch (error) {
1570
+ this.log.error(`Commissioning failed for node ${nodeLabel}: ${error instanceof Error ? error.message : error}`);
1571
+ }
1572
+ }
1573
+ else if (hasParameter('discover')) {
1574
+ this.log.info('Discovering commissionable devices...');
1575
+ const discovery = this.controllerNode.peers.discover();
1576
+ discovery.discovered.on((node) => {
1577
+ this.log.info(`Discovered commissionable device: ${node.id}`);
1578
+ });
1579
+ }
1580
+ else if (hasParameter('peers')) {
1581
+ await this.controllerNode.start();
1582
+ const peers = this.controllerNode.peers;
1583
+ this.log.info(`Matter commissioning controller paired nodes: ${peers.size}`);
1584
+ for (const peer of peers) {
1585
+ const match = /^peer(\d+)$/.exec(peer.id);
1586
+ this.log.info(`- ${match ? `Node ${match[1]}` : peer.id}`);
1587
+ }
1588
+ }
1589
+ else if (hasParameter('unpair')) {
1590
+ await this.controllerNode.start();
1591
+ const requestedNodeId = getIntParameter('node');
1592
+ if (requestedNodeId === undefined) {
1593
+ this.log.error('Missing parameter --node for --unpair');
1594
+ return;
1595
+ }
1596
+ const nodeLabel = `Node ${requestedNodeId}`;
1597
+ const peerId = `peer${requestedNodeId}`;
1598
+ this.log.info(`Matter commissioning controller decommissioning node: ${nodeLabel}`);
1599
+ const peers = this.controllerNode.peers;
1600
+ for (const peer of peers) {
1601
+ if (peer.id === peerId) {
1602
+ await peer.decommission();
1603
+ this.log.info(`Matter commissioning controller decommissioned node: ${nodeLabel}`);
1604
+ return;
1605
+ }
1549
1606
  }
1550
- this.log.info('Commissioning with options:', options);
1551
- const nodeId = this.controllerNode.peers.commission(options);
1552
- this.log.info(`Commissioning successfully done with nodeId: ${nodeId}`);
1607
+ this.log.warn(`Matter commissioning controller node not found: ${nodeLabel}`);
1553
1608
  }
1554
- if (hasParameter('unpairall')) {
1609
+ else if (hasParameter('unpairall')) {
1610
+ await this.controllerNode.start();
1555
1611
  this.log.info('Matter commissioning controller decommissioning all nodes...');
1556
1612
  const nodeIds = this.controllerNode.peers;
1557
1613
  for (const nodeId of nodeIds) {
@@ -1559,7 +1615,120 @@ export class Matterbridge extends EventEmitter {
1559
1615
  await nodeId.decommission();
1560
1616
  }
1561
1617
  this.log.info('Matter commissioning controller decommissioned all nodes');
1562
- return;
1618
+ }
1619
+ else {
1620
+ this.log.info('Starting matter commissioning controller...');
1621
+ await this.controllerNode.start();
1622
+ this.log.info(`Matter commissioning controller started with ${this.controllerNode.peers.size} paired nodes`);
1623
+ const tracerSubscriptions = new Map();
1624
+ const peersToStart = [];
1625
+ if (peerId) {
1626
+ const peer = this.controllerNode.peers.get(peerId);
1627
+ if (!peer) {
1628
+ this.log.warn(`Matter commissioning controller peer not found: ${nodeLabel ?? peerId}`);
1629
+ }
1630
+ else {
1631
+ peersToStart.push(peer);
1632
+ }
1633
+ }
1634
+ else {
1635
+ for (const peer of this.controllerNode.peers) {
1636
+ peersToStart.push(peer);
1637
+ }
1638
+ }
1639
+ for (const peer of peersToStart) {
1640
+ const match = /^peer(\d+)$/.exec(peer.id);
1641
+ const startedLabel = match ? `Node ${match[1]}` : peer.id;
1642
+ peer.lifecycle.online.on(() => {
1643
+ this.log.notice(`Matter commissioning controller peer ${startedLabel} is online`);
1644
+ if (peer.stateOf(NetworkClient).autoSubscribe) {
1645
+ this.log.info(`Matter commissioning controller autoSubscribe is enabled for peer ${startedLabel}`);
1646
+ }
1647
+ else {
1648
+ this.log.info(`Matter commissioning controller setting autoSubscribe for peer ${startedLabel}`);
1649
+ peer.setStateOf(NetworkClient, { autoSubscribe: true }).catch((error) => {
1650
+ this.log.error(`Failed to set autoSubscribe for peer ${startedLabel}: ${error instanceof Error ? error.message : error}`);
1651
+ });
1652
+ }
1653
+ if (hasParameter('trace-peer') && !tracerSubscriptions.has(peer.id)) {
1654
+ this.log.info(`Matter commissioning controller enabling trace-peer for ${startedLabel}`);
1655
+ const subscribe = Subscribe({
1656
+ fabricFilter: true,
1657
+ keepSubscriptions: false,
1658
+ attributes: [{}],
1659
+ events: [{ isUrgent: true }],
1660
+ });
1661
+ const subscriptionPromise = peer.interaction.subscribe({
1662
+ ...subscribe,
1663
+ updated: async (update) => {
1664
+ for await (const chunk of update) {
1665
+ for (const report of chunk) {
1666
+ if (report.kind === 'attr-value') {
1667
+ this.log.info(`Peer ${startedLabel} attr ${report.path.endpointId}/${report.path.clusterId}/${report.path.attributeId} v${report.version}: ${typeof report.value === 'object' ? debugStringify(report.value ?? { none: true }) : String(report.value)}`);
1668
+ }
1669
+ else if (report.kind === 'event-value') {
1670
+ this.log.info(`Peer ${startedLabel} event ${report.path.endpointId}/${report.path.clusterId}/${report.path.eventId} #${report.number}: ${typeof report.value === 'object' ? debugStringify(report.value ?? { none: true }) : String(report.value)}`);
1671
+ }
1672
+ else if (report.kind === 'attr-status') {
1673
+ this.log.warn(`Peer ${startedLabel} attr-status ${report.path.endpointId}/${report.path.clusterId}/${report.path.attributeId}: ${String(report.status)}`);
1674
+ }
1675
+ else if (report.kind === 'event-status') {
1676
+ this.log.warn(`Peer ${startedLabel} event-status ${report.path.endpointId}/${report.path.clusterId}/${report.path.eventId}: ${String(report.status)}`);
1677
+ }
1678
+ }
1679
+ }
1680
+ },
1681
+ closed: () => {
1682
+ this.log.warn(`Matter commissioning controller trace-peer subscription closed for ${startedLabel}`);
1683
+ tracerSubscriptions.delete(peer.id);
1684
+ },
1685
+ });
1686
+ tracerSubscriptions.set(peer.id, subscriptionPromise);
1687
+ subscriptionPromise.catch((error) => {
1688
+ tracerSubscriptions.delete(peer.id);
1689
+ this.log.error(`Matter commissioning controller trace-peer subscription failed for ${startedLabel}: ${error instanceof Error ? error.message : error}`);
1690
+ });
1691
+ }
1692
+ if (hasParameter('dump-peer')) {
1693
+ void (async () => {
1694
+ try {
1695
+ for await (const _chunk of peer.interaction.read(Read({ fabricFilter: true, attributes: [{}] }))) {
1696
+ }
1697
+ const basicInfo = peer.stateOf(BasicInformationClient);
1698
+ Logger.get(`BasicInformation: ${startedLabel}`).info(basicInfo);
1699
+ }
1700
+ catch (error) {
1701
+ this.log.error(`Matter commissioning controller dump-peer failed for ${startedLabel}: ${error instanceof Error ? error.message : error}`);
1702
+ }
1703
+ })();
1704
+ }
1705
+ });
1706
+ peer.lifecycle.offline.on(() => {
1707
+ this.log.warn(`Matter commissioning controller peer ${startedLabel} is offline`);
1708
+ });
1709
+ peer.eventsOf(NetworkClient).autoSubscribe$Changed.on((value, oldValue) => {
1710
+ this.log.info(`Matter commissioning controller peer ${startedLabel} autoSubscribe changed from ${oldValue ? 'active' : 'inactive'} to ${value ? 'active' : 'inactive'}`);
1711
+ });
1712
+ peer.eventsOf(NetworkClient).defaultSubscription$Changed.on((value, oldValue) => {
1713
+ this.log.info(`Matter commissioning controller peer ${startedLabel} defaultSubscription changed from ${oldValue ? 'active' : 'inactive'} to ${value ? 'active' : 'inactive'}`);
1714
+ });
1715
+ peer.eventsOf(NetworkClient).subscriptionStatusChanged.on((isActive) => {
1716
+ this.log.info(`Matter commissioning controller peer ${startedLabel} subscription is ${isActive ? 'active' : 'inactive'}`);
1717
+ });
1718
+ peer.eventsOf(NetworkClient).subscriptionAlive.on(() => {
1719
+ this.log.info(`Matter commissioning controller peer ${startedLabel} subscription alive event received`);
1720
+ });
1721
+ this.log.notice(`Starting matter commissioning controller peer ${startedLabel}...`);
1722
+ peer
1723
+ .start()
1724
+ .then(() => {
1725
+ this.log.notice(`Matter commissioning controller peer ${startedLabel} started successfully`);
1726
+ return;
1727
+ })
1728
+ .catch((error) => {
1729
+ this.log.error(`Matter commissioning controller failed to start peer ${startedLabel}: ${error instanceof Error ? error.message : error}`);
1730
+ });
1731
+ }
1563
1732
  }
1564
1733
  }
1565
1734
  async startMatterStorage() {
@@ -93,3 +93,7 @@ export declare const waterHeater: DeviceTypeDefinition;
93
93
  export declare const solarPower: DeviceTypeDefinition;
94
94
  export declare const batteryStorage: DeviceTypeDefinition;
95
95
  export declare const heatPump: DeviceTypeDefinition;
96
+ export declare const soilSensor: DeviceTypeDefinition;
97
+ export declare const irrigationSystem: DeviceTypeDefinition;
98
+ export declare const closure: DeviceTypeDefinition;
99
+ export declare const closurePanel: DeviceTypeDefinition;
@@ -86,6 +86,9 @@ import { WaterHeaterManagement } from '@matter/types/clusters/water-heater-manag
86
86
  import { WaterHeaterMode } from '@matter/types/clusters/water-heater-mode';
87
87
  import { WindowCovering } from '@matter/types/clusters/window-covering';
88
88
  import { DeviceTypeId } from '@matter/types/datatype';
89
+ import { ClosureControl } from './clusters/closure-control.js';
90
+ import { ClosureDimension } from './clusters/closure-dimension.js';
91
+ import { SoilMeasurement } from './clusters/soil-measurement.js';
89
92
  export var DeviceClasses;
90
93
  (function (DeviceClasses) {
91
94
  DeviceClasses["Node"] = "Node";
@@ -643,3 +646,37 @@ export const heatPump = DeviceTypeDefinition({
643
646
  requiredServerClusters: [],
644
647
  optionalServerClusters: [Identify.Cluster.id, Thermostat.Cluster.id],
645
648
  });
649
+ export const soilSensor = DeviceTypeDefinition({
650
+ name: 'MA-soilSensor',
651
+ code: 0x0045,
652
+ deviceClass: DeviceClasses.Simple,
653
+ revision: 1,
654
+ requiredServerClusters: [Identify.Cluster.id, SoilMeasurement.Cluster.id],
655
+ optionalServerClusters: [TemperatureMeasurement.Cluster.id],
656
+ });
657
+ export const irrigationSystem = DeviceTypeDefinition({
658
+ name: 'MA-irrigationSystem',
659
+ code: 0x0040,
660
+ deviceClass: DeviceClasses.Simple,
661
+ revision: 1,
662
+ requiredServerClusters: [],
663
+ optionalServerClusters: [Identify.Cluster.id, OperationalState.Cluster.id, FlowMeasurement.Cluster.id],
664
+ requiredClientClusters: [],
665
+ optionalClientClusters: [FlowMeasurement.Cluster.id],
666
+ });
667
+ export const closure = DeviceTypeDefinition({
668
+ name: 'MA-closure',
669
+ code: 0x0230,
670
+ deviceClass: DeviceClasses.Simple,
671
+ revision: 1,
672
+ requiredServerClusters: [Identify.Cluster.id, ClosureControl.Cluster.id],
673
+ optionalServerClusters: [],
674
+ });
675
+ export const closurePanel = DeviceTypeDefinition({
676
+ name: 'MA-closurePanel',
677
+ code: 0x0231,
678
+ deviceClass: DeviceClasses.Simple,
679
+ revision: 1,
680
+ requiredServerClusters: [ClosureDimension.Cluster.id],
681
+ optionalServerClusters: [],
682
+ });
@@ -16,7 +16,7 @@ import { PressureMeasurement } from '@matter/types/clusters/pressure-measurement
16
16
  import { RelativeHumidityMeasurement } from '@matter/types/clusters/relative-humidity-measurement';
17
17
  import { TemperatureMeasurement } from '@matter/types/clusters/temperature-measurement';
18
18
  import { ClusterId } from '@matter/types/datatype';
19
- import { MeasurementType } from '@matter/types/globals';
19
+ import { MeasurementType, Semtag } from '@matter/types/globals';
20
20
  import { AnsiLogger } from 'node-ansi-logger';
21
21
  import { MatterbridgeDeviceEnergyManagementModeServer, MatterbridgeDeviceEnergyManagementServer, MatterbridgeOperationalStateServer, MatterbridgePowerSourceServer } from './matterbridgeBehaviors.js';
22
22
  import { MatterbridgeEndpoint } from './matterbridgeEndpoint.js';
@@ -26,6 +26,7 @@ export declare function lowercaseFirstLetter(name: string): string;
26
26
  export declare function checkNotLatinCharacters(deviceName: string): boolean;
27
27
  export declare function generateUniqueId(deviceName: string): string;
28
28
  export declare function createUniqueId(param1: string, param2: string, param3: string, param4: string): string;
29
+ export declare function getSemtag(semtag: Semtag, label?: Semtag['label'], mfgCode?: Semtag['mfgCode']): Semtag;
29
30
  export declare function featuresFor(endpoint: MatterbridgeEndpoint, cluster: Behavior.Type | ClusterType | ClusterId | string): Record<string, boolean | undefined>;
30
31
  export declare function getBehaviourTypesFromClusterServerIds(clusterServerList: ClusterId[]): Behavior.Type[];
31
32
  export declare function getBehaviourTypesFromClusterClientIds(clusterClientList: ClusterId[]): Behavior.Type[];
@@ -113,6 +113,11 @@ export function createUniqueId(param1, param2, param3, param4) {
113
113
  hash.update(param1 + param2 + param3 + param4);
114
114
  return hash.digest('hex');
115
115
  }
116
+ export function getSemtag(semtag, label = null, mfgCode = null) {
117
+ if (label !== null && typeof label === 'string')
118
+ label = label.trim().slice(0, 64);
119
+ return { mfgCode, namespaceId: semtag.namespaceId, tag: semtag.tag, label };
120
+ }
116
121
  export function featuresFor(endpoint, cluster) {
117
122
  const behaviorId = getBehavior(endpoint, cluster)?.id;
118
123
  if (!behaviorId) {
@@ -40,6 +40,8 @@ export interface MatterbridgeEndpointCommands {
40
40
  stopMotion: HandlerFunction;
41
41
  goToLiftPercentage: HandlerFunction;
42
42
  goToTiltPercentage: HandlerFunction;
43
+ moveTo: HandlerFunction;
44
+ setTarget: HandlerFunction;
43
45
  lockDoor: HandlerFunction;
44
46
  unlockDoor: HandlerFunction;
45
47
  setpointRaiseLower: HandlerFunction;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.6.0-dev-20260302-e3a7eb0",
3
+ "version": "3.6.0-dev-20260303-2f99eca",
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.6.0-dev-20260302-e3a7eb0",
126
- "@matterbridge/thread": "3.6.0-dev-20260302-e3a7eb0",
127
- "@matterbridge/types": "3.6.0-dev-20260302-e3a7eb0",
128
- "@matterbridge/utils": "3.6.0-dev-20260302-e3a7eb0",
125
+ "@matterbridge/dgram": "3.6.0-dev-20260303-2f99eca",
126
+ "@matterbridge/thread": "3.6.0-dev-20260303-2f99eca",
127
+ "@matterbridge/types": "3.6.0-dev-20260303-2f99eca",
128
+ "@matterbridge/utils": "3.6.0-dev-20260303-2f99eca",
129
129
  "archiver": "7.0.1",
130
130
  "express": "5.2.1",
131
131
  "glob": "13.0.6",