zigbee-clusters 2.9.1 → 2.10.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.
- package/AGENTS.md +47 -3
- package/index.d.ts +44 -4
- package/index.js +2 -0
- package/lib/BoundCluster.js +1 -1
- package/lib/Cluster.js +10 -4
- package/lib/Endpoint.js +25 -4
- package/lib/clusters/ota.js +274 -3
- package/lib/util/index.js +10 -0
- package/package.json +3 -3
- package/scripts/generate-types.js +28 -6
package/AGENTS.md
CHANGED
|
@@ -303,6 +303,7 @@ attr3: { ... },
|
|
|
303
303
|
| `id` | Yes | Always 4-digit hex format (0x0000); add decimal comment if > 9 |
|
|
304
304
|
| `args` | If has params | Object with typed fields |
|
|
305
305
|
| `response` | If expects response | Has own `id` and `args` |
|
|
306
|
+
| `encodeMissingFieldsBehavior` | Controls the behavior of missing fields when encoding the struct | Set to `skip` if some fields are optional, omit if all fields are mandatory |
|
|
306
307
|
| `direction` | For server→client | `Cluster.DIRECTION_SERVER_TO_CLIENT` |
|
|
307
308
|
|
|
308
309
|
#### Command Sections
|
|
@@ -333,11 +334,54 @@ lockDoor: {
|
|
|
333
334
|
},
|
|
334
335
|
```
|
|
335
336
|
|
|
336
|
-
**Server→client commands**
|
|
337
|
-
-
|
|
338
|
-
-
|
|
337
|
+
**Server→client commands** should only be added as standalone commands when they can be sent **unsolicited** (not just as a response to a request). Examples:
|
|
338
|
+
- Unsolicited notifications (e.g., `operationEventNotification` for door locks) — add as standalone
|
|
339
|
+
- Responses that the server can also send unsolicited (e.g., `upgradeEndResponse` for synchronized upgrades) — add as standalone
|
|
340
|
+
- Responses that are **only** sent in reply to a request (e.g., `queryNextImageResponse`) — do **NOT** add as standalone; they are already covered by the inline `response:` on the request command
|
|
339
341
|
- These require `direction: Cluster.DIRECTION_SERVER_TO_CLIENT`
|
|
340
342
|
|
|
343
|
+
#### Optional fields
|
|
344
|
+
Some commands contain optional fields. If this is the case add `encodeMissingFieldsBehavior: 'skip'` to the command definition.
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
lockDoor: {
|
|
348
|
+
id: 0x0000,
|
|
349
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
350
|
+
args: { pinCode: ZCLDataTypes.octstr },
|
|
351
|
+
response: {
|
|
352
|
+
id: 0x0000,
|
|
353
|
+
args: { status: ZCLDataTypes.uint8 },
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
#### Command variants
|
|
359
|
+
Some commands might change their structure based on a status field. If this is the case, add `encodeMissingFieldsBehavior: 'skip'`. And add a comment above each field
|
|
360
|
+
when the field should be present.
|
|
361
|
+
|
|
362
|
+
**IMPORTANT**: This applies to `response:` structs too, not just the top-level command. If a response has variant fields (e.g., different fields depending on a status value), add `encodeMissingFieldsBehavior: 'skip'` to the response and include all variant fields with comments indicating when each group is present.
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
imageBlockRequest: {
|
|
366
|
+
id: 0x0003,
|
|
367
|
+
args: { ... },
|
|
368
|
+
response: {
|
|
369
|
+
id: 0x0005,
|
|
370
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
371
|
+
args: {
|
|
372
|
+
status: ZCLDataTypes.enum8Status,
|
|
373
|
+
// When status is SUCCESS
|
|
374
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
375
|
+
fileOffset: ZCLDataTypes.uint32,
|
|
376
|
+
imageData: ZCLDataTypes.buffer,
|
|
377
|
+
// When status is WAIT_FOR_DATA
|
|
378
|
+
currentTime: ZCLDataTypes.uint32,
|
|
379
|
+
requestTime: ZCLDataTypes.uint32,
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
341
385
|
---
|
|
342
386
|
|
|
343
387
|
### ZCLDataTypes Reference
|
package/index.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ type ZCLNodeConstructorInput = {
|
|
|
31
31
|
) => Promise<void>;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
type ZCLEnum8Status = 'SUCCESS' | 'FAILURE' | 'NOT_AUTHORIZED' | 'RESERVED_FIELD_NOT_ZERO' | 'MALFORMED_COMMAND' | 'UNSUP_CLUSTER_COMMAND' | 'UNSUP_GENERAL_COMMAND' | 'UNSUP_MANUF_CLUSTER_COMMAND' | 'UNSUP_MANUF_GENERAL_COMMAND' | 'INVALID_FIELD' | 'UNSUPPORTED_ATTRIBUTE' | 'INVALID_VALUE' | 'READ_ONLY' | 'INSUFFICIENT_SPACE' | 'DUPLICATE_EXISTS' | 'NOT_FOUND' | 'UNREPORTABLE_ATTRIBUTE' | 'INVALID_DATA_TYPE' | 'INVALID_SELECTOR' | 'WRITE_ONLY' | 'INCONSISTENT_STARTUP_STATE' | 'DEFINED_OUT_OF_BAND' | 'INCONSISTENT' | 'ACTION_DENIED' | 'TIMEOUT' | 'ABORT' | 'INVALID_IMAGE' | 'WAIT_FOR_DATA' | 'NO_IMAGE_AVAILABLE' | 'REQUIRE_MORE_IMAGE' | 'NOTIFICATION_PENDING' | 'HARDWARE_FAILURE' | 'SOFTWARE_FAILURE' | 'CALIBRATION_ERROR' | 'UNSUPPORTED_CLUSTER';
|
|
35
|
+
|
|
34
36
|
export interface ZCLNodeCluster extends EventEmitter {
|
|
35
37
|
discoverCommandsGenerated(params?: {
|
|
36
38
|
startValue?: number;
|
|
@@ -449,10 +451,10 @@ export interface GroupsCluster extends ZCLNodeCluster {
|
|
|
449
451
|
writeAttributes(attributes: Partial<GroupsClusterAttributes>, opts?: { timeout?: number }): Promise<unknown>;
|
|
450
452
|
on<K extends keyof GroupsClusterAttributes & string>(eventName: `attr.${K}`, listener: (value: GroupsClusterAttributes[K]) => void): this;
|
|
451
453
|
once<K extends keyof GroupsClusterAttributes & string>(eventName: `attr.${K}`, listener: (value: GroupsClusterAttributes[K]) => void): this;
|
|
452
|
-
addGroup(args: { groupId: number; groupName: string }, opts?: ClusterCommandOptions): Promise<{ status:
|
|
453
|
-
viewGroup(args: { groupId: number }, opts?: ClusterCommandOptions): Promise<{ status:
|
|
454
|
+
addGroup(args: { groupId: number; groupName: string }, opts?: ClusterCommandOptions): Promise<{ status: ZCLEnum8Status; groupId: number }>;
|
|
455
|
+
viewGroup(args: { groupId: number }, opts?: ClusterCommandOptions): Promise<{ status: ZCLEnum8Status; groupId: number; groupNames: string }>;
|
|
454
456
|
getGroupMembership(args: { groupIds: number[] }, opts?: ClusterCommandOptions): Promise<{ capacity: number; groups: number[] }>;
|
|
455
|
-
removeGroup(args: { groupId: number }, opts?: ClusterCommandOptions): Promise<{ status:
|
|
457
|
+
removeGroup(args: { groupId: number }, opts?: ClusterCommandOptions): Promise<{ status: ZCLEnum8Status; groupId: number }>;
|
|
456
458
|
removeAllGroups(opts?: ClusterCommandOptions): Promise<void>;
|
|
457
459
|
addGroupIfIdentify(args: { groupId: number; groupName: string }, opts?: ClusterCommandOptions): Promise<void>;
|
|
458
460
|
}
|
|
@@ -848,7 +850,36 @@ export interface OnOffCluster extends ZCLNodeCluster {
|
|
|
848
850
|
export interface OnOffSwitchCluster extends ZCLNodeCluster {
|
|
849
851
|
}
|
|
850
852
|
|
|
853
|
+
export interface OTAClusterAttributes {
|
|
854
|
+
upgradeServerID?: string;
|
|
855
|
+
fileOffset?: number;
|
|
856
|
+
currentFileVersion?: number;
|
|
857
|
+
currentZigBeeStackVersion?: number;
|
|
858
|
+
downloadedFileVersion?: number;
|
|
859
|
+
downloadedZigBeeStackVersion?: number;
|
|
860
|
+
imageUpgradeStatus?: 'normal' | 'downloadInProgress' | 'downloadComplete' | 'waitingToUpgrade' | 'countDown' | 'waitForMore' | 'waitingToUpgradeViaExternalEvent';
|
|
861
|
+
manufacturerID?: number;
|
|
862
|
+
imageTypeID?: number;
|
|
863
|
+
minimumBlockPeriod?: number;
|
|
864
|
+
imageStamp?: number;
|
|
865
|
+
upgradeActivationPolicy?: 'otaServerActivationAllowed' | 'outOfBandActivationOnly';
|
|
866
|
+
upgradeTimeoutPolicy?: 'applyUpgradeAfterTimeout' | 'doNotApplyUpgradeAfterTimeout';
|
|
867
|
+
}
|
|
868
|
+
|
|
851
869
|
export interface OTACluster extends ZCLNodeCluster {
|
|
870
|
+
readAttributes<K extends 'upgradeServerID' | 'fileOffset' | 'currentFileVersion' | 'currentZigBeeStackVersion' | 'downloadedFileVersion' | 'downloadedZigBeeStackVersion' | 'imageUpgradeStatus' | 'manufacturerID' | 'imageTypeID' | 'minimumBlockPeriod' | 'imageStamp' | 'upgradeActivationPolicy' | 'upgradeTimeoutPolicy'>(attributeNames: K[], opts?: { timeout?: number }): Promise<Pick<OTAClusterAttributes, K>>;
|
|
871
|
+
readAttributes(attributeNames: Array<keyof OTAClusterAttributes | number>, opts?: { timeout?: number }): Promise<Partial<OTAClusterAttributes> & Record<number, unknown>>;
|
|
872
|
+
writeAttributes(attributes: Partial<OTAClusterAttributes>, opts?: { timeout?: number }): Promise<unknown>;
|
|
873
|
+
on<K extends keyof OTAClusterAttributes & string>(eventName: `attr.${K}`, listener: (value: OTAClusterAttributes[K]) => void): this;
|
|
874
|
+
once<K extends keyof OTAClusterAttributes & string>(eventName: `attr.${K}`, listener: (value: OTAClusterAttributes[K]) => void): this;
|
|
875
|
+
imageNotify(args?: { payloadType?: 'queryJitter' | 'queryJitterAndManufacturerCode' | 'queryJitterAndManufacturerCodeAndImageType' | 'queryJitterAndManufacturerCodeAndImageTypeAndNewFileVersion'; queryJitter?: number; manufacturerCode?: number; imageType?: number; newFileVersion?: number }, opts?: ClusterCommandOptions): Promise<void>;
|
|
876
|
+
queryNextImageRequest(args?: { fieldControl?: Partial<{ hardwareVersionPresent: boolean }>; manufacturerCode?: number; imageType?: number; fileVersion?: number; hardwareVersion?: number }, opts?: ClusterCommandOptions): Promise<{ status?: ZCLEnum8Status; manufacturerCode?: number; imageType?: number; fileVersion?: number; imageSize?: number }>;
|
|
877
|
+
imageBlockRequest(args?: { fieldControl?: Partial<{ requestNodeAddressPresent: boolean; minimumBlockPeriodPresent: boolean }>; manufacturerCode?: number; imageType?: number; fileVersion?: number; fileOffset?: number; maximumDataSize?: number; requestNodeAddress?: string; minimumBlockPeriod?: number }, opts?: ClusterCommandOptions): Promise<{ status?: ZCLEnum8Status; manufacturerCode?: number; imageType?: number; fileVersion?: number; fileOffset?: number; dataSize?: number; imageData?: Buffer; currentTime?: number; requestTime?: number; minimumBlockPeriod?: number }>;
|
|
878
|
+
imagePageRequest(args?: { fieldControl?: Partial<{ requestNodeAddressPresent: boolean }>; manufacturerCode?: number; imageType?: number; fileVersion?: number; fileOffset?: number; maximumDataSize?: number; pageSize?: number; responseSpacing?: number; requestNodeAddress?: string }, opts?: ClusterCommandOptions): Promise<{ status?: ZCLEnum8Status; manufacturerCode?: number; imageType?: number; fileVersion?: number; fileOffset?: number; dataSize?: number; imageData?: Buffer; currentTime?: number; requestTime?: number; minimumBlockPeriod?: number }>;
|
|
879
|
+
imageBlockResponse(args?: { status?: ZCLEnum8Status; manufacturerCode?: number; imageType?: number; fileVersion?: number; fileOffset?: number; dataSize?: number; imageData?: Buffer; currentTime?: number; requestTime?: number; minimumBlockPeriod?: number }, opts?: ClusterCommandOptions): Promise<void>;
|
|
880
|
+
upgradeEndRequest(args: { status: ZCLEnum8Status; manufacturerCode: number; imageType: number; fileVersion: number }, opts?: ClusterCommandOptions): Promise<{ manufacturerCode: number; imageType: number; fileVersion: number; currentTime: number; upgradeTime: number }>;
|
|
881
|
+
upgradeEndResponse(args: { manufacturerCode: number; imageType: number; fileVersion: number; currentTime: number; upgradeTime: number }, opts?: ClusterCommandOptions): Promise<void>;
|
|
882
|
+
queryDeviceSpecificFileRequest(args: { requestNodeAddress: string; manufacturerCode: number; imageType: number; fileVersion: number; zigBeeStackVersion: number }, opts?: ClusterCommandOptions): Promise<{ status?: ZCLEnum8Status; manufacturerCode?: number; imageType?: number; fileVersion?: number; imageSize?: number }>;
|
|
852
883
|
}
|
|
853
884
|
|
|
854
885
|
export interface PollControlClusterAttributes {
|
|
@@ -1165,7 +1196,7 @@ export interface ClusterAttributesByName {
|
|
|
1165
1196
|
occupancySensing: OccupancySensingClusterAttributes;
|
|
1166
1197
|
onOff: OnOffClusterAttributes;
|
|
1167
1198
|
onOffSwitch: Record<string, unknown>;
|
|
1168
|
-
ota:
|
|
1199
|
+
ota: OTAClusterAttributes;
|
|
1169
1200
|
pollControl: PollControlClusterAttributes;
|
|
1170
1201
|
powerConfiguration: PowerConfigurationClusterAttributes;
|
|
1171
1202
|
powerProfile: Record<string, unknown>;
|
|
@@ -1197,6 +1228,7 @@ export type ZCLNodeEndpoint = {
|
|
|
1197
1228
|
clusters: ClusterRegistry & {
|
|
1198
1229
|
[clusterName: string]: ZCLNodeCluster | undefined;
|
|
1199
1230
|
};
|
|
1231
|
+
bind(clusterName: string, impl: BoundCluster): void;
|
|
1200
1232
|
};
|
|
1201
1233
|
|
|
1202
1234
|
export interface ZCLNode {
|
|
@@ -1486,6 +1518,14 @@ export const CLUSTER: {
|
|
|
1486
1518
|
COMMANDS: unknown;
|
|
1487
1519
|
};
|
|
1488
1520
|
};
|
|
1521
|
+
export class BoundCluster {
|
|
1522
|
+
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
export class ZCLError extends Error {
|
|
1526
|
+
zclStatus: ZCLEnum8Status;
|
|
1527
|
+
constructor(zclStatus?: ZCLEnum8Status);
|
|
1528
|
+
}
|
|
1489
1529
|
export const AlarmsCluster: {
|
|
1490
1530
|
new (...args: any[]): AlarmsCluster;
|
|
1491
1531
|
ID: 9;
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const Clusters = require('./lib/clusters');
|
|
|
8
8
|
const BoundCluster = require('./lib/BoundCluster');
|
|
9
9
|
const zclTypes = require('./lib/zclTypes');
|
|
10
10
|
const zclFrames = require('./lib/zclFrames');
|
|
11
|
+
const { ZCLError } = require('./lib/util');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Enables or disables debug logging.
|
|
@@ -55,6 +56,7 @@ module.exports = {
|
|
|
55
56
|
ZCLDataTypes,
|
|
56
57
|
ZCLDataType,
|
|
57
58
|
ZCLStruct,
|
|
59
|
+
ZCLError,
|
|
58
60
|
...Clusters,
|
|
59
61
|
debug,
|
|
60
62
|
ZIGBEE_PROFILE_ID,
|
package/lib/BoundCluster.js
CHANGED
|
@@ -317,7 +317,7 @@ class BoundCluster {
|
|
|
317
317
|
const result = await this[command.name](args, meta, frame, rawFrame);
|
|
318
318
|
if (command.response && command.response.args) {
|
|
319
319
|
// eslint-disable-next-line new-cap
|
|
320
|
-
return [command.response.id, new command.response.args(result)];
|
|
320
|
+
return [command.response.id, new command.response.args(result), command.response];
|
|
321
321
|
}
|
|
322
322
|
// eslint-disable-next-line consistent-return
|
|
323
323
|
return;
|
package/lib/Cluster.js
CHANGED
|
@@ -789,7 +789,7 @@ class Cluster extends EventEmitter {
|
|
|
789
789
|
const response = await handler.call(this, args, meta, frame, rawFrame);
|
|
790
790
|
if (command.response && command.response.args) {
|
|
791
791
|
// eslint-disable-next-line new-cap
|
|
792
|
-
return [command.response.id, new command.response.args(response)];
|
|
792
|
+
return [command.response.id, new command.response.args(response), command.response];
|
|
793
793
|
}
|
|
794
794
|
// eslint-disable-next-line consistent-return
|
|
795
795
|
return;
|
|
@@ -1027,7 +1027,9 @@ class Cluster extends EventEmitter {
|
|
|
1027
1027
|
clusterClass.commandsById = Object.entries(clusterClass.commands).reduce((r, [name, _cmd]) => {
|
|
1028
1028
|
const cmd = { ..._cmd, name };
|
|
1029
1029
|
if (cmd.args) {
|
|
1030
|
-
cmd.args = ZCLStruct(`${clusterClass.NAME}.${name}`, cmd.args
|
|
1030
|
+
cmd.args = ZCLStruct(`${clusterClass.NAME}.${name}`, cmd.args, {
|
|
1031
|
+
encodeMissingFieldsBehavior: cmd.encodeMissingFieldsBehavior,
|
|
1032
|
+
});
|
|
1031
1033
|
if (_cmd === GLOBAL_COMMANDS.defaultResponse) {
|
|
1032
1034
|
clusterClass.defaultResponseArgsType = cmd.args;
|
|
1033
1035
|
}
|
|
@@ -1045,7 +1047,9 @@ class Cluster extends EventEmitter {
|
|
|
1045
1047
|
res.id = cmd.id;
|
|
1046
1048
|
}
|
|
1047
1049
|
if (res.args) {
|
|
1048
|
-
res.args = ZCLStruct(`${clusterClass.NAME}.${res.name}`, res.args
|
|
1050
|
+
res.args = ZCLStruct(`${clusterClass.NAME}.${res.name}`, res.args, {
|
|
1051
|
+
encodeMissingFieldsBehavior: res.encodeMissingFieldsBehavior,
|
|
1052
|
+
});
|
|
1049
1053
|
}
|
|
1050
1054
|
if (cmd.global) res.global = true;
|
|
1051
1055
|
if (cmd.manufacturerSpecific) res.manufacturerSpecific = true;
|
|
@@ -1096,7 +1100,9 @@ class Cluster extends EventEmitter {
|
|
|
1096
1100
|
}
|
|
1097
1101
|
|
|
1098
1102
|
if (cmd.args) {
|
|
1099
|
-
const CommandArgs = ZCLStruct(`${this.name}.${cmdName}`, cmd.args
|
|
1103
|
+
const CommandArgs = ZCLStruct(`${this.name}.${cmdName}`, cmd.args, {
|
|
1104
|
+
encodeMissingFieldsBehavior: cmd.encodeMissingFieldsBehavior,
|
|
1105
|
+
});
|
|
1100
1106
|
payload.data = new CommandArgs(args);
|
|
1101
1107
|
}
|
|
1102
1108
|
|
package/lib/Endpoint.js
CHANGED
|
@@ -7,7 +7,8 @@ const BoundCluster = require('./BoundCluster');
|
|
|
7
7
|
const { ZCLStandardHeader, ZCLMfgSpecificHeader } = require('./zclFrames');
|
|
8
8
|
|
|
9
9
|
let { debug } = require('./util');
|
|
10
|
-
const { getLogId } = require('./util');
|
|
10
|
+
const { getLogId, ZCLError } = require('./util');
|
|
11
|
+
const { ZCLDataTypes } = require('./zclTypes');
|
|
11
12
|
|
|
12
13
|
debug = debug.extend('endpoint');
|
|
13
14
|
|
|
@@ -120,7 +121,10 @@ class Endpoint extends EventEmitter {
|
|
|
120
121
|
|
|
121
122
|
// If cluster specific error, respond with a default response error frame
|
|
122
123
|
if (clusterSpecificError) {
|
|
123
|
-
const
|
|
124
|
+
const status = clusterSpecificError instanceof ZCLError
|
|
125
|
+
? clusterSpecificError.zclStatus : undefined;
|
|
126
|
+
|
|
127
|
+
const defaultResponseErrorFrame = this.makeDefaultResponseFrame(frame, false, status);
|
|
124
128
|
this.sendFrame(clusterId, defaultResponseErrorFrame.toBuffer()).catch(err => {
|
|
125
129
|
debug(`${this.getLogId(clusterId)}, error while sending default error response`, err, { response: defaultResponseErrorFrame });
|
|
126
130
|
});
|
|
@@ -135,7 +139,10 @@ class Endpoint extends EventEmitter {
|
|
|
135
139
|
// If a cluster specific response was generated, set the response data
|
|
136
140
|
// and cmdId in the response frame.
|
|
137
141
|
if (clusterSpecificResponse) {
|
|
138
|
-
const [cmdId, data] = clusterSpecificResponse;
|
|
142
|
+
const [cmdId, data, cmd] = clusterSpecificResponse;
|
|
143
|
+
if (!cmd.global) {
|
|
144
|
+
responseFrame.frameControl.clusterSpecific = true;
|
|
145
|
+
}
|
|
139
146
|
responseFrame.data = data.toBuffer();
|
|
140
147
|
responseFrame.cmdId = cmdId;
|
|
141
148
|
}
|
|
@@ -185,9 +192,10 @@ class Endpoint extends EventEmitter {
|
|
|
185
192
|
* Returns a default response frame with an error status code.
|
|
186
193
|
* @param {*} receivedFrame
|
|
187
194
|
* @param {boolean} success
|
|
195
|
+
* @param {string} [status] - Optional ZCL status code to use in the response
|
|
188
196
|
* @returns {ZCLStandardHeader|ZCLMfgSpecificHeader}
|
|
189
197
|
*/
|
|
190
|
-
makeDefaultResponseFrame(receivedFrame, success) {
|
|
198
|
+
makeDefaultResponseFrame(receivedFrame, success, status) {
|
|
191
199
|
let responseFrame;
|
|
192
200
|
if (receivedFrame instanceof ZCLStandardHeader) {
|
|
193
201
|
responseFrame = new ZCLStandardHeader();
|
|
@@ -205,6 +213,19 @@ class Endpoint extends EventEmitter {
|
|
|
205
213
|
responseFrame.trxSequenceNumber = receivedFrame.trxSequenceNumber;
|
|
206
214
|
responseFrame.cmdId = 0x0B;
|
|
207
215
|
responseFrame.data = Buffer.from([receivedFrame.cmdId, success ? 0 : 1]);
|
|
216
|
+
|
|
217
|
+
// If not successful, set the status code in the response frame data
|
|
218
|
+
// Note that if the status code is invalid, enum8Status.toBuffer will throw an error,
|
|
219
|
+
// and the default error status will be FAILURE. This also allows overriding the status code
|
|
220
|
+
// with SUCCESS, which is intentional. The OTA Cluster requires this in some cases.
|
|
221
|
+
if (!success && typeof status === 'string') {
|
|
222
|
+
try {
|
|
223
|
+
ZCLDataTypes.enum8Status.toBuffer(responseFrame.data, status, 1);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
// Ignore invalid status codes and keep the default FAILURE status.
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
208
229
|
return responseFrame;
|
|
209
230
|
}
|
|
210
231
|
|
package/lib/clusters/ota.js
CHANGED
|
@@ -1,15 +1,286 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Cluster = require('../Cluster');
|
|
4
|
+
const { ZCLDataTypes } = require('../zclTypes');
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Server Attributes
|
|
8
|
+
// ============================================================================
|
|
9
|
+
const ATTRIBUTES = {
|
|
10
|
+
// OTA Upgrade Cluster Attributes (0x0000 - 0x000C)
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
// The IEEE address of the upgrade server resulted from the discovery of the
|
|
13
|
+
// upgrade server's identity. If the value is set to a non-zero value and
|
|
14
|
+
// corresponds to an IEEE address of a device that is no longer accessible, a
|
|
15
|
+
// device MAY choose to discover a new Upgrade Server depending on its own
|
|
16
|
+
// security policies.
|
|
17
|
+
upgradeServerID: { id: 0x0000, type: ZCLDataTypes.EUI64 }, // Mandatory
|
|
18
|
+
|
|
19
|
+
// The parameter indicates the current location in the OTA upgrade image. It is
|
|
20
|
+
// essentially the (start of the) address of the image data that is being
|
|
21
|
+
// transferred from the OTA server to the client.
|
|
22
|
+
fileOffset: { id: 0x0001, type: ZCLDataTypes.uint32 }, // Optional
|
|
23
|
+
|
|
24
|
+
// The file version of the running firmware image on the device.
|
|
25
|
+
currentFileVersion: { id: 0x0002, type: ZCLDataTypes.uint32 }, // Optional
|
|
26
|
+
|
|
27
|
+
// The ZigBee stack version of the running image on the device.
|
|
28
|
+
currentZigBeeStackVersion: { id: 0x0003, type: ZCLDataTypes.uint16 }, // Optional
|
|
29
|
+
|
|
30
|
+
// The file version of the downloaded image on additional memory space on the
|
|
31
|
+
// device.
|
|
32
|
+
downloadedFileVersion: { id: 0x0004, type: ZCLDataTypes.uint32 }, // Optional
|
|
33
|
+
|
|
34
|
+
// The ZigBee stack version of the downloaded image on additional memory space
|
|
35
|
+
// on the device.
|
|
36
|
+
downloadedZigBeeStackVersion: { id: 0x0005, type: ZCLDataTypes.uint16 }, // Optional
|
|
37
|
+
|
|
38
|
+
// The upgrade status of the client device. The status indicates where the client
|
|
39
|
+
// device is at in terms of the download and upgrade process.
|
|
40
|
+
imageUpgradeStatus: { // Mandatory
|
|
41
|
+
id: 0x0006,
|
|
42
|
+
type: ZCLDataTypes.enum8({
|
|
43
|
+
normal: 0x00,
|
|
44
|
+
downloadInProgress: 0x01,
|
|
45
|
+
downloadComplete: 0x02,
|
|
46
|
+
waitingToUpgrade: 0x03,
|
|
47
|
+
countDown: 0x04,
|
|
48
|
+
waitForMore: 0x05,
|
|
49
|
+
waitingToUpgradeViaExternalEvent: 0x06,
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// The ZigBee assigned value for the manufacturer of the device.
|
|
54
|
+
manufacturerID: { id: 0x0007, type: ZCLDataTypes.uint16 }, // Optional
|
|
55
|
+
|
|
56
|
+
// The image type identifier of the file that the client is currently downloading,
|
|
57
|
+
// or a file that has been completely downloaded but not upgraded to yet.
|
|
58
|
+
imageTypeID: { id: 0x0008, type: ZCLDataTypes.uint16 }, // Optional
|
|
59
|
+
|
|
60
|
+
// This attribute acts as a rate limiting feature for the server to slow down the
|
|
61
|
+
// client download and prevent saturating the network with block requests. The
|
|
62
|
+
// value is in milliseconds.
|
|
63
|
+
minimumBlockPeriod: { id: 0x0009, type: ZCLDataTypes.uint16 }, // Optional
|
|
64
|
+
|
|
65
|
+
// A 32 bit value used as a second verification to identify the image. The value
|
|
66
|
+
// must be consistent during the lifetime of the same image and unique for each
|
|
67
|
+
// different build of the image.
|
|
68
|
+
imageStamp: { id: 0x000A, type: ZCLDataTypes.uint32 }, // 10, Optional
|
|
69
|
+
|
|
70
|
+
// Indicates what behavior the client device supports for activating a fully
|
|
71
|
+
// downloaded but not installed upgrade image.
|
|
72
|
+
upgradeActivationPolicy: { // Optional
|
|
73
|
+
id: 0x000B, // 11
|
|
74
|
+
type: ZCLDataTypes.enum8({
|
|
75
|
+
otaServerActivationAllowed: 0x00,
|
|
76
|
+
outOfBandActivationOnly: 0x01,
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Dictates the behavior of the device in situations where an explicit activation
|
|
81
|
+
// command cannot be retrieved.
|
|
82
|
+
upgradeTimeoutPolicy: { // Optional
|
|
83
|
+
id: 0x000C, // 12
|
|
84
|
+
type: ZCLDataTypes.enum8({
|
|
85
|
+
applyUpgradeAfterTimeout: 0x00,
|
|
86
|
+
doNotApplyUpgradeAfterTimeout: 0x01,
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Response to the Image Block Request or Image Page Request. The payload varies
|
|
92
|
+
// based on the status field.
|
|
93
|
+
const IMAGE_BLOCK_RESPONSE = {
|
|
94
|
+
id: 0x0005,
|
|
95
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
96
|
+
args: {
|
|
97
|
+
status: ZCLDataTypes.enum8Status,
|
|
98
|
+
// When status is SUCCESS
|
|
99
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
100
|
+
imageType: ZCLDataTypes.uint16,
|
|
101
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
102
|
+
fileOffset: ZCLDataTypes.uint32,
|
|
103
|
+
dataSize: ZCLDataTypes.uint8,
|
|
104
|
+
imageData: ZCLDataTypes.buffer,
|
|
105
|
+
// When status is WAIT_FOR_DATA
|
|
106
|
+
currentTime: ZCLDataTypes.uint32,
|
|
107
|
+
requestTime: ZCLDataTypes.uint32,
|
|
108
|
+
minimumBlockPeriod: ZCLDataTypes.uint16,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const UPGRADE_END_RESPONSE = {
|
|
113
|
+
id: 0x0007,
|
|
114
|
+
args: {
|
|
115
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
116
|
+
imageType: ZCLDataTypes.uint16,
|
|
117
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
118
|
+
currentTime: ZCLDataTypes.uint32,
|
|
119
|
+
upgradeTime: ZCLDataTypes.uint32,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// Commands
|
|
125
|
+
// ============================================================================
|
|
126
|
+
const COMMANDS = {
|
|
127
|
+
// --- Server to Client Commands ---
|
|
128
|
+
|
|
129
|
+
// The purpose of sending Image Notify command is so the server has a way to
|
|
130
|
+
// notify client devices of when the OTA upgrade images are available for them.
|
|
131
|
+
imageNotify: { // Optional
|
|
132
|
+
id: 0x0000,
|
|
133
|
+
direction: Cluster.DIRECTION_SERVER_TO_CLIENT,
|
|
134
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
135
|
+
frameControl: ['directionToClient', 'clusterSpecific', 'disableDefaultResponse'],
|
|
136
|
+
args: {
|
|
137
|
+
payloadType: ZCLDataTypes.enum8({
|
|
138
|
+
queryJitter: 0x00,
|
|
139
|
+
queryJitterAndManufacturerCode: 0x01,
|
|
140
|
+
queryJitterAndManufacturerCodeAndImageType: 0x02,
|
|
141
|
+
queryJitterAndManufacturerCodeAndImageTypeAndNewFileVersion: 0x03,
|
|
142
|
+
}),
|
|
143
|
+
queryJitter: ZCLDataTypes.uint8,
|
|
144
|
+
// Present when payloadType >= 0x01
|
|
145
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
146
|
+
// Present when payloadType >= 0x02
|
|
147
|
+
imageType: ZCLDataTypes.uint16,
|
|
148
|
+
// Present when payloadType >= 0x03
|
|
149
|
+
newFileVersion: ZCLDataTypes.uint32,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// --- Client to Server Commands ---
|
|
154
|
+
|
|
155
|
+
// Client queries the server if new OTA upgrade image is available.
|
|
156
|
+
queryNextImageRequest: { // Mandatory
|
|
157
|
+
id: 0x0001,
|
|
158
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
159
|
+
args: {
|
|
160
|
+
fieldControl: ZCLDataTypes.map8('hardwareVersionPresent'),
|
|
161
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
162
|
+
imageType: ZCLDataTypes.uint16,
|
|
163
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
164
|
+
// Present when fieldControl bit 0 is set
|
|
165
|
+
hardwareVersion: ZCLDataTypes.uint16,
|
|
166
|
+
},
|
|
167
|
+
response: {
|
|
168
|
+
id: 0x0002,
|
|
169
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
170
|
+
args: {
|
|
171
|
+
status: ZCLDataTypes.enum8Status,
|
|
172
|
+
// Remaining fields only present when status is SUCCESS
|
|
173
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
174
|
+
imageType: ZCLDataTypes.uint16,
|
|
175
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
176
|
+
imageSize: ZCLDataTypes.uint32,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// Client requests a block of data from the OTA upgrade image.
|
|
182
|
+
imageBlockRequest: { // Mandatory
|
|
183
|
+
id: 0x0003,
|
|
184
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
185
|
+
args: {
|
|
186
|
+
fieldControl: ZCLDataTypes.map8(
|
|
187
|
+
'requestNodeAddressPresent',
|
|
188
|
+
'minimumBlockPeriodPresent',
|
|
189
|
+
),
|
|
190
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
191
|
+
imageType: ZCLDataTypes.uint16,
|
|
192
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
193
|
+
fileOffset: ZCLDataTypes.uint32,
|
|
194
|
+
maximumDataSize: ZCLDataTypes.uint8,
|
|
195
|
+
// Present when fieldControl bit 0 is set
|
|
196
|
+
requestNodeAddress: ZCLDataTypes.EUI64,
|
|
197
|
+
// Present when fieldControl bit 1 is set
|
|
198
|
+
minimumBlockPeriod: ZCLDataTypes.uint16,
|
|
199
|
+
},
|
|
200
|
+
response: IMAGE_BLOCK_RESPONSE,
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// Client requests pages of data from the OTA upgrade image. Using Image Page
|
|
204
|
+
// Request reduces the number of requests sent from the client to the upgrade
|
|
205
|
+
// server, compared to using Image Block Request command.
|
|
206
|
+
imagePageRequest: { // Optional
|
|
207
|
+
id: 0x0004,
|
|
208
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
209
|
+
args: {
|
|
210
|
+
fieldControl: ZCLDataTypes.map8('requestNodeAddressPresent'),
|
|
211
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
212
|
+
imageType: ZCLDataTypes.uint16,
|
|
213
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
214
|
+
fileOffset: ZCLDataTypes.uint32,
|
|
215
|
+
maximumDataSize: ZCLDataTypes.uint8,
|
|
216
|
+
pageSize: ZCLDataTypes.uint16,
|
|
217
|
+
responseSpacing: ZCLDataTypes.uint16,
|
|
218
|
+
// Present when fieldControl bit 0 is set
|
|
219
|
+
requestNodeAddress: ZCLDataTypes.EUI64,
|
|
220
|
+
},
|
|
221
|
+
response: IMAGE_BLOCK_RESPONSE,
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
imageBlockResponse: { // Mandatory
|
|
225
|
+
...IMAGE_BLOCK_RESPONSE,
|
|
226
|
+
direction: Cluster.DIRECTION_SERVER_TO_CLIENT,
|
|
227
|
+
frameControl: ['directionToClient', 'clusterSpecific', 'disableDefaultResponse'],
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// Sent by the client when it has completed downloading an image. The status
|
|
231
|
+
// value SHALL be SUCCESS, INVALID_IMAGE, REQUIRE_MORE_IMAGE, or ABORT.
|
|
232
|
+
upgradeEndRequest: { // Mandatory
|
|
233
|
+
id: 0x0006,
|
|
234
|
+
args: {
|
|
235
|
+
status: ZCLDataTypes.enum8Status,
|
|
236
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
237
|
+
imageType: ZCLDataTypes.uint16,
|
|
238
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
239
|
+
},
|
|
240
|
+
response: UPGRADE_END_RESPONSE,
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// --- Server to Client Commands ---
|
|
244
|
+
|
|
245
|
+
// Response to the Upgrade End Request, indicating when the client should upgrade
|
|
246
|
+
// to the new image.
|
|
247
|
+
upgradeEndResponse: { // Mandatory
|
|
248
|
+
...UPGRADE_END_RESPONSE,
|
|
249
|
+
direction: Cluster.DIRECTION_SERVER_TO_CLIENT,
|
|
250
|
+
frameControl: ['directionToClient', 'clusterSpecific', 'disableDefaultResponse'],
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// Client requests a device specific file such as security credential,
|
|
254
|
+
// configuration or log.
|
|
255
|
+
queryDeviceSpecificFileRequest: { // Optional
|
|
256
|
+
id: 0x0008,
|
|
257
|
+
args: {
|
|
258
|
+
requestNodeAddress: ZCLDataTypes.EUI64,
|
|
259
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
260
|
+
imageType: ZCLDataTypes.uint16,
|
|
261
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
262
|
+
zigBeeStackVersion: ZCLDataTypes.uint16,
|
|
263
|
+
},
|
|
264
|
+
response: {
|
|
265
|
+
id: 0x0009,
|
|
266
|
+
encodeMissingFieldsBehavior: 'skip',
|
|
267
|
+
args: {
|
|
268
|
+
status: ZCLDataTypes.enum8Status,
|
|
269
|
+
// Remaining fields only present when status is SUCCESS
|
|
270
|
+
manufacturerCode: ZCLDataTypes.uint16,
|
|
271
|
+
imageType: ZCLDataTypes.uint16,
|
|
272
|
+
fileVersion: ZCLDataTypes.uint32,
|
|
273
|
+
imageSize: ZCLDataTypes.uint32,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
};
|
|
8
279
|
|
|
9
280
|
class OTACluster extends Cluster {
|
|
10
281
|
|
|
11
282
|
static get ID() {
|
|
12
|
-
return
|
|
283
|
+
return 0x0019; // 25
|
|
13
284
|
}
|
|
14
285
|
|
|
15
286
|
static get NAME() {
|
package/lib/util/index.js
CHANGED
|
@@ -21,8 +21,18 @@ function getLogId(endpointId, clusterName, clusterId) {
|
|
|
21
21
|
return `ep: ${endpointId}, cl: ${clusterName} (${clusterId})`;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
class ZCLError extends Error {
|
|
25
|
+
|
|
26
|
+
constructor(zclStatus = 'FAILURE') {
|
|
27
|
+
super(`ZCL Error: ${zclStatus}`);
|
|
28
|
+
this.zclStatus = zclStatus;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
module.exports = {
|
|
25
34
|
debug,
|
|
26
35
|
getLogId,
|
|
27
36
|
getPropertyDescriptor,
|
|
37
|
+
ZCLError,
|
|
28
38
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zigbee-clusters",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
4
4
|
"description": "Zigbee Cluster Library for Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/athombv/node-zigbee-clusters#readme",
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@athombv/jsdoc-template": "^1.6.
|
|
35
|
+
"@athombv/jsdoc-template": "^1.6.3",
|
|
36
36
|
"@types/node": "^25.0.10",
|
|
37
37
|
"@types/sinon": "^17.0.3",
|
|
38
38
|
"concurrently": "^5.2.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"watch": "^1.0.2"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@athombv/data-types": "^1.
|
|
50
|
+
"@athombv/data-types": "^1.2.0",
|
|
51
51
|
"debug": "^4.1.1"
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
+
const { ZCLDataTypes } = require('../lib/zclTypes');
|
|
12
13
|
|
|
13
14
|
const OUTPUT_FILE = path.join(__dirname, '../index.d.ts');
|
|
14
15
|
|
|
@@ -17,7 +18,7 @@ const OUTPUT_FILE = path.join(__dirname, '../index.d.ts');
|
|
|
17
18
|
* @param {object} dataType - ZCLDataType object with shortName and args
|
|
18
19
|
* @returns {string} TypeScript type string
|
|
19
20
|
*/
|
|
20
|
-
function zclTypeToTS(dataType) {
|
|
21
|
+
function zclTypeToTS(dataType, useEnum8StatusType = true) {
|
|
21
22
|
if (!dataType || !dataType.shortName) return 'unknown';
|
|
22
23
|
|
|
23
24
|
const { shortName, args } = dataType;
|
|
@@ -49,6 +50,10 @@ function zclTypeToTS(dataType) {
|
|
|
49
50
|
return 'Buffer';
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
if (dataType === ZCLDataTypes.enum8Status && useEnum8StatusType) {
|
|
54
|
+
return 'ZCLEnum8Status';
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
// Enum types - extract keys from args[0]
|
|
53
58
|
if (/^enum(4|8|16|32)$/.test(shortName)) {
|
|
54
59
|
if (args && args[0] && typeof args[0] === 'object') {
|
|
@@ -111,6 +116,8 @@ function parseCluster(ClusterClass, exportName) {
|
|
|
111
116
|
for (const [name, def] of Object.entries(cmds)) {
|
|
112
117
|
const cmdArgs = [];
|
|
113
118
|
const responseArgs = [];
|
|
119
|
+
const cmdArgsOptional = def && def.encodeMissingFieldsBehavior === 'skip';
|
|
120
|
+
const responseArgsOptional = def && def.response && def.response.encodeMissingFieldsBehavior === 'skip';
|
|
114
121
|
if (def && def.args) {
|
|
115
122
|
for (const [argName, argType] of Object.entries(def.args)) {
|
|
116
123
|
cmdArgs.push({
|
|
@@ -128,7 +135,9 @@ function parseCluster(ClusterClass, exportName) {
|
|
|
128
135
|
});
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
|
-
commands.push({
|
|
138
|
+
commands.push({
|
|
139
|
+
name, args: cmdArgs, responseArgs, cmdArgsOptional, responseArgsOptional,
|
|
140
|
+
});
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
return {
|
|
@@ -188,13 +197,14 @@ function generateClusterInterface(cluster) {
|
|
|
188
197
|
// Determine return type based on response args
|
|
189
198
|
let returnType = 'void';
|
|
190
199
|
if (cmd.responseArgs && cmd.responseArgs.length > 0) {
|
|
191
|
-
|
|
200
|
+
const sep = cmd.responseArgsOptional ? '?: ' : ': ';
|
|
201
|
+
returnType = `{ ${cmd.responseArgs.map(a => `${a.name}${sep}${a.tsType}`).join('; ')} }`;
|
|
192
202
|
}
|
|
193
203
|
|
|
194
204
|
if (cmd.args.length > 0) {
|
|
195
|
-
//
|
|
196
|
-
const allArgsOptional = cmd.args.every(a => a.tsType === 'Buffer');
|
|
197
|
-
const argsType = `{ ${cmd.args.map(a => `${a.name}${a.tsType === 'Buffer' ? '?' : ''}: ${a.tsType}`).join('; ')} }`;
|
|
205
|
+
// Args are optional when encodeMissingFieldsBehavior is 'skip', or when all args are Buffers
|
|
206
|
+
const allArgsOptional = cmd.cmdArgsOptional || cmd.args.every(a => a.tsType === 'Buffer');
|
|
207
|
+
const argsType = `{ ${cmd.args.map(a => `${a.name}${(cmd.cmdArgsOptional || a.tsType === 'Buffer') ? '?' : ''}: ${a.tsType}`).join('; ')} }`;
|
|
198
208
|
// If all args are optional, make the entire args object optional
|
|
199
209
|
lines.push(` ${cmd.name}(args${allArgsOptional ? '?' : ''}: ${argsType}, opts?: ClusterCommandOptions): Promise<${returnType}>;`);
|
|
200
210
|
} else {
|
|
@@ -270,6 +280,8 @@ type ZCLNodeConstructorInput = {
|
|
|
270
280
|
meta?: unknown
|
|
271
281
|
) => Promise<void>;
|
|
272
282
|
};
|
|
283
|
+
|
|
284
|
+
type ZCLEnum8Status = ${zclTypeToTS(ZCLDataTypes.enum8Status, false)};
|
|
273
285
|
`);
|
|
274
286
|
|
|
275
287
|
// Base ZCLNodeCluster interface
|
|
@@ -372,6 +384,7 @@ type ZCLNodeConstructorInput = {
|
|
|
372
384
|
clusters: ClusterRegistry & {
|
|
373
385
|
[clusterName: string]: ZCLNodeCluster | undefined;
|
|
374
386
|
};
|
|
387
|
+
bind(clusterName: string, impl: BoundCluster): void;
|
|
375
388
|
};
|
|
376
389
|
|
|
377
390
|
export interface ZCLNode {
|
|
@@ -402,6 +415,15 @@ export const CLUSTER: {
|
|
|
402
415
|
}
|
|
403
416
|
lines.push('};');
|
|
404
417
|
|
|
418
|
+
lines.push(`export class BoundCluster {
|
|
419
|
+
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export class ZCLError extends Error {
|
|
423
|
+
zclStatus: ZCLEnum8Status;
|
|
424
|
+
constructor(zclStatus?: ZCLEnum8Status);
|
|
425
|
+
}`);
|
|
426
|
+
|
|
405
427
|
// Export all cluster classes
|
|
406
428
|
for (const cluster of clusters) {
|
|
407
429
|
const interfaceName = toInterfaceName(cluster);
|