@matter/node 0.17.7-alpha.0-20260724-baac832f7 → 0.17.7

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.
Files changed (49) hide show
  1. package/dist/cjs/behavior/system/controller/ControllerBehavior.d.ts +8 -0
  2. package/dist/cjs/behavior/system/controller/ControllerBehavior.d.ts.map +1 -1
  3. package/dist/cjs/behavior/system/controller/ControllerBehavior.js +11 -0
  4. package/dist/cjs/behavior/system/controller/ControllerBehavior.js.map +1 -1
  5. package/dist/cjs/node/ClientNode.d.ts.map +1 -1
  6. package/dist/cjs/node/ClientNode.js +2 -2
  7. package/dist/cjs/node/ClientNode.js.map +1 -1
  8. package/dist/cjs/node/Node.d.ts +6 -0
  9. package/dist/cjs/node/Node.d.ts.map +1 -1
  10. package/dist/cjs/node/Node.js +8 -0
  11. package/dist/cjs/node/Node.js.map +1 -1
  12. package/dist/cjs/node/NodePhysicalProperties.d.ts.map +1 -1
  13. package/dist/cjs/node/NodePhysicalProperties.js +23 -0
  14. package/dist/cjs/node/NodePhysicalProperties.js.map +1 -1
  15. package/dist/cjs/node/client/ClientStructure.d.ts.map +1 -1
  16. package/dist/cjs/node/client/ClientStructure.js +3 -2
  17. package/dist/cjs/node/client/ClientStructure.js.map +1 -1
  18. package/dist/cjs/node/client/PeerBehavior.d.ts +6 -1
  19. package/dist/cjs/node/client/PeerBehavior.d.ts.map +1 -1
  20. package/dist/cjs/node/client/PeerBehavior.js +11 -6
  21. package/dist/cjs/node/client/PeerBehavior.js.map +1 -1
  22. package/dist/esm/behavior/system/controller/ControllerBehavior.d.ts +8 -0
  23. package/dist/esm/behavior/system/controller/ControllerBehavior.d.ts.map +1 -1
  24. package/dist/esm/behavior/system/controller/ControllerBehavior.js +11 -0
  25. package/dist/esm/behavior/system/controller/ControllerBehavior.js.map +1 -1
  26. package/dist/esm/node/ClientNode.d.ts.map +1 -1
  27. package/dist/esm/node/ClientNode.js +3 -3
  28. package/dist/esm/node/ClientNode.js.map +1 -1
  29. package/dist/esm/node/Node.d.ts +6 -0
  30. package/dist/esm/node/Node.d.ts.map +1 -1
  31. package/dist/esm/node/Node.js +8 -0
  32. package/dist/esm/node/Node.js.map +1 -1
  33. package/dist/esm/node/NodePhysicalProperties.d.ts.map +1 -1
  34. package/dist/esm/node/NodePhysicalProperties.js +23 -0
  35. package/dist/esm/node/NodePhysicalProperties.js.map +1 -1
  36. package/dist/esm/node/client/ClientStructure.d.ts.map +1 -1
  37. package/dist/esm/node/client/ClientStructure.js +4 -4
  38. package/dist/esm/node/client/ClientStructure.js.map +1 -1
  39. package/dist/esm/node/client/PeerBehavior.d.ts +6 -1
  40. package/dist/esm/node/client/PeerBehavior.d.ts.map +1 -1
  41. package/dist/esm/node/client/PeerBehavior.js +11 -6
  42. package/dist/esm/node/client/PeerBehavior.js.map +1 -1
  43. package/package.json +6 -6
  44. package/src/behavior/system/controller/ControllerBehavior.ts +14 -0
  45. package/src/node/ClientNode.ts +4 -4
  46. package/src/node/Node.ts +9 -0
  47. package/src/node/NodePhysicalProperties.ts +49 -0
  48. package/src/node/client/ClientStructure.ts +3 -3
  49. package/src/node/client/PeerBehavior.ts +25 -7
@@ -12,6 +12,7 @@ import { IcdManagementClient } from "#behaviors/icd-management";
12
12
  import { NetworkCommissioningClient } from "#behaviors/network-commissioning";
13
13
  import { PowerSourceClient } from "#behaviors/power-source";
14
14
  import { ThreadNetworkDiagnosticsClient } from "#behaviors/thread-network-diagnostics";
15
+ import { WiFiNetworkDiagnosticsClient } from "#behaviors/wi-fi-network-diagnostics";
15
16
  import { Endpoint } from "#endpoint/Endpoint.js";
16
17
  import { AggregatorEndpoint } from "#endpoints/aggregator";
17
18
  import { Node } from "#node/Node.js";
@@ -50,9 +51,57 @@ export function NodePhysicalProperties(node: Node) {
50
51
 
51
52
  inspectEndpoint(node, properties);
52
53
 
54
+ if (!properties.supportsWifi && !properties.supportsThread && !properties.supportsEthernet) {
55
+ inferNetworkMediumFromDiagnostics(node, properties);
56
+ }
57
+
53
58
  return properties;
54
59
  }
55
60
 
61
+ /**
62
+ * Determine the operational medium of a node that reports no network interfaces via Network Commissioning ("commissioned
63
+ * by other means") from the network diagnostics cluster of the medium it operates on.
64
+ *
65
+ * The mandatory diagnostics attributes are all nullable and null while the interface is not associated, so a populated
66
+ * one distinguishes an operational interface from a cluster that merely exists.
67
+ *
68
+ * Diagnostics describe the node itself, so only the root endpoint is considered.
69
+ */
70
+ function inferNetworkMediumFromDiagnostics(node: Node, properties: PhysicalDeviceProperties) {
71
+ const wifiDiagnosticsType = node.behaviors.typeFor(WiFiNetworkDiagnosticsClient);
72
+ const wifi = wifiDiagnosticsType ? node.maybeStateOf(wifiDiagnosticsType) : undefined;
73
+ if (wifi?.bssid !== undefined && wifi.bssid !== null) {
74
+ properties.supportsWifi = true;
75
+ }
76
+
77
+ const threadDiagnosticsType = node.behaviors.typeFor(ThreadNetworkDiagnosticsClient);
78
+ const thread = threadDiagnosticsType ? node.maybeStateOf(threadDiagnosticsType) : undefined;
79
+ if (
80
+ thread !== undefined &&
81
+ (isPresent(thread.extendedPanId) ||
82
+ isPresent(thread.panId) ||
83
+ isPresent(thread.channel) ||
84
+ isPresent(thread.networkName))
85
+ ) {
86
+ properties.supportsThread = true;
87
+
88
+ // A null extended PAN ID leaves threadActive false. When Thread is the only medium the node reports we trust
89
+ // the remaining details instead, otherwise the node stays unclassified despite being reachable.
90
+ if (
91
+ !properties.supportsWifi &&
92
+ properties.wifiActive === undefined &&
93
+ properties.ethernetActive === undefined
94
+ ) {
95
+ properties.threadActive = true;
96
+ properties.threadChannel ??= thread.channel ?? undefined;
97
+ }
98
+ }
99
+ }
100
+
101
+ function isPresent(value: unknown) {
102
+ return value !== undefined && value !== null;
103
+ }
104
+
56
105
  // Device types are collected node-wide (including bridged endpoints behind an aggregator) so consumers such as the
57
106
  // subscription-interval policy can react to them. Power/network/thread properties describe the physical node itself,
58
107
  // so bridged endpoints (behindAggregator) must not contribute to them.
@@ -32,7 +32,6 @@ import {
32
32
  DeviceClassification,
33
33
  FeatureMap,
34
34
  GeneratedCommandList,
35
- Matter,
36
35
  type FeatureBitmap,
37
36
  } from "@matter/model";
38
37
  import { ReadScope, Val, type Read, type ReadResult } from "@matter/protocol";
@@ -689,6 +688,7 @@ export class ClientStructure {
689
688
  if (this.#commandFactory) {
690
689
  shape.commandFactory = this.#commandFactory;
691
690
  }
691
+ shape.matter = this.#node.matter;
692
692
  const behaviorType = PeerBehavior(shape);
693
693
 
694
694
  if (endpoint.lifecycle.isInstalled) {
@@ -730,7 +730,7 @@ export class ClientStructure {
730
730
  }
731
731
 
732
732
  let isApp = false;
733
- const model = Matter.deviceTypes(dt.deviceType);
733
+ const model = this.#node.matter.deviceTypes(dt.deviceType);
734
734
  if (model !== undefined) {
735
735
  isApp = DeviceClassification.isApplication(model.classification);
736
736
  }
@@ -917,7 +917,7 @@ export class ClientStructure {
917
917
  }
918
918
 
919
919
  // Try to resolve by looking up the cluster model by capitalized behavior name (e.g. "onOff" → "OnOff")
920
- const clusterModel = Matter.clusters(capitalize(behaviorId));
920
+ const clusterModel = this.#node.matter.clusters(capitalize(behaviorId));
921
921
  if (clusterModel) {
922
922
  return this.#clusterFor(endpoint, clusterModel.id as ClusterId);
923
923
  }
@@ -18,6 +18,7 @@ import {
18
18
  FeatureBitmap,
19
19
  FeatureSet,
20
20
  Matter,
21
+ MatterModel,
21
22
  type ValueModel,
22
23
  } from "@matter/model";
23
24
  import { AttributeId, ClusterId, CommandId } from "@matter/types";
@@ -25,7 +26,10 @@ import { ClientCommandMethod } from "./ClientCommandMethod.js";
25
26
 
26
27
  const BIT_BLOCK_SIZE = Math.log2(Number.MAX_SAFE_INTEGER);
27
28
 
28
- const discoveredCaches = new Map<ClusterBehaviorType.CommandFactory, Record<string, ClusterBehavior.Type>>();
29
+ const discoveredCaches = new Map<
30
+ ClusterBehaviorType.CommandFactory,
31
+ WeakMap<MatterModel, Record<string, ClusterBehavior.Type>>
32
+ >();
29
33
  const knownCaches = new Map<ClusterBehaviorType.CommandFactory, WeakMap<ClusterBehavior.Type, ClusterBehavior.Type>>();
30
34
 
31
35
  const isPeer = Symbol("is-peer");
@@ -73,6 +77,12 @@ export namespace PeerBehavior {
73
77
  commands?: CommandId[];
74
78
  generatedCommands?: CommandId[];
75
79
  commandFactory?: ClusterBehaviorType.CommandFactory;
80
+
81
+ /**
82
+ * Model used to resolve standard element names/shapes. Defaults to the global {@link Matter} model; a client
83
+ * node supplies its own so custom or extended clusters resolve to real names.
84
+ */
85
+ matter?: MatterModel;
76
86
  }
77
87
 
78
88
  /**
@@ -86,12 +96,17 @@ export namespace PeerBehavior {
86
96
  }
87
97
 
88
98
  function instrumentDiscoveredShape(shape: PeerBehavior.DiscoveredClusterShape) {
89
- const analysis = DiscoveredShapeAnalysis(shape);
99
+ const matter = shape.matter ?? Matter;
100
+ const analysis = DiscoveredShapeAnalysis(shape, matter);
90
101
  const factory = shape.commandFactory ?? ClientCommandMethod;
91
102
 
92
- let cache = discoveredCaches.get(factory);
103
+ let byModel = discoveredCaches.get(factory);
104
+ if (!byModel) {
105
+ discoveredCaches.set(factory, (byModel = new WeakMap()));
106
+ }
107
+ let cache = byModel.get(matter);
93
108
  if (!cache) {
94
- discoveredCaches.set(factory, (cache = {}));
109
+ byModel.set(matter, (cache = {}));
95
110
  }
96
111
 
97
112
  const fingerprint = createFingerprint(analysis);
@@ -102,7 +117,7 @@ function instrumentDiscoveredShape(shape: PeerBehavior.DiscoveredClusterShape) {
102
117
 
103
118
  // Find a base behavior for the standard cluster, if available
104
119
  let baseType: Behavior.Type | undefined;
105
- const standardSchema = Matter.get(ClusterModel, shape.id);
120
+ const standardSchema = matter.get(ClusterModel, shape.id);
106
121
  if (standardSchema) {
107
122
  // Create a base behavior from the standard schema
108
123
  baseType = ClusterBehaviorType({
@@ -360,8 +375,11 @@ interface DiscoveredShapeAnalysis {
360
375
  /**
361
376
  * Analyze a discovered cluster shape to determine how we should override the behavior and schema.
362
377
  */
363
- function DiscoveredShapeAnalysis(shape: PeerBehavior.DiscoveredClusterShape): DiscoveredShapeAnalysis {
364
- const standardCluster = Matter.clusters(shape.id);
378
+ function DiscoveredShapeAnalysis(
379
+ shape: PeerBehavior.DiscoveredClusterShape,
380
+ matter: MatterModel = Matter,
381
+ ): DiscoveredShapeAnalysis {
382
+ const standardCluster = matter.clusters(shape.id);
365
383
  const schema =
366
384
  standardCluster ??
367
385
  new ClusterModel({ id: shape.id, name: createUnknownName("Cluster", shape.id), revision: shape.revision });