diff --git a/.github/workflows/generate-types.yml b/.github/workflows/generate-types.yml new file mode 100644 index 0000000..a225f32 --- /dev/null +++ b/.github/workflows/generate-types.yml @@ -0,0 +1,63 @@ +name: Generate TypeScript Types + +on: + push: + branches: + - develop + paths: + - 'lib/clusters/**/*.js' + - 'scripts/generate-types.js' + +jobs: + generate-types: + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: develop + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Generate TypeScript types + run: npm run generate-types + + - name: Validate TypeScript types compile + run: npx tsc --noEmit index.d.ts + + - name: Check for changes + id: check-changes + run: | + if git diff --quiet index.d.ts; then + echo "changed=false" >> $GITHUB_OUTPUT + echo "No changes to index.d.ts" + else + echo "changed=true" >> $GITHUB_OUTPUT + echo "index.d.ts has been updated" + git diff --stat index.d.ts + fi + + - name: Commit and push changes + if: steps.check-changes.outputs.changed == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add index.d.ts + git commit -m "chore(types): auto-generate TypeScript definitions + + Updated by GitHub Actions after cluster changes. + + [skip ci]" + git push origin develop diff --git a/README.md b/README.md index ef8f1fb..4f7cf69 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,28 @@ zclNode.endpoints[1].clusters["scenes"].ikeaSceneMove({ mode: 0, transitionTime: This also works for `BoundClusters`, if a node sends commands to Homey using a custom cluster it is necessary to implement a custom `BoundCluster` and bind it to the `ZCLNode` instance. For an example check the implementation in the `com.ikea.tradfri` driver [remote_control](https://github.com/athombv/com.ikea.tradfri-example/tree/master/drivers/remote_control/device.js). +## TypeScript Types + +This project includes auto-generated TypeScript definitions (`index.d.ts`) for all clusters, attributes, and commands. + +### Manual Generation + +To regenerate TypeScript types after modifying cluster definitions: + +```bash +npm run generate-types +``` + +This runs `scripts/generate-types.js` which loads all cluster modules and generates typed interfaces. + +### Automatic Generation (GitHub Actions) + +TypeScript types are automatically regenerated when changes are pushed to the `develop` branch that affect: +- `lib/clusters/**/*.js` - cluster definitions +- `scripts/generate-types.js` - the generator script + +The workflow commits updated types back to `develop` if changes are detected. + ## Contributing Great if you'd like to contribute to this project, a few things to take note of before submitting a PR: diff --git a/index.d.ts b/index.d.ts index 90d3e58..3eb6ec8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1,6 @@ +// Auto-generated TypeScript definitions for zigbee-clusters +// Generated by scripts/generate-types.js + import * as EventEmitter from "events"; type EndpointDescriptor = { @@ -10,294 +13,1016 @@ type ConstructorOptions = { endpointDescriptors: EndpointDescriptor[]; sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise; }; -interface ZCLNodeCluster extends EventEmitter { - /** - * Command which requests the remote cluster to report its generated commands. Generated - * commands are commands which may be sent by the remote cluster. - * - * TODO: handle the case where `lastResponse===false`. It might be possible that there are - * more commands to be reported than can be transmitted in one report (in practice very - * unlikely though). If `lastResponse===false` invoke `discoverCommandsGenerated` again - * starting from the index where the previous invocation stopped (`maxResults`). - * - * TODO: The manufacturer-specific sub-field SHALL be set to 0 to discover standard commands - * in a ZigBee cluster or 1 to discover manufacturer-specific commands in either a standard or - * a manufacturer-specific cluster. A manufacturer ID in this field of 0xffff (wildcard) will - * discover any manufacture- specific - * commands. - * - * @param {object} [opts=] - * @param {number} [opts.startValue=0] - * @param {number} [opts.maxResults=250] - * @returns {Promise} - */ - discoverCommandsGenerated({ - startValue, - maxResults, - }?: { + +export interface ZCLNodeCluster extends EventEmitter { + discoverCommandsGenerated(opts?: { startValue?: number; maxResults?: number; }): Promise; - /** - * Command which requests the remote cluster to report its received commands. Received - * commands are commands which may be received by the remote cluster. - * - * TODO: handle the case where `lastResponse===false`. It might be possible that there are - * more commands to be reported than can be transmitted in one report (in practice very - * unlikely though). If `lastResponse===false` invoke `discoverCommandsGenerated` again - * starting from the index where the previous invocation stopped (`maxResults`). - * - * TODO: The manufacturer-specific sub-field SHALL be set to 0 to discover standard commands - * in a ZigBee cluster or 1 to discover manufacturer-specific commands in either a standard or - * a manufacturer-specific cluster. A manufacturer ID in this field of 0xffff (wildcard) will - * discover any manufacture- specific commands. - * - * @param {object} [opts=] - * @param {number} [opts.startValue=0] - * @param {number} [opts.maxResults=255] - * @returns {Promise} - */ - discoverCommandsReceived({ - startValue, - maxResults, - }?: { + + discoverCommandsReceived(opts?: { startValue?: number; maxResults?: number; }): Promise; - /** - * Command which reads a given set of attributes from the remote cluster. - * Note: do not mix regular and manufacturer specific attributes. - * @param {string[]} attributeNames - * @param {{timeout: number}} [opts=] - * @returns {Promise>} - Object with values (e.g. `{ onOff: true }`) - */ + readAttributes( attributeNames: string[], - opts?: { - timeout: number; - } - ): Promise<{ - [x: string]: unknown; - }>; - /** - * Command which writes a given set of attribute key-value pairs to the remote cluster. - * Note: do not mix regular and manufacturer specific attributes. - * @param {object} attributes - Object with attribute names as keys and their values (e.g. `{ - * onOff: true, fakeAttributeName: 10 }`. - * @returns {Promise<*|{attributes: *}>} - */ - writeAttributes(attributes?: object): Promise< - | any - | { - attributes: any; - } - >; - /** - * Command which configures attribute reporting for the given `attributes` on the remote cluster. - * Note: do not mix regular and manufacturer specific attributes. - * @param {object} attributes - Attribute reporting configuration (e.g. `{ onOff: { - * minInterval: 0, maxInterval: 300, minChange: 1 } }`) - * @returns {Promise} - */ + opts?: { timeout?: number } + ): Promise<{ [x: string]: unknown }>; + + writeAttributes(attributes?: object): Promise; + configureReporting(attributes?: object): Promise; - /** - * @typedef {object} ReadReportingConfiguration - * @property {ZCLDataTypes.enum8Status} status - * @property {'reported'|'received'} direction - * @property {number} attributeId - * @property {ZCLDataType.id} [attributeDataType] - * @property {number} [minInterval] - * @property {number} [maxInterval] - * @property {number} [minChange] - * @property {number} [timeoutPeriod] - */ - /** - * Command which retrieves the reporting configurations for the given `attributes` from the - * remote cluster. Currently this only takes the 'reported' into account, this represents the - * reports the remote cluster would sent out, instead of receive (which is likely the most - * interesting). - * Note: do not mix regular and manufacturer specific attributes. - * @param {Array} attributes - Array with number/strings (either attribute id, or attribute name). - * @returns {Promise} - Returns array with - * ReadReportingConfiguration objects per attribute. - */ - readReportingConfiguration(attributes?: any[]): Promise< - { - status: any; - direction: "reported" | "received"; - attributeId: number; - attributeDataType?: number; - minInterval?: number; - maxInterval?: number; - minChange?: number; - timeoutPeriod?: number; - }[] - >; - /** - * Command which discovers the implemented attributes on the remote cluster. - * - * TODO: handle the case where `lastResponse===false`. It might be possible that there are - * more commands to be reported than can be transmitted in one report (in practice very - * unlikely though). If `lastResponse===false` invoke `discoverCommandsGenerated` again - * starting from the index where the previous invocation stopped (`maxResults`). - * - * TODO: The manufacturer specific sub-field SHALL be set to 0 to discover standard attributes - * in a ZigBee cluster or 1 to discover manufacturer specific attributes in either a standard - * or a manufacturer specific cluster. - * - * @returns {Promise} - Array with string or number values (depending on if the - * attribute - * is implemented in zigbee-clusters or not). - */ - discoverAttributes(): Promise; - /** - * Command which discovers the implemented attributes on the remote cluster, the difference with - * `discoverAttributes` is that this command also reports the access control field of the - * attribute (whether it is readable/writable/reportable). - * - * TODO: handle the case where `lastResponse===false`. It might be possible that there are - * more commands to be reported than can be transmitted in one report (in practice very - * unlikely though). If `lastResponse===false` invoke `discoverCommandsGenerated` again - * starting from the index where the previous invocation stopped (`maxResults`). - * - * TODO: The manufacturer-specific sub-field SHALL be set to 0 to discover standard attributes - * in a ZigBee cluster or 1 to discover manufacturer-specific attributes in either a standard - * or a manufacturer- specific cluster. A manufacturer ID in this field of 0xffff (wildcard) - * will discover any manufacture-specific attributes. - * - * @returns {Promise} - Returns an array with objects with attribute names as keys and - * following object as values: `{name: string, id: number, acl: { readable: boolean, writable: - * boolean, reportable: boolean } }`. Note that `name` is optional based on whether the - * attribute is implemented in zigbee-clusters. - */ - discoverAttributesExtended(): Promise; -} - -interface BasicCluster extends ZCLNodeCluster { + + readReportingConfiguration(attributes?: (string | number)[]): Promise<{ + status: string; + direction: 'reported' | 'received'; + attributeId: number; + attributeDataType?: number; + minInterval?: number; + maxInterval?: number; + minChange?: number; + timeoutPeriod?: number; + }[]>; + + discoverAttributes(): Promise<(string | number)[]>; + + discoverAttributesExtended(): Promise<{ + name?: string; + id: number; + acl: { readable: boolean; writable: boolean; reportable: boolean }; + }[]>; +} + +export interface AlarmsCluster extends ZCLNodeCluster { + resetAllAlarms(): Promise; + getAlarm(): Promise; + resetAlarmLog(): Promise; +} + +export interface AnalogInputClusterAttributes { + description?: string; + maxPresentValue?: number; + minPresentValue?: number; + outOfService?: boolean; + presentValue?: number; + reliability?: 'noFaultDetected' | 'noSensor' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'noOutput' | 'unreliableOther' | 'processError' | 'configurationError'; + resolution?: number; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} + +export interface AnalogInputCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface AnalogOutputClusterAttributes { + description?: string; + maxPresentValue?: number; + minPresentValue?: number; + outOfService?: boolean; + presentValue?: number; + reliability?: 'noFaultDetected' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'unreliableOther' | 'processError' | 'configurationError'; + relinquishDefault?: number; + resolution?: number; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} + +export interface AnalogOutputCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface AnalogValueClusterAttributes { + description?: string; + outOfService?: boolean; + presentValue?: number; + reliability?: 'noFaultDetected' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'unreliableOther' | 'processError' | 'configurationError'; + relinquishDefault?: number; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} + +export interface AnalogValueCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface BallastConfigurationClusterAttributes { + physicalMinLevel?: number; + physicalMaxLevel?: number; + ballastStatus?: Partial<{ nonOperational: boolean; lampNotInSocket: boolean }>; + minLevel?: number; + maxLevel?: number; + powerOnLevel?: number; + powerOnFadeTime?: number; + intrinsicBallastFactor?: number; + ballastFactorAdjustment?: number; + lampQuantity?: number; + lampType?: string; + lampManufacturer?: string; + lampRatedHours?: number; + lampBurnHours?: number; + lampAlarmMode?: Partial<{ lampBurnHours: boolean }>; + lampBurnHoursTripPoint?: number; +} + +export interface BallastConfigurationCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface BasicClusterAttributes { + zclVersion?: number; + appVersion?: number; + stackVersion?: number; + hwVersion?: number; + manufacturerName?: string; + modelId?: string; + dateCode?: string; + powerSource?: 'unknown' | 'mains' | 'mains3phase' | 'battery' | 'dc' | 'emergencyMains' | 'emergencyTransfer'; + appProfileVersion?: number; + locationDesc?: string; + physicalEnv?: 'Unspecified' | 'Atrium' | 'Bar' | 'Courtyard' | 'Bathroom' | 'Bedroom' | 'BilliardRoom' | 'UtilityRoom' | 'Cellar' | 'StorageCloset' | 'Theater' | 'Office' | 'Deck' | 'Den' | 'DiningRoom' | 'ElectricalRoom' | 'Elevator' | 'Entry' | 'FamilyRoom' | 'MainFloor' | 'Upstairs' | 'Downstairs' | 'Basement' | 'Gallery' | 'GameRoom' | 'Garage' | 'Gym' | 'Hallway' | 'House' | 'Kitchen' | 'LaundryRoom' | 'Library' | 'MasterBedroom' | 'MudRoom' | 'Nursery' | 'Pantry' | 'Outside' | 'Pool' | 'Porch' | 'SewingRoom' | 'SittingRoom' | 'Stairway' | 'Yard' | 'Attic' | 'HotTub' | 'LivingRoom' | 'Sauna' | 'Workshop' | 'GuestBedroom' | 'GuestBath' | 'PowderRoom' | 'BackYard' | 'FrontYard' | 'Patio' | 'Driveway' | 'SunRoom' | 'Spa' | 'Whirlpool' | 'Shed' | 'EquipmentStorage' | 'HobbyRoom' | 'Fountain' | 'Pond' | 'ReceptionRoom' | 'BreakfastRoom' | 'Nook' | 'Garden' | 'Balcony' | 'PanicRoom' | 'Terrace' | 'Roof' | 'Toilet' | 'ToiletMain' | 'OutsideToilet' | 'ShowerRoom' | 'Study' | 'FrontGarden' | 'BackGarden' | 'Kettle' | 'Television' | 'Stove' | 'Microwave' | 'Toaster' | 'Vacuum' | 'Appliance' | 'FrontDoor' | 'BackDoor' | 'FridgeDoor' | 'MedicationCabinetDoor' | 'WardrobeDoor' | 'FrontCupboardDoor' | 'OtherDoor' | 'WaitingRoom' | 'TriageRoom' | 'DoctorsOffice' | 'PatientsPrivateRoom' | 'ConsultationRoom' | 'NurseStation' | 'Ward' | 'Corridor' | 'OperatingTheatre' | 'DentalSurgeryRoom' | 'MedicalImagingRoom' | 'DecontaminationRoom' | 'Unknown'; + deviceEnabled?: boolean; + alarmMask?: Partial<{ hardwareFault: boolean; softwareFault: boolean }>; + disableLocalConfig?: Partial<{ factoryResetDisabled: boolean; configurationDisabled: boolean }>; + swBuildId?: string; +} + +export interface BasicCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; factoryReset(): Promise; } -interface PowerConfigurationCluster extends ZCLNodeCluster {} +export interface BinaryInputClusterAttributes { + activeText?: string; + description?: string; + inactiveText?: string; + outOfService?: boolean; + polarity?: 'normal' | 'reverse'; + presentValue?: boolean; + reliability?: 'noFaultDetected' | 'noSensor' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'noOutput' | 'unreliableOther' | 'processError' | 'configurationError'; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} -interface OnOffCluster extends ZCLNodeCluster { - setOn(): Promise; +export interface BinaryInputCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface BinaryOutputClusterAttributes { + activeText?: string; + description?: string; + inactiveText?: string; + minimumOffTime?: number; + minimumOnTime?: number; + outOfService?: boolean; + polarity?: 'normal' | 'reverse'; + presentValue?: boolean; + reliability?: 'noFaultDetected' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'unreliableOther' | 'processError' | 'configurationError'; + relinquishDefault?: boolean; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} + +export interface BinaryOutputCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface BinaryValueClusterAttributes { + activeText?: string; + description?: string; + inactiveText?: string; + minimumOffTime?: number; + minimumOnTime?: number; + outOfService?: boolean; + polarity?: 'normal' | 'reverse'; + presentValue?: boolean; + reliability?: 'noFaultDetected' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'unreliableOther' | 'processError' | 'configurationError'; + relinquishDefault?: boolean; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} + +export interface BinaryValueCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface ColorControlClusterAttributes { + currentHue?: number; + currentSaturation?: number; + currentX?: number; + currentY?: number; + colorTemperatureMireds?: number; + colorMode?: 'currentHueAndCurrentSaturation' | 'currentXAndCurrentY' | 'colorTemperatureMireds'; + colorCapabilities?: Partial<{ hueAndSaturation: boolean; enhancedHue: boolean; colorLoop: boolean; xy: boolean; colorTemperature: boolean }>; + colorTempPhysicalMinMireds?: number; + colorTempPhysicalMaxMireds?: number; +} + +export interface ColorControlCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + moveToHue(args: { hue: number; direction: 'shortestDistance' | 'longestDistance' | 'up' | 'down'; transitionTime: number }): Promise; + moveToSaturation(args: { saturation: number; transitionTime: number }): Promise; + moveToHueAndSaturation(args: { hue: number; saturation: number; transitionTime: number }): Promise; + moveToColor(args: { colorX: number; colorY: number; transitionTime: number }): Promise; + moveToColorTemperature(args: { colorTemperature: number; transitionTime: number }): Promise; +} + +export interface DehumidificationControlCluster extends ZCLNodeCluster { +} + +export interface DeviceTemperatureClusterAttributes { + currentTemperature?: number; + minTempExperienced?: number; + maxTempExperienced?: number; + overTempTotalDwell?: number; + deviceTempAlarmMask?: Partial<{ deviceTemperatureTooLow: boolean; deviceTemperatureTooHigh: boolean }>; + lowTempThreshold?: number; + highTempThreshold?: number; + lowTempDwellTripPoint?: number; + highTempDwellTripPoint?: number; +} + +export interface DeviceTemperatureCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface DiagnosticsCluster extends ZCLNodeCluster { +} + +export interface DoorLockClusterAttributes { + lockState?: 'notFullyLocked' | 'locked' | 'unlocked' | 'undefined'; + lockType?: 'deadBolt' | 'magnetic' | 'other' | 'mortise' | 'rim' | 'latchBolt' | 'cylindricalLock' | 'tubularLock' | 'interconnectedLock' | 'deadLatch' | 'doorFurniture'; + actuatorEnabled?: boolean; + doorState?: 'open' | 'closed' | 'errorJammed' | 'errorForcedOpen' | 'errorUnspecified' | 'undefined'; + doorOpenEvents?: number; + doorClosedEvents?: number; + openPeriod?: number; + numberOfLogRecordsSupported?: number; + numberOfTotalUsersSupported?: number; + numberOfPINUsersSupported?: number; + numberOfRFIDUsersSupported?: number; + numberOfWeekDaySchedulesSupportedPerUser?: number; + numberOfYearDaySchedulesSupportedPerUser?: number; + numberOfHolidaySchedulesSupported?: number; + maxPINCodeLength?: number; + minPINCodeLength?: number; + maxRFIDCodeLength?: number; + minRFIDCodeLength?: number; + enableLogging?: boolean; + language?: string; + ledSettings?: number; + autoRelockTime?: number; + soundVolume?: number; + operatingMode?: 'normal' | 'vacation' | 'privacy' | 'noRFLockOrUnlock' | 'passage'; + supportedOperatingModes?: Partial<{ normal: boolean; vacation: boolean; privacy: boolean; noRFLockOrUnlock: boolean; passage: boolean }>; + defaultConfigurationRegister?: Partial<{ enableLocalProgramming: boolean; keypadInterfaceDefaultAccess: boolean; rfInterfaceDefaultAccess: boolean; reserved3: boolean; reserved4: boolean; soundEnabled: boolean; autoRelockTimeSet: boolean; ledSettingsSet: boolean }>; + enableLocalProgramming?: boolean; + enableOneTouchLocking?: boolean; + enableInsideStatusLED?: boolean; + enablePrivacyModeButton?: boolean; + wrongCodeEntryLimit?: number; + userCodeTemporaryDisableTime?: number; + sendPINOverTheAir?: boolean; + requirePINforRFOperation?: boolean; + securityLevel?: 'network' | 'apsSecurity'; + alarmMask?: Partial<{ deadboltJammed: boolean; lockResetToFactoryDefaults: boolean; reserved2: boolean; rfModulePowerCycled: boolean; tamperAlarmWrongCodeEntryLimit: boolean; tamperAlarmFrontEscutcheonRemoved: boolean; forcedDoorOpenUnderDoorLockedCondition: boolean }>; + keypadOperationEventMask?: Partial<{ unknownOrManufacturerSpecificKeypadOperationEvent: boolean; lockSourceKeypad: boolean; unlockSourceKeypad: boolean; lockSourceKeypadErrorInvalidPIN: boolean; lockSourceKeypadErrorInvalidSchedule: boolean; unlockSourceKeypadErrorInvalidCode: boolean; unlockSourceKeypadErrorInvalidSchedule: boolean; nonAccessUserOperationEventSourceKeypad: boolean }>; + rfOperationEventMask?: Partial<{ unknownOrManufacturerSpecificKeypadOperationEvent: boolean; lockSourceRF: boolean; unlockSourceRF: boolean; lockSourceRFErrorInvalidCode: boolean; lockSourceRFErrorInvalidSchedule: boolean; unlockSourceRFErrorInvalidCode: boolean; unlockSourceRFErrorInvalidSchedule: boolean }>; + manualOperationEventMask?: Partial<{ unknownOrManufacturerSpecificManualOperationEvent: boolean; thumbturnLock: boolean; thumbturnUnlock: boolean; oneTouchLock: boolean; keyLock: boolean; keyUnlock: boolean; autoLock: boolean; scheduleLock: boolean; scheduleUnlock: boolean; manualLock: boolean; manualUnlock: boolean }>; + rfidOperationEventMask?: Partial<{ unknownOrManufacturerSpecificKeypadOperationEvent: boolean; lockSourceRFID: boolean; unlockSourceRFID: boolean; lockSourceRFIDErrorInvalidRFIDID: boolean; lockSourceRFIDErrorInvalidSchedule: boolean; unlockSourceRFIDErrorInvalidRFIDID: boolean; unlockSourceRFIDErrorInvalidSchedule: boolean }>; + keypadProgrammingEventMask?: Partial<{ unknownOrManufacturerSpecificKeypadProgrammingEvent: boolean; masterCodeChanged: boolean; pinCodeAdded: boolean; pinCodeDeleted: boolean; pinCodeChanged: boolean }>; + rfProgrammingEventMask?: Partial<{ unknownOrManufacturerSpecificRFProgrammingEvent: boolean; reserved1: boolean; pinCodeAdded: boolean; pinCodeDeleted: boolean; pinCodeChanged: boolean; rfidCodeAdded: boolean; rfidCodeDeleted: boolean }>; + rfidProgrammingEventMask?: Partial<{ unknownOrManufacturerSpecificRFIDProgrammingEvent: boolean; rfidCodeAdded: boolean; rfidCodeDeleted: boolean }>; +} + +export interface DoorLockCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + lockDoor(args?: { pinCode?: Buffer }): Promise<{ status: number }>; + unlockDoor(args?: { pinCode?: Buffer }): Promise<{ status: number }>; + toggle(args?: { pinCode?: Buffer }): Promise<{ status: number }>; + unlockWithTimeout(args: { timeout: number; pinCode?: Buffer }): Promise<{ status: number }>; + getLogRecord(args: { logIndex: number }): Promise<{ logEntryId: number; timestamp: number; eventType: number; source: number; eventIdOrAlarmCode: number; userId: number; pin: Buffer }>; + setPINCode(args: { userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported'; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported'; pinCode?: Buffer }): Promise<{ status: number }>; + getPINCode(args: { userId: number }): Promise<{ userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported'; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported'; pinCode: Buffer }>; + clearPINCode(args: { userId: number }): Promise<{ status: number }>; + clearAllPINCodes(): Promise<{ status: number }>; + setUserStatus(args: { userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported' }): Promise<{ status: number }>; + getUserStatus(args: { userId: number }): Promise<{ userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported' }>; + setWeekDaySchedule(args: { scheduleId: number; userId: number; daysMask: Partial<{ sunday: boolean; monday: boolean; tuesday: boolean; wednesday: boolean; thursday: boolean; friday: boolean; saturday: boolean }>; startHour: number; startMinute: number; endHour: number; endMinute: number }): Promise<{ status: number }>; + getWeekDaySchedule(args: { scheduleId: number; userId: number }): Promise<{ scheduleId: number; userId: number; status: number; daysMask: Partial<{ sunday: boolean; monday: boolean; tuesday: boolean; wednesday: boolean; thursday: boolean; friday: boolean; saturday: boolean }>; startHour: number; startMinute: number; endHour: number; endMinute: number }>; + clearWeekDaySchedule(args: { scheduleId: number; userId: number }): Promise<{ status: number }>; + setYearDaySchedule(args: { scheduleId: number; userId: number; localStartTime: number; localEndTime: number }): Promise<{ status: number }>; + getYearDaySchedule(args: { scheduleId: number; userId: number }): Promise<{ scheduleId: number; userId: number; status: number; localStartTime: number; localEndTime: number }>; + clearYearDaySchedule(args: { scheduleId: number; userId: number }): Promise<{ status: number }>; + setHolidaySchedule(args: { holidayScheduleId: number; localStartTime: number; localEndTime: number; operatingModeDuringHoliday: 'normal' | 'vacation' | 'privacy' | 'noRFLockOrUnlock' | 'passage' }): Promise<{ status: number }>; + getHolidaySchedule(args: { holidayScheduleId: number }): Promise<{ holidayScheduleId: number; status: number; localStartTime: number; localEndTime: number; operatingMode: 'normal' | 'vacation' | 'privacy' | 'noRFLockOrUnlock' | 'passage' }>; + clearHolidaySchedule(args: { holidayScheduleId: number }): Promise<{ status: number }>; + setUserType(args: { userId: number; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported' }): Promise<{ status: number }>; + getUserType(args: { userId: number }): Promise<{ userId: number; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported' }>; + setRFIDCode(args: { userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported'; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported'; rfidCode?: Buffer }): Promise<{ status: number }>; + getRFIDCode(args: { userId: number }): Promise<{ userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported'; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported'; rfidCode: Buffer }>; + clearRFIDCode(args: { userId: number }): Promise<{ status: number }>; + clearAllRFIDCodes(): Promise<{ status: number }>; + operationEventNotification(args: { operationEventSource: number; operationEventCode: number; userId: number; pin?: Buffer; zigBeeLocalTime: number; data?: Buffer }): Promise; + programmingEventNotification(args: { programEventSource: number; programEventCode: number; userId: number; pin?: Buffer; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported'; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported'; zigBeeLocalTime: number; data?: Buffer }): Promise; +} + +export interface ElectricalMeasurementClusterAttributes { + measurementType?: Partial<{ activeMeasurementAC: boolean; reactiveMeasurementAC: boolean; apparentMeasurementAC: boolean; phaseAMeasurement: boolean; phaseBMeasurement: boolean; phaseCMeasurement: boolean; dcMeasurement: boolean; harmonicsMeasurement: boolean; powerQualityMeasurement: boolean }>; + acFrequency?: number; + measuredPhase1stHarmonicCurrent?: number; + acFrequencyMultiplier?: number; + acFrequencyDivisor?: number; + phaseHarmonicCurrentMultiplier?: number; + rmsVoltage?: number; + rmsCurrent?: number; + activePower?: number; + reactivePower?: number; + acVoltageMultiplier?: number; + acVoltageDivisor?: number; + acCurrentMultiplier?: number; + acCurrentDivisor?: number; + acPowerMultiplier?: number; + acPowerDivisor?: number; + acAlarmsMask?: Partial<{ voltageOverload: boolean; currentOverload: boolean; activePowerOverload: boolean; reactivePowerOverload: boolean; averageRMSOverVoltage: boolean; averageRMSUnderVoltage: boolean; rmsExtremeOverVoltage: boolean; rmsExtremeUnderVoltage: boolean; rmsVoltageSag: boolean; rmsVoltageSwell: boolean }>; + acVoltageOverload?: number; + acCurrentOverload?: number; + acActivePowerOverload?: number; +} + +export interface ElectricalMeasurementCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface FanControlCluster extends ZCLNodeCluster { +} + +export interface FlowMeasurementClusterAttributes { + measuredValue?: number; + minMeasuredValue?: number; + maxMeasuredValue?: number; + tolerance?: number; +} + +export interface FlowMeasurementCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface GroupsClusterAttributes { + nameSupport?: Partial<{ groupNames: boolean }>; +} + +export interface GroupsCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + addGroup(args: { groupId: number; groupName: string }): Promise<{ status: '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'; groupId: number }>; + viewGroup(args: { groupId: number }): Promise<{ status: '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'; groupId: number; groupNames: string }>; + getGroupMembership(args: { groupIds: number[] }): Promise<{ capacity: number; groups: number[] }>; + removeGroup(args: { groupId: number }): Promise<{ status: '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'; groupId: number }>; + removeAllGroups(): Promise; + addGroupIfIdentify(args: { groupId: number; groupName: string }): Promise; +} + +export interface IasACECluster extends ZCLNodeCluster { +} + +export interface IasWDCluster extends ZCLNodeCluster { +} + +export interface IasZoneClusterAttributes { + zoneState?: 'notEnrolled' | 'enrolled'; + zoneType?: 'standardCIE' | 'motionSensor' | 'contactSwitch' | 'fireSensor' | 'waterSensor' | 'cabonMonoxideSensor' | 'personalEmergencyDevice' | 'vibrationMovementSensor' | 'remoteControl' | 'keyfob' | 'keypad' | 'standardWarningDevice' | 'glassBreakSensor' | 'securityRepeater' | 'invalidZoneType'; + zoneStatus?: Partial<{ alarm1: boolean; alarm2: boolean; tamper: boolean; battery: boolean; supervisionReports: boolean; restoreReports: boolean; trouble: boolean; acMains: boolean; test: boolean; batteryDefect: boolean }>; + iasCIEAddress?: string; + zoneId?: number; +} + +export interface IasZoneCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + zoneStatusChangeNotification(args: { zoneStatus: Partial<{ alarm1: boolean; alarm2: boolean; tamper: boolean; battery: boolean; supervisionReports: boolean; restoreReports: boolean; trouble: boolean; acMains: boolean; test: boolean; batteryDefect: boolean }>; extendedStatus: number; zoneId: number; delay: number }): Promise; + zoneEnrollResponse(args: { enrollResponseCode: 'success' | 'notSupported' | 'noEnrollPermit' | 'tooManyZones'; zoneId: number }): Promise; + zoneEnrollRequest(args: { zoneType: 'standard' | 'motionSensor' | 'contactSwitch' | 'fireSensor' | 'waterSensor' | 'carbonMonoxideSensor' | 'personalEmergencyDevice' | 'vibrationMovementSensor' | 'remoteControl' | 'keyFob' | 'keyPad' | 'standardWarningDevice' | 'glassBreakSensor' | 'securityRepeater' | 'invalid'; manufacturerCode: number }): Promise; + initiateNormalOperationMode(): Promise; +} + +export interface IdentifyClusterAttributes { + identifyTime?: number; +} + +export interface IdentifyCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + identify(args: { identifyTime: number }): Promise; + identifyQuery(): Promise<{ timeout: number }>; + triggerEffect(args: { effectIdentifier: 'blink' | 'breathe' | 'okay' | 'channelChange' | 'finish' | 'stop'; effectVariant: number }): Promise; +} + +export interface IlluminanceLevelSensingClusterAttributes { + levelStatus?: 'illuminanceOnTarget' | 'illuminanceBelowTarget' | 'illuminanceAboveTarget'; + lightSensorType?: 'photodiode' | 'cmos' | 'unknown'; + illuminanceTargetLevel?: number; +} + +export interface IlluminanceLevelSensingCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface IlluminanceMeasurementClusterAttributes { + measuredValue?: number; + minMeasuredValue?: number; + maxMeasuredValue?: number; + tolerance?: number; + lightSensorType?: 'photodiode' | 'cmos' | 'unknown'; +} + +export interface IlluminanceMeasurementCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface LevelControlClusterAttributes { + currentLevel?: number; + remainingTime?: number; + onOffTransitionTime?: number; + onLevel?: number; + onTransitionTime?: number; + offTransitionTime?: number; + defaultMoveRate?: number; +} + +export interface LevelControlCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + moveToLevel(args: { level: number; transitionTime: number }): Promise; + move(args: { moveMode: 'up' | 'down'; rate: number }): Promise; + step(args: { mode: 'up' | 'down'; stepSize: number; transitionTime: number }): Promise; + stop(): Promise; + moveToLevelWithOnOff(args: { level: number; transitionTime: number }): Promise; + moveWithOnOff(args: { moveMode: 'up' | 'down'; rate: number }): Promise; + stepWithOnOff(args: { mode: 'up' | 'down'; stepSize: number; transitionTime: number }): Promise; + stopWithOnOff(): Promise; +} + +export interface MeteringClusterAttributes { + currentSummationDelivered?: number; + currentSummationReceived?: number; + currentMaxDemandDelivered?: number; + currentMaxDemandReceived?: number; + dftSummation?: number; + dailyFreezeTime?: number; + powerFactor?: number; + readingSnapShotTime?: number; + currentMaxDemandDeliveredTime?: number; + currentMaxDemandReceivedTime?: number; + defaultUpdatePeriod?: number; + fastPollUpdatePeriod?: number; + currentBlockPeriodConsumptionDelivered?: number; + dailyConsumptionTarget?: number; + currentBlock?: unknown; + profileIntervalPeriod?: unknown; + currentTier1SummationDelivered?: number; + currentTier1SummationReceived?: number; + currentTier2SummationDelivered?: number; + currentTier2SummationReceived?: number; + currentTier3SummationDelivered?: number; + currentTier3SummationReceived?: number; + currentTier4SummationDelivered?: number; + currentTier4SummationReceived?: number; + status?: unknown; + remainingBatteryLife?: number; + hoursInOperation?: number; + hoursInFault?: number; + extendedStatus?: unknown; + unitOfMeasure?: unknown; + multiplier?: number; + divisor?: number; + summationFormatting?: unknown; + demandFormatting?: unknown; + historicalConsumptionFormatting?: unknown; + meteringDeviceType?: unknown; + siteId?: Buffer; + meterSerialNumber?: Buffer; + energyCarrierUnitOfMeasure?: unknown; + energyCarrierSummationFormatting?: unknown; + energyCarrierDemandFormatting?: unknown; + temperatureUnitOfMeasure?: unknown; + temperatureFormatting?: unknown; + moduleSerialNumber?: Buffer; + operatingTariffLabelDelivered?: Buffer; + operatingTariffLabelReceived?: Buffer; + customerIdNumber?: Buffer; + alternativeUnitOfMeasure?: unknown; + alternativeDemandFormatting?: unknown; + alternativeConsumptionFormatting?: unknown; + instantaneousDemand?: number; + currentDayConsumptionDelivered?: number; + currentDayConsumptionReceived?: number; + previousDayConsumptionDelivered?: number; + previousDayConsumptionReceived?: number; + currentPartialProfileIntervalStartTimeDelivered?: number; + currentPartialProfileIntervalStartTimeReceived?: number; + currentPartialProfileIntervalValueDelivered?: number; + currentPartialProfileIntervalValueReceived?: number; + currentDayMaxPressure?: number; + currentDayMinPressure?: number; + previousDayMaxPressure?: number; + previousDayMinPressure?: number; + currentDayMaxDemand?: number; + previousDayMaxDemand?: number; + currentMonthMaxDemand?: number; + currentYearMaxDemand?: number; + currentDayMaxEnergyCarrierDemand?: number; + previousDayMaxEnergyCarrierDemand?: number; + currentMonthMaxEnergyCarrierDemand?: number; + currentMonthMinEnergyCarrierDemand?: number; + currentYearMaxEnergyCarrierDemand?: number; + currentYearMinEnergyCarrierDemand?: number; + maxNumberOfPeriodsDelivered?: number; + currentDemandDelivered?: number; + demandLimit?: number; + demandIntegrationPeriod?: number; + numberOfDemandSubintervals?: number; + demandLimitArmDuration?: number; + currentNoTierBlock1SummationDelivered?: number; + currentNoTierBlock2SummationDelivered?: number; + currentNoTierBlock3SummationDelivered?: number; + currentNoTierBlock4SummationDelivered?: number; + currentNoTierBlock5SummationDelivered?: number; + currentNoTierBlock6SummationDelivered?: number; + currentNoTierBlock7SummationDelivered?: number; + currentNoTierBlock8SummationDelivered?: number; + currentNoTierBlock9SummationDelivered?: number; + currentNoTierBlock10SummationDelivered?: number; + currentNoTierBlock11SummationDelivered?: number; + currentNoTierBlock12SummationDelivered?: number; + currentNoTierBlock13SummationDelivered?: number; + currentNoTierBlock14SummationDelivered?: number; + currentNoTierBlock15SummationDelivered?: number; + currentNoTierBlock16SummationDelivered?: number; + currentTier1Block1SummationDelivered?: number; + currentTier1Block2SummationDelivered?: number; + currentTier1Block3SummationDelivered?: number; + currentTier1Block4SummationDelivered?: number; + currentTier1Block5SummationDelivered?: number; + currentTier1Block6SummationDelivered?: number; + currentTier1Block7SummationDelivered?: number; + currentTier1Block8SummationDelivered?: number; + currentTier1Block9SummationDelivered?: number; + currentTier1Block10SummationDelivered?: number; + currentTier1Block11SummationDelivered?: number; + currentTier1Block12SummationDelivered?: number; + currentTier1Block13SummationDelivered?: number; + currentTier1Block14SummationDelivered?: number; + currentTier1Block15SummationDelivered?: number; + currentTier1Block16SummationDelivered?: number; + currentTier2Block1SummationDelivered?: number; + currentTier2Block2SummationDelivered?: number; + currentTier2Block3SummationDelivered?: number; + currentTier2Block4SummationDelivered?: number; + currentTier2Block5SummationDelivered?: number; + currentTier2Block6SummationDelivered?: number; + currentTier2Block7SummationDelivered?: number; + currentTier2Block8SummationDelivered?: number; + currentTier2Block9SummationDelivered?: number; + currentTier2Block10SummationDelivered?: number; + currentTier2Block11SummationDelivered?: number; + currentTier2Block12SummationDelivered?: number; + currentTier2Block13SummationDelivered?: number; + currentTier2Block14SummationDelivered?: number; + currentTier2Block15SummationDelivered?: number; + currentTier2Block16SummationDelivered?: number; + currentTier3Block1SummationDelivered?: number; + currentTier3Block2SummationDelivered?: number; + currentTier3Block3SummationDelivered?: number; + currentTier3Block4SummationDelivered?: number; + currentTier3Block5SummationDelivered?: number; + currentTier3Block6SummationDelivered?: number; + currentTier3Block7SummationDelivered?: number; + currentTier3Block8SummationDelivered?: number; + currentTier3Block9SummationDelivered?: number; + currentTier3Block10SummationDelivered?: number; + currentTier3Block11SummationDelivered?: number; + currentTier3Block12SummationDelivered?: number; + currentTier3Block13SummationDelivered?: number; + currentTier3Block14SummationDelivered?: number; + currentTier3Block15SummationDelivered?: number; + currentTier3Block16SummationDelivered?: number; + currentTier4Block1SummationDelivered?: number; + currentTier4Block2SummationDelivered?: number; + currentTier4Block3SummationDelivered?: number; + currentTier4Block4SummationDelivered?: number; + currentTier4Block5SummationDelivered?: number; + currentTier4Block6SummationDelivered?: number; + currentTier4Block7SummationDelivered?: number; + currentTier4Block8SummationDelivered?: number; + currentTier4Block9SummationDelivered?: number; + currentTier4Block10SummationDelivered?: number; + currentTier4Block11SummationDelivered?: number; + currentTier4Block12SummationDelivered?: number; + currentTier4Block13SummationDelivered?: number; + currentTier4Block14SummationDelivered?: number; + currentTier4Block15SummationDelivered?: number; + currentTier4Block16SummationDelivered?: number; + genericAlarmMask?: unknown; + electricityAlarmMask?: unknown; + genericFlowPressureAlarmMask?: unknown; + waterSpecificAlarmMask?: unknown; + heatAndCoolingSpecificAlarmMask?: unknown; + gasSpecificAlarmMask?: unknown; + extendedGenericAlarmMask?: unknown; + manufacturerAlarmMask?: unknown; + currentNoTierBlock1SummationReceived?: number; + currentNoTierBlock2SummationReceived?: number; + currentNoTierBlock3SummationReceived?: number; + currentNoTierBlock4SummationReceived?: number; + currentNoTierBlock5SummationReceived?: number; + currentNoTierBlock6SummationReceived?: number; + currentNoTierBlock7SummationReceived?: number; + currentNoTierBlock8SummationReceived?: number; + currentNoTierBlock9SummationReceived?: number; + currentNoTierBlock10SummationReceived?: number; + currentNoTierBlock11SummationReceived?: number; + currentNoTierBlock12SummationReceived?: number; + currentNoTierBlock13SummationReceived?: number; + currentNoTierBlock14SummationReceived?: number; + currentNoTierBlock15SummationReceived?: number; + currentNoTierBlock16SummationReceived?: number; + billToDateDelivered?: number; + billToDateTimeStampDelivered?: number; + projectedBillDelivered?: number; + projectedBillTimeStampDelivered?: number; + billDeliveredTrailingDigit?: unknown; + billToDateReceived?: number; + billToDateTimeStampReceived?: number; + projectedBillReceived?: number; + projectedBillTimeStampReceived?: number; + billReceivedTrailingDigit?: unknown; + proposedChangeSupplyImplementationTime?: number; + proposedChangeSupplyStatus?: unknown; + uncontrolledFlowThreshold?: number; + uncontrolledFlowThresholdUnitOfMeasure?: unknown; + uncontrolledFlowMultiplier?: number; + uncontrolledFlowDivisor?: number; + flowStabilisationPeriod?: number; + flowMeasurementPeriod?: number; +} + +export interface MeteringCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + getProfileResponse(args: { endTime: number; status: unknown; profileIntervalPeriod: unknown; numberOfPeriodsDelivered: number; intervals?: Buffer }): Promise; + requestMirror(): Promise; + removeMirror(): Promise; + requestFastPollModeResponse(args: { appliedUpdatePeriod: number; fastPollModeEndTime: number }): Promise; + scheduleSnapshotResponse(args: { issuerEventId: number; snapshotResponsePayload?: Buffer }): Promise; + takeSnapshotResponse(args: { snapshotId: number; snapshotConfirmation: unknown }): Promise; + publishSnapshot(args: { snapshotId: number; snapshotTime: number; totalSnapshotsFound: number; commandIndex: number; totalCommands: number; snapshotCause: unknown; snapshotPayloadType: unknown; snapshotPayload?: Buffer }): Promise; + getSampledDataResponse(args: { sampleId: number; sampleStartTime: number; sampleType: unknown; sampleRequestInterval: number; numberOfSamples: number; samples?: Buffer }): Promise; + configureMirror(args: { issuerEventId: number; reportingInterval: number; mirrorNotificationReporting: boolean; notificationScheme: number }): Promise; + configureNotificationScheme(args: { issuerEventId: number; notificationScheme: number; notificationFlagOrder: unknown }): Promise; + configureNotificationFlags(args: { issuerEventId: number; notificationScheme: number; notificationFlagAttributeId: number; clusterId: number; manufacturerCode: number; numberOfCommands: number; commandIds?: Buffer }): Promise; + getNotifiedMessage(args: { notificationScheme: number; notificationFlagAttributeId: number; notificationFlagsN: unknown }): Promise; + supplyStatusResponse(args: { providerId: number; issuerEventId: number; implementationDateTime: number; supplyStatus: unknown }): Promise; + startSamplingResponse(args: { sampleId: number }): Promise; + getProfile(args: { intervalChannel: unknown; endTime: number; numberOfPeriods: number }): Promise; + requestMirrorResponse(args: { endpointId: number }): Promise; + mirrorRemoved(args: { endpointId: number }): Promise; + requestFastPollMode(args: { fastPollUpdatePeriod: number; duration: number }): Promise; + scheduleSnapshot(args: { issuerEventId: number; commandIndex: number; commandCount: number; snapshotSchedulePayload?: Buffer }): Promise; + takeSnapshot(args: { snapshotCause: unknown }): Promise; + getSnapshot(args: { earliestStartTime: number; latestEndTime: number; snapshotOffset: number; snapshotCause: unknown }): Promise; + startSampling(args: { issuerEventId: number; startSamplingTime: number; sampleType: unknown; sampleRequestInterval: number; maxNumberOfSamples: number }): Promise; + getSampledData(args: { sampleId: number; earliestSampleTime: number; sampleType: unknown; numberOfSamples: number }): Promise; + mirrorReportAttributeResponse(args: { notificationScheme: number; notificationFlags?: Buffer }): Promise; + resetLoadLimitCounter(args: { providerId: number; issuerEventId: number }): Promise; + changeSupply(args: { providerId: number; issuerEventId: number; requestDateTime: number; implementationDateTime: number; proposedSupplyStatus: unknown; supplyControlBits: unknown }): Promise; + localChangeSupply(args: { proposedSupplyStatus: unknown }): Promise; + setSupplyStatus(args: { issuerEventId: number; supplyTamperState: unknown; supplyDepletionState: unknown; supplyUncontrolledFlowState: unknown; loadLimitSupplyState: unknown }): Promise; + setUncontrolledFlowThreshold(args: { providerId: number; issuerEventId: number; uncontrolledFlowThreshold: number; unitOfMeasure: unknown; multiplier: number; divisor: number; stabilisationPeriod: number; measurementPeriod: number }): Promise; +} + +export interface MultistateInputClusterAttributes { + description?: string; + numberOfStates?: number; + outOfService?: boolean; + presentValue?: number; + reliability?: 'noFaultDetected' | 'noSensor' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'noOutput' | 'unreliableOther' | 'processError' | 'multiStateFault' | 'configurationError'; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} + +export interface MultistateInputCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface MultistateOutputClusterAttributes { + description?: string; + numberOfStates?: number; + outOfService?: boolean; + presentValue?: number; + reliability?: 'noFaultDetected' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'unreliableOther' | 'processError' | 'multiStateFault' | 'configurationError'; + relinquishDefault?: number; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} + +export interface MultistateOutputCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface MultistateValueClusterAttributes { + description?: string; + numberOfStates?: number; + outOfService?: boolean; + presentValue?: number; + reliability?: 'noFaultDetected' | 'noSensor' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'noOutput' | 'unreliableOther' | 'processError' | 'multiStateFault' | 'configurationError'; + relinquishDefault?: number; + statusFlags?: Partial<{ inAlarm: boolean; fault: boolean; overridden: boolean; outOfService: boolean }>; + applicationType?: number; +} + +export interface MultistateValueCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface OccupancySensingClusterAttributes { + occupancy?: Partial<{ occupied: boolean }>; + occupancySensorType?: 'pir' | 'ultrasonic' | 'pirAndUltrasonic' | 'physicalContact'; + occupancySensorTypeBitmap?: Partial<{ pir: boolean; ultrasonic: boolean; physicalContact: boolean }>; + pirOccupiedToUnoccupiedDelay?: number; + pirUnoccupiedToOccupiedDelay?: number; + pirUnoccupiedToOccupiedThreshold?: number; + ultrasonicOccupiedToUnoccupiedDelay?: number; + ultrasonicUnoccupiedToOccupiedDelay?: number; + ultrasonicUnoccupiedToOccupiedThreshold?: number; + physicalContactOccupiedToUnoccupiedDelay?: number; + physicalContactUnoccupiedToOccupiedDelay?: number; + physicalContactUnoccupiedToOccupiedThreshold?: number; +} + +export interface OccupancySensingCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface OnOffClusterAttributes { + onOff?: boolean; + onTime?: number; + offWaitTime?: number; +} + +export interface OnOffCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; setOff(): Promise; + setOn(): Promise; toggle(): Promise; - offWithEffect({ - effectIdentifier, - effectVariant, - }: { - effectIdentifier: number; - effectVariant: number; - }): Promise; + offWithEffect(args: { effectIdentifier: number; effectVariant: number }): Promise; onWithRecallGlobalScene(): Promise; - onWithTimedOff({ - onOffControl, - onTime, - offWaitTime, - }: { - onOffControl: number; - onTime: number; - offWaitTime: number; - }): Promise; -} - -interface LevelControlCluster extends ZCLNodeCluster { - moveToLevel({ level, transitionTime }: { level: number; transitionTime: number }): Promise; - move({ moveMode, rate }: { moveMode: "up" | "down"; rate: number }): Promise; - step({ - moveMode, - stepSize, - transitionTime, - }: { - moveMode: "up" | "down"; - stepSize: number; - transitionTime: number; - }): Promise; - moveToLevelWithOnOff({ - level, - transitionTime, - }: { - level: number; - transitionTime: number; - }): Promise; - moveWithOnOff({ moveMode, rate }: { moveMode: "up" | "down"; rate: number }): Promise; - stepWithOnOff({ - moveMode, - stepSize, - transitionTime, - }: { - moveMode: "up" | "down"; - stepSize: number; - transitionTime: number; - }): Promise; - stopWithOnOff(): Promise; + onWithTimedOff(args: { onOffControl: number; onTime: number; offWaitTime: number }): Promise; +} + +export interface OnOffSwitchCluster extends ZCLNodeCluster { +} + +export interface OtaCluster extends ZCLNodeCluster { } -interface ColorControlCluster extends ZCLNodeCluster { - moveToHue({ - hue, - direction, - transitionTime, - }: { - hue: number; - direction: "shortestDistance" | "longestDistance" | "up" | "down"; - transitionTime: number; - }): Promise; - moveToSaturation({ - saturation, - transitionTime, - }: { - saturation: number; - transitionTime: number; - }): Promise; - moveToHueAndSaturation({ - hue, - saturation, - transitionTime, - }: { - hue: number; - saturation: number; - transitionTime: number; - }): Promise; - moveToColor({ - colorX, - colorY, - transitionTime, - }: { - colorX: number; - colorY: number; - transitionTime: number; - }): Promise; - moveToColorTemperature({ - colorTemperature, - transitionTime, - }: { - colorTemperature: number; - transitionTime: number; - }): Promise; -} - -interface MeteringCluster extends ZCLNodeCluster {} - -interface ElectricalMeasurementCluster extends ZCLNodeCluster {} - -interface PollControlCluster extends ZCLNodeCluster { +export interface PollControlClusterAttributes { + checkInInterval?: number; + longPollInterval?: number; + shortPollInterval?: number; + fastPollTimeout?: number; + checkInIntervalMin?: number; + longPollIntervalMin?: number; + fastPollTimeoutMax?: number; +} + +export interface PollControlCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; fastPollStop(): Promise; - setLongPollInterval(opts: { newLongPollInterval: number }): Promise; - setShortPollInterval(opts: { newShortPollInterval: number }): Promise; + setLongPollInterval(args: { newLongPollInterval: number }): Promise; + setShortPollInterval(args: { newShortPollInterval: number }): Promise; +} + +export interface PowerConfigurationClusterAttributes { + batteryVoltage?: number; + batteryPercentageRemaining?: number; + batterySize?: 'noBattery' | 'builtIn' | 'other' | 'AA' | 'AAA' | 'C' | 'D' | 'CR2' | 'CR123A' | 'unknown'; + batteryQuantity?: number; + batteryRatedVoltage?: number; + batteryVoltageMinThreshold?: number; + batteryAlarmState?: Partial<{ batteryThresholdBatterySource1: boolean; batteryThreshold1BatterySource1: boolean; batteryThreshold2BatterySource1: boolean; batteryThreshold3BatterySource1: boolean; reserved4: boolean; reserved5: boolean; reserved6: boolean; reserved7: boolean; reserved8: boolean; reserved9: boolean; batteryThresholdBatterySource2: boolean; batteryThreshold1BatterySource2: boolean; batteryThreshold2BatterySource2: boolean; batteryThreshold3BatterySource2: boolean; reserved14: boolean; reserved15: boolean; reserved16: boolean; reserved17: boolean; reserved18: boolean; reserved19: boolean; batteryThresholdBatterySource3: boolean; batteryThreshold1BatterySource3: boolean; batteryThreshold2BatterySource3: boolean; batteryThreshold3BatterySource3: boolean; reserved24: boolean; reserved25: boolean; reserved26: boolean; reserved27: boolean; reserved28: boolean; reserved29: boolean; mainsPowerSupplyLostUnavailable: boolean }>; +} + +export interface PowerConfigurationCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; } -type ZCLNodeEndpoint = { - clusters: { - onOff?: OnOffCluster; - levelControl?: LevelControlCluster; - colorControl?: ColorControlCluster; +export interface PowerProfileCluster extends ZCLNodeCluster { +} + +export interface PressureMeasurementClusterAttributes { + measuredValue?: number; + minMeasuredValue?: number; + maxMeasuredValue?: number; + tolerance?: number; + scaledValue?: number; + minScaledValue?: number; + maxScaledValue?: number; + scaledTolerance?: number; + scale?: number; +} + +export interface PressureMeasurementCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface PumpConfigurationAndControlCluster extends ZCLNodeCluster { +} + +export interface RelativeHumidityClusterAttributes { + measuredValue?: number; + minMeasuredValue?: number; + maxMeasuredValue?: number; + tolerance?: number; +} + +export interface RelativeHumidityCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface ScenesCluster extends ZCLNodeCluster { +} + +export interface ShadeConfigurationCluster extends ZCLNodeCluster { +} + +export interface TemperatureMeasurementClusterAttributes { + measuredValue?: number; + minMeasuredValue?: number; + maxMeasuredValue?: number; +} + +export interface TemperatureMeasurementCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; +} + +export interface ThermostatClusterAttributes { + localTemperature?: number; + outdoorTemperature?: number; + occupancy?: Partial<{ occupied: boolean }>; + absMinHeatSetpointLimit?: number; + absMaxHeatSetpointLimit?: number; + absMinCoolSetpointLimit?: number; + absMaxCoolSetpointLimit?: number; + pICoolingDemand?: number; + pIHeatingDemand?: number; + localTemperatureCalibration?: number; + occupiedCoolingSetpoint?: number; + occupiedHeatingSetpoint?: number; + unoccupiedCoolingSetpoint?: number; + unoccupiedHeatingSetpoint?: number; + minHeatSetpointLimit?: number; + maxHeatSetpointLimit?: number; + minCoolSetpointLimit?: number; + maxCoolSetpointLimit?: number; + minSetpointDeadBand?: number; + remoteSensing?: Partial<{ localTemperature: boolean; outdoorTemperature: boolean; occupancy: boolean }>; + controlSequenceOfOperation?: 'cooling' | 'coolingWithReheat' | 'heating' | 'heatingWithReheat' | 'coolingAndHeating4Pipes' | 'coolingAndHeating4PipesWithReheat'; + systemMode?: 'off' | 'auto' | 'cool' | 'heat' | 'emergencyHeating' | 'precooling' | 'fanOnly' | 'dry' | 'sleep'; + alarmMask?: Partial<{ initializationFailure: boolean; hardwareFailure: boolean; selfCalibrationFailure: boolean }>; +} + +export interface ThermostatCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + setSetpoint(args: { mode: 'heat' | 'cool' | 'both'; amount: number }): Promise; +} + +export interface TimeCluster extends ZCLNodeCluster { +} + +export interface TouchlinkCluster extends ZCLNodeCluster { + getGroups(args: { startIdx: number }): Promise<{ total: number; startIndex: number; groups: unknown[] }>; +} + +export interface WindowCoveringClusterAttributes { + windowCoveringType?: 'rollershade' | 'rollershade2Motor' | 'rollershadeExterior' | 'rollershadeExterior2Motor' | 'drapery' | 'awning' | 'shutter' | 'tiltBlindTiltOnly' | 'tiltBlindLiftAndTilt' | 'projectorScreen'; + physicalClosedLimitLift?: number; + physicalClosedLimitTilt?: number; + currentPositionLift?: number; + currentPositionTilt?: number; + numberofActuationsLift?: number; + numberofActuationsTilt?: number; + configStatus?: Partial<{ operational: boolean; online: boolean; reversalLiftCommands: boolean; controlLift: boolean; controlTilt: boolean; encoderLift: boolean; encoderTilt: boolean; reserved: boolean }>; + currentPositionLiftPercentage?: number; + currentPositionTiltPercentage?: number; + installedOpenLimitLift?: number; + installedClosedLimitLift?: number; + installedOpenLimitTilt?: number; + installedClosedLimitTilt?: number; + velocityLift?: number; + accelerationTimeLift?: number; + decelerationTimeLift?: number; + mode?: Partial<{ motorDirectionReversed: boolean; calibrationMode: boolean; maintenanceMode: boolean; ledFeedback: boolean }>; + intermediateSetpointsLift?: Buffer; + intermediateSetpointsTilt?: Buffer; +} + +export interface WindowCoveringCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + upOpen(): Promise; + downClose(): Promise; + stop(): Promise; + goToLiftValue(args: { liftValue: number }): Promise; + goToLiftPercentage(args: { percentageLiftValue: number }): Promise; + goToTiltValue(args: { tiltValue: number }): Promise; + goToTiltPercentage(args: { percentageTiltValue: number }): Promise; +} + +/** Type-safe cluster registry */ +export interface ClusterRegistry { + alarms?: AlarmsCluster; + analogInput?: AnalogInputCluster; + analogOutput?: AnalogOutputCluster; + analogValue?: AnalogValueCluster; + ballastConfiguration?: BallastConfigurationCluster; + basic?: BasicCluster; + binaryInput?: BinaryInputCluster; + binaryOutput?: BinaryOutputCluster; + binaryValue?: BinaryValueCluster; + colorControl?: ColorControlCluster; + dehumidificationControl?: DehumidificationControlCluster; + deviceTemperature?: DeviceTemperatureCluster; + diagnostics?: DiagnosticsCluster; + doorLock?: DoorLockCluster; + electricalMeasurement?: ElectricalMeasurementCluster; + fanControl?: FanControlCluster; + flowMeasurement?: FlowMeasurementCluster; + groups?: GroupsCluster; + iasACE?: IasACECluster; + iasWD?: IasWDCluster; + iasZone?: IasZoneCluster; + identify?: IdentifyCluster; + illuminanceLevelSensing?: IlluminanceLevelSensingCluster; + illuminanceMeasurement?: IlluminanceMeasurementCluster; + levelControl?: LevelControlCluster; + metering?: MeteringCluster; + multistateInput?: MultistateInputCluster; + multistateOutput?: MultistateOutputCluster; + multistateValue?: MultistateValueCluster; + occupancySensing?: OccupancySensingCluster; + onOff?: OnOffCluster; + onOffSwitch?: OnOffSwitchCluster; + ota?: OtaCluster; + pollControl?: PollControlCluster; + powerConfiguration?: PowerConfigurationCluster; + powerProfile?: PowerProfileCluster; + pressureMeasurement?: PressureMeasurementCluster; + pumpConfigurationAndControl?: PumpConfigurationAndControlCluster; + relativeHumidity?: RelativeHumidityCluster; + scenes?: ScenesCluster; + shadeConfiguration?: ShadeConfigurationCluster; + temperatureMeasurement?: TemperatureMeasurementCluster; + thermostat?: ThermostatCluster; + time?: TimeCluster; + touchlink?: TouchlinkCluster; + windowCovering?: WindowCoveringCluster; +} + +export type ZCLNodeEndpoint = { + clusters: ClusterRegistry & { [clusterName: string]: ZCLNodeCluster | undefined; }; }; -interface ZCLNode { +export interface ZCLNode { endpoints: { [endpointId: number | string]: ZCLNodeEndpoint }; handleFrame: ( endpointId: number, @@ -308,16 +1033,57 @@ interface ZCLNode { } declare module "zigbee-clusters" { - export var ZCLNode: { + export const ZCLNode: { new (options: ConstructorOptions): ZCLNode; }; export const CLUSTER: { - [key: string]: { ID: number; NAME: string; ATTRIBUTES: any; COMMANDS: any }; + [key: string]: { ID: number; NAME: string; ATTRIBUTES: unknown; COMMANDS: unknown }; }; - export var ZCLNodeCluster; - export var BasicCluster; - export var OnOffCluster; - export var LevelControlCluster; - export var ColorControlCluster; - export var PowerConfigurationCluster; -} + export { ZCLNodeCluster }; + export const AlarmsCluster: AlarmsCluster; + export const AnalogInputCluster: AnalogInputCluster; + export const AnalogOutputCluster: AnalogOutputCluster; + export const AnalogValueCluster: AnalogValueCluster; + export const BallastConfigurationCluster: BallastConfigurationCluster; + export const BasicCluster: BasicCluster; + export const BinaryInputCluster: BinaryInputCluster; + export const BinaryOutputCluster: BinaryOutputCluster; + export const BinaryValueCluster: BinaryValueCluster; + export const ColorControlCluster: ColorControlCluster; + export const DehumidificationControlCluster: DehumidificationControlCluster; + export const DeviceTemperatureCluster: DeviceTemperatureCluster; + export const DiagnosticsCluster: DiagnosticsCluster; + export const DoorLockCluster: DoorLockCluster; + export const ElectricalMeasurementCluster: ElectricalMeasurementCluster; + export const FanControlCluster: FanControlCluster; + export const FlowMeasurementCluster: FlowMeasurementCluster; + export const GroupsCluster: GroupsCluster; + export const IasACECluster: IasACECluster; + export const IasWDCluster: IasWDCluster; + export const IasZoneCluster: IasZoneCluster; + export const IdentifyCluster: IdentifyCluster; + export const IlluminanceLevelSensingCluster: IlluminanceLevelSensingCluster; + export const IlluminanceMeasurementCluster: IlluminanceMeasurementCluster; + export const LevelControlCluster: LevelControlCluster; + export const MeteringCluster: MeteringCluster; + export const MultistateInputCluster: MultistateInputCluster; + export const MultistateOutputCluster: MultistateOutputCluster; + export const MultistateValueCluster: MultistateValueCluster; + export const OccupancySensingCluster: OccupancySensingCluster; + export const OnOffCluster: OnOffCluster; + export const OnOffSwitchCluster: OnOffSwitchCluster; + export const OtaCluster: OtaCluster; + export const PollControlCluster: PollControlCluster; + export const PowerConfigurationCluster: PowerConfigurationCluster; + export const PowerProfileCluster: PowerProfileCluster; + export const PressureMeasurementCluster: PressureMeasurementCluster; + export const PumpConfigurationAndControlCluster: PumpConfigurationAndControlCluster; + export const RelativeHumidityCluster: RelativeHumidityCluster; + export const ScenesCluster: ScenesCluster; + export const ShadeConfigurationCluster: ShadeConfigurationCluster; + export const TemperatureMeasurementCluster: TemperatureMeasurementCluster; + export const ThermostatCluster: ThermostatCluster; + export const TimeCluster: TimeCluster; + export const TouchlinkCluster: TouchlinkCluster; + export const WindowCoveringCluster: WindowCoveringCluster; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2aa2e83..83fe723 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ }, "devDependencies": { "@athombv/jsdoc-template": "^1.6.1", + "@types/node": "^25.0.10", "@types/sinon": "^17.0.3", "concurrently": "^5.2.0", "eslint": "^6.8.0", @@ -23,6 +24,7 @@ "mocha": "^10.1.0", "serve": "^14.0.1", "sinon": "^19.0.2", + "typescript": "^5.9.3", "watch": "^1.0.2" }, "engines": { @@ -429,13 +431,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", - "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~7.16.0" } }, "node_modules/@types/sinon": { @@ -5533,6 +5535,20 @@ "node": ">=8" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -5546,11 +5562,11 @@ "dev": true }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/update-browserslist-db": { "version": "1.1.0", @@ -6330,13 +6346,12 @@ "dev": true }, "@types/node": { - "version": "22.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", - "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "dev": true, - "peer": true, "requires": { - "undici-types": "~6.19.2" + "undici-types": "~7.16.0" } }, "@types/sinon": { @@ -10189,6 +10204,12 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true + }, "uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -10202,11 +10223,10 @@ "dev": true }, "undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "peer": true + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true }, "update-browserslist-db": { "version": "1.1.0", diff --git a/package.json b/package.json index acfd269..55550b0 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "test": "mocha --reporter list", "lint": "eslint .", + "generate-types": "node scripts/generate-types.js", "serve": "concurrently \"serve build/\" \"npm run build:watch\"", "build": "jsdoc --configure ./docs/jsdoc.json", "build:clean": "rm -rf ./build", @@ -32,6 +33,7 @@ "homepage": "https://github.com/athombv/node-zigbee-clusters#readme", "devDependencies": { "@athombv/jsdoc-template": "^1.6.1", + "@types/node": "^25.0.10", "@types/sinon": "^17.0.3", "concurrently": "^5.2.0", "eslint": "^6.8.0", @@ -41,6 +43,7 @@ "mocha": "^10.1.0", "serve": "^14.0.1", "sinon": "^19.0.2", + "typescript": "^5.9.3", "watch": "^1.0.2" }, "dependencies": { diff --git a/scripts/generate-types.js b/scripts/generate-types.js new file mode 100644 index 0000000..a99aa37 --- /dev/null +++ b/scripts/generate-types.js @@ -0,0 +1,360 @@ +'use strict'; + +/* eslint-disable no-console, global-require */ + +/** + * Type generation script for zigbee-clusters + * Loads cluster modules directly and generates TypeScript interfaces + */ + +const fs = require('fs'); +const path = require('path'); + +const OUTPUT_FILE = path.join(__dirname, '../index.d.ts'); + +/** + * Convert a ZCLDataType object to TypeScript type string + * @param {object} dataType - ZCLDataType object with shortName and args + * @returns {string} TypeScript type string + */ +function zclTypeToTS(dataType) { + if (!dataType || !dataType.shortName) return 'unknown'; + + const { shortName, args } = dataType; + + // No data + if (shortName === 'noData') return 'null'; + + // Boolean + if (shortName === 'bool') return 'boolean'; + + // Numeric types + if (/^u?int\d+$/.test(shortName)) return 'number'; + // data8-32 use readUIntBE, return number + if (/^data(8|16|24|32)$/.test(shortName)) return 'number'; + // data40-64 use buf.slice, return Buffer + if (/^data(40|48|56|64)$/.test(shortName)) return 'Buffer'; + // Float types + if (shortName === 'single' || shortName === 'double') return 'number'; + + // String types + if (shortName === 'string' || shortName === '_FixedString') return 'string'; + // EUI addresses return colon-separated hex strings + if (shortName === 'EUI64' || shortName === 'EUI48') return 'string'; + // key128 returns colon-separated hex string + if (shortName === 'key128') return 'string'; + + // Buffer types + if (shortName === 'octstr' || shortName === '_buffer' || shortName === '_buffer8' || shortName === '_buffer16') { + return 'Buffer'; + } + + // Enum types - extract keys from args[0] + if (/^enum(4|8|16|32)$/.test(shortName)) { + if (args && args[0] && typeof args[0] === 'object') { + const keys = Object.keys(args[0]); + if (keys.length > 0) { + return keys.map(k => `'${k}'`).join(' | '); + } + } + return 'number'; + } + + // Map/bitmap types - extract flag names from args + if (/^map(4|\d+)$/.test(shortName)) { + if (args && args.length > 0) { + const flags = args.filter(a => typeof a === 'string'); + if (flags.length > 0) { + return `Partial<{ ${flags.map(f => `${f}: boolean`).join('; ')} }>`; + } + } + return 'Partial>'; + } + + // Array types (note: shortName has underscore prefix: _Array0, _Array8) + // Recursively determine element type from args[0] + if (/^_?Array(0|8|16)$/.test(shortName)) { + if (args && args[0]) { + const elementType = zclTypeToTS(args[0]); + return `${elementType}[]`; + } + return 'unknown[]'; + } + + return 'unknown'; +} + +/** + * Parse a cluster and extract its type information + * @param {Function} ClusterClass - Cluster class with static ATTRIBUTES/COMMANDS + * @returns {object} Cluster definition + */ +function parseCluster(ClusterClass) { + const clusterName = ClusterClass.NAME; + const clusterId = ClusterClass.ID; + const attributes = []; + const commands = []; + + // Parse attributes + const attrs = ClusterClass.ATTRIBUTES || {}; + for (const [name, def] of Object.entries(attrs)) { + if (def && def.type) { + attributes.push({ + name, + tsType: zclTypeToTS(def.type), + }); + } + } + + // Parse commands + const cmds = ClusterClass.COMMANDS || {}; + for (const [name, def] of Object.entries(cmds)) { + const cmdArgs = []; + const responseArgs = []; + if (def && def.args) { + for (const [argName, argType] of Object.entries(def.args)) { + cmdArgs.push({ + name: argName, + tsType: zclTypeToTS(argType), + }); + } + } + // Parse response type if present + if (def && def.response && def.response.args) { + for (const [argName, argType] of Object.entries(def.response.args)) { + responseArgs.push({ + name: argName, + tsType: zclTypeToTS(argType), + }); + } + } + commands.push({ name, args: cmdArgs, responseArgs }); + } + + return { + clusterName, clusterId, attributes, commands, + }; +} + +/** + * Convert cluster name to PascalCase interface name + * @param {string} clusterName + * @returns {string} + */ +function toInterfaceName(clusterName) { + const name = clusterName.charAt(0).toUpperCase() + clusterName.slice(1); + return `${name}Cluster`; +} + +/** + * Generate TypeScript interface for a cluster + * @param {object} cluster - Parsed cluster definition + * @returns {string} TypeScript interface code + */ +function generateClusterInterface(cluster) { + const interfaceName = toInterfaceName(cluster.clusterName); + const lines = []; + + // Generate attributes interface + if (cluster.attributes.length > 0) { + lines.push(`export interface ${interfaceName}Attributes {`); + for (const attr of cluster.attributes) { + lines.push(` ${attr.name}?: ${attr.tsType};`); + } + lines.push('}'); + lines.push(''); + } + + // Generate cluster interface + lines.push(`export interface ${interfaceName} extends ZCLNodeCluster {`); + + // Add typed readAttributes if we have attributes + if (cluster.attributes.length > 0) { + const attrNames = cluster.attributes.map(a => `'${a.name}'`).join(' | '); + lines.push(` readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>;`); + lines.push(` writeAttributes(attributes: Partial<${interfaceName}Attributes>): Promise;`); + } + + // Add command methods + for (const cmd of cluster.commands) { + // Determine return type based on response args + let returnType = 'void'; + if (cmd.responseArgs && cmd.responseArgs.length > 0) { + returnType = `{ ${cmd.responseArgs.map(a => `${a.name}: ${a.tsType}`).join('; ')} }`; + } + + if (cmd.args.length > 0) { + // Buffer arguments are optional - ZCL allows empty octet strings + const allArgsOptional = cmd.args.every(a => a.tsType === 'Buffer'); + const argsType = `{ ${cmd.args.map(a => `${a.name}${a.tsType === 'Buffer' ? '?' : ''}: ${a.tsType}`).join('; ')} }`; + // If all args are optional, make the entire args object optional + lines.push(` ${cmd.name}(args${allArgsOptional ? '?' : ''}: ${argsType}): Promise<${returnType}>;`); + } else { + lines.push(` ${cmd.name}(): Promise<${returnType}>;`); + } + } + + lines.push('}'); + + return lines.join('\n'); +} + +/** + * Generate the full index.d.ts file + * @param {object[]} clusters - Array of parsed cluster definitions + * @returns {string} Complete TypeScript definitions file + */ +function generateTypesFile(clusters) { + const lines = []; + + // Header + lines.push('// Auto-generated TypeScript definitions for zigbee-clusters'); + lines.push('// Generated by scripts/generate-types.js'); + lines.push(''); + lines.push('import * as EventEmitter from "events";'); + lines.push(''); + + // Base types + lines.push(`type EndpointDescriptor = { + endpointId: number; + inputClusters: number[]; + outputClusters: number[]; +}; + +type ConstructorOptions = { + endpointDescriptors: EndpointDescriptor[]; + sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise; +}; +`); + + // Base ZCLNodeCluster interface + lines.push(`export interface ZCLNodeCluster extends EventEmitter { + discoverCommandsGenerated(opts?: { + startValue?: number; + maxResults?: number; + }): Promise; + + discoverCommandsReceived(opts?: { + startValue?: number; + maxResults?: number; + }): Promise; + + readAttributes( + attributeNames: string[], + opts?: { timeout?: number } + ): Promise<{ [x: string]: unknown }>; + + writeAttributes(attributes?: object): Promise; + + configureReporting(attributes?: object): Promise; + + readReportingConfiguration(attributes?: (string | number)[]): Promise<{ + status: string; + direction: 'reported' | 'received'; + attributeId: number; + attributeDataType?: number; + minInterval?: number; + maxInterval?: number; + minChange?: number; + timeoutPeriod?: number; + }[]>; + + discoverAttributes(): Promise<(string | number)[]>; + + discoverAttributesExtended(): Promise<{ + name?: string; + id: number; + acl: { readable: boolean; writable: boolean; reportable: boolean }; + }[]>; +} +`); + + // Generate individual cluster interfaces + for (const cluster of clusters) { + lines.push(generateClusterInterface(cluster)); + lines.push(''); + } + + // Generate cluster registry type + lines.push('/** Type-safe cluster registry */'); + lines.push('export interface ClusterRegistry {'); + for (const cluster of clusters) { + const interfaceName = toInterfaceName(cluster.clusterName); + lines.push(` ${cluster.clusterName}?: ${interfaceName};`); + } + lines.push('}'); + lines.push(''); + + // Generate endpoint type + lines.push(`export type ZCLNodeEndpoint = { + clusters: ClusterRegistry & { + [clusterName: string]: ZCLNodeCluster | undefined; + }; +}; + +export interface ZCLNode { + endpoints: { [endpointId: number | string]: ZCLNodeEndpoint }; + handleFrame: ( + endpointId: number, + clusterId: number, + frame: Buffer, + meta?: unknown + ) => Promise; +} +`); + + // Module declaration for CommonJS compatibility + lines.push(`declare module "zigbee-clusters" { + export const ZCLNode: { + new (options: ConstructorOptions): ZCLNode; + }; + export const CLUSTER: { + [key: string]: { ID: number; NAME: string; ATTRIBUTES: unknown; COMMANDS: unknown }; + }; + export { ZCLNodeCluster };`); + + // Export all cluster classes + for (const cluster of clusters) { + const interfaceName = toInterfaceName(cluster.clusterName); + lines.push(` export const ${interfaceName}: ${interfaceName};`); + } + + lines.push('}'); + + return lines.join('\n'); +} + +/** + * Main entry point + */ +function main() { + console.log('Loading cluster modules...'); + + // Load all clusters via the index + const clustersModule = require('../lib/clusters'); + const clusters = []; + + // Get all exported cluster classes (end with 'Cluster') + for (const [name, value] of Object.entries(clustersModule)) { + if (name.endsWith('Cluster') && typeof value === 'function' && value.NAME) { + try { + const cluster = parseCluster(value); + clusters.push(cluster); + console.log(` ✓ ${cluster.clusterName} (${cluster.attributes.length} attrs, ${cluster.commands.length} cmds)`); + } catch (err) { + console.warn(` ✗ Failed to parse ${name}: ${err.message}`); + } + } + } + + // Sort clusters alphabetically + clusters.sort((a, b) => a.clusterName.localeCompare(b.clusterName)); + + console.log(`\nGenerating ${OUTPUT_FILE}...`); + const output = generateTypesFile(clusters); + fs.writeFileSync(OUTPUT_FILE, output); + + console.log(`Done! Generated types for ${clusters.length} clusters.`); +} + +main(); diff --git a/test/doorLock.js b/test/doorLock.js index c14ff0d..4c9c9ee 100644 --- a/test/doorLock.js +++ b/test/doorLock.js @@ -33,6 +33,31 @@ describe('Door Lock', function() { assert.deepStrictEqual(receivedData.pinCode, Buffer.from([0x31, 0x32, 0x33, 0x34])); }); + it('should receive lockDoor without arguments', async function() { + const node = createMockNode({ + loopback: true, + endpoints: [{ + endpointId: 1, + inputClusters: [DoorLockCluster.ID], + }], + }); + + let called = false; + node.endpoints[1].bind('doorLock', new (class extends BoundCluster { + + async lockDoor(data) { + called = true; + // pinCode should be empty buffer when not provided + assert.deepStrictEqual(data.pinCode, Buffer.from([])); + } + + })()); + + // Call without any arguments - should not throw + await node.endpoints[1].clusters.doorLock.lockDoor(); + assert.strictEqual(called, true); + }); + it('should receive unlockDoor', async function() { const node = createMockNode({ loopback: true,