@matter/node 0.16.0-alpha.0-20250820-24939dd26 → 0.16.0-alpha.0-20250821-dd03e1003
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/cjs/behavior/Events.d.ts +5 -0
- package/dist/cjs/behavior/Events.d.ts.map +1 -1
- package/dist/cjs/behavior/Events.js +7 -4
- package/dist/cjs/behavior/Events.js.map +1 -1
- package/dist/cjs/behavior/Transitions.d.ts.map +1 -1
- package/dist/cjs/behavior/Transitions.js +2 -2
- package/dist/cjs/behavior/Transitions.js.map +1 -1
- package/dist/cjs/behavior/context/ActionContext.d.ts +0 -7
- package/dist/cjs/behavior/context/ActionContext.d.ts.map +1 -1
- package/dist/cjs/behavior/context/server/ContextAgents.d.ts +4 -5
- package/dist/cjs/behavior/context/server/ContextAgents.d.ts.map +1 -1
- package/dist/cjs/behavior/context/server/ContextAgents.js +9 -0
- package/dist/cjs/behavior/context/server/ContextAgents.js.map +1 -1
- package/dist/cjs/behavior/context/server/OfflineContext.d.ts.map +1 -1
- package/dist/cjs/behavior/context/server/OfflineContext.js +0 -8
- package/dist/cjs/behavior/context/server/OfflineContext.js.map +1 -1
- package/dist/cjs/behavior/context/server/OnlineContext.d.ts.map +1 -1
- package/dist/cjs/behavior/context/server/OnlineContext.js +0 -8
- package/dist/cjs/behavior/context/server/OnlineContext.js.map +1 -1
- package/dist/cjs/behavior/internal/Reactors.js +1 -1
- package/dist/cjs/behavior/internal/Reactors.js.map +1 -1
- package/dist/cjs/behavior/system/network/NetworkClient.d.ts.map +1 -1
- package/dist/cjs/behavior/system/network/NetworkClient.js +1 -0
- package/dist/cjs/behavior/system/network/NetworkClient.js.map +1 -1
- package/dist/cjs/behavior/system/parts/PartsBehavior.js +1 -1
- package/dist/cjs/behavior/system/parts/PartsBehavior.js.map +1 -1
- package/dist/cjs/behavior/system/product-description/ProductDescriptionServer.js +1 -1
- package/dist/cjs/behavior/system/product-description/ProductDescriptionServer.js.map +1 -1
- package/dist/cjs/behaviors/general-commissioning/ServerNodeFailsafeContext.js +1 -1
- package/dist/cjs/behaviors/general-commissioning/ServerNodeFailsafeContext.js.map +1 -1
- package/dist/cjs/behaviors/groups/GroupsServer.js +4 -4
- package/dist/cjs/behaviors/groups/GroupsServer.js.map +1 -1
- package/dist/cjs/endpoint/Agent.js +1 -1
- package/dist/cjs/endpoint/Agent.js.map +1 -1
- package/dist/cjs/endpoint/Endpoint.d.ts +9 -0
- package/dist/cjs/endpoint/Endpoint.d.ts.map +1 -1
- package/dist/cjs/endpoint/Endpoint.js +12 -1
- package/dist/cjs/endpoint/Endpoint.js.map +1 -1
- package/dist/cjs/endpoint/properties/Behaviors.js +3 -3
- package/dist/cjs/endpoint/properties/Behaviors.js.map +1 -1
- package/dist/cjs/endpoint/properties/Commands.js +1 -1
- package/dist/cjs/endpoint/properties/Commands.js.map +1 -1
- package/dist/cjs/node/ClientNode.d.ts.map +1 -1
- package/dist/cjs/node/ClientNode.js +2 -0
- package/dist/cjs/node/ClientNode.js.map +1 -1
- package/dist/cjs/node/client/ClientBehavior.js +11 -4
- package/dist/cjs/node/client/ClientBehavior.js.map +1 -1
- package/dist/cjs/node/client/ClientEventEmitter.d.ts +19 -0
- package/dist/cjs/node/client/ClientEventEmitter.d.ts.map +1 -0
- package/dist/cjs/node/client/ClientEventEmitter.js +97 -0
- package/dist/cjs/node/client/ClientEventEmitter.js.map +6 -0
- package/dist/cjs/node/client/ClientStructure.d.ts +7 -3
- package/dist/cjs/node/client/ClientStructure.d.ts.map +1 -1
- package/dist/cjs/node/client/ClientStructure.js +42 -22
- package/dist/cjs/node/client/ClientStructure.js.map +1 -1
- package/dist/cjs/node/server/IdentityService.js +1 -1
- package/dist/cjs/node/server/IdentityService.js.map +1 -1
- package/dist/cjs/node/server/ProtocolService.js +1 -1
- package/dist/cjs/node/server/ProtocolService.js.map +1 -1
- package/dist/esm/behavior/Events.d.ts +5 -0
- package/dist/esm/behavior/Events.d.ts.map +1 -1
- package/dist/esm/behavior/Events.js +8 -4
- package/dist/esm/behavior/Events.js.map +1 -1
- package/dist/esm/behavior/Transitions.d.ts.map +1 -1
- package/dist/esm/behavior/Transitions.js +2 -2
- package/dist/esm/behavior/Transitions.js.map +1 -1
- package/dist/esm/behavior/context/ActionContext.d.ts +0 -7
- package/dist/esm/behavior/context/ActionContext.d.ts.map +1 -1
- package/dist/esm/behavior/context/server/ContextAgents.d.ts +4 -5
- package/dist/esm/behavior/context/server/ContextAgents.d.ts.map +1 -1
- package/dist/esm/behavior/context/server/ContextAgents.js +9 -0
- package/dist/esm/behavior/context/server/ContextAgents.js.map +1 -1
- package/dist/esm/behavior/context/server/OfflineContext.d.ts.map +1 -1
- package/dist/esm/behavior/context/server/OfflineContext.js +0 -8
- package/dist/esm/behavior/context/server/OfflineContext.js.map +1 -1
- package/dist/esm/behavior/context/server/OnlineContext.d.ts.map +1 -1
- package/dist/esm/behavior/context/server/OnlineContext.js +0 -8
- package/dist/esm/behavior/context/server/OnlineContext.js.map +1 -1
- package/dist/esm/behavior/internal/Reactors.js +1 -1
- package/dist/esm/behavior/internal/Reactors.js.map +1 -1
- package/dist/esm/behavior/system/network/NetworkClient.d.ts.map +1 -1
- package/dist/esm/behavior/system/network/NetworkClient.js +1 -0
- package/dist/esm/behavior/system/network/NetworkClient.js.map +1 -1
- package/dist/esm/behavior/system/parts/PartsBehavior.js +1 -1
- package/dist/esm/behavior/system/parts/PartsBehavior.js.map +1 -1
- package/dist/esm/behavior/system/product-description/ProductDescriptionServer.js +1 -1
- package/dist/esm/behavior/system/product-description/ProductDescriptionServer.js.map +1 -1
- package/dist/esm/behaviors/general-commissioning/ServerNodeFailsafeContext.js +1 -1
- package/dist/esm/behaviors/general-commissioning/ServerNodeFailsafeContext.js.map +1 -1
- package/dist/esm/behaviors/groups/GroupsServer.js +4 -4
- package/dist/esm/behaviors/groups/GroupsServer.js.map +1 -1
- package/dist/esm/endpoint/Agent.js +1 -1
- package/dist/esm/endpoint/Agent.js.map +1 -1
- package/dist/esm/endpoint/Endpoint.d.ts +9 -0
- package/dist/esm/endpoint/Endpoint.d.ts.map +1 -1
- package/dist/esm/endpoint/Endpoint.js +12 -1
- package/dist/esm/endpoint/Endpoint.js.map +1 -1
- package/dist/esm/endpoint/properties/Behaviors.js +3 -3
- package/dist/esm/endpoint/properties/Behaviors.js.map +1 -1
- package/dist/esm/endpoint/properties/Commands.js +1 -1
- package/dist/esm/endpoint/properties/Commands.js.map +1 -1
- package/dist/esm/node/ClientNode.d.ts.map +1 -1
- package/dist/esm/node/ClientNode.js +2 -0
- package/dist/esm/node/ClientNode.js.map +1 -1
- package/dist/esm/node/client/ClientBehavior.js +11 -4
- package/dist/esm/node/client/ClientBehavior.js.map +1 -1
- package/dist/esm/node/client/ClientEventEmitter.d.ts +19 -0
- package/dist/esm/node/client/ClientEventEmitter.d.ts.map +1 -0
- package/dist/esm/node/client/ClientEventEmitter.js +77 -0
- package/dist/esm/node/client/ClientEventEmitter.js.map +6 -0
- package/dist/esm/node/client/ClientStructure.d.ts +7 -3
- package/dist/esm/node/client/ClientStructure.d.ts.map +1 -1
- package/dist/esm/node/client/ClientStructure.js +42 -22
- package/dist/esm/node/client/ClientStructure.js.map +1 -1
- package/dist/esm/node/server/IdentityService.js +1 -1
- package/dist/esm/node/server/IdentityService.js.map +1 -1
- package/dist/esm/node/server/ProtocolService.js +1 -1
- package/dist/esm/node/server/ProtocolService.js.map +1 -1
- package/package.json +7 -7
- package/src/behavior/Events.ts +19 -6
- package/src/behavior/Transitions.ts +7 -6
- package/src/behavior/context/ActionContext.ts +0 -6
- package/src/behavior/context/server/ContextAgents.ts +17 -1
- package/src/behavior/context/server/OfflineContext.ts +0 -11
- package/src/behavior/context/server/OnlineContext.ts +0 -12
- package/src/behavior/internal/Reactors.ts +1 -1
- package/src/behavior/system/network/NetworkClient.ts +1 -0
- package/src/behavior/system/parts/PartsBehavior.ts +1 -1
- package/src/behavior/system/product-description/ProductDescriptionServer.ts +1 -1
- package/src/behaviors/general-commissioning/ServerNodeFailsafeContext.ts +1 -1
- package/src/behaviors/groups/GroupsServer.ts +4 -4
- package/src/endpoint/Agent.ts +1 -1
- package/src/endpoint/Endpoint.ts +14 -1
- package/src/endpoint/properties/Behaviors.ts +3 -3
- package/src/endpoint/properties/Commands.ts +1 -1
- package/src/node/ClientNode.ts +4 -1
- package/src/node/client/ClientBehavior.ts +12 -5
- package/src/node/client/ClientEventEmitter.ts +115 -0
- package/src/node/client/ClientStructure.ts +58 -34
- package/src/node/server/IdentityService.ts +1 -1
- package/src/node/server/ProtocolService.ts +1 -1
|
@@ -116,7 +116,7 @@ function inferDeviceType(agent: Agent): DeviceTypeId | undefined {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
for (const child of agent.endpoint.parts) {
|
|
119
|
-
const deviceType = inferDeviceType(agent.context
|
|
119
|
+
const deviceType = inferDeviceType(child.agentFor(agent.context));
|
|
120
120
|
if (deviceType !== undefined) {
|
|
121
121
|
return deviceType;
|
|
122
122
|
}
|
|
@@ -81,7 +81,7 @@ export class ServerNodeFailsafeContext extends FailsafeContext {
|
|
|
81
81
|
await this.#node.visit(async endpoint => {
|
|
82
82
|
const networks = this.#storedState?.networks.get(endpoint);
|
|
83
83
|
if (networks) {
|
|
84
|
-
|
|
84
|
+
endpoint.agentFor(context).get(NetworkCommissioningBehavior).state.networks = [...networks];
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
});
|
|
@@ -67,7 +67,7 @@ export class GroupsServer extends GroupsBase {
|
|
|
67
67
|
|
|
68
68
|
// We need to search the root here ourselves because we cannot include ServerNode because else we generate a
|
|
69
69
|
// circular dependency
|
|
70
|
-
#rootEndpoint(): Endpoint<RootEndpoint> {
|
|
70
|
+
get #rootEndpoint(): Endpoint<RootEndpoint> {
|
|
71
71
|
const rootEndpoint = this.endpoint.ownerOfType(RootEndpoint);
|
|
72
72
|
if (rootEndpoint === undefined) {
|
|
73
73
|
throw new InternalError("RootEndpoint not found");
|
|
@@ -76,7 +76,7 @@ export class GroupsServer extends GroupsBase {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async #actOnGroupKeyManagement<T>(act: (groupKeyManagement: GroupKeyManagementServer) => T): Promise<T> {
|
|
79
|
-
const agent = this.
|
|
79
|
+
const agent = this.#rootEndpoint.agentFor(this.context);
|
|
80
80
|
const gkm = agent.get(GroupKeyManagementServer);
|
|
81
81
|
await agent.context.transaction.addResources(gkm);
|
|
82
82
|
await agent.context.transaction.begin();
|
|
@@ -122,7 +122,7 @@ export class GroupsServer extends GroupsBase {
|
|
|
122
122
|
const fabricIndex = fabric.fabricIndex;
|
|
123
123
|
const endpointNumber = this.endpoint.number;
|
|
124
124
|
|
|
125
|
-
const { groupTable } = this.#rootEndpoint
|
|
125
|
+
const { groupTable } = this.#rootEndpoint.stateOf(GroupKeyManagementServer);
|
|
126
126
|
const groupEntry = groupTable.find(entry => entry.groupId === groupId && entry.fabricIndex === fabricIndex);
|
|
127
127
|
if (groupEntry === undefined || !groupEntry.endpoints.includes(endpointNumber)) {
|
|
128
128
|
return { status: StatusCode.NotFound, groupId, groupName: "" };
|
|
@@ -137,7 +137,7 @@ export class GroupsServer extends GroupsBase {
|
|
|
137
137
|
const fabricIndex = fabric.fabricIndex;
|
|
138
138
|
const endpointNumber = this.endpoint.number;
|
|
139
139
|
|
|
140
|
-
const { groupTable } = this.#rootEndpoint
|
|
140
|
+
const { groupTable } = this.#rootEndpoint.stateOf(GroupKeyManagementServer);
|
|
141
141
|
const endpointGroups = groupTable.filter(
|
|
142
142
|
entry => entry.endpoints.includes(endpointNumber) && entry.fabricIndex === fabricIndex,
|
|
143
143
|
);
|
package/src/endpoint/Agent.ts
CHANGED
package/src/endpoint/Endpoint.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Behavior } from "#behavior/Behavior.js";
|
|
8
|
+
import { ActionContext } from "#behavior/context/ActionContext.js";
|
|
8
9
|
import { NodeActivity } from "#behavior/context/NodeActivity.js";
|
|
10
|
+
import { ContextAgents } from "#behavior/context/server/ContextAgents.js";
|
|
9
11
|
import { OfflineContext } from "#behavior/context/server/OfflineContext.js";
|
|
10
12
|
import {
|
|
11
13
|
Construction,
|
|
@@ -112,6 +114,17 @@ export class Endpoint<T extends EndpointType = EndpointType.Empty> {
|
|
|
112
114
|
return this.#owner;
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Access an {@link Agent} for this endpoint.
|
|
119
|
+
*
|
|
120
|
+
* An {@link Agent} allows you to interact directly with the behaviors supported by the endpoint. Normally you
|
|
121
|
+
* would use {@link act} to obtain an agent but {@link agentFor} is useful if you need to interact with multiple
|
|
122
|
+
* endpoints in the same context.
|
|
123
|
+
*/
|
|
124
|
+
agentFor<T extends EndpointType>(this: Endpoint<T>, context: ActionContext) {
|
|
125
|
+
return ContextAgents(context).agentFor(this);
|
|
126
|
+
}
|
|
127
|
+
|
|
115
128
|
get endpointProtocol() {
|
|
116
129
|
if (this.#number === undefined || !this.env.has(ProtocolService)) {
|
|
117
130
|
return undefined;
|
|
@@ -599,7 +612,7 @@ export class Endpoint<T extends EndpointType = EndpointType.Empty> {
|
|
|
599
612
|
return OfflineContext.act(
|
|
600
613
|
purpose,
|
|
601
614
|
context => {
|
|
602
|
-
return actor(
|
|
615
|
+
return actor(this.agentFor(context));
|
|
603
616
|
},
|
|
604
617
|
{ activity: this.#activity },
|
|
605
618
|
);
|
|
@@ -186,7 +186,7 @@ export class Behaviors {
|
|
|
186
186
|
|
|
187
187
|
// Initialization action. We initialize all behaviors in the same transaction
|
|
188
188
|
const initializeBehaviors = (context: ActionContext): MaybePromise => {
|
|
189
|
-
const agent =
|
|
189
|
+
const agent = this.#endpoint.agentFor(context);
|
|
190
190
|
|
|
191
191
|
// Activate behaviors
|
|
192
192
|
//
|
|
@@ -377,7 +377,7 @@ export class Behaviors {
|
|
|
377
377
|
*/
|
|
378
378
|
async close() {
|
|
379
379
|
const dispose = async (context: ActionContext) => {
|
|
380
|
-
const agent =
|
|
380
|
+
const agent = this.#endpoint.agentFor(context);
|
|
381
381
|
|
|
382
382
|
let destroyNow = new Set(Object.keys(this.#backings));
|
|
383
383
|
while (destroyNow.size) {
|
|
@@ -565,7 +565,7 @@ export class Behaviors {
|
|
|
565
565
|
const result = OfflineContext.act(
|
|
566
566
|
"behavior-late-activation",
|
|
567
567
|
context => {
|
|
568
|
-
this.activate(type,
|
|
568
|
+
this.activate(type, this.#endpoint.agentFor(context));
|
|
569
569
|
|
|
570
570
|
// Agent must remain active until backing is initialized
|
|
571
571
|
const backing = this.#backingFor(type);
|
|
@@ -67,7 +67,7 @@ function Implementation(endpoint: Endpoint, type: Behavior.Type, name: string):
|
|
|
67
67
|
|
|
68
68
|
// Create function to perform invocation
|
|
69
69
|
function invokerFor(context: ActionContext) {
|
|
70
|
-
const agent =
|
|
70
|
+
const agent = endpoint.agentFor(context);
|
|
71
71
|
const behavior = agent.get(type);
|
|
72
72
|
const method = (behavior as unknown as Record<string, Commands.Command>)[name];
|
|
73
73
|
if (typeof method !== "function") {
|
package/src/node/ClientNode.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { NetworkRuntime } from "#behavior/system/network/NetworkRuntime.js";
|
|
|
12
12
|
import { Agent } from "#endpoint/Agent.js";
|
|
13
13
|
import { EndpointInitializer } from "#endpoint/properties/EndpointInitializer.js";
|
|
14
14
|
import { Identity, Lifecycle, MaybePromise } from "#general";
|
|
15
|
-
import { Interactable } from "#protocol";
|
|
15
|
+
import { Interactable, OccurrenceManager } from "#protocol";
|
|
16
16
|
import { ClientNodeStore } from "#storage/client/ClientNodeStore.js";
|
|
17
17
|
import { RemoteWriter } from "#storage/client/RemoteWriter.js";
|
|
18
18
|
import { ServerNodeStore } from "#storage/server/ServerNodeStore.js";
|
|
@@ -41,6 +41,9 @@ export class ClientNode extends Node<ClientNode.RootEndpoint> {
|
|
|
41
41
|
|
|
42
42
|
super(opts);
|
|
43
43
|
|
|
44
|
+
// Block the OccurranceManager from parent environment so we don't attempt to record events from peers
|
|
45
|
+
this.env.block(OccurrenceManager);
|
|
46
|
+
|
|
44
47
|
this.env.set(Node, this);
|
|
45
48
|
this.env.set(ClientNode, this);
|
|
46
49
|
|
|
@@ -75,6 +75,7 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
let { schema } = analysis;
|
|
78
|
+
let isCloned = false;
|
|
78
79
|
const { extraAttrs, extraCommands } = analysis;
|
|
79
80
|
|
|
80
81
|
// Obtain a ClusterType. This provides TLV for known elements
|
|
@@ -99,8 +100,7 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
|
|
|
99
100
|
.map(([k]) => k);
|
|
100
101
|
if (featureNames.length) {
|
|
101
102
|
// Update ClusterModel
|
|
102
|
-
|
|
103
|
-
schema.supportedFeatures = featureNames;
|
|
103
|
+
cloneSchema();
|
|
104
104
|
|
|
105
105
|
// Update the cluster. Note that we do not validate feature combinations. What the device sends we work with
|
|
106
106
|
cluster = new ClusterComposer(cluster, true).compose(featureNames.map(capitalize));
|
|
@@ -109,7 +109,7 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
|
|
|
109
109
|
// If the schema does not match what the device actually returned, further augment both the ClusterModel and
|
|
110
110
|
// ClusterType with unknown attributes and/or commands
|
|
111
111
|
if (schema.revision !== analysis.shape.revision || extraAttrs.size || extraCommands.size) {
|
|
112
|
-
|
|
112
|
+
cloneSchema();
|
|
113
113
|
|
|
114
114
|
cluster = {
|
|
115
115
|
...cluster,
|
|
@@ -118,8 +118,6 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
|
|
|
118
118
|
commands: { ...cluster.commands },
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
schema.supportedFeatures = supportedFeatures;
|
|
122
|
-
|
|
123
121
|
for (const id of extraAttrs) {
|
|
124
122
|
const name = createUnknownName("attr", id);
|
|
125
123
|
cluster.attributes[camelize(name, false)] = Attribute(id, TlvAny);
|
|
@@ -145,6 +143,15 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
|
|
|
145
143
|
|
|
146
144
|
return type;
|
|
147
145
|
|
|
146
|
+
function cloneSchema() {
|
|
147
|
+
if (isCloned) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
schema = schema.clone();
|
|
151
|
+
schema.supportedFeatures = featureNames;
|
|
152
|
+
isCloned = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
148
155
|
function implementCommand(command: ClusterType.Command) {
|
|
149
156
|
return async function (this: ClusterBehavior, fields?: {}) {
|
|
150
157
|
const node = this.env.get(Node) as ClientNode;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2025 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ElementEvent, Events } from "#behavior/Events.js";
|
|
8
|
+
import { camelize, Logger } from "#general";
|
|
9
|
+
import { ClusterModel, EventModel, MatterModel } from "#model";
|
|
10
|
+
import { ClientNode } from "#node/ClientNode.js";
|
|
11
|
+
import { ReadResult } from "#protocol";
|
|
12
|
+
import { ClusterId, EndpointNumber, EventId } from "#types";
|
|
13
|
+
import { ClientStructure } from "./ClientStructure.js";
|
|
14
|
+
|
|
15
|
+
const logger = Logger.get("ClientEventEmitter");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Event handler for Matter events transmitted by a peer.
|
|
19
|
+
*
|
|
20
|
+
* TODO - set priority on context when split for server vs. client
|
|
21
|
+
* TODO - record latest event number for each subscription shape (or just wildcard?)
|
|
22
|
+
*/
|
|
23
|
+
export interface ClientEventEmitter {
|
|
24
|
+
(event: ReadResult.EventValue): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Cache of MatterModel + cluster + event ID -> event name.
|
|
29
|
+
*/
|
|
30
|
+
const nameCache = new WeakMap<
|
|
31
|
+
MatterModel,
|
|
32
|
+
Record<`${ClusterId}-${EventId}`, undefined | { cluster: string; event: string }>
|
|
33
|
+
>();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* We warn for each cluster or cluster+event that we don't support.
|
|
37
|
+
*/
|
|
38
|
+
const warnedForUnknown = new Set<ClusterId | `${ClusterId}-${EventId}`>();
|
|
39
|
+
|
|
40
|
+
export function ClientEventEmitter(node: ClientNode, structure: ClientStructure) {
|
|
41
|
+
return emitClientEvent;
|
|
42
|
+
|
|
43
|
+
function emitClientEvent(occurrence: ReadResult.EventValue) {
|
|
44
|
+
const names = getNames(node.matter, occurrence);
|
|
45
|
+
if (!names) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const event = getEvent(occurrence.path.endpointId, names.cluster, names.event);
|
|
50
|
+
if (event) {
|
|
51
|
+
node.act(agent => {
|
|
52
|
+
// Current ActionContext is not writable, could skip act() but meh, see TODO above
|
|
53
|
+
//agent.context.priority = occurrence.priority;
|
|
54
|
+
event.emit(occurrence.value, agent.context);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getEvent(endpointId: EndpointNumber, clusterName: string, eventName: string) {
|
|
60
|
+
const endpoint = structure.endpointFor(endpointId);
|
|
61
|
+
if (endpoint === undefined) {
|
|
62
|
+
logger.error(`Received event for unsupported endpoint #${endpointId}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const events = (endpoint.events as Events.Generic<ElementEvent>)[clusterName];
|
|
67
|
+
if (events === undefined) {
|
|
68
|
+
logger.error(`Received event ${eventName} for unsupported cluster ${clusterName} on ${endpoint}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const event = events[eventName];
|
|
73
|
+
if (event === undefined) {
|
|
74
|
+
logger.error(`Received unsupported event ${eventName} for cluster ${clusterName} on ${endpoint}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return event;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getNames(matter: MatterModel, { path: { clusterId, eventId } }: ReadResult.EventValue) {
|
|
83
|
+
let matterCache = nameCache.get(matter);
|
|
84
|
+
if (matterCache === undefined) {
|
|
85
|
+
matterCache = {};
|
|
86
|
+
nameCache.set(matter, matterCache);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const key = `${clusterId}-${eventId}` as const;
|
|
90
|
+
if (key in matterCache) {
|
|
91
|
+
return matterCache[key];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const cluster = matter.get(ClusterModel, clusterId);
|
|
95
|
+
if (cluster === undefined) {
|
|
96
|
+
if (!warnedForUnknown.has(clusterId)) {
|
|
97
|
+
logger.warn(`Ignoring events for unknown cluster #${clusterId}`);
|
|
98
|
+
warnedForUnknown.add(clusterId);
|
|
99
|
+
matterCache[key] = undefined;
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const event = cluster.get(EventModel, eventId);
|
|
105
|
+
if (event === undefined) {
|
|
106
|
+
if (!warnedForUnknown.has(key)) {
|
|
107
|
+
logger.warn(`Ignoring unknown event #${eventId} for ${cluster.name} cluster`);
|
|
108
|
+
warnedForUnknown.add(key);
|
|
109
|
+
matterCache[key] = undefined;
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (matterCache[key] = { cluster: camelize(cluster.name), event: camelize(event.name) });
|
|
115
|
+
}
|
|
@@ -17,6 +17,7 @@ import { DatasourceCache } from "#storage/client/DatasourceCache.js";
|
|
|
17
17
|
import { ClientNodeStore } from "#storage/index.js";
|
|
18
18
|
import type { AttributeId, ClusterId, ClusterType, CommandId, DeviceTypeId, EndpointNumber } from "#types";
|
|
19
19
|
import { ClientBehavior } from "./ClientBehavior.js";
|
|
20
|
+
import { ClientEventEmitter } from "./ClientEventEmitter.js";
|
|
20
21
|
|
|
21
22
|
const DEVICE_TYPE_LIST_ATTR_ID = DescriptorCluster.attributes.deviceTypeList.id;
|
|
22
23
|
const SERVER_LIST_ATTR_ID = DescriptorCluster.attributes.serverList.id;
|
|
@@ -28,6 +29,7 @@ const PARTS_LIST_ATTR_ID = DescriptorCluster.attributes.partsList.id;
|
|
|
28
29
|
export class ClientStructure {
|
|
29
30
|
#nodeStore: ClientNodeStore;
|
|
30
31
|
#endpoints: Record<EndpointNumber, EndpointStructure> = {};
|
|
32
|
+
#emitEvent: ClientEventEmitter;
|
|
31
33
|
|
|
32
34
|
constructor(node: ClientNode) {
|
|
33
35
|
this.#nodeStore = node.env.get(ClientNodeStore);
|
|
@@ -35,6 +37,7 @@ export class ClientStructure {
|
|
|
35
37
|
endpoint: node,
|
|
36
38
|
clusters: {},
|
|
37
39
|
};
|
|
40
|
+
this.#emitEvent = ClientEventEmitter(node, this);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/**
|
|
@@ -76,9 +79,9 @@ export class ClientStructure {
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
/**
|
|
82
|
+
* Obtain the store for a non-cluster behavior.
|
|
79
83
|
*
|
|
80
|
-
*
|
|
81
|
-
* @returns
|
|
84
|
+
* The data for these behaviors is managed locally and not synced from the peer.
|
|
82
85
|
*/
|
|
83
86
|
storeForLocal(endpoint: Endpoint, type: Behavior.Type) {
|
|
84
87
|
return this.#nodeStore.storeForEndpoint(endpoint).createStoreForLocalBehavior(type.id);
|
|
@@ -132,39 +135,17 @@ export class ClientStructure {
|
|
|
132
135
|
|
|
133
136
|
for await (const chunk of changes) {
|
|
134
137
|
for (const change of chunk) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const { endpointId, clusterId, attributeId } = change.path;
|
|
138
|
+
switch (change.kind) {
|
|
139
|
+
case "attr-value":
|
|
140
|
+
currentUpdates = await this.#mutateAttribute(change, scope, currentUpdates);
|
|
141
|
+
break;
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
currentUpdates &&
|
|
145
|
-
(currentUpdates.endpointId !== endpointId || currentUpdates.clusterId !== clusterId)
|
|
146
|
-
) {
|
|
147
|
-
await this.#updateCluster(currentUpdates);
|
|
148
|
-
currentUpdates = undefined;
|
|
143
|
+
case "event-value":
|
|
144
|
+
this.#emitEvent(change);
|
|
145
|
+
break;
|
|
149
146
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// Updating a new endpoint/cluster
|
|
153
|
-
currentUpdates = {
|
|
154
|
-
endpointId,
|
|
155
|
-
clusterId,
|
|
156
|
-
values: {
|
|
157
|
-
[attributeId]: change.value,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// Update version but only if this was a wildcard read
|
|
162
|
-
if (scope.isWildcard(endpointId, clusterId)) {
|
|
163
|
-
currentUpdates.values[DatasourceCache.VERSION_KEY] = change.version;
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
// Add value to change set for current endpoint/cluster
|
|
167
|
-
currentUpdates.values[attributeId] = change.value;
|
|
147
|
+
if (change.kind !== "attr-value") {
|
|
148
|
+
continue;
|
|
168
149
|
}
|
|
169
150
|
}
|
|
170
151
|
|
|
@@ -176,8 +157,44 @@ export class ClientStructure {
|
|
|
176
157
|
}
|
|
177
158
|
}
|
|
178
159
|
|
|
160
|
+
async #mutateAttribute(
|
|
161
|
+
change: ReadResult.AttributeValue,
|
|
162
|
+
scope: ReadScope,
|
|
163
|
+
currentUpdates: undefined | AttributeUpdates,
|
|
164
|
+
) {
|
|
165
|
+
const { endpointId, clusterId, attributeId } = change.path;
|
|
166
|
+
|
|
167
|
+
// If we are building updates to a cluster and the cluster/endpoint changes, apply the current update
|
|
168
|
+
// set
|
|
169
|
+
if (currentUpdates && (currentUpdates.endpointId !== endpointId || currentUpdates.clusterId !== clusterId)) {
|
|
170
|
+
await this.#updateCluster(currentUpdates);
|
|
171
|
+
currentUpdates = undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (currentUpdates === undefined) {
|
|
175
|
+
// Updating a new endpoint/cluster
|
|
176
|
+
currentUpdates = {
|
|
177
|
+
endpointId,
|
|
178
|
+
clusterId,
|
|
179
|
+
values: {
|
|
180
|
+
[attributeId]: change.value,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Update version but only if this was a wildcard read
|
|
185
|
+
if (scope.isWildcard(endpointId, clusterId)) {
|
|
186
|
+
currentUpdates.values[DatasourceCache.VERSION_KEY] = change.version;
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
// Add value to change set for current endpoint/cluster
|
|
190
|
+
currentUpdates.values[attributeId] = change.value;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return currentUpdates;
|
|
194
|
+
}
|
|
195
|
+
|
|
179
196
|
/**
|
|
180
|
-
* Obtain the {@link ClusterType} for an
|
|
197
|
+
* Obtain the {@link ClusterType} for an {@link EndpointNumber} and {@link ClusterId}.
|
|
181
198
|
*/
|
|
182
199
|
clusterFor(endpoint: EndpointNumber, cluster: ClusterId) {
|
|
183
200
|
const ep = this.#endpointFor(endpoint);
|
|
@@ -188,6 +205,13 @@ export class ClientStructure {
|
|
|
188
205
|
return this.#clusterFor(ep, cluster)?.behavior?.cluster;
|
|
189
206
|
}
|
|
190
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Obtain the {@link Endpoint} for a {@link EndpointNumber}.
|
|
210
|
+
*/
|
|
211
|
+
endpointFor(endpoint: EndpointNumber): Endpoint | undefined {
|
|
212
|
+
return this.#endpoints[endpoint]?.endpoint;
|
|
213
|
+
}
|
|
214
|
+
|
|
191
215
|
/**
|
|
192
216
|
* Apply new attribute values for specific endpoint/cluster.
|
|
193
217
|
*
|
|
@@ -44,7 +44,7 @@ export class IdentityService {
|
|
|
44
44
|
other = this.#node;
|
|
45
45
|
} else {
|
|
46
46
|
if (this.#partsById === undefined) {
|
|
47
|
-
this.#partsById = OfflineContext.ReadOnly
|
|
47
|
+
this.#partsById = this.#node.agentFor(OfflineContext.ReadOnly).get(IndexBehavior).partsById;
|
|
48
48
|
}
|
|
49
49
|
other = this.#partsById?.[number];
|
|
50
50
|
}
|