zigbee-clusters 2.9.0 → 2.10.0
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 +46 -26
- 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/metering.js +41 -21
- package/lib/clusters/ota.js +274 -3
- package/lib/util/index.js +10 -0
- package/package.json +2 -2
- 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
|
}
|
|
@@ -579,32 +581,22 @@ export interface MeteringClusterAttributes {
|
|
|
579
581
|
currentTier3SummationReceived?: number;
|
|
580
582
|
currentTier4SummationDelivered?: number;
|
|
581
583
|
currentTier4SummationReceived?: number;
|
|
582
|
-
status?:
|
|
584
|
+
status?: Partial<{ checkMeter: boolean; lowBattery: boolean; tamperDetect: boolean; powerFailure: boolean; powerQuality: boolean; leakDetect: boolean; serviceDisconnect: boolean }>;
|
|
583
585
|
remainingBatteryLife?: number;
|
|
584
586
|
hoursInOperation?: number;
|
|
585
587
|
hoursInFault?: number;
|
|
586
|
-
extendedStatus?: unknown;
|
|
587
588
|
unitOfMeasure?: unknown;
|
|
588
589
|
multiplier?: number;
|
|
589
590
|
divisor?: number;
|
|
590
|
-
summationFormatting?: unknown;
|
|
591
|
-
demandFormatting?: unknown;
|
|
592
|
-
historicalConsumptionFormatting?: unknown;
|
|
593
|
-
meteringDeviceType?: unknown;
|
|
594
591
|
siteId?: Buffer;
|
|
595
592
|
meterSerialNumber?: Buffer;
|
|
596
593
|
energyCarrierUnitOfMeasure?: unknown;
|
|
597
|
-
energyCarrierSummationFormatting?: unknown;
|
|
598
|
-
energyCarrierDemandFormatting?: unknown;
|
|
599
594
|
temperatureUnitOfMeasure?: unknown;
|
|
600
|
-
temperatureFormatting?: unknown;
|
|
601
595
|
moduleSerialNumber?: Buffer;
|
|
602
596
|
operatingTariffLabelDelivered?: Buffer;
|
|
603
597
|
operatingTariffLabelReceived?: Buffer;
|
|
604
598
|
customerIdNumber?: Buffer;
|
|
605
599
|
alternativeUnitOfMeasure?: unknown;
|
|
606
|
-
alternativeDemandFormatting?: unknown;
|
|
607
|
-
alternativeConsumptionFormatting?: unknown;
|
|
608
600
|
instantaneousDemand?: number;
|
|
609
601
|
currentDayConsumptionDelivered?: number;
|
|
610
602
|
currentDayConsumptionReceived?: number;
|
|
@@ -714,14 +706,6 @@ export interface MeteringClusterAttributes {
|
|
|
714
706
|
currentTier4Block14SummationDelivered?: number;
|
|
715
707
|
currentTier4Block15SummationDelivered?: number;
|
|
716
708
|
currentTier4Block16SummationDelivered?: number;
|
|
717
|
-
genericAlarmMask?: unknown;
|
|
718
|
-
electricityAlarmMask?: unknown;
|
|
719
|
-
genericFlowPressureAlarmMask?: unknown;
|
|
720
|
-
waterSpecificAlarmMask?: unknown;
|
|
721
|
-
heatAndCoolingSpecificAlarmMask?: unknown;
|
|
722
|
-
gasSpecificAlarmMask?: unknown;
|
|
723
|
-
extendedGenericAlarmMask?: unknown;
|
|
724
|
-
manufacturerAlarmMask?: unknown;
|
|
725
709
|
currentNoTierBlock1SummationReceived?: number;
|
|
726
710
|
currentNoTierBlock2SummationReceived?: number;
|
|
727
711
|
currentNoTierBlock3SummationReceived?: number;
|
|
@@ -742,12 +726,10 @@ export interface MeteringClusterAttributes {
|
|
|
742
726
|
billToDateTimeStampDelivered?: number;
|
|
743
727
|
projectedBillDelivered?: number;
|
|
744
728
|
projectedBillTimeStampDelivered?: number;
|
|
745
|
-
billDeliveredTrailingDigit?: unknown;
|
|
746
729
|
billToDateReceived?: number;
|
|
747
730
|
billToDateTimeStampReceived?: number;
|
|
748
731
|
projectedBillReceived?: number;
|
|
749
732
|
projectedBillTimeStampReceived?: number;
|
|
750
|
-
billReceivedTrailingDigit?: unknown;
|
|
751
733
|
proposedChangeSupplyImplementationTime?: number;
|
|
752
734
|
proposedChangeSupplyStatus?: unknown;
|
|
753
735
|
uncontrolledFlowThreshold?: number;
|
|
@@ -759,7 +741,7 @@ export interface MeteringClusterAttributes {
|
|
|
759
741
|
}
|
|
760
742
|
|
|
761
743
|
export interface MeteringCluster extends ZCLNodeCluster {
|
|
762
|
-
readAttributes<K extends 'currentSummationDelivered' | 'currentSummationReceived' | 'currentMaxDemandDelivered' | 'currentMaxDemandReceived' | 'dftSummation' | 'dailyFreezeTime' | 'powerFactor' | 'readingSnapShotTime' | 'currentMaxDemandDeliveredTime' | 'currentMaxDemandReceivedTime' | 'defaultUpdatePeriod' | 'fastPollUpdatePeriod' | 'currentBlockPeriodConsumptionDelivered' | 'dailyConsumptionTarget' | 'currentBlock' | 'profileIntervalPeriod' | 'currentTier1SummationDelivered' | 'currentTier1SummationReceived' | 'currentTier2SummationDelivered' | 'currentTier2SummationReceived' | 'currentTier3SummationDelivered' | 'currentTier3SummationReceived' | 'currentTier4SummationDelivered' | 'currentTier4SummationReceived' | 'status' | 'remainingBatteryLife' | 'hoursInOperation' | 'hoursInFault' | '
|
|
744
|
+
readAttributes<K extends 'currentSummationDelivered' | 'currentSummationReceived' | 'currentMaxDemandDelivered' | 'currentMaxDemandReceived' | 'dftSummation' | 'dailyFreezeTime' | 'powerFactor' | 'readingSnapShotTime' | 'currentMaxDemandDeliveredTime' | 'currentMaxDemandReceivedTime' | 'defaultUpdatePeriod' | 'fastPollUpdatePeriod' | 'currentBlockPeriodConsumptionDelivered' | 'dailyConsumptionTarget' | 'currentBlock' | 'profileIntervalPeriod' | 'currentTier1SummationDelivered' | 'currentTier1SummationReceived' | 'currentTier2SummationDelivered' | 'currentTier2SummationReceived' | 'currentTier3SummationDelivered' | 'currentTier3SummationReceived' | 'currentTier4SummationDelivered' | 'currentTier4SummationReceived' | 'status' | 'remainingBatteryLife' | 'hoursInOperation' | 'hoursInFault' | 'unitOfMeasure' | 'multiplier' | 'divisor' | 'siteId' | 'meterSerialNumber' | 'energyCarrierUnitOfMeasure' | 'temperatureUnitOfMeasure' | 'moduleSerialNumber' | 'operatingTariffLabelDelivered' | 'operatingTariffLabelReceived' | 'customerIdNumber' | 'alternativeUnitOfMeasure' | 'instantaneousDemand' | 'currentDayConsumptionDelivered' | 'currentDayConsumptionReceived' | 'previousDayConsumptionDelivered' | 'previousDayConsumptionReceived' | 'currentPartialProfileIntervalStartTimeDelivered' | 'currentPartialProfileIntervalStartTimeReceived' | 'currentPartialProfileIntervalValueDelivered' | 'currentPartialProfileIntervalValueReceived' | 'currentDayMaxPressure' | 'currentDayMinPressure' | 'previousDayMaxPressure' | 'previousDayMinPressure' | 'currentDayMaxDemand' | 'previousDayMaxDemand' | 'currentMonthMaxDemand' | 'currentYearMaxDemand' | 'currentDayMaxEnergyCarrierDemand' | 'previousDayMaxEnergyCarrierDemand' | 'currentMonthMaxEnergyCarrierDemand' | 'currentMonthMinEnergyCarrierDemand' | 'currentYearMaxEnergyCarrierDemand' | 'currentYearMinEnergyCarrierDemand' | 'maxNumberOfPeriodsDelivered' | 'currentDemandDelivered' | 'demandLimit' | 'demandIntegrationPeriod' | 'numberOfDemandSubintervals' | 'demandLimitArmDuration' | 'currentNoTierBlock1SummationDelivered' | 'currentNoTierBlock2SummationDelivered' | 'currentNoTierBlock3SummationDelivered' | 'currentNoTierBlock4SummationDelivered' | 'currentNoTierBlock5SummationDelivered' | 'currentNoTierBlock6SummationDelivered' | 'currentNoTierBlock7SummationDelivered' | 'currentNoTierBlock8SummationDelivered' | 'currentNoTierBlock9SummationDelivered' | 'currentNoTierBlock10SummationDelivered' | 'currentNoTierBlock11SummationDelivered' | 'currentNoTierBlock12SummationDelivered' | 'currentNoTierBlock13SummationDelivered' | 'currentNoTierBlock14SummationDelivered' | 'currentNoTierBlock15SummationDelivered' | 'currentNoTierBlock16SummationDelivered' | 'currentTier1Block1SummationDelivered' | 'currentTier1Block2SummationDelivered' | 'currentTier1Block3SummationDelivered' | 'currentTier1Block4SummationDelivered' | 'currentTier1Block5SummationDelivered' | 'currentTier1Block6SummationDelivered' | 'currentTier1Block7SummationDelivered' | 'currentTier1Block8SummationDelivered' | 'currentTier1Block9SummationDelivered' | 'currentTier1Block10SummationDelivered' | 'currentTier1Block11SummationDelivered' | 'currentTier1Block12SummationDelivered' | 'currentTier1Block13SummationDelivered' | 'currentTier1Block14SummationDelivered' | 'currentTier1Block15SummationDelivered' | 'currentTier1Block16SummationDelivered' | 'currentTier2Block1SummationDelivered' | 'currentTier2Block2SummationDelivered' | 'currentTier2Block3SummationDelivered' | 'currentTier2Block4SummationDelivered' | 'currentTier2Block5SummationDelivered' | 'currentTier2Block6SummationDelivered' | 'currentTier2Block7SummationDelivered' | 'currentTier2Block8SummationDelivered' | 'currentTier2Block9SummationDelivered' | 'currentTier2Block10SummationDelivered' | 'currentTier2Block11SummationDelivered' | 'currentTier2Block12SummationDelivered' | 'currentTier2Block13SummationDelivered' | 'currentTier2Block14SummationDelivered' | 'currentTier2Block15SummationDelivered' | 'currentTier2Block16SummationDelivered' | 'currentTier3Block1SummationDelivered' | 'currentTier3Block2SummationDelivered' | 'currentTier3Block3SummationDelivered' | 'currentTier3Block4SummationDelivered' | 'currentTier3Block5SummationDelivered' | 'currentTier3Block6SummationDelivered' | 'currentTier3Block7SummationDelivered' | 'currentTier3Block8SummationDelivered' | 'currentTier3Block9SummationDelivered' | 'currentTier3Block10SummationDelivered' | 'currentTier3Block11SummationDelivered' | 'currentTier3Block12SummationDelivered' | 'currentTier3Block13SummationDelivered' | 'currentTier3Block14SummationDelivered' | 'currentTier3Block15SummationDelivered' | 'currentTier3Block16SummationDelivered' | 'currentTier4Block1SummationDelivered' | 'currentTier4Block2SummationDelivered' | 'currentTier4Block3SummationDelivered' | 'currentTier4Block4SummationDelivered' | 'currentTier4Block5SummationDelivered' | 'currentTier4Block6SummationDelivered' | 'currentTier4Block7SummationDelivered' | 'currentTier4Block8SummationDelivered' | 'currentTier4Block9SummationDelivered' | 'currentTier4Block10SummationDelivered' | 'currentTier4Block11SummationDelivered' | 'currentTier4Block12SummationDelivered' | 'currentTier4Block13SummationDelivered' | 'currentTier4Block14SummationDelivered' | 'currentTier4Block15SummationDelivered' | 'currentTier4Block16SummationDelivered' | 'currentNoTierBlock1SummationReceived' | 'currentNoTierBlock2SummationReceived' | 'currentNoTierBlock3SummationReceived' | 'currentNoTierBlock4SummationReceived' | 'currentNoTierBlock5SummationReceived' | 'currentNoTierBlock6SummationReceived' | 'currentNoTierBlock7SummationReceived' | 'currentNoTierBlock8SummationReceived' | 'currentNoTierBlock9SummationReceived' | 'currentNoTierBlock10SummationReceived' | 'currentNoTierBlock11SummationReceived' | 'currentNoTierBlock12SummationReceived' | 'currentNoTierBlock13SummationReceived' | 'currentNoTierBlock14SummationReceived' | 'currentNoTierBlock15SummationReceived' | 'currentNoTierBlock16SummationReceived' | 'billToDateDelivered' | 'billToDateTimeStampDelivered' | 'projectedBillDelivered' | 'projectedBillTimeStampDelivered' | 'billToDateReceived' | 'billToDateTimeStampReceived' | 'projectedBillReceived' | 'projectedBillTimeStampReceived' | 'proposedChangeSupplyImplementationTime' | 'proposedChangeSupplyStatus' | 'uncontrolledFlowThreshold' | 'uncontrolledFlowThresholdUnitOfMeasure' | 'uncontrolledFlowMultiplier' | 'uncontrolledFlowDivisor' | 'flowStabilisationPeriod' | 'flowMeasurementPeriod'>(attributeNames: K[], opts?: { timeout?: number }): Promise<Pick<MeteringClusterAttributes, K>>;
|
|
763
745
|
readAttributes(attributeNames: Array<keyof MeteringClusterAttributes | number>, opts?: { timeout?: number }): Promise<Partial<MeteringClusterAttributes> & Record<number, unknown>>;
|
|
764
746
|
writeAttributes(attributes: Partial<MeteringClusterAttributes>, opts?: { timeout?: number }): Promise<unknown>;
|
|
765
747
|
on<K extends keyof MeteringClusterAttributes & string>(eventName: `attr.${K}`, listener: (value: MeteringClusterAttributes[K]) => void): this;
|
|
@@ -868,7 +850,36 @@ export interface OnOffCluster extends ZCLNodeCluster {
|
|
|
868
850
|
export interface OnOffSwitchCluster extends ZCLNodeCluster {
|
|
869
851
|
}
|
|
870
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
|
+
|
|
871
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 }>;
|
|
872
883
|
}
|
|
873
884
|
|
|
874
885
|
export interface PollControlClusterAttributes {
|
|
@@ -1185,7 +1196,7 @@ export interface ClusterAttributesByName {
|
|
|
1185
1196
|
occupancySensing: OccupancySensingClusterAttributes;
|
|
1186
1197
|
onOff: OnOffClusterAttributes;
|
|
1187
1198
|
onOffSwitch: Record<string, unknown>;
|
|
1188
|
-
ota:
|
|
1199
|
+
ota: OTAClusterAttributes;
|
|
1189
1200
|
pollControl: PollControlClusterAttributes;
|
|
1190
1201
|
powerConfiguration: PowerConfigurationClusterAttributes;
|
|
1191
1202
|
powerProfile: Record<string, unknown>;
|
|
@@ -1217,6 +1228,7 @@ export type ZCLNodeEndpoint = {
|
|
|
1217
1228
|
clusters: ClusterRegistry & {
|
|
1218
1229
|
[clusterName: string]: ZCLNodeCluster | undefined;
|
|
1219
1230
|
};
|
|
1231
|
+
bind(clusterName: string, impl: BoundCluster): void;
|
|
1220
1232
|
};
|
|
1221
1233
|
|
|
1222
1234
|
export interface ZCLNode {
|
|
@@ -1506,6 +1518,14 @@ export const CLUSTER: {
|
|
|
1506
1518
|
COMMANDS: unknown;
|
|
1507
1519
|
};
|
|
1508
1520
|
};
|
|
1521
|
+
export class BoundCluster {
|
|
1522
|
+
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
export class ZCLError extends Error {
|
|
1526
|
+
zclStatus: ZCLEnum8Status;
|
|
1527
|
+
constructor(zclStatus?: ZCLEnum8Status);
|
|
1528
|
+
}
|
|
1509
1529
|
export const AlarmsCluster: {
|
|
1510
1530
|
new (...args: any[]): AlarmsCluster;
|
|
1511
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/metering.js
CHANGED
|
@@ -33,34 +33,51 @@ const ATTRIBUTES = {
|
|
|
33
33
|
currentTier4SummationReceived: { id: 0x0107, type: ZCLDataTypes.uint48 }, // 263, Optional
|
|
34
34
|
|
|
35
35
|
// Meter Status (0x0200 - 0x02FF)
|
|
36
|
-
status: {
|
|
36
|
+
status: { // 512, Optional
|
|
37
|
+
id: 0x0200,
|
|
38
|
+
type: ZCLDataTypes.map8(
|
|
39
|
+
'checkMeter',
|
|
40
|
+
'lowBattery',
|
|
41
|
+
'tamperDetect',
|
|
42
|
+
'powerFailure',
|
|
43
|
+
'powerQuality',
|
|
44
|
+
'leakDetect',
|
|
45
|
+
'serviceDisconnect',
|
|
46
|
+
),
|
|
47
|
+
},
|
|
37
48
|
remainingBatteryLife: { id: 0x0201, type: ZCLDataTypes.uint8 }, // 513, Optional
|
|
38
49
|
hoursInOperation: { id: 0x0202, type: ZCLDataTypes.uint24 }, // 514, Optional
|
|
39
50
|
hoursInFault: { id: 0x0203, type: ZCLDataTypes.uint24 }, // 515, Optional
|
|
40
|
-
|
|
51
|
+
// TODO: map64 bitmap with general flags (bits 0-13) and meter-type-specific flags (bits 24+)
|
|
52
|
+
// extendedStatus: { id: 0x0204, type: ZCLDataTypes.map64() }, // 516, Optional
|
|
41
53
|
|
|
42
54
|
// Formatting Set (0x0300 - 0x03FF)
|
|
43
55
|
unitOfMeasure: { id: 0x0300, type: ZCLDataTypes.enum8 }, // 768, Mandatory
|
|
44
56
|
multiplier: { id: 0x0301, type: ZCLDataTypes.uint24 }, // 769, Optional
|
|
45
57
|
divisor: { id: 0x0302, type: ZCLDataTypes.uint24 }, // 770, Optional
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
// TODO: map8 packed fields — bits 0-2: right digits, 3-6: left digits, 7: suppress zeros
|
|
59
|
+
// summationFormatting: { id: 0x0303, type: ZCLDataTypes.map8() }, // 771, Mandatory
|
|
60
|
+
// demandFormatting: { id: 0x0304, type: ZCLDataTypes.map8() }, // 772, Optional
|
|
61
|
+
// historicalConsumptionFormatting: { id: 0x0305, type: ZCLDataTypes.map8() }, // 773, Optional
|
|
62
|
+
// TODO: ZCL spec says enum values, but wire type is map8 for backwards compat
|
|
63
|
+
// meteringDeviceType: { id: 0x0306, type: ZCLDataTypes.map8() }, // 774, Mandatory
|
|
50
64
|
siteId: { id: 0x0307, type: ZCLDataTypes.octstr }, // 775, Optional
|
|
51
65
|
meterSerialNumber: { id: 0x0308, type: ZCLDataTypes.octstr }, // 776, Optional
|
|
52
66
|
energyCarrierUnitOfMeasure: { id: 0x0309, type: ZCLDataTypes.enum8 }, // 777, Optional
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
// TODO: map8 packed fields — bits 0-2: right digits, 3-6: left digits, 7: suppress zeros
|
|
68
|
+
// energyCarrierSummationFormatting: { id: 0x030A, type: ZCLDataTypes.map8() }, // 778, Optional
|
|
69
|
+
// energyCarrierDemandFormatting: { id: 0x030B, type: ZCLDataTypes.map8() }, // 779, Optional
|
|
55
70
|
temperatureUnitOfMeasure: { id: 0x030C, type: ZCLDataTypes.enum8 }, // 780, Optional
|
|
56
|
-
|
|
71
|
+
// TODO: map8 packed fields — bits 0-2: right digits, 3-6: left digits, 7: suppress zeros
|
|
72
|
+
// temperatureFormatting: { id: 0x030D, type: ZCLDataTypes.map8() }, // 781, Optional
|
|
57
73
|
moduleSerialNumber: { id: 0x030E, type: ZCLDataTypes.octstr }, // 782, Optional
|
|
58
74
|
operatingTariffLabelDelivered: { id: 0x030F, type: ZCLDataTypes.octstr }, // 783, Optional
|
|
59
75
|
operatingTariffLabelReceived: { id: 0x0310, type: ZCLDataTypes.octstr }, // 784, Optional
|
|
60
76
|
customerIdNumber: { id: 0x0311, type: ZCLDataTypes.octstr }, // 785, Optional
|
|
61
77
|
alternativeUnitOfMeasure: { id: 0x0312, type: ZCLDataTypes.enum8 }, // 786, Optional
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
// TODO: map8 packed fields — bits 0-2: right digits, 3-6: left digits, 7: suppress zeros
|
|
79
|
+
// alternativeDemandFormatting: { id: 0x0313, type: ZCLDataTypes.map8() }, // 787, Optional
|
|
80
|
+
// alternativeConsumptionFormatting: { id: 0x0314, type: ZCLDataTypes.map8() }, // 788, Optional
|
|
64
81
|
|
|
65
82
|
// Historical Consumption (0x0400 - 0x04FF)
|
|
66
83
|
instantaneousDemand: { id: 0x0400, type: ZCLDataTypes.int24 }, // 1024, Optional
|
|
@@ -193,14 +210,15 @@ const ATTRIBUTES = {
|
|
|
193
210
|
currentTier4Block16SummationDelivered: { id: 0x074F, type: ZCLDataTypes.uint48 },
|
|
194
211
|
|
|
195
212
|
// Alarms (0x0800 - 0x08FF)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
213
|
+
// TODO: map16/32/48 alarm masks — bits map to alarm codes in their group (spec 10.4.2.2.9.1)
|
|
214
|
+
// genericAlarmMask: { id: 0x0800, type: ZCLDataTypes.map16() }, // 2048, Optional
|
|
215
|
+
// electricityAlarmMask: { id: 0x0801, type: ZCLDataTypes.map32() }, // 2049, Optional
|
|
216
|
+
// genericFlowPressureAlarmMask: { id: 0x0802, type: ZCLDataTypes.map16() }, // 2050, Optional
|
|
217
|
+
// waterSpecificAlarmMask: { id: 0x0803, type: ZCLDataTypes.map16() }, // 2051, Optional
|
|
218
|
+
// heatAndCoolingSpecificAlarmMask: { id: 0x0804, type: ZCLDataTypes.map16() }, // 2052, Optional
|
|
219
|
+
// gasSpecificAlarmMask: { id: 0x0805, type: ZCLDataTypes.map16() }, // 2053, Optional
|
|
220
|
+
// extendedGenericAlarmMask: { id: 0x0806, type: ZCLDataTypes.map48() }, // 2054, Optional
|
|
221
|
+
// manufacturerAlarmMask: { id: 0x0807, type: ZCLDataTypes.map16() }, // 2055, Optional
|
|
204
222
|
|
|
205
223
|
// Block Information Received (0x0900 - 0x09FF)
|
|
206
224
|
// All attributes in this section are Optional
|
|
@@ -226,12 +244,14 @@ const ATTRIBUTES = {
|
|
|
226
244
|
billToDateTimeStampDelivered: { id: 0x0A01, type: ZCLDataTypes.uint32 }, // 2561, Optional
|
|
227
245
|
projectedBillDelivered: { id: 0x0A02, type: ZCLDataTypes.uint32 }, // 2562, Optional
|
|
228
246
|
projectedBillTimeStampDelivered: { id: 0x0A03, type: ZCLDataTypes.uint32 }, // 2563, Optional
|
|
229
|
-
|
|
247
|
+
// TODO: map8 packed field — most significant nibble = digits right of decimal
|
|
248
|
+
// billDeliveredTrailingDigit: { id: 0x0A04, type: ZCLDataTypes.map8() }, // 2564, Optional
|
|
230
249
|
billToDateReceived: { id: 0x0A10, type: ZCLDataTypes.uint32 }, // 2576, Optional
|
|
231
250
|
billToDateTimeStampReceived: { id: 0x0A11, type: ZCLDataTypes.uint32 }, // 2577, Optional
|
|
232
251
|
projectedBillReceived: { id: 0x0A12, type: ZCLDataTypes.uint32 }, // 2578, Optional
|
|
233
252
|
projectedBillTimeStampReceived: { id: 0x0A13, type: ZCLDataTypes.uint32 }, // 2579, Optional
|
|
234
|
-
|
|
253
|
+
// TODO: map8 packed field — most significant nibble = digits right of decimal
|
|
254
|
+
// billReceivedTrailingDigit: { id: 0x0A14, type: ZCLDataTypes.map8() }, // 2580, Optional
|
|
235
255
|
|
|
236
256
|
// Supply Control (0x0B00 - 0x0BFF)
|
|
237
257
|
proposedChangeSupplyImplementationTime: { // Optional
|
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.0",
|
|
4
4
|
"description": "Zigbee Cluster Library for Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -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);
|