@matter-server/ws-controller 0.6.2-alpha.0-20260426-e2eae3d → 0.6.2
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/esm/controller/ControllerCommandHandler.d.ts +1 -6
- package/dist/esm/controller/ControllerCommandHandler.d.ts.map +1 -1
- package/dist/esm/controller/ControllerCommandHandler.js +89 -225
- package/dist/esm/controller/ControllerCommandHandler.js.map +2 -2
- package/dist/esm/controller/Nodes.d.ts +1 -19
- package/dist/esm/controller/Nodes.d.ts.map +1 -1
- package/dist/esm/controller/Nodes.js +0 -31
- package/dist/esm/controller/Nodes.js.map +1 -1
- package/dist/esm/types/CommandHandler.d.ts +1 -117
- package/dist/esm/types/CommandHandler.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/controller/ControllerCommandHandler.ts +100 -248
- package/src/controller/Nodes.ts +1 -48
- package/src/types/CommandHandler.ts +1 -122
|
@@ -3,27 +3,10 @@
|
|
|
3
3
|
* Copyright 2025-2026 Open Home Foundation
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import { AttributeId, ClusterId,
|
|
6
|
+
import { AttributeId, ClusterId, Duration, NodeId } from "@matter/main";
|
|
7
7
|
import { CommissionableDeviceIdentifiers } from "@matter/main/protocol";
|
|
8
8
|
import { EndpointNumber, Status } from "@matter/main/types";
|
|
9
9
|
import { ControllerCommissioningFlowOptions } from "@matter/protocol";
|
|
10
|
-
export type ReadAttributeRequest = {
|
|
11
|
-
nodeId: NodeId;
|
|
12
|
-
/** Endpoint ID or undefined for wildcard */
|
|
13
|
-
endpointId?: EndpointNumber;
|
|
14
|
-
/** Cluster ID or undefined for wildcard */
|
|
15
|
-
clusterId?: ClusterId;
|
|
16
|
-
/** Attribute ID or undefined for wildcard */
|
|
17
|
-
attributeId?: AttributeId;
|
|
18
|
-
fabricFiltered?: boolean;
|
|
19
|
-
};
|
|
20
|
-
export type AttributeResponseData = {
|
|
21
|
-
clusterId: number;
|
|
22
|
-
attributeId: number;
|
|
23
|
-
endpointId: number;
|
|
24
|
-
dataVersion: number;
|
|
25
|
-
value: unknown;
|
|
26
|
-
};
|
|
27
10
|
export type AttributeResponseStatus = {
|
|
28
11
|
clusterId: number;
|
|
29
12
|
attributeId: number;
|
|
@@ -31,33 +14,6 @@ export type AttributeResponseStatus = {
|
|
|
31
14
|
status?: Status;
|
|
32
15
|
clusterStatus?: number;
|
|
33
16
|
};
|
|
34
|
-
export type ReadAttributeResponse = {
|
|
35
|
-
values: AttributeResponseData[];
|
|
36
|
-
status?: AttributeResponseStatus[];
|
|
37
|
-
};
|
|
38
|
-
export type ReadByIdRequest = {
|
|
39
|
-
nodeId: NodeId;
|
|
40
|
-
endpointId: EndpointNumber;
|
|
41
|
-
clusterId: ClusterId;
|
|
42
|
-
attributeId: AttributeId;
|
|
43
|
-
fabricFiltered?: boolean;
|
|
44
|
-
};
|
|
45
|
-
export type AttributeErrorResponseData = {
|
|
46
|
-
clusterId: number;
|
|
47
|
-
attributeId: number;
|
|
48
|
-
endpointId: number;
|
|
49
|
-
error: string;
|
|
50
|
-
};
|
|
51
|
-
export type ReadByIdResponse = AttributeErrorResponseData;
|
|
52
|
-
export type SubscribeAttributeRequest = ReadAttributeRequest & {
|
|
53
|
-
minInterval: number;
|
|
54
|
-
maxInterval: number;
|
|
55
|
-
changeListener: (data: AttributeResponseData) => void;
|
|
56
|
-
};
|
|
57
|
-
export type SubscribeAttributeResponse = {
|
|
58
|
-
values: AttributeResponseData[];
|
|
59
|
-
updated: Observable<[void]>;
|
|
60
|
-
};
|
|
61
17
|
export type WriteAttributeRequest = {
|
|
62
18
|
nodeId: NodeId;
|
|
63
19
|
endpointId: EndpointNumber;
|
|
@@ -65,47 +21,6 @@ export type WriteAttributeRequest = {
|
|
|
65
21
|
attributeId: AttributeId;
|
|
66
22
|
value: unknown;
|
|
67
23
|
};
|
|
68
|
-
export type WriteAttributeByIdRequest = {
|
|
69
|
-
nodeId: NodeId;
|
|
70
|
-
endpointId: EndpointNumber;
|
|
71
|
-
clusterId: ClusterId;
|
|
72
|
-
attributeId: AttributeId;
|
|
73
|
-
value: unknown;
|
|
74
|
-
};
|
|
75
|
-
export type ReadEventRequest = {
|
|
76
|
-
nodeId: NodeId;
|
|
77
|
-
endpointId: EndpointNumber;
|
|
78
|
-
clusterId: ClusterId;
|
|
79
|
-
eventId: EventId;
|
|
80
|
-
eventMin?: EventNumber;
|
|
81
|
-
};
|
|
82
|
-
export type EventResponseData = {
|
|
83
|
-
clusterId: number;
|
|
84
|
-
eventId: number;
|
|
85
|
-
endpointId: number;
|
|
86
|
-
eventNumber: number | bigint;
|
|
87
|
-
value: unknown;
|
|
88
|
-
};
|
|
89
|
-
export type EventResponseStatus = {
|
|
90
|
-
clusterId: number;
|
|
91
|
-
eventId: number;
|
|
92
|
-
endpointId: number;
|
|
93
|
-
status?: Status;
|
|
94
|
-
clusterStatus?: number;
|
|
95
|
-
};
|
|
96
|
-
export type ReadEventResponse = {
|
|
97
|
-
values: EventResponseData[];
|
|
98
|
-
status?: EventResponseStatus[];
|
|
99
|
-
};
|
|
100
|
-
export type SubscribeEventRequest = ReadEventRequest & {
|
|
101
|
-
minInterval: number;
|
|
102
|
-
maxInterval: number;
|
|
103
|
-
changeListener: (data: EventResponseData) => void;
|
|
104
|
-
};
|
|
105
|
-
export type SubscribeEventResponse = {
|
|
106
|
-
values: EventResponseData[];
|
|
107
|
-
updated: Observable<[void]>;
|
|
108
|
-
};
|
|
109
24
|
export type InvokeRequest = {
|
|
110
25
|
nodeId: NodeId;
|
|
111
26
|
endpointId: EndpointNumber;
|
|
@@ -115,24 +30,6 @@ export type InvokeRequest = {
|
|
|
115
30
|
timedInteractionTimeoutMs?: Duration;
|
|
116
31
|
interactionTimeoutMs?: Duration;
|
|
117
32
|
};
|
|
118
|
-
export type InvokeResponse = {
|
|
119
|
-
clusterId: number;
|
|
120
|
-
commandId?: number;
|
|
121
|
-
endpointId: number;
|
|
122
|
-
value?: unknown;
|
|
123
|
-
};
|
|
124
|
-
export type InvokeByIdRequest = {
|
|
125
|
-
nodeId: NodeId;
|
|
126
|
-
endpointId: EndpointNumber;
|
|
127
|
-
clusterId: ClusterId;
|
|
128
|
-
commandId: CommandId;
|
|
129
|
-
data: unknown;
|
|
130
|
-
timedInteractionTimeoutMs?: number;
|
|
131
|
-
};
|
|
132
|
-
export type DelayRequest = {
|
|
133
|
-
nodeId?: NodeId;
|
|
134
|
-
expireExistingSession?: boolean;
|
|
135
|
-
};
|
|
136
33
|
export type CommissioningRequest = {
|
|
137
34
|
nodeId?: NodeId;
|
|
138
35
|
knownAddress?: {
|
|
@@ -187,19 +84,6 @@ export type DiscoveryResponse = {
|
|
|
187
84
|
mrpSessionIdleInterval?: number;
|
|
188
85
|
mrpSessionActiveInterval?: number;
|
|
189
86
|
}[];
|
|
190
|
-
export type RootCertificateResponse = {
|
|
191
|
-
RCAC: Uint8Array;
|
|
192
|
-
};
|
|
193
|
-
export type IssueNocChainRequest = {
|
|
194
|
-
elements: Uint8Array;
|
|
195
|
-
nodeId: NodeId;
|
|
196
|
-
};
|
|
197
|
-
export type IssueNocChainResponse = {
|
|
198
|
-
ICAC?: Uint8Array;
|
|
199
|
-
IPK: Uint8Array;
|
|
200
|
-
NOC: Uint8Array;
|
|
201
|
-
RCAC: Uint8Array;
|
|
202
|
-
};
|
|
203
87
|
export type OpenCommissioningWindowRequest = {
|
|
204
88
|
nodeId: NodeId;
|
|
205
89
|
timeout?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandHandler.d.ts","sourceRoot":"","sources":["../../../src/types/CommandHandler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"CommandHandler.d.ts","sourceRoot":"","sources":["../../../src/types/CommandHandler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matter-server/ws-controller",
|
|
3
|
-
"version": "0.6.2
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "matter.js based Matter controller library",
|
|
6
6
|
"bugs": {
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"node": ">=20.19.0 <22.0.0 || >=22.13.0"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@matter/nodejs-ble": "0.17.0-alpha.0-
|
|
37
|
+
"@matter/nodejs-ble": "0.17.0-alpha.0-20260426-5bf9dab53"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@matter/main": "0.17.0-alpha.0-
|
|
41
|
-
"@matter-server/ws-client": "0.6.2
|
|
42
|
-
"@project-chip/matter.js": "0.17.0-alpha.0-
|
|
40
|
+
"@matter/main": "0.17.0-alpha.0-20260426-5bf9dab53",
|
|
41
|
+
"@matter-server/ws-client": "0.6.2",
|
|
42
|
+
"@project-chip/matter.js": "0.17.0-alpha.0-20260426-5bf9dab53",
|
|
43
43
|
"ws": "^8.20.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
AsyncObservable,
|
|
10
10
|
camelize,
|
|
11
11
|
ClientNode,
|
|
12
|
-
ClientNodeInteraction,
|
|
13
12
|
CommissioningClient,
|
|
14
13
|
FabricId,
|
|
15
14
|
FabricIndex,
|
|
@@ -84,12 +83,6 @@ import {
|
|
|
84
83
|
MatterNodeData,
|
|
85
84
|
OpenCommissioningWindowRequest,
|
|
86
85
|
OpenCommissioningWindowResponse,
|
|
87
|
-
ReadEventRequest,
|
|
88
|
-
ReadEventResponse,
|
|
89
|
-
SubscribeAttributeRequest,
|
|
90
|
-
SubscribeAttributeResponse,
|
|
91
|
-
SubscribeEventRequest,
|
|
92
|
-
SubscribeEventResponse,
|
|
93
86
|
WriteAttributeRequest,
|
|
94
87
|
} from "../types/CommandHandler.js";
|
|
95
88
|
import {
|
|
@@ -468,7 +461,7 @@ export class ControllerCommandHandler {
|
|
|
468
461
|
}),
|
|
469
462
|
includeKnownVersions: true, // do not send DataVersionFilters, so we do a new clean read
|
|
470
463
|
};
|
|
471
|
-
for await (const _chunk of
|
|
464
|
+
for await (const _chunk of node.node.interaction.read(read));
|
|
472
465
|
}
|
|
473
466
|
|
|
474
467
|
/**
|
|
@@ -520,42 +513,44 @@ export class ControllerCommandHandler {
|
|
|
520
513
|
fabricFiltered = false,
|
|
521
514
|
): Promise<AttributesData> {
|
|
522
515
|
const result: AttributesData = {};
|
|
523
|
-
const
|
|
516
|
+
const node = this.#nodes.get(nodeId);
|
|
524
517
|
const batchSize = 9;
|
|
525
|
-
|
|
526
|
-
// Parse all paths (wildcards become undefined for that component)
|
|
527
518
|
const parsedPaths = attributePaths.map(path => splitAttributePath(path));
|
|
528
519
|
|
|
529
|
-
// Process in batches of up to 9
|
|
530
520
|
for (let i = 0; i < parsedPaths.length; i += batchSize) {
|
|
531
521
|
const batch = parsedPaths.slice(i, i + batchSize);
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
for (const { path: attrPath, value } of attributeData) {
|
|
544
|
-
const { pathStr, value: wsValue } = this.#convertAttributeToWebSocket(
|
|
545
|
-
{
|
|
546
|
-
endpointId: EndpointNumber(attrPath.endpointId),
|
|
547
|
-
clusterId: ClusterId(attrPath.clusterId),
|
|
548
|
-
attributeId: attrPath.attributeId,
|
|
549
|
-
},
|
|
550
|
-
value,
|
|
551
|
-
);
|
|
552
|
-
result[pathStr] = wsValue;
|
|
553
|
-
}
|
|
522
|
+
const readRequest = {
|
|
523
|
+
...Read({
|
|
524
|
+
fabricFilter: fabricFiltered,
|
|
525
|
+
attributes: batch.map(({ endpointId, clusterId, attributeId }) => ({
|
|
526
|
+
endpointId: endpointId !== undefined ? EndpointNumber(endpointId) : undefined,
|
|
527
|
+
clusterId: clusterId !== undefined ? ClusterId(clusterId) : undefined,
|
|
528
|
+
attributeId: attributeId !== undefined ? AttributeId(attributeId) : undefined,
|
|
529
|
+
})),
|
|
530
|
+
}),
|
|
531
|
+
includeKnownVersions: true,
|
|
532
|
+
};
|
|
554
533
|
|
|
555
|
-
|
|
556
|
-
for (const
|
|
557
|
-
|
|
558
|
-
|
|
534
|
+
for await (const chunk of node.node.interaction.read(readRequest)) {
|
|
535
|
+
for (const entry of chunk) {
|
|
536
|
+
if (entry.kind === "attr-value") {
|
|
537
|
+
const { pathStr, value: wsValue } = this.#convertAttributeToWebSocket(
|
|
538
|
+
{
|
|
539
|
+
endpointId: EndpointNumber(entry.path.endpointId),
|
|
540
|
+
clusterId: ClusterId(entry.path.clusterId),
|
|
541
|
+
attributeId: entry.path.attributeId,
|
|
542
|
+
},
|
|
543
|
+
entry.value,
|
|
544
|
+
);
|
|
545
|
+
result[pathStr] = wsValue;
|
|
546
|
+
} else if (entry.kind === "attr-status") {
|
|
547
|
+
const pathStr = buildAttributePath(
|
|
548
|
+
entry.path.endpointId,
|
|
549
|
+
entry.path.clusterId,
|
|
550
|
+
entry.path.attributeId,
|
|
551
|
+
);
|
|
552
|
+
logger.warn(`Failed to read attribute ${pathStr}: status=${entry.status}`);
|
|
553
|
+
}
|
|
559
554
|
}
|
|
560
555
|
}
|
|
561
556
|
}
|
|
@@ -585,6 +580,33 @@ export class ControllerCommandHandler {
|
|
|
585
580
|
};
|
|
586
581
|
}
|
|
587
582
|
|
|
583
|
+
/**
|
|
584
|
+
* Write a single attribute on a remote node. Uses `setStateOf(string, ...)` (not `set({...})`)
|
|
585
|
+
* because peer cluster behaviors are dynamically registered and aren't on the agent's cached property getters.
|
|
586
|
+
*/
|
|
587
|
+
async #writeAttribute(
|
|
588
|
+
nodeId: NodeId,
|
|
589
|
+
endpointId: EndpointNumber,
|
|
590
|
+
clusterId: ClusterId,
|
|
591
|
+
attributeName: string,
|
|
592
|
+
value: unknown,
|
|
593
|
+
): Promise<{ status: number; clusterStatus?: number }> {
|
|
594
|
+
const node = this.#nodes.get(nodeId);
|
|
595
|
+
const clusterEntry = ClusterMap[clusterId];
|
|
596
|
+
if (!clusterEntry) {
|
|
597
|
+
throw ServerError.invalidArguments(`Cluster Id "${clusterId}" unknown`);
|
|
598
|
+
}
|
|
599
|
+
const clusterProperty = clusterEntry.model.propertyName;
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
await node.node.endpoints.for(endpointId).setStateOf(clusterProperty, { [attributeName]: value });
|
|
603
|
+
return { status: 0 };
|
|
604
|
+
} catch (error) {
|
|
605
|
+
StatusResponseError.accept(error);
|
|
606
|
+
return { status: error.code, clusterStatus: error.clusterCode };
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
588
610
|
/**
|
|
589
611
|
* Set the fabric label. Pass null or empty string to reset to "Home".
|
|
590
612
|
* Note: matter.js requires non-empty labels (1-32 chars), so null/empty resets to default.
|
|
@@ -597,165 +619,27 @@ export class ControllerCommandHandler {
|
|
|
597
619
|
return this.#controller.disconnectNode(nodeId, true);
|
|
598
620
|
}
|
|
599
621
|
|
|
600
|
-
async handleReadEvent(data: ReadEventRequest): Promise<ReadEventResponse> {
|
|
601
|
-
const { nodeId, endpointId, clusterId, eventId, eventMin } = data;
|
|
602
|
-
const client = this.#nodes.interactionClientFor(nodeId);
|
|
603
|
-
const { eventData, eventStatus } = await client.getMultipleEventsAndStatus({
|
|
604
|
-
events: [
|
|
605
|
-
{
|
|
606
|
-
endpointId,
|
|
607
|
-
clusterId,
|
|
608
|
-
eventId,
|
|
609
|
-
},
|
|
610
|
-
],
|
|
611
|
-
eventFilters: eventMin ? [{ eventMin }] : undefined,
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
return {
|
|
615
|
-
values: eventData.flatMap(({ path: { endpointId, clusterId, eventId }, events }) =>
|
|
616
|
-
events.map(({ eventNumber, data }) => ({
|
|
617
|
-
eventId,
|
|
618
|
-
clusterId,
|
|
619
|
-
endpointId,
|
|
620
|
-
eventNumber,
|
|
621
|
-
value: data,
|
|
622
|
-
})),
|
|
623
|
-
),
|
|
624
|
-
status: eventStatus?.map(({ path: { endpointId, clusterId, eventId }, status, clusterStatus }) => ({
|
|
625
|
-
clusterId,
|
|
626
|
-
endpointId,
|
|
627
|
-
eventId,
|
|
628
|
-
status,
|
|
629
|
-
clusterStatus,
|
|
630
|
-
})),
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
async handleSubscribeAttribute(data: SubscribeAttributeRequest): Promise<SubscribeAttributeResponse> {
|
|
635
|
-
const { nodeId, endpointId, clusterId, attributeId, minInterval, maxInterval, changeListener } = data;
|
|
636
|
-
const client = this.#nodes.interactionClientFor(nodeId);
|
|
637
|
-
const updated = Observable<[void]>();
|
|
638
|
-
let ignoreData = true; // We ignore data coming in during initial seeding
|
|
639
|
-
const { attributeReports = [] } = await client.subscribeMultipleAttributesAndEvents({
|
|
640
|
-
attributes: [
|
|
641
|
-
{
|
|
642
|
-
endpointId,
|
|
643
|
-
clusterId,
|
|
644
|
-
attributeId,
|
|
645
|
-
},
|
|
646
|
-
],
|
|
647
|
-
minIntervalFloorSeconds: minInterval,
|
|
648
|
-
maxIntervalCeilingSeconds: maxInterval,
|
|
649
|
-
attributeListener: data => {
|
|
650
|
-
if (ignoreData) return;
|
|
651
|
-
changeListener({
|
|
652
|
-
attributeId: data.path.attributeId,
|
|
653
|
-
clusterId: data.path.clusterId,
|
|
654
|
-
endpointId: data.path.endpointId,
|
|
655
|
-
dataVersion: data.version,
|
|
656
|
-
value: data.value,
|
|
657
|
-
});
|
|
658
|
-
},
|
|
659
|
-
updateReceived: () => {
|
|
660
|
-
updated.emit();
|
|
661
|
-
},
|
|
662
|
-
keepSubscriptions: false,
|
|
663
|
-
});
|
|
664
|
-
ignoreData = false;
|
|
665
|
-
|
|
666
|
-
return {
|
|
667
|
-
values: attributeReports.map(
|
|
668
|
-
({ path: { endpointId, clusterId, attributeId }, value, version: dataVersion }) => ({
|
|
669
|
-
attributeId,
|
|
670
|
-
clusterId,
|
|
671
|
-
endpointId,
|
|
672
|
-
dataVersion,
|
|
673
|
-
value,
|
|
674
|
-
}),
|
|
675
|
-
),
|
|
676
|
-
updated,
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
async handleSubscribeEvent(data: SubscribeEventRequest): Promise<SubscribeEventResponse> {
|
|
681
|
-
const { nodeId, endpointId, clusterId, eventId, minInterval, maxInterval, changeListener } = data;
|
|
682
|
-
const client = this.#nodes.interactionClientFor(nodeId);
|
|
683
|
-
const updated = Observable<[void]>();
|
|
684
|
-
let ignoreData = true; // We ignore data coming in during initial seeding
|
|
685
|
-
const { eventReports = [] } = await client.subscribeMultipleAttributesAndEvents({
|
|
686
|
-
events: [
|
|
687
|
-
{
|
|
688
|
-
endpointId,
|
|
689
|
-
clusterId,
|
|
690
|
-
eventId,
|
|
691
|
-
},
|
|
692
|
-
],
|
|
693
|
-
minIntervalFloorSeconds: minInterval,
|
|
694
|
-
maxIntervalCeilingSeconds: maxInterval,
|
|
695
|
-
eventListener: data => {
|
|
696
|
-
if (ignoreData) return;
|
|
697
|
-
data.events.forEach(event =>
|
|
698
|
-
changeListener({
|
|
699
|
-
eventId: data.path.eventId,
|
|
700
|
-
clusterId: data.path.clusterId,
|
|
701
|
-
endpointId: data.path.endpointId,
|
|
702
|
-
eventNumber: event.eventNumber,
|
|
703
|
-
value: event.data,
|
|
704
|
-
}),
|
|
705
|
-
);
|
|
706
|
-
},
|
|
707
|
-
updateReceived: () => {
|
|
708
|
-
updated.emit();
|
|
709
|
-
},
|
|
710
|
-
keepSubscriptions: false,
|
|
711
|
-
});
|
|
712
|
-
ignoreData = false;
|
|
713
|
-
|
|
714
|
-
return {
|
|
715
|
-
values: eventReports.flatMap(({ path: { endpointId, clusterId, eventId }, events }) =>
|
|
716
|
-
events.map(({ eventNumber, data }) => ({
|
|
717
|
-
eventId,
|
|
718
|
-
clusterId,
|
|
719
|
-
endpointId,
|
|
720
|
-
eventNumber,
|
|
721
|
-
value: data,
|
|
722
|
-
})),
|
|
723
|
-
),
|
|
724
|
-
updated,
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
|
|
728
622
|
async handleWriteAttribute(data: WriteAttributeRequest): Promise<AttributeResponseStatus> {
|
|
729
623
|
const { nodeId, endpointId, clusterId, attributeId } = data;
|
|
730
624
|
let { value } = data;
|
|
731
625
|
|
|
732
|
-
const client = this.#nodes.clusterClientByIdFor(nodeId, endpointId, clusterId);
|
|
733
|
-
|
|
734
626
|
const clusterEntry = ClusterMap[clusterId];
|
|
735
|
-
const
|
|
736
|
-
if (
|
|
737
|
-
|
|
627
|
+
const attributeModel = clusterEntry?.attributes[attributeId];
|
|
628
|
+
if (!clusterEntry || !attributeModel) {
|
|
629
|
+
throw ServerError.invalidArguments(`Attribute ${attributeId} on cluster ${clusterId} unknown`);
|
|
738
630
|
}
|
|
739
631
|
|
|
632
|
+
value = convertWebSocketTagBasedToMatter(value, attributeModel, clusterEntry.model);
|
|
633
|
+
|
|
740
634
|
logger.info("Writing attribute", attributeId, "with value", value);
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
} catch (error) {
|
|
750
|
-
StatusResponseError.accept(error);
|
|
751
|
-
return {
|
|
752
|
-
attributeId,
|
|
753
|
-
clusterId,
|
|
754
|
-
endpointId,
|
|
755
|
-
status: error.code,
|
|
756
|
-
clusterStatus: error.clusterCode,
|
|
757
|
-
};
|
|
758
|
-
}
|
|
635
|
+
const { status, clusterStatus } = await this.#writeAttribute(
|
|
636
|
+
nodeId,
|
|
637
|
+
endpointId,
|
|
638
|
+
clusterId,
|
|
639
|
+
attributeModel.propertyName,
|
|
640
|
+
value,
|
|
641
|
+
);
|
|
642
|
+
return { attributeId, clusterId, endpointId, status, clusterStatus };
|
|
759
643
|
}
|
|
760
644
|
|
|
761
645
|
async #invokeCommand<const C extends Specifier.ClusterLike>(
|
|
@@ -1184,7 +1068,7 @@ export class ControllerCommandHandler {
|
|
|
1184
1068
|
includeKnownVersions: true, // we want to read from device
|
|
1185
1069
|
};
|
|
1186
1070
|
|
|
1187
|
-
for await (const chunk of
|
|
1071
|
+
for await (const chunk of node.node.interaction.read(read)) {
|
|
1188
1072
|
for (const attr of chunk) {
|
|
1189
1073
|
if (attr.kind === "attr-value" && Array.isArray(attr.value)) {
|
|
1190
1074
|
// We only expect one array response
|
|
@@ -1209,12 +1093,8 @@ export class ControllerCommandHandler {
|
|
|
1209
1093
|
/**
|
|
1210
1094
|
* Set Access Control List entries on a node.
|
|
1211
1095
|
* Writes to the ACL attribute on the AccessControl cluster (endpoint 0).
|
|
1212
|
-
* TODO Migrate to new Node API
|
|
1213
1096
|
*/
|
|
1214
1097
|
async setAclEntry(nodeId: NodeId, entries: AccessControlEntry[]): Promise<AttributeWriteResult[] | null> {
|
|
1215
|
-
const client = this.#nodes.clusterClientFor(nodeId, EndpointNumber(0), AccessControl.Cluster);
|
|
1216
|
-
|
|
1217
|
-
// Convert from WebSocket format (snake_case) to Matter.js format (camelCase)
|
|
1218
1098
|
const aclEntries: AccessControl.AccessControlEntry[] = entries.map(entry => ({
|
|
1219
1099
|
privilege: entry.privilege as AccessControl.AccessControlEntryPrivilege,
|
|
1220
1100
|
authMode: entry.auth_mode as AccessControl.AccessControlEntryAuthMode,
|
|
@@ -1230,46 +1110,30 @@ export class ControllerCommandHandler {
|
|
|
1230
1110
|
|
|
1231
1111
|
logger.info("Setting ACL entries", aclEntries);
|
|
1232
1112
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
StatusResponseError.accept(error);
|
|
1247
|
-
return [
|
|
1248
|
-
{
|
|
1249
|
-
path: {
|
|
1250
|
-
endpoint_id: 0,
|
|
1251
|
-
cluster_id: AccessControl.Cluster.id,
|
|
1252
|
-
attribute_id: 0,
|
|
1253
|
-
},
|
|
1254
|
-
status: error.code,
|
|
1255
|
-
},
|
|
1256
|
-
];
|
|
1257
|
-
}
|
|
1113
|
+
const { status } = await this.#writeAttribute(
|
|
1114
|
+
nodeId,
|
|
1115
|
+
EndpointNumber(0),
|
|
1116
|
+
AccessControl.Cluster.id,
|
|
1117
|
+
"acl",
|
|
1118
|
+
aclEntries,
|
|
1119
|
+
);
|
|
1120
|
+
return [
|
|
1121
|
+
{
|
|
1122
|
+
path: { endpoint_id: 0, cluster_id: AccessControl.Cluster.id, attribute_id: 0 },
|
|
1123
|
+
status,
|
|
1124
|
+
},
|
|
1125
|
+
];
|
|
1258
1126
|
}
|
|
1259
1127
|
|
|
1260
1128
|
/**
|
|
1261
1129
|
* Set bindings on a specific endpoint of a node.
|
|
1262
1130
|
* Writes to the Binding attribute on the Binding cluster.
|
|
1263
|
-
* TODO Migrate to new Node API
|
|
1264
1131
|
*/
|
|
1265
1132
|
async setNodeBinding(
|
|
1266
1133
|
nodeId: NodeId,
|
|
1267
1134
|
endpointId: EndpointNumber,
|
|
1268
1135
|
bindings: BindingTarget[],
|
|
1269
1136
|
): Promise<AttributeWriteResult[] | null> {
|
|
1270
|
-
const client = this.#nodes.clusterClientFor(nodeId, endpointId, Binding.Cluster);
|
|
1271
|
-
|
|
1272
|
-
// Convert from WebSocket format to Matter.js format
|
|
1273
1137
|
const bindingEntries: Binding.Target[] = bindings.map(binding => ({
|
|
1274
1138
|
node: binding.node !== null ? NodeId(binding.node) : undefined,
|
|
1275
1139
|
group: binding.group !== null ? GroupId(binding.group) : undefined,
|
|
@@ -1280,31 +1144,19 @@ export class ControllerCommandHandler {
|
|
|
1280
1144
|
|
|
1281
1145
|
logger.info("Setting bindings on endpoint", endpointId, bindingEntries);
|
|
1282
1146
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
StatusResponseError.accept(error);
|
|
1297
|
-
return [
|
|
1298
|
-
{
|
|
1299
|
-
path: {
|
|
1300
|
-
endpoint_id: endpointId,
|
|
1301
|
-
cluster_id: Binding.Cluster.id,
|
|
1302
|
-
attribute_id: 0,
|
|
1303
|
-
},
|
|
1304
|
-
status: error.code,
|
|
1305
|
-
},
|
|
1306
|
-
];
|
|
1307
|
-
}
|
|
1147
|
+
const { status } = await this.#writeAttribute(
|
|
1148
|
+
nodeId,
|
|
1149
|
+
endpointId,
|
|
1150
|
+
Binding.Cluster.id,
|
|
1151
|
+
"binding",
|
|
1152
|
+
bindingEntries,
|
|
1153
|
+
);
|
|
1154
|
+
return [
|
|
1155
|
+
{
|
|
1156
|
+
path: { endpoint_id: endpointId, cluster_id: Binding.Cluster.id, attribute_id: 0 },
|
|
1157
|
+
status,
|
|
1158
|
+
},
|
|
1159
|
+
];
|
|
1308
1160
|
}
|
|
1309
1161
|
|
|
1310
1162
|
/**
|