diff --git a/.github/workflows/generate-types.yml b/.github/workflows/generate-types.yml new file mode 100644 index 0000000..454eac6 --- /dev/null +++ b/.github/workflows/generate-types.yml @@ -0,0 +1,60 @@ +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: 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/index.d.ts b/index.d.ts index 90d3e58..5ae2133 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,887 @@ type ConstructorOptions = { endpointDescriptors: EndpointDescriptor[]; sendFrame: (endpointId: number, clusterId: number, frame: Buffer) => Promise; }; -interface ZCLNodeCluster extends EventEmitter { + +export 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} + * Command which requests the remote cluster to report its generated commands. */ - discoverCommandsGenerated({ - startValue, - maxResults, - }?: { + 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} + * Command which requests the remote cluster to report its received commands. */ - 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; - }>; + 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; - } - >; + writeAttributes(attributes?: object): Promise; + /** - * 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} + * Command which configures attribute reporting for the given attributes on the remote cluster. */ 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. + * Command which retrieves the reporting configurations for the given attributes. */ - readReportingConfiguration(attributes?: any[]): Promise< - { - status: any; - direction: "reported" | "received"; - attributeId: number; - attributeDataType?: number; - minInterval?: number; - maxInterval?: number; - minChange?: number; - timeoutPeriod?: number; - }[] - >; + readReportingConfiguration(attributes?: (string | number)[]): Promise<{ + status: string; + 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; + discoverAttributes(): Promise<(string | number)[]>; + /** - * 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. + * Command which discovers the implemented attributes with access control info. */ - discoverAttributesExtended(): Promise; + 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?: unknown; + minPresentValue?: unknown; + outOfService?: boolean; + presentValue?: unknown; + reliability?: 'noFaultDetected' | 'noSensor' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'noOutput' | 'unreliableOther' | 'processError' | 'configurationError'; + resolution?: unknown; + 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?: unknown; + minPresentValue?: unknown; + outOfService?: boolean; + presentValue?: unknown; + reliability?: 'noFaultDetected' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'unreliableOther' | 'processError' | 'configurationError'; + relinquishDefault?: unknown; + resolution?: unknown; + 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; } -interface BasicCluster extends ZCLNodeCluster { +export interface AnalogValueClusterAttributes { + description?: string; + outOfService?: boolean; + presentValue?: unknown; + reliability?: 'noFaultDetected' | 'overRange' | 'underRange' | 'openLoop' | 'shortedLoop' | 'unreliableOther' | 'processError' | 'configurationError'; + relinquishDefault?: unknown; + 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; + appProfileVersion?: number; + locationDesc?: string; + physicalEnv?: 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' | 'apsLinkKey'; + 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; + unlockDoor(args: { pinCode?: Buffer }): Promise; + toggle(args: { pinCode?: Buffer }): Promise; + unlockWithTimeout(args: { timeout: number; pinCode?: Buffer }): Promise; + getLogRecord(args: { logIndex: number }): Promise; + setPINCode(args: { userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported'; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported'; pinCode?: Buffer }): Promise; + getPINCode(args: { userId: number }): Promise; + clearPINCode(args: { userId: number }): Promise; + clearAllPINCodes(): Promise; + setUserStatus(args: { userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported' }): Promise; + getUserStatus(args: { userId: number }): Promise; + 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; + getWeekDaySchedule(args: { scheduleId: number; userId: number }): Promise; + clearWeekDaySchedule(args: { scheduleId: number; userId: number }): Promise; + setYearDaySchedule(args: { scheduleId: number; userId: number; localStartTime: number; localEndTime: number }): Promise; + getYearDaySchedule(args: { scheduleId: number; userId: number }): Promise; + clearYearDaySchedule(args: { scheduleId: number; userId: number }): Promise; + setHolidaySchedule(args: { holidayScheduleId: number; localStartTime: number; localEndTime: number; operatingModeDuringHoliday: 'normal' | 'vacation' | 'privacy' | 'noRFLockOrUnlock' | 'passage' }): Promise; + getHolidaySchedule(args: { holidayScheduleId: number }): Promise; + clearHolidaySchedule(args: { holidayScheduleId: number }): Promise; + setUserType(args: { userId: number; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported' }): Promise; + getUserType(args: { userId: number }): Promise; + setRFIDCode(args: { userId: number; userStatus: 'available' | 'occupiedEnabled' | 'occupiedDisabled' | 'notSupported'; userType: 'unrestricted' | 'yearDayScheduleUser' | 'weekDayScheduleUser' | 'masterUser' | 'nonAccessUser' | 'notSupported'; rfidCode?: Buffer }): Promise; + getRFIDCode(args: { userId: number }): Promise; + clearRFIDCode(args: { userId: number }): Promise; + clearAllRFIDCodes(): 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; + viewGroup(args: { groupId: number }): Promise; + getGroupMembership(args: { groupIds: number }): Promise; + removeGroup(args: { groupId: number }): Promise; + 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?: unknown; + iasCIEAddress?: string; + zoneId?: number; +} + +export interface IasZoneCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): Promise; + zoneStatusChangeNotification(args: { zoneStatus: unknown; 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(args: { timeout: number }): Promise; + 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; +} + +export interface MeteringCluster extends ZCLNodeCluster { + readAttributes(attributeNames: K[], opts?: { timeout?: number }): Promise>; + writeAttributes(attributes: Partial): 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; +} + +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 +904,59 @@ 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; } + +export { ZCLNode, ZCLNodeCluster, ZCLNodeEndpoint, ClusterRegistry }; \ 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..bb358f5 --- /dev/null +++ b/scripts/generate-types.js @@ -0,0 +1,577 @@ +'use strict'; + +/* eslint-disable no-console, no-use-before-define */ + +/** + * Type generation script for zigbee-clusters + * Parses cluster definitions and generates TypeScript interfaces + */ + +const fs = require('fs'); +const path = require('path'); + +const CLUSTERS_DIR = path.join(__dirname, '../lib/clusters'); +const OUTPUT_FILE = path.join(__dirname, '../index.d.ts'); + +// Files to skip (not actual cluster definitions) +const SKIP_FILES = ['index.js']; + +/** + * Convert ZCLDataType to TypeScript type string + */ +function zclTypeToTS(typeStr) { + // Handle simple types - check these first (more specific matches) + if (typeStr.includes('ZCLDataTypes.bool')) return 'boolean'; + if (typeStr.includes('ZCLDataTypes.uint48')) return 'number'; + if (typeStr.includes('ZCLDataTypes.uint40')) return 'number'; + if (typeStr.includes('ZCLDataTypes.uint32')) return 'number'; + if (typeStr.includes('ZCLDataTypes.uint24')) return 'number'; + if (typeStr.includes('ZCLDataTypes.uint16')) return 'number'; + if (typeStr.includes('ZCLDataTypes.uint8')) return 'number'; + if (typeStr.includes('ZCLDataTypes.int32')) return 'number'; + if (typeStr.includes('ZCLDataTypes.int24')) return 'number'; + if (typeStr.includes('ZCLDataTypes.int16')) return 'number'; + if (typeStr.includes('ZCLDataTypes.int8')) return 'number'; + if (typeStr.includes('ZCLDataTypes.string')) return 'string'; + if (typeStr.includes('ZCLDataTypes.octstr')) return 'Buffer'; + if (typeStr.includes('ZCLDataTypes.data32')) return 'number'; + if (typeStr.includes('ZCLDataTypes.data24')) return 'number'; + if (typeStr.includes('ZCLDataTypes.data16')) return 'number'; + if (typeStr.includes('ZCLDataTypes.data8')) return 'number'; + if (typeStr.includes('ZCLDataTypes.EUI64')) return 'string'; + if (typeStr.includes('ZCLDataTypes.securityKey128')) return 'Buffer'; + if (typeStr.includes('ZCLDataTypes.buffer')) return 'Buffer'; + + // Handle enum8/enum16 - extract keys (multiline support) + const enumMatch = typeStr.match(/ZCLDataTypes\.enum(?:8|16)\(\{([\s\S]*?)\}\)/); + if (enumMatch) { + const enumBody = enumMatch[1]; + const keys = extractEnumKeys(enumBody); + if (keys.length > 0) { + return keys.map(k => `'${k}'`).join(' | '); + } + } + + // Handle map8/map16/map32 - extract flag names (can span multiple lines) + const mapMatch = typeStr.match(/ZCLDataTypes\.map(?:8|16|32)\(([\s\S]*?)\)/); + if (mapMatch) { + const mapArgs = mapMatch[1]; + const flags = extractMapFlags(mapArgs); + if (flags.length > 0) { + return `Partial<{ ${flags.map(f => `${f}: boolean`).join('; ')} }>`; + } + } + + // Handle Array0/Array8 + if (typeStr.includes('ZCLDataTypes.Array')) { + return 'unknown[]'; + } + + // Fallback + return 'unknown'; +} + +/** + * Extract enum keys from enum body string + */ +function extractEnumKeys(enumBody) { + const keys = []; + // Match patterns like: keyName: 0, or 'keyName': 0 + const keyPattern = /['"]?(\w+)['"]?\s*:/g; + let match = keyPattern.exec(enumBody); + while (match !== null) { + keys.push(match[1]); + match = keyPattern.exec(enumBody); + } + return keys; +} + +/** + * Extract map flag names from map arguments + */ +function extractMapFlags(mapArgs) { + const flags = []; + // Match quoted strings + const flagPattern = /['"](\w+)['"]/g; + let match = flagPattern.exec(mapArgs); + while (match !== null) { + flags.push(match[1]); + match = flagPattern.exec(mapArgs); + } + return flags; +} + +/** + * Parse a cluster file and extract definitions + */ +function parseClusterFile(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const fileName = path.basename(filePath, '.js'); + + // Extract cluster name + const nameMatch = content.match(/static\s+get\s+NAME\(\)\s*\{\s*return\s+['"](\w+)['"]/); + if (!nameMatch) { + console.warn(`Could not find NAME in ${fileName}`); + return null; + } + const clusterName = nameMatch[1]; + + // Extract cluster ID + const idMatch = content.match(/static\s+get\s+ID\(\)\s*\{\s*return\s+(\d+)/); + const clusterId = idMatch ? parseInt(idMatch[1], 10) : null; + + // Extract ATTRIBUTES block + const attributes = parseAttributesBlock(content); + + // Extract COMMANDS block + const commands = parseCommandsBlock(content); + + return { + fileName, + clusterName, + clusterId, + attributes, + commands, + }; +} + +/** + * Strip comments from code + */ +function stripComments(code) { + // Remove multi-line comments + code = code.replace(/\/\*[\s\S]*?\*\//g, ''); + // Remove single-line comments + code = code.replace(/\/\/.*$/gm, ''); + return code; +} + +/** + * Parse ATTRIBUTES block from file content + */ +function parseAttributesBlock(content) { + const attributes = []; + + // Find ATTRIBUTES object - handle both const ATTRIBUTES = { and ATTRIBUTES: { + const attrMatch = content.match(/(?:const\s+)?ATTRIBUTES\s*=\s*\{/); + if (!attrMatch) return attributes; + + const startIdx = attrMatch.index + attrMatch[0].length; + let braceCount = 1; + let idx = startIdx; + + // Find matching closing brace + while (braceCount > 0 && idx < content.length) { + if (content[idx] === '{') braceCount++; + else if (content[idx] === '}') braceCount--; + idx++; + } + + let attrBlock = content.substring(startIdx, idx - 1); + + // Strip comments for easier parsing + attrBlock = stripComments(attrBlock); + + // Split into top-level attribute entries + const attrEntries = splitTopLevelEntries(attrBlock); + + for (const entry of attrEntries) { + // Match attribute name - may have leading whitespace/newlines after comment removal + const nameMatch = entry.match(/^\s*(\w+)\s*:/); + if (!nameMatch) continue; + + const attrName = nameMatch[1]; + + // Extract the type definition - look for type: followed by the type value + const typeMatch = entry.match(/type\s*:\s*([\s\S]+?)(?:,\s*(?:id|manufacturerId)\s*:|$)/); + if (!typeMatch) { + // Try alternative: type is last in object + const typeMatchAlt = entry.match(/type\s*:\s*([\s\S]+?)\s*[,}]?\s*$/); + if (typeMatchAlt) { + const typeStr = typeMatchAlt[1].trim().replace(/,\s*$/, ''); + const tsType = zclTypeToTS(typeStr); + attributes.push({ name: attrName, tsType }); + } + continue; + } + + const typeStr = typeMatch[1].trim().replace(/,\s*$/, ''); + const tsType = zclTypeToTS(typeStr); + attributes.push({ name: attrName, tsType }); + } + + return attributes; +} + +/** + * Parse COMMANDS block from file content + */ +function parseCommandsBlock(content) { + const commands = []; + + const cmdMatch = content.match(/(?:const\s+)?COMMANDS\s*=\s*\{/); + if (!cmdMatch) return commands; + + const startIdx = cmdMatch.index + cmdMatch[0].length; + let braceCount = 1; + let idx = startIdx; + + while (braceCount > 0 && idx < content.length) { + if (content[idx] === '{') braceCount++; + else if (content[idx] === '}') braceCount--; + idx++; + } + + let cmdBlock = content.substring(startIdx, idx - 1); + + // Strip comments for easier parsing + cmdBlock = stripComments(cmdBlock); + + // Parse commands - need to handle nested braces for args + // Split by top-level command definitions + const cmdEntries = splitTopLevelEntries(cmdBlock); + + for (const entry of cmdEntries) { + const nameMatch = entry.match(/^\s*(\w+)\s*:/); + if (!nameMatch) continue; + + const cmdName = nameMatch[1]; + const args = parseCommandArgs(entry); + + commands.push({ name: cmdName, args }); + } + + return commands; +} + +/** + * Split a block into top-level entries (handling nested braces and parentheses) + */ +function splitTopLevelEntries(block) { + const entries = []; + let braceCount = 0; + let parenCount = 0; + let currentEntry = ''; + let inString = false; + let stringChar = ''; + + for (let i = 0; i < block.length; i++) { + const char = block[i]; + const prevChar = i > 0 ? block[i - 1] : ''; + + // Handle strings + if ((char === '"' || char === "'") && prevChar !== '\\') { + if (!inString) { + inString = true; + stringChar = char; + } else if (char === stringChar) { + inString = false; + } + } + + if (!inString) { + if (char === '{') braceCount++; + else if (char === '}') braceCount--; + else if (char === '(') parenCount++; + else if (char === ')') parenCount--; + else if (char === ',' && braceCount === 0 && parenCount === 0) { + if (currentEntry.trim()) { + entries.push(currentEntry.trim()); + } + currentEntry = ''; + continue; + } + } + + currentEntry += char; + } + + if (currentEntry.trim()) { + entries.push(currentEntry.trim()); + } + + return entries; +} + +/** + * Parse command arguments from a command entry + */ +function parseCommandArgs(cmdEntry) { + const args = []; + + // Find args block + const argsMatch = cmdEntry.match(/args\s*:\s*\{/); + if (!argsMatch) return args; + + const startIdx = argsMatch.index + argsMatch[0].length; + let braceCount = 1; + let idx = startIdx; + + while (braceCount > 0 && idx < cmdEntry.length) { + if (cmdEntry[idx] === '{') braceCount++; + else if (cmdEntry[idx] === '}') braceCount--; + idx++; + } + + const argsBlock = cmdEntry.substring(startIdx, idx - 1); + + // Parse each argument + const argEntries = splitTopLevelEntries(argsBlock); + + for (const argEntry of argEntries) { + const nameMatch = argEntry.match(/^\s*(\w+)\s*:/); + if (!nameMatch) continue; + + const argName = nameMatch[1]; + const typeStr = argEntry.substring(argEntry.indexOf(':') + 1).trim(); + const tsType = zclTypeToTS(typeStr); + + args.push({ name: argName, tsType }); + } + + return args; +} + +/** + * Convert cluster name to PascalCase interface name + */ +function toInterfaceName(clusterName) { + // Handle special cases + const name = clusterName.charAt(0).toUpperCase() + clusterName.slice(1); + return `${name}Cluster`; +} + +/** + * Generate TypeScript interface for a cluster + */ +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) { + if (cmd.args.length > 0) { + // Buffer arguments (octstr, securityKey128, buffer) are optional because ZCL allows + // empty octet strings (length 0). The data-types library serializes undefined/omitted + // Buffer args as empty Buffers. Example: DoorLock.lockDoor({ pinCode }) - pinCode is + // optional when the lock doesn't require PIN authentication. + const argsType = `{ ${cmd.args.map(a => `${a.name}${a.tsType === 'Buffer' ? '?' : ''}: ${a.tsType}`).join('; ')} }`; + lines.push(` ${cmd.name}(args: ${argsType}): Promise;`); + } else { + lines.push(` ${cmd.name}(): Promise;`); + } + } + + lines.push('}'); + + return lines.join('\n'); +} + +/** + * Generate the full index.d.ts 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 { + /** + * Command which requests the remote cluster to report its generated commands. + */ + discoverCommandsGenerated(opts?: { + startValue?: number; + maxResults?: number; + }): Promise; + + /** + * Command which requests the remote cluster to report its received commands. + */ + discoverCommandsReceived(opts?: { + startValue?: number; + maxResults?: number; + }): Promise; + + /** + * Command which reads a given set of attributes from the remote cluster. + */ + 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. + */ + writeAttributes(attributes?: object): Promise; + + /** + * Command which configures attribute reporting for the given attributes on the remote cluster. + */ + configureReporting(attributes?: object): Promise; + + /** + * Command which retrieves the reporting configurations for the given attributes. + */ + readReportingConfiguration(attributes?: (string | number)[]): Promise<{ + status: string; + 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. + */ + discoverAttributes(): Promise<(string | number)[]>; + + /** + * Command which discovers the implemented attributes with access control info. + */ + 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('}'); + lines.push(''); + + // Also export at top level for ESM + lines.push('export { ZCLNode, ZCLNodeCluster, ZCLNodeEndpoint, ClusterRegistry };'); + + return lines.join('\n'); +} + +/** + * Main entry point + */ +function main() { + console.log('Scanning cluster files...'); + + const files = fs.readdirSync(CLUSTERS_DIR) + .filter(f => f.endsWith('.js') && !SKIP_FILES.includes(f)); + + console.log(`Found ${files.length} cluster files`); + + const clusters = []; + + for (const file of files) { + const filePath = path.join(CLUSTERS_DIR, file); + try { + const cluster = parseClusterFile(filePath); + if (cluster) { + clusters.push(cluster); + console.log(` ✓ ${cluster.clusterName} (${cluster.attributes.length} attrs, ${cluster.commands.length} cmds)`); + } + } catch (err) { + console.warn(` ✗ Failed to parse ${file}: ${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();