diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6d375a26..0a5e925c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,8 +35,8 @@ jobs: # Only run this job if initiator is not the Homey Github Actions Bot to prevent loops and check if if a version bump is provided and current branch is production or testing if: github.actor != 'homey-bot' - && (github.ref == 'refs/heads/production' || github.ref == 'refs/heads/testing') - && (github.event.inputs.versionBumpType == 'patch' || github.event.inputs.versionBumpType == 'minor' || github.event.inputs.versionBumpType == 'major' || contains(github.event.head_commit.message, '#patch') || contains(github.event.head_commit.message, '#minor') || contains(github.event.head_commit.message, '#major')) + && (github.ref == 'refs/heads/production' || github.ref == 'refs/heads/testing') + && (github.event.inputs.versionBumpType == 'patch' || github.event.inputs.versionBumpType == 'minor' || github.event.inputs.versionBumpType == 'major' || contains(github.event.head_commit.message, '#patch') || contains(github.event.head_commit.message, '#minor') || contains(github.event.head_commit.message, '#major')) runs-on: ubuntu-latest @@ -47,7 +47,6 @@ jobs: id-token: write steps: - # Checks out the current repository. - name: Checkout git repository uses: actions/checkout@v4 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 67806c8b..ecf72bdd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,7 +19,7 @@ name: Deploy Documentation To GitHub Pages on: push: - branches: [ production ] + branches: [production] workflow_dispatch: jobs: @@ -27,22 +27,22 @@ jobs: name: Build & Deploy to GitHub Pages runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - # Build - - name: Build - run: | - npm ci - npm run build - - # Deploy - - name: Deploy To GitHub Pages - uses: peaceiris/actions-gh-pages@v3.8.0 - with: - personal_token: ${{ secrets.HOMEY_GITHUB_ACTIONS_BOT_PERSONAL_ACCESS_TOKEN }} - publish_dir: ./build \ No newline at end of file + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + + # Build + - name: Build + run: | + npm ci + npm run build + + # Deploy + - name: Deploy To GitHub Pages + uses: peaceiris/actions-gh-pages@v3.8.0 + with: + personal_token: ${{ secrets.HOMEY_GITHUB_ACTIONS_BOT_PERSONAL_ACCESS_TOKEN }} + publish_dir: ./build diff --git a/.github/workflows/generate-types.yml b/.github/workflows/generate-types.yml index e37e92af..7b6149fc 100644 --- a/.github/workflows/generate-types.yml +++ b/.github/workflows/generate-types.yml @@ -6,12 +6,12 @@ on: - production - testing paths: - - "lib/clusters/**/*.js" - - "scripts/generate-types.mts" - - "scripts/template.d.ts.txt" - - "index.js" - - "lib/Cluster.js" - - "lib/constants.js" + - 'lib/clusters/**/*.js' + - 'scripts/generate-types.mts' + - 'scripts/template.d.ts.txt' + - 'index.js' + - 'lib/Cluster.js' + - 'lib/constants.js' workflow_dispatch: jobs: @@ -32,7 +32,7 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: "npm" + cache: 'npm' - name: Install dependencies run: npm ci diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ea438fc9..d94386dd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,6 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - # Checks out the current repository. - uses: actions/checkout@v4 @@ -34,3 +33,5 @@ jobs: - run: npm ci - run: npm run lint + + - run: npm run format:check diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..a3ed78cc --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +build +index.d.ts +*.pdf +.claude + +# Literal formatting is the content here, and Prettier mangles the M* emphasis markers. +AGENTS.md diff --git a/.prettierrc b/.prettierrc index 77e64750..24e8f6ce 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,6 +2,5 @@ "semi": true, "trailingComma": "all", "singleQuote": true, - "printWidth": 100, - "plugins": ["prettier-plugin-jsdoc"] + "printWidth": 100 } diff --git a/AGENTS.md b/AGENTS.md index a28a2e6f..bff09c9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -172,7 +172,8 @@ const COMMANDS = { // --- Client to Server Commands --- // Description from spec. - commandName: { // Mandatory + // Mandatory + commandName: { id: 0x0000, args: { argName: ZCLDataTypes.uint8, @@ -186,7 +187,8 @@ const COMMANDS = { // --- Server to Client Commands --- // Description from spec. - notificationName: { // Optional + // Optional + notificationName: { id: 0x0020, // 32 direction: Cluster.DIRECTION_SERVER_TO_CLIENT, args: { @@ -224,11 +226,13 @@ module.exports = ExampleCluster; 1. **Description placement**: ABOVE the attribute/command 2. **M/O marker placement**: - Single-line: at END of line (`attrName: { ... }, // Mandatory`) - - Multi-line: on OPENING brace (`attrName: { // Mandatory`) + - Multi-line: on its own line ABOVE the key (`// Mandatory` then `attrName: {`), + below the description if there is one. Not on the opening brace: Prettier pulls a + comment there inside the braces, where it reads as if it belongs to `id`. - NEVER on closing brace 3. **Copy exactly**: Text from spec, 1-on-1 4. **Skip if >5 sentences**: Skip and only refer to section in spec -5. **Line wrap**: Respect 100 char limit (ESLint) +5. **Line wrap**: Respect 100 char limit (Prettier `printWidth`); run `npm run format:write` 6. **M/O source for attrs**: Server-side column in spec table 7. **M/O source for cmds**: - Server "receives" → server-side M/O @@ -252,7 +256,7 @@ pirUnoccupiedToOccupiedDelay: { id: 0x0011, type: ZCLDataTypes.uint16 }, // 17, | Field | Format | Notes | |-------|--------|-------| -| `id` | Hex (`0x0000`) | Always 4-digit format (0x0000); add decimal comment if > 9 | +| `id` | Hex (`0x0000`) | Always 4-digit format (0x0000), lowercase digits; add decimal comment if > 9 | | `type` | `ZCLDataTypes.*` | See type reference below | | M/O | Inline comment | `// Mandatory`, `// Optional`, or `// Conditional` | @@ -260,7 +264,7 @@ pirUnoccupiedToOccupiedDelay: { id: 0x0011, type: ZCLDataTypes.uint16 }, // 17, For hex values > 9 (where hex differs from decimal), add decimal in comment: ```javascript -id: 0x000A, // 10 +id: 0x000a, // 10 id: 0x0010, // 16 id: 0x0100, // 256 ``` @@ -271,9 +275,10 @@ id: 0x0000, id: 0x0009, ``` -For multi-line definitions, decimal goes on the `id:` line, M/O on opening brace: +For multi-line definitions, decimal goes on the `id:` line, M/O above the key: ```javascript -operatingMode: { // Optional +// Optional +operatingMode: { id: 0x0025, // 37 type: ZCLDataTypes.enum8({ normal: 0, @@ -300,7 +305,7 @@ attr3: { ... }, | Field | Required | Notes | |-------|----------|-------| -| `id` | Yes | Always 4-digit hex format (0x0000); add decimal comment if > 9 | +| `id` | Yes | Always 4-digit hex format (0x0000), lowercase digits; add decimal comment if > 9 | | `args` | If has params | Object with typed fields | | `response` | If expects response | Has own `id` and `args` | | `encodeMissingFieldsBehavior` | Controls the behavior of missing fields when encoding the struct | Set to `skip` if some fields are optional, omit if all fields are mandatory | @@ -313,7 +318,8 @@ attr3: { ... }, lockDoor: { id: 0x0000, ... }, // Mandatory // --- Server to Client Commands --- -operationEventNotification: { // Optional +// Optional +operationEventNotification: { id: 0x0020, // 32 direction: Cluster.DIRECTION_SERVER_TO_CLIENT, ... diff --git a/README.md b/README.md index c9284e81..0a8a5ad9 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ v2.0.0 - Changed `Cluster.readAttributes` signature, attributes must now be specified as an array of strings. ```js -zclNode.endpoints[1].clusters.basic.readAttributes(["modelId", "manufacturerName"]); +zclNode.endpoints[1].clusters.basic.readAttributes(['modelId', 'manufacturerName']); ``` ## Release @@ -70,8 +70,8 @@ In order to communicate with a Zigbee node retrieve a `node` instance from `Mana `/drivers/my-driver/device.js` ```js -const Homey = require("homey"); -const { ZCLNode, CLUSTER } = require("zigbee-clusters"); +const Homey = require('homey'); +const { ZCLNode, CLUSTER } = require('zigbee-clusters'); class MyDevice extends Homey.Device { onInit() { @@ -101,7 +101,7 @@ class MyDevice extends Homey.Device { // timeout (10000ms) before the command is considered rejected. // The timeout starts after sending the frame and receiving a low-level ack. timeout: 5000, - } + }, ); }); } @@ -113,8 +113,8 @@ class MyDevice extends Homey.Device { `/drivers/my-driver/device.js` ```js -const Homey = require("homey"); -const { ZCLNode, CLUSTER } = require("zigbee-clusters"); +const Homey = require('homey'); +const { ZCLNode, CLUSTER } = require('zigbee-clusters'); class MyDevice extends Homey.Device { onInit() { @@ -134,10 +134,10 @@ class MyDevice extends Homey.Device { // And listen for incoming attribute reports by binding a listener on the cluster zclNode.endpoints[1].clusters[CLUSTER.COLOR_CONTROL.NAME].on( - "attr.currentSaturation", + 'attr.currentSaturation', (currentSaturation) => { // handle reported attribute value - } + }, ); }); } @@ -181,7 +181,7 @@ class OnOffCluster extends Cluster { } static get NAME() { - return "onOff"; // The cluster name + return 'onOff'; // The cluster name } static get ATTRIBUTES() { @@ -214,7 +214,7 @@ Zigbee nodes can send commands to Homey via bound clusters. This requires a bind `/lib/LevelControlBoundCluster.js` ```js -const { BoundCluster } = require("zigbee-clusters"); +const { BoundCluster } = require('zigbee-clusters'); class LevelControlBoundCluster extends BoundCluster { constructor({ onMove }) { @@ -237,7 +237,7 @@ module.exports = LevelControlBoundCluster; `/drivers/my-driver/device.js` ```js -const LevelControlBoundCluster = require("../../lib/LevelControlBoundCluster"); +const LevelControlBoundCluster = require('../../lib/LevelControlBoundCluster'); // Register the `BoundCluster` implementation with the `ZCLNode` zclNode.endpoints[1].bind( @@ -246,7 +246,7 @@ zclNode.endpoints[1].bind( onMove: (payload) => { // Do something with the received payload }, - }) + }), ); ``` @@ -257,7 +257,7 @@ There are cases where it is required to implement a custom cluster, for example `lib/IkeaSpecificSceneCluster.js` ```js -const { ScenesCluster, ZCLDataTypes } = require("zigbee-clusters"); +const { ScenesCluster, ZCLDataTypes } = require('zigbee-clusters'); class IkeaSpecificSceneCluster extends ScenesCluster { // Here we override the `COMMANDS` getter from the `ScenesClusters` by @@ -299,14 +299,14 @@ module.exports = IkeaSpecificSceneCluster; `/drivers/my-driver/device.js` ```js -const IkeaSpecificSceneCluster = require("../../lib/IkeaSpecificSceneCluster"); +const IkeaSpecificSceneCluster = require('../../lib/IkeaSpecificSceneCluster'); // Important: we have created a new `Cluster` instance which needs to be added before // it becomes available on any `ZCLNode` instance. Cluster.addCluster(IkeaSpecificSceneCluster); // Example invocation of custom cluster command -zclNode.endpoints[1].clusters["scenes"].ikeaSceneMove({ mode: 0, transitionTime: 10 }); +zclNode.endpoints[1].clusters['scenes'].ikeaSceneMove({ mode: 0, transitionTime: 10 }); ``` 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). @@ -328,6 +328,7 @@ This runs `scripts/generate-types.js` which loads all cluster modules and genera ### 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 diff --git a/docs/jsdoc.json b/docs/jsdoc.json index 32fedb29..803baca7 100755 --- a/docs/jsdoc.json +++ b/docs/jsdoc.json @@ -9,10 +9,7 @@ "readme": "./README.md", "destination": "./build" }, - "plugins": [ - "jsdoc-ts-utils", - "plugins/markdown" - ], + "plugins": ["jsdoc-ts-utils", "plugins/markdown"], "templates": { "default": { "outputSourceFiles": false diff --git a/index.js b/index.js index b02760de..048c3315 100644 --- a/index.js +++ b/index.js @@ -39,11 +39,7 @@ function debug(flag = true, namespaces = '*') { _debug.disable(); } -const { - ZCLDataTypes, - ZCLDataType, - ZCLStruct, -} = zclTypes; +const { ZCLDataTypes, ZCLDataType, ZCLStruct } = zclTypes; const { ZIGBEE_PROFILE_ID, ZIGBEE_DEVICE_ID, IAS_ZONE_TYPE } = require('./lib/constants'); diff --git a/lib/BoundCluster.js b/lib/BoundCluster.js index 9af80b27..63031bd7 100644 --- a/lib/BoundCluster.js +++ b/lib/BoundCluster.js @@ -25,7 +25,6 @@ debug = debug.extend('bound-cluster'); * zclNode.endpoints[1].bind('onOff', new MyBoundCluster()); */ class BoundCluster { - /** * Create a new BoundCluster instance. */ @@ -52,43 +51,42 @@ class BoundCluster { async readAttributes({ attributes }) { debug(this.logId, 'received read attributes command'); const result = Buffer.alloc(255); - const attributeMap = attributes - .map(aId => { - const attr = this.cluster.attributesById[aId]; - try { - if (!attr) { - throw new ZCLError('UNSUPPORTED_ATTRIBUTE'); - } - const value = this[attr.name]; - if (typeof value === 'undefined') { - throw new ZCLError('UNSUPPORTED_ATTRIBUTE'); - } - attr.type.toBuffer(result, value, 0); + const attributeMap = attributes.map((aId) => { + const attr = this.cluster.attributesById[aId]; + try { + if (!attr) { + throw new ZCLError('UNSUPPORTED_ATTRIBUTE'); + } + const value = this[attr.name]; + if (typeof value === 'undefined') { + throw new ZCLError('UNSUPPORTED_ATTRIBUTE'); + } + attr.type.toBuffer(result, value, 0); + return { + id: aId, + status: 'SUCCESS', + value, + }; + } catch (e) { + // A ZCLError names the status to report for this one attribute. Thrown just above for an + // attribute that does not exist here, but a getter may throw one too - honour that rather + // than flattening it to FAILURE, so an implementation can pick its own per-attribute + // status. + if (e instanceof ZCLError) { + debug(this.logId, 'Attribute rejected:', attr ? attr.name || aId : aId, e.zclStatus); return { id: aId, - status: 'SUCCESS', - value, + status: e.zclStatus, }; - } catch (e) { - // A ZCLError names the status to report for this one attribute. Thrown just above for an - // attribute that does not exist here, but a getter may throw one too - honour that rather - // than flattening it to FAILURE, so an implementation can pick its own per-attribute - // status. - if (e instanceof ZCLError) { - debug(this.logId, 'Attribute rejected:', attr ? attr.name || aId : aId, e.zclStatus); - return { - id: aId, - status: e.zclStatus, - }; - } - debug(this.logId, 'Failed to parse attribute:', attr ? attr.name || aId : aId, e.message); } + debug(this.logId, 'Failed to parse attribute:', attr ? attr.name || aId : aId, e.message); + } - return { - id: aId, - status: 'FAILURE', - }; - }); + return { + id: aId, + status: 'FAILURE', + }; + }); const len = this.cluster.attributeArrayStatusDataType.toBuffer(result, attributeMap, 0); const attributesResult = { attributes: result.slice(0, len) }; @@ -108,49 +106,58 @@ class BoundCluster { debug(this.logId, 'received write attributes command', attributes); attributes = this.cluster.attributeArrayDataType.fromBuffer(attributes, 0); - const attributeMap = attributes - .map(attrValue => { - const attr = this.cluster.attributesById[attrValue.id]; - try { - if (!attr) { - throw new ZCLError('UNSUPPORTED_ATTRIBUTE'); - } - if (typeof attrValue.value === 'undefined') { - throw new Error('not_parsable'); - } - const descriptor = getPropertyDescriptor(this, attr.name); - if (!descriptor) { - // Neither a getter nor a setter: this device does not implement the attribute at all. - throw new ZCLError('UNSUPPORTED_ATTRIBUTE'); - } - if (!descriptor.set) { - throw new ZCLError('READ_ONLY'); - } - - this[attr.name] = attrValue.value; + const attributeMap = attributes.map((attrValue) => { + const attr = this.cluster.attributesById[attrValue.id]; + try { + if (!attr) { + throw new ZCLError('UNSUPPORTED_ATTRIBUTE'); + } + if (typeof attrValue.value === 'undefined') { + throw new Error('not_parsable'); + } + const descriptor = getPropertyDescriptor(this, attr.name); + if (!descriptor) { + // Neither a getter nor a setter: this device does not implement the attribute at all. + throw new ZCLError('UNSUPPORTED_ATTRIBUTE'); + } + if (!descriptor.set) { + throw new ZCLError('READ_ONLY'); + } + + this[attr.name] = attrValue.value; + return { + id: attrValue.id, + status: 'SUCCESS', + }; + } catch (e) { + // As in readAttributes: a ZCLError carries the status for this record, whether it came + // from the checks above or from a setter that wants to reject the write on its own terms. + if (e instanceof ZCLError) { + debug( + this.logId, + 'Rejected write to attribute:', + attr ? attr.name || attrValue.id : attrValue.id, + e.zclStatus, + ); return { id: attrValue.id, - status: 'SUCCESS', + status: e.zclStatus, }; - } catch (e) { - // As in readAttributes: a ZCLError carries the status for this record, whether it came - // from the checks above or from a setter that wants to reject the write on its own terms. - if (e instanceof ZCLError) { - debug(this.logId, 'Rejected write to attribute:', attr ? attr.name || attrValue.id : attrValue.id, e.zclStatus); - return { - id: attrValue.id, - status: e.zclStatus, - }; - } - debug(this.logId, 'Error: failed to parse attribute:', attr ? attr.name || attrValue.id : attrValue.id, e.message); } + debug( + this.logId, + 'Error: failed to parse attribute:', + attr ? attr.name || attrValue.id : attrValue.id, + e.message, + ); + } - return { - id: attrValue.id, - status: 'FAILURE', - }; - }); + return { + id: attrValue.id, + status: 'FAILURE', + }; + }); return { attributes: attributeMap }; } @@ -167,11 +174,12 @@ class BoundCluster { async discoverCommandsReceived({ startValue = 0, maxResults = 250 } = {}) { debug(this.logId, 'received discover commands received command'); - const cmds = [].concat(...Object.values(this.cluster.commandsById)) - .filter(c => !c.global && !c.isResponse && this[c.name]) - .map(c => c.id) + const cmds = [] + .concat(...Object.values(this.cluster.commandsById)) + .filter((c) => !c.global && !c.isResponse && this[c.name]) + .map((c) => c.id) .sort() - .filter(cId => cId >= startValue); + .filter((cId) => cId >= startValue); const result = cmds.slice(0, maxResults); const response = { @@ -194,11 +202,12 @@ class BoundCluster { async discoverCommandsGenerated({ startValue = 0, maxResults = 250 } = {}) { debug(this.logId, 'received discover commands generated command'); - const cmds = [].concat(...Object.values(this.cluster.commandsById)) - .filter(c => !c.global && c.response && this[c.name]) - .map(c => c.response.id) + const cmds = [] + .concat(...Object.values(this.cluster.commandsById)) + .filter((c) => !c.global && c.response && this[c.name]) + .map((c) => c.response.id) .sort() - .filter(cId => cId >= startValue); + .filter((cId) => cId >= startValue); const result = cmds.slice(0, maxResults); const response = { @@ -234,12 +243,13 @@ class BoundCluster { * attributes and lastResponse true, whatever the device asked for. */ async discoverAttributes({ startValue: _startValue = 0, maxResults: _maxResults = 250 } = {}) { - const attributes = Object.values(this.cluster.attributesById) - .filter(attr => attr.type instanceof ZCLDataType && getPropertyDescriptor(this, attr.name)); + const attributes = Object.values(this.cluster.attributesById).filter( + (attr) => attr.type instanceof ZCLDataType && getPropertyDescriptor(this, attr.name), + ); const response = { lastResponse: true, - attributes: attributes.map(a => ({ id: a.id, dataTypeId: a.type.id })), + attributes: attributes.map((a) => ({ id: a.id, dataTypeId: a.type.id })), }; debug(this.logId, 'received discover attributes command, response:', response); return response; @@ -270,16 +280,20 @@ class BoundCluster { * TODO: paginate. startValue and maxResults are ignored, so every call answers with all * attributes and lastResponse true, whatever the device asked for. */ - async discoverAttributesExtended({ startValue: _startValue = 0, maxResults: _maxResults = 250 } = {}) { - const attributes = Object.values(this.cluster.attributesById) - .filter(attr => attr.type instanceof ZCLDataType && getPropertyDescriptor(this, attr.name)); + async discoverAttributesExtended({ + startValue: _startValue = 0, + maxResults: _maxResults = 250, + } = {}) { + const attributes = Object.values(this.cluster.attributesById).filter( + (attr) => attr.type instanceof ZCLDataType && getPropertyDescriptor(this, attr.name), + ); let { REPORTABLE_ATTRIBUTES } = this; if (!Array.isArray(REPORTABLE_ATTRIBUTES)) REPORTABLE_ATTRIBUTES = []; const response = { lastResponse: true, - attributes: attributes.map(a => { + attributes: attributes.map((a) => { const acl = []; const prop = getPropertyDescriptor(this, a.name); @@ -315,11 +329,14 @@ class BoundCluster { async handleFrame(frame, meta, rawFrame) { const commands = this.cluster.commandsById[frame.cmdId] || []; - let filteredCommands = commands - .filter(cmd => frame.frameControl.clusterSpecific === !cmd.global - && (cmd.global || frame.frameControl.manufacturerSpecific === !!cmd.manufacturerId) - && (cmd.global || !frame.frameControl.manufacturerSpecific - || frame.manufacturerId === cmd.manufacturerId)); + let filteredCommands = commands.filter( + (cmd) => + frame.frameControl.clusterSpecific === !cmd.global && + (cmd.global || frame.frameControl.manufacturerSpecific === !!cmd.manufacturerId) && + (cmd.global || + !frame.frameControl.manufacturerSpecific || + frame.manufacturerId === cmd.manufacturerId), + ); // Try to filter based on frame direction, note: this is optional as a cluster command // does not always have a 'direction' property. The filter ensure that multiple commands @@ -327,13 +344,15 @@ class BoundCluster { // to both command definitions (see iasZone.js as an example). Cluster.js only receives // frames with directionToClient=true. BoundCluster.js receives frames with // directionToClient=false. - filteredCommands = filteredCommands.filter(command => { + filteredCommands = filteredCommands.filter((command) => { // Only filter commands that have a direction string property if (typeof command.direction === 'string') { // Filter out commands marked as DIRECTION_SERVER_TO_CLIENT when // frameControl.directionToClient = false - if (frame.frameControl.directionToClient === false - && command.direction === Cluster.DIRECTION_SERVER_TO_CLIENT) { + if ( + frame.frameControl.directionToClient === false && + command.direction === Cluster.DIRECTION_SERVER_TO_CLIENT + ) { return false; } } @@ -345,9 +364,7 @@ class BoundCluster { .pop(); if (command) { - const args = command.args - ? command.args.fromBuffer(frame.data, 0) - : undefined; + const args = command.args ? command.args.fromBuffer(frame.data, 0) : undefined; if (this[command.name]) { debug(this.logId, 'received command', command.name, args); @@ -411,10 +428,12 @@ class BoundCluster { // TODO: implement when needed async readAttributesStructured({ - attributes: _attributes = [{ - // attributeId, - // indexPath, - }], + attributes: _attributes = [ + { + // attributeId, + // indexPath, + }, + ], }) { throw new Error('not_implemented'); // return { attributes }; @@ -422,17 +441,18 @@ class BoundCluster { // TODO: implement when needed async writeAttributesStructured({ - attributes: _attributes = [{ - // attributeId, - // indexPath, - // dataTypeId, - // value, - }], + attributes: _attributes = [ + { + // attributeId, + // indexPath, + // dataTypeId, + // value, + }, + ], }) { throw new Error('not_implemented'); // return { attributes }; } - } module.exports = BoundCluster; diff --git a/lib/Cluster.js b/lib/Cluster.js index f3f8ea06..371c65d2 100644 --- a/lib/Cluster.js +++ b/lib/Cluster.js @@ -1,6 +1,6 @@ 'use strict'; -const EventEmitter = require('events'); +const EventEmitter = require('node:events'); let { debug } = require('./util'); const { getLogId, ZCLError } = require('./util'); @@ -8,7 +8,10 @@ const { getLogId, ZCLError } = require('./util'); debug = debug.extend('cluster'); const { - ZCLStandardHeader, ZCLMfgSpecificHeader, ZCLAttributeDataRecord, ZCLConfigureReportingRecords, + ZCLStandardHeader, + ZCLMfgSpecificHeader, + ZCLAttributeDataRecord, + ZCLConfigureReportingRecords, } = require('./zclFrames'); const { ZCLStruct, ZCLDataTypes } = require('./zclTypes'); @@ -46,10 +49,12 @@ const GLOBAL_COMMANDS = { response: { id: 0x04, args: { - attributes: ZCLDataTypes.Array0(ZCLStruct('AttributeResponse', { - status: ZCLDataTypes.enum8Status, - id: ZCLDataTypes.uint16, - })), + attributes: ZCLDataTypes.Array0( + ZCLStruct('AttributeResponse', { + status: ZCLDataTypes.enum8Status, + id: ZCLDataTypes.uint16, + }), + ), }, }, global: true, @@ -63,10 +68,12 @@ const GLOBAL_COMMANDS = { response: { id: 0x04, args: { - attributes: ZCLDataTypes.Array0(ZCLStruct('AttributeResponse', { - status: ZCLDataTypes.enum8Status, - id: ZCLDataTypes.uint16, - })), + attributes: ZCLDataTypes.Array0( + ZCLStruct('AttributeResponse', { + status: ZCLDataTypes.enum8Status, + id: ZCLDataTypes.uint16, + }), + ), }, }, global: true, @@ -88,14 +95,16 @@ const GLOBAL_COMMANDS = { response: { id: 0x07, args: { - reports: ZCLDataTypes.Array0(ZCLStruct('ConfigureReportingResponse', { - status: ZCLDataTypes.enum8Status, - direction: ZCLDataTypes.enum8({ - reported: 0, - received: 1, + reports: ZCLDataTypes.Array0( + ZCLStruct('ConfigureReportingResponse', { + status: ZCLDataTypes.enum8Status, + direction: ZCLDataTypes.enum8({ + reported: 0, + received: 1, + }), + attributeId: ZCLDataTypes.uint16, }), - attributeId: ZCLDataTypes.uint16, - })), + ), }, }, global: true, @@ -104,13 +113,15 @@ const GLOBAL_COMMANDS = { readReportingConfiguration: { id: 0x08, args: { - attributes: ZCLDataTypes.Array0(ZCLStruct('ReadReportingConfiguration', { - direction: ZCLDataTypes.enum8({ - reported: 0, - received: 1, + attributes: ZCLDataTypes.Array0( + ZCLStruct('ReadReportingConfiguration', { + direction: ZCLDataTypes.enum8({ + reported: 0, + received: 1, + }), + attributeId: ZCLDataTypes.uint16, }), - attributeId: ZCLDataTypes.uint16, - })), + ), }, response: { id: 0x09, @@ -122,7 +133,7 @@ const GLOBAL_COMMANDS = { }, reportAttributes: { - id: 0x0A, + id: 0x0a, args: { attributes: ZCLDataTypes.buffer, }, @@ -130,7 +141,7 @@ const GLOBAL_COMMANDS = { }, defaultResponse: { - id: 0x0B, + id: 0x0b, args: { cmdId: ZCLDataTypes.uint8, status: ZCLDataTypes.enum8Status, @@ -139,31 +150,35 @@ const GLOBAL_COMMANDS = { }, discoverAttributes: { - id: 0x0C, + id: 0x0c, args: { startValue: ZCLDataTypes.uint16, maxResults: ZCLDataTypes.uint8, }, response: { - id: 0x0D, + id: 0x0d, args: { lastResponse: ZCLDataTypes.bool, - attributes: ZCLDataTypes.Array0(ZCLStruct('DiscoveredAttribute', { - id: ZCLDataTypes.uint16, - dataTypeId: ZCLDataTypes.uint8, - })), + attributes: ZCLDataTypes.Array0( + ZCLStruct('DiscoveredAttribute', { + id: ZCLDataTypes.uint16, + dataTypeId: ZCLDataTypes.uint8, + }), + ), }, }, global: true, }, readAttributesStructured: { - id: 0x0E, + id: 0x0e, args: { - attributes: ZCLDataTypes.Array0(ZCLStruct('AttributeSelector', { - attributeId: ZCLDataTypes.uint16, - indexPath: ZCLDataTypes.Array8(ZCLDataTypes.uint16), - })), + attributes: ZCLDataTypes.Array0( + ZCLStruct('AttributeSelector', { + attributeId: ZCLDataTypes.uint16, + indexPath: ZCLDataTypes.Array8(ZCLDataTypes.uint16), + }), + ), }, response: { id: 0x01, @@ -175,14 +190,16 @@ const GLOBAL_COMMANDS = { }, writeAttributesStructured: { - id: 0x0F, + id: 0x0f, args: { - attributes: ZCLDataTypes.Array0(ZCLStruct('AttributeSelector', { - attributeId: ZCLDataTypes.uint16, - indexPath: ZCLDataTypes.Array8(ZCLDataTypes.uint16), - dataTypeId: ZCLDataTypes.uint8, - value: ZCLDataTypes.buffer, - })), + attributes: ZCLDataTypes.Array0( + ZCLStruct('AttributeSelector', { + attributeId: ZCLDataTypes.uint16, + indexPath: ZCLDataTypes.Array8(ZCLDataTypes.uint16), + dataTypeId: ZCLDataTypes.uint8, + value: ZCLDataTypes.buffer, + }), + ), }, response: { id: 0x10, @@ -235,11 +252,13 @@ const GLOBAL_COMMANDS = { id: 0x16, args: { lastResponse: ZCLDataTypes.bool, - attributes: ZCLDataTypes.Array0(ZCLStruct('DiscoveredAttributeExtended', { - id: ZCLDataTypes.uint16, - dataTypeId: ZCLDataTypes.uint8, - acl: ZCLDataTypes.map8('readable', 'writable', 'reportable'), - })), + attributes: ZCLDataTypes.Array0( + ZCLStruct('DiscoveredAttributeExtended', { + id: ZCLDataTypes.uint16, + dataTypeId: ZCLDataTypes.uint8, + acl: ZCLDataTypes.map8('readable', 'writable', 'reportable'), + }), + ), }, }, global: true, @@ -250,7 +269,6 @@ const GLOBAL_COMMANDS = { * The base cluster class every other cluster implementation must extend from. */ class Cluster extends EventEmitter { - /** * Create a new cluster instance. * @param {Endpoint} endpoint - A node {@link Endpoint} instance @@ -347,16 +365,23 @@ class Cluster extends EventEmitter { * @returns {Promise} */ async discoverCommandsGenerated({ startValue = 0, maxResults = 250 } = {}, opts) { - const { commandIds } = await super.discoverCommandsGenerated({ - startValue, - maxResults, - }, opts); + const { commandIds } = await super.discoverCommandsGenerated( + { + startValue, + maxResults, + }, + opts, + ); - const res = commandIds.map(cId => ((this.constructor.commandsById[cId] || []) - .filter(c => !c.global) - .sort((a, b) => (a.isResponse ? 1 : 0) - (b.isResponse ? 1 : 0)) // TODO - .pop() || {}) - .name || cId); + const res = commandIds.map( + (cId) => + ( + (this.constructor.commandsById[cId] || []) + .filter((c) => !c.global) + .sort((a, b) => (a.isResponse ? 1 : 0) - (b.isResponse ? 1 : 0)) // TODO + .pop() || {} + ).name || cId, + ); debug(this.logId, 'discoverCommandsGenerated', res); return res; @@ -384,16 +409,23 @@ class Cluster extends EventEmitter { * @returns {Promise} */ async discoverCommandsReceived({ startValue = 0, maxResults = 255 } = {}, opts) { - const { commandIds } = await super.discoverCommandsReceived({ - startValue, - maxResults, - }, opts); + const { commandIds } = await super.discoverCommandsReceived( + { + startValue, + maxResults, + }, + opts, + ); - const res = commandIds.map(cId => ((this.constructor.commandsById[cId] || []) - .filter(c => !c.global) - .sort((a, b) => (a.isResponse ? 0 : 1) - (b.isResponse ? 0 : 1)) // TODO - .pop() || {}) - .name || cId); + const res = commandIds.map( + (cId) => + ( + (this.constructor.commandsById[cId] || []) + .filter((c) => !c.global) + .sort((a, b) => (a.isResponse ? 0 : 1) - (b.isResponse ? 0 : 1)) // TODO + .pop() || {} + ).name || cId, + ); debug(this.logId, 'discoverCommandsReceived', res); return res; @@ -410,7 +442,9 @@ class Cluster extends EventEmitter { */ async readAttributes(attributes, opts) { if (attributes instanceof Array === false) { - throw new Error('Expected attributes array, as of zigbee-clusters@2.0.0 call readAttributes([\'myAttr\'])'); + throw new Error( + "Expected attributes array, as of zigbee-clusters@2.0.0 call readAttributes(['myAttr'])", + ); } if (!attributes.length) { @@ -422,8 +456,10 @@ class Cluster extends EventEmitter { const attrIds = new Set(); for (const attr of attributes) { if (typeof attr === 'number') { - if (!Number.isInteger(attr) || attr < 0 || attr > 0xFFFF) { - throw new TypeError(`${attr} is not a valid numeric attribute ID for ${this.name}, must be an integer between 0 and 0xFFFF`); + if (!Number.isInteger(attr) || attr < 0 || attr > 0xffff) { + throw new TypeError( + `${attr} is not a valid numeric attribute ID for ${this.name}, must be an integer between 0 and 0xFFFF`, + ); } numericIds.add(attr); if (idToName[attr] === undefined) idToName[attr] = attr; @@ -440,19 +476,27 @@ class Cluster extends EventEmitter { while (attrIds.size) { // Check if command should get manufacturerSpecific flag const manufacturerId = this._checkForManufacturerSpecificAttributes(Array.from(attrIds)); - debug(this.logId, 'read attributes', [...attrIds], manufacturerId ? `manufacturer specific id ${manufacturerId}` : ''); + debug( + this.logId, + 'read attributes', + [...attrIds], + manufacturerId ? `manufacturer specific id ${manufacturerId}` : '', + ); - const response = await super.readAttributes({ - attributes: [...attrIds], - manufacturerId, - }, opts); + const response = await super.readAttributes( + { + attributes: [...attrIds], + manufacturerId, + }, + opts, + ); debug(this.logId, 'read attributes result', response); const { attributeArrayStatusDataType } = this.constructor; const result = attributeArrayStatusDataType.fromBuffer(response.attributes, 0); if (!result.length) break; - result.forEach(a => { + result.forEach((a) => { attrIds.delete(a.id); if (a.status === 'SUCCESS') { resultObj[idToName[a.id]] = a.value; @@ -476,7 +520,7 @@ class Cluster extends EventEmitter { * @returns {Promise<*|{attributes: *}>} */ async writeAttributes(attributes = {}, opts) { - const arr = Object.keys(attributes).map(n => { + const arr = Object.keys(attributes).map((n) => { const attr = this.constructor.attributes[n]; if (!attr) { throw new TypeError(`${n} is not a valid attribute of ${this.name}`); @@ -489,13 +533,18 @@ class Cluster extends EventEmitter { // Check if command should get manufacturerSpecific flag const manufacturerId = this._checkForManufacturerSpecificAttributes( - Object.keys(attributes).map(n => this.constructor.attributes[n].id), + Object.keys(attributes).map((n) => this.constructor.attributes[n].id), ); let data = Buffer.alloc(1024); data = data.slice(0, this.constructor.attributeArrayDataType.toBuffer(data, arr, 0)); - debug(this.logId, 'write attributes', attributes, manufacturerId ? `manufacturer specific id ${manufacturerId}` : ''); + debug( + this.logId, + 'write attributes', + attributes, + manufacturerId ? `manufacturer specific id ${manufacturerId}` : '', + ); return super.writeAttributes({ attributes: data, manufacturerId }, opts); } @@ -540,10 +589,15 @@ class Cluster extends EventEmitter { if (req.length) { // Check if command should get manufacturerSpecific flag const manufacturerId = this._checkForManufacturerSpecificAttributes( - Object.keys(attributes).map(n => this.constructor.attributes[n].id), + Object.keys(attributes).map((n) => this.constructor.attributes[n].id), ); - debug(this.logId, 'configure reporting', req, manufacturerId ? `manufacturer specific id ${manufacturerId}` : ''); + debug( + this.logId, + 'configure reporting', + req, + manufacturerId ? `manufacturer specific id ${manufacturerId}` : '', + ); const { reports } = await super.configureReporting({ reports: req, manufacturerId }, opts); @@ -609,22 +663,29 @@ class Cluster extends EventEmitter { // Check if command should get manufacturerSpecific flag const manufacturerId = this._checkForManufacturerSpecificAttributes( - attributes - .map(attributeNameOrId => { - if (typeof attributeNameOrId === 'number') return attributeNameOrId; - return this.constructor.attributes[attributeNameOrId].id; - }), + attributes.map((attributeNameOrId) => { + if (typeof attributeNameOrId === 'number') return attributeNameOrId; + return this.constructor.attributes[attributeNameOrId].id; + }), ); - debug(this.logId, 'read reporting configuration', req, manufacturerId ? `manufacturer specific id ${manufacturerId}` : ''); + debug( + this.logId, + 'read reporting configuration', + req, + manufacturerId ? `manufacturer specific id ${manufacturerId}` : '', + ); // If a request has been constructed execute it if (req.length) { // Perform request, it returns a buffer that needs to be parsed - const { reports } = await super.readReportingConfiguration({ - attributes: req, - manufacturerId, - }, opts); + const { reports } = await super.readReportingConfiguration( + { + attributes: req, + manufacturerId, + }, + opts, + ); debug(this.logId, 'read reporting configuration result', reports); // Return the parsed reports @@ -654,17 +715,22 @@ class Cluster extends EventEmitter { * is implemented in zigbee-clusters or not). */ async discoverAttributes(opts) { - const { attributes } = await super.discoverAttributes({ - startValue: 0, - maxResults: 255, - }, opts); + const { attributes } = await super.discoverAttributes( + { + startValue: 0, + maxResults: 255, + }, + opts, + ); const result = []; for (const attr of attributes) { // Push the name if attribute is implemented in zigbee-clusters otherwise push attribute id - result.push(this.constructor.attributesById[attr.id] - ? this.constructor.attributesById[attr.id].name - : attr.id); + result.push( + this.constructor.attributesById[attr.id] + ? this.constructor.attributesById[attr.id].name + : attr.id, + ); } debug(this.logId, 'discover attributes', result); return result; @@ -693,10 +759,13 @@ class Cluster extends EventEmitter { * attribute is implemented in zigbee-clusters. */ async discoverAttributesExtended(opts) { - const { attributes } = await super.discoverAttributesExtended({ - startValue: 0, - maxResults: 250, - }, opts); + const { attributes } = await super.discoverAttributesExtended( + { + startValue: 0, + maxResults: 250, + }, + opts, + ); const result = []; for (const attr of attributes) { @@ -741,10 +810,14 @@ class Cluster extends EventEmitter { // Filter commands of this cluster based on cluster specific vs global, // and manufacturer specific vs not manufacturer specific. - let filteredCommands = commands.filter(cmd => frame.frameControl.clusterSpecific === !cmd.global - && (cmd.global || frame.frameControl.manufacturerSpecific === !!cmd.manufacturerId) - && (cmd.global || !frame.frameControl.manufacturerSpecific - || frame.manufacturerId === cmd.manufacturerId)); + let filteredCommands = commands.filter( + (cmd) => + frame.frameControl.clusterSpecific === !cmd.global && + (cmd.global || frame.frameControl.manufacturerSpecific === !!cmd.manufacturerId) && + (cmd.global || + !frame.frameControl.manufacturerSpecific || + frame.manufacturerId === cmd.manufacturerId), + ); // Try to filter based on frame direction, note: this is optional as a cluster command // does not always have a 'direction' property. The filter ensure that multiple commands @@ -752,13 +825,15 @@ class Cluster extends EventEmitter { // to both command definitions (see iasZone.js as an example). Cluster.js only receives // frames with directionToClient=true. BoundCluster.js receives frames with // directionToClient=false. - filteredCommands = filteredCommands.filter(command => { + filteredCommands = filteredCommands.filter((command) => { // Only filter commands that have a direction string property if (typeof command.direction === 'string') { // Filter out commands marked as DIRECTION_CLIENT_TO_SERVER when // frameControl.directionToClient = true - if (frame.frameControl.directionToClient - && command.direction === Cluster.DIRECTION_CLIENT_TO_SERVER) { + if ( + frame.frameControl.directionToClient && + command.direction === Cluster.DIRECTION_CLIENT_TO_SERVER + ) { return false; } } @@ -774,9 +849,7 @@ class Cluster extends EventEmitter { const handlerName = `on${command.name.charAt(0).toUpperCase()}${command.name.slice(1)}`; // Parse the command arguments - const args = command.args - ? command.args.fromBuffer(frame.data, 0) - : undefined; + const args = command.args ? command.args.fromBuffer(frame.data, 0) : undefined; debug(this.logId, 'received frame', command.name, args); @@ -785,8 +858,8 @@ class Cluster extends EventEmitter { const pendingTrx = this._trxHandlers[frame.trxSequenceNumber]; let isTrxResponse = false; if (pendingTrx) { - const isDefaultResponseToCommand = command.name === 'defaultResponse' - && args.cmdId === pendingTrx.cmdId; + const isDefaultResponseToCommand = + command.name === 'defaultResponse' && args.cmdId === pendingTrx.cmdId; let isReply; if (pendingTrx.responseCmdId === undefined) { @@ -800,7 +873,13 @@ class Cluster extends EventEmitter { isTrxResponse = isReply || isDefaultResponseToCommand; } if (pendingTrx && !isTrxResponse) { - debug(this.logId, 'frame with pending trx', frame.trxSequenceNumber, 'is not its response:', command.name); + debug( + this.logId, + 'frame with pending trx', + frame.trxSequenceNumber, + 'is not its response:', + command.name, + ); } // Invoke the right handler @@ -860,7 +939,7 @@ class Cluster extends EventEmitter { attributes = this.constructor.attributeArrayDataType.fromBuffer(attributes, 0); // Only expose fields chosen here, not the stack's frame meta const reportMeta = { transactionSequenceNumber: frame.trxSequenceNumber }; - attributes.forEach(attr => this.emit(`attr.${attr.name}`, attr.value, reportMeta)); + attributes.forEach((attr) => this.emit(`attr.${attr.name}`, attr.value, reportMeta)); } /** @@ -872,11 +951,12 @@ class Cluster extends EventEmitter { * @private */ async onDiscoverCommandsGenerated({ startValue = 0, maxResults = 250 } = {}) { - const cmds = [].concat(...Object.values(this.constructor.commandsById)) - .filter(c => !c.global && !c.isResponse && this[c.name]) - .map(c => c.id) + const cmds = [] + .concat(...Object.values(this.constructor.commandsById)) + .filter((c) => !c.global && !c.isResponse && this[c.name]) + .map((c) => c.id) .sort() - .filter(cId => cId >= startValue); + .filter((cId) => cId >= startValue); const result = cmds.slice(0, maxResults); debug(this.logId, 'onDiscoverCommandsGenerated', { @@ -899,12 +979,17 @@ class Cluster extends EventEmitter { * @private */ async onDiscoverCommandsReceived({ startValue = 0, maxResults = 250 } = {}) { - const cmds = [].concat(...Object.values(this.constructor.commandsById)) - .filter(c => !c.global && c.response - && (this[c.name] || this[`on${c.name.charAt(0).toUpperCase()}${c.name.slice(1)}`])) - .map(c => c.response.id) + const cmds = [] + .concat(...Object.values(this.constructor.commandsById)) + .filter( + (c) => + !c.global && + c.response && + (this[c.name] || this[`on${c.name.charAt(0).toUpperCase()}${c.name.slice(1)}`]), + ) + .map((c) => c.response.id) .sort() - .filter(cId => cId >= startValue); + .filter((cId) => cId >= startValue); const result = cmds.slice(0, maxResults); debug(this.logId, 'onDiscoverCommandsReceived', { @@ -1174,10 +1259,11 @@ class Cluster extends EventEmitter { // Lets handleFrame accept only a response to this command cmdId: cmd.id, // A declared response without id reuses the command id (see _addPrototypeMethods) - responseCmdId: cmd.response - && (typeof cmd.response.id === 'number' ? cmd.response.id : cmd.id), + responseCmdId: + cmd.response && + (typeof cmd.response.id === 'number' ? cmd.response.id : cmd.id), global: !!cmd.global, - handle: async frame => { + handle: async (frame) => { delete this._trxHandlers[payload.trxSequenceNumber]; if (awaitResponseTimeout) clearTimeout(awaitResponseTimeout); resolveAwaitResponse(frame); @@ -1200,7 +1286,7 @@ class Cluster extends EventEmitter { rejectAwaitResponse(new Error('Timeout: Expected Response')); }, responseTimeout); }) - .catch(err => { + .catch((err) => { // The stack can report a send failure (e.g. no APS ACK in time) while the device // already answered; that response proves delivery, so don't fail the command. if (this._trxHandlers[payload.trxSequenceNumber] == null) return; @@ -1247,20 +1333,25 @@ class Cluster extends EventEmitter { // Do not allow different manufacturer ids in one command if (new Set(manufacturerIds).size > 1) { - throw new Error('Error: detected multiple manufacturer ids, can only read from one at a time'); + throw new Error( + 'Error: detected multiple manufacturer ids, can only read from one at a time', + ); } // Show warning if a manufacturer specific attribute was found amongst non-manufacturer // specific attributes if (manufacturerIds.length > 0 && attrIdsSet.size !== manufacturerIds.length) { - debug(this.logId, 'WARNING expected only manufacturer specific attributes got:', manufacturerIds); + debug( + this.logId, + 'WARNING expected only manufacturer specific attributes got:', + manufacturerIds, + ); } // Return the manufacturerId that was found in the attributes if (attrIdsSet.size === manufacturerIds.length) return manufacturerIds[0]; return null; } - } Cluster.clusters = {}; diff --git a/lib/Endpoint.js b/lib/Endpoint.js index 74cc1509..fa273bae 100644 --- a/lib/Endpoint.js +++ b/lib/Endpoint.js @@ -1,6 +1,6 @@ 'use strict'; -const EventEmitter = require('events'); +const EventEmitter = require('node:events'); const Cluster = require('./Cluster'); const BoundCluster = require('./BoundCluster'); @@ -16,7 +16,6 @@ debug = debug.extend('endpoint'); * Class representing an endpoint on a node. It contains a reference to its clusters and bindings. */ class Endpoint extends EventEmitter { - /** * Creates a new Endpoint instance. * @param {ZCLNode} node @@ -34,7 +33,7 @@ class Endpoint extends EventEmitter { this._nextTrxSeqNr = Math.floor(Math.random() * 256); // Iterate all the input clusters and instantiate a Cluster for each - descriptor.inputClusters.forEach(cId => { + descriptor.inputClusters.forEach((cId) => { const InputCluster = Cluster.getCluster(cId); if (InputCluster) { this.clusters[InputCluster.NAME] = new InputCluster(this); @@ -127,12 +126,15 @@ class Endpoint extends EventEmitter { clusterSpecificResponse = await this.handleZCLFrame(clusterId, frame, meta, rawFrame); } catch (err) { clusterSpecificError = err; - debug(`${this.getLogId(clusterId)}, error while handling frame`, err.message, { meta, frame }); + debug(`${this.getLogId(clusterId)}, error while handling frame`, err.message, { + meta, + frame, + }); } // Don't respond to a default response or a group cast (ZCL spec 2.5.12.2). Only global // command 0x0B is a default response, cluster-specific ids are per cluster (2.4.1.4). - const isDefaultResponse = frame.cmdId === 0x0B && !frame.frameControl.clusterSpecific; + const isDefaultResponse = frame.cmdId === 0x0b && !frame.frameControl.clusterSpecific; if (isDefaultResponse || (meta && typeof meta.groupId === 'number')) return; // Default responses are for unicast only (ZCL spec 2.5.12.2). `broadcast` is set by the stack; @@ -143,12 +145,14 @@ class Endpoint extends EventEmitter { if (clusterSpecificError) { if (isBroadcast) return; - const status = clusterSpecificError instanceof ZCLError - ? clusterSpecificError.zclStatus : undefined; + const status = + clusterSpecificError instanceof ZCLError ? clusterSpecificError.zclStatus : undefined; const defaultResponseErrorFrame = this.makeDefaultResponseFrame(frame, false, status); - this.sendFrame(clusterId, defaultResponseErrorFrame.toBuffer()).catch(err => { - debug(`${this.getLogId(clusterId)}, error while sending default error response`, err, { response: defaultResponseErrorFrame }); + this.sendFrame(clusterId, defaultResponseErrorFrame.toBuffer()).catch((err) => { + debug(`${this.getLogId(clusterId)}, error while sending default error response`, err, { + response: defaultResponseErrorFrame, + }); }); // No further handling for this frame @@ -178,7 +182,11 @@ class Endpoint extends EventEmitter { try { await this.sendFrame(clusterId, responseFrame.toBuffer()); } catch (err) { - debug(`${this.getLogId(clusterId)}, error while sending cluster specific or default success response`, err, { response: responseFrame }); + debug( + `${this.getLogId(clusterId)}, error while sending cluster specific or default success response`, + err, + { response: responseFrame }, + ); } } @@ -235,7 +243,7 @@ class Endpoint extends EventEmitter { responseFrame.frameControl.directionToClient = !receivedFrame.frameControl.directionToClient; responseFrame.trxSequenceNumber = receivedFrame.trxSequenceNumber; - responseFrame.cmdId = 0x0B; + responseFrame.cmdId = 0x0b; responseFrame.data = Buffer.from([receivedFrame.cmdId, success ? 0 : 1]); // If not successful, set the status code in the response frame data @@ -259,7 +267,6 @@ class Endpoint extends EventEmitter { } return ZCLStandardHeader.fromBuffer(frame); } - } module.exports = Endpoint; diff --git a/lib/Node.js b/lib/Node.js index f3f6b04e..8029d4c6 100644 --- a/lib/Node.js +++ b/lib/Node.js @@ -1,6 +1,6 @@ 'use strict'; -const EventEmitter = require('events'); +const EventEmitter = require('node:events'); let { debug } = require('./util'); const { getLogId } = require('./util'); @@ -26,7 +26,6 @@ debug = debug.extend('node'); * @alias ZCLNode */ class Node extends EventEmitter { - /** * Create a new ZCLNode instance. * @param {Homey.ZigBeeNode} node - As provided by `Homey.ManagerZigBee.getNode()`. @@ -62,7 +61,7 @@ class Node extends EventEmitter { this.endpoints = {}; if (Array.isArray(node.endpointDescriptors)) { - node.endpointDescriptors.forEach(ep => { + node.endpointDescriptors.forEach((ep) => { this.endpoints[ep.endpointId] = new Endpoint(this, ep); }); } @@ -108,10 +107,12 @@ class Node extends EventEmitter { if (this.endpoints[endpointId]) { await this.endpoints[endpointId].handleFrame(clusterId, frame, meta); } else { - debug(`${this.getLogId(endpointId, clusterId)}, error while handling frame, endpoint unavailable`, { meta, frame }); + debug( + `${this.getLogId(endpointId, clusterId)}, error while handling frame, endpoint unavailable`, + { meta, frame }, + ); } } - } module.exports = Node; diff --git a/lib/clusters/alarms.js b/lib/clusters/alarms.js index a80c5ae8..d153596b 100644 --- a/lib/clusters/alarms.js +++ b/lib/clusters/alarms.js @@ -29,7 +29,6 @@ const COMMANDS = { }; class AlarmsCluster extends Cluster { - static get ID() { return 9; // 0x9 } @@ -45,7 +44,6 @@ class AlarmsCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(AlarmsCluster); diff --git a/lib/clusters/analogInput.js b/lib/clusters/analogInput.js index db6536b3..eff398bd 100644 --- a/lib/clusters/analogInput.js +++ b/lib/clusters/analogInput.js @@ -25,14 +25,16 @@ const ATTRIBUTES = { }), }, resolution: { id: 106, type: ZCLDataTypes.single }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class AnalogInputCluster extends Cluster { - static get ID() { return 12; // 0x0c } @@ -48,7 +50,6 @@ class AnalogInputCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(AnalogInputCluster); diff --git a/lib/clusters/analogOutput.js b/lib/clusters/analogOutput.js index d8ef234c..302339b2 100644 --- a/lib/clusters/analogOutput.js +++ b/lib/clusters/analogOutput.js @@ -24,14 +24,16 @@ const ATTRIBUTES = { }, relinquishDefault: { id: 104, type: ZCLDataTypes.single }, resolution: { id: 106, type: ZCLDataTypes.single }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class AnalogOutputCluster extends Cluster { - static get ID() { return 13; } @@ -47,7 +49,6 @@ class AnalogOutputCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(AnalogOutputCluster); diff --git a/lib/clusters/analogValue.js b/lib/clusters/analogValue.js index 073a7cdc..a7fdd8fa 100644 --- a/lib/clusters/analogValue.js +++ b/lib/clusters/analogValue.js @@ -21,14 +21,16 @@ const ATTRIBUTES = { }), }, relinquishDefault: { id: 104, type: ZCLDataTypes.single }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class AnalogValueCluster extends Cluster { - static get ID() { return 14; } @@ -44,7 +46,6 @@ class AnalogValueCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(AnalogValueCluster); diff --git a/lib/clusters/ballastConfiguration.js b/lib/clusters/ballastConfiguration.js index 92b1f0a5..d66114a5 100644 --- a/lib/clusters/ballastConfiguration.js +++ b/lib/clusters/ballastConfiguration.js @@ -6,7 +6,6 @@ const { ZCLDataTypes } = require('../zclTypes'); const COMMANDS = {}; const ATTRIBUTES = { - /* 0x0000: PhysicalMinLevel (optional, read-only) * Description: Minimum light level the ballast can achieve. * Default: 0x01 @@ -185,7 +184,6 @@ const ATTRIBUTES = { }; class BallastConfigurationCluster extends Cluster { - static get ID() { return 769; // 0x0301 } @@ -201,7 +199,6 @@ class BallastConfigurationCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(BallastConfigurationCluster); diff --git a/lib/clusters/basic.js b/lib/clusters/basic.js index 9f91fce4..ac926f71 100644 --- a/lib/clusters/basic.js +++ b/lib/clusters/basic.js @@ -191,7 +191,6 @@ const COMMANDS = { }; class BasicCluster extends Cluster { - static get ID() { return 0; } @@ -207,7 +206,6 @@ class BasicCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(BasicCluster); diff --git a/lib/clusters/binaryInput.js b/lib/clusters/binaryInput.js index a6d62f6b..0192de38 100644 --- a/lib/clusters/binaryInput.js +++ b/lib/clusters/binaryInput.js @@ -31,14 +31,16 @@ const ATTRIBUTES = { configurationError: 10, // CONFIGURATION-ERROR (10) }), }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class BinaryInputCluster extends Cluster { - static get ID() { return 15; } @@ -54,7 +56,6 @@ class BinaryInputCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(BinaryInputCluster); diff --git a/lib/clusters/binaryOutput.js b/lib/clusters/binaryOutput.js index bd7b33f4..163b11bb 100644 --- a/lib/clusters/binaryOutput.js +++ b/lib/clusters/binaryOutput.js @@ -32,14 +32,16 @@ const ATTRIBUTES = { }), }, relinquishDefault: { id: 104, type: ZCLDataTypes.bool }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class BinaryOutputCluster extends Cluster { - static get ID() { return 16; } @@ -55,7 +57,6 @@ class BinaryOutputCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(BinaryOutputCluster); diff --git a/lib/clusters/binaryValue.js b/lib/clusters/binaryValue.js index 718263b9..170047c5 100644 --- a/lib/clusters/binaryValue.js +++ b/lib/clusters/binaryValue.js @@ -32,14 +32,16 @@ const ATTRIBUTES = { }), }, relinquishDefault: { id: 104, type: ZCLDataTypes.bool }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class BinaryValueCluster extends Cluster { - static get ID() { return 17; } @@ -55,7 +57,6 @@ class BinaryValueCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(BinaryValueCluster); diff --git a/lib/clusters/colorControl.js b/lib/clusters/colorControl.js index ef699186..90a3a10e 100644 --- a/lib/clusters/colorControl.js +++ b/lib/clusters/colorControl.js @@ -113,7 +113,16 @@ const ATTRIBUTES = { // Bit 4: colorTemperature mandatory commands: moveToColorTemperature, moveColorTemperature, // stepColorTemperature, stopMoveStep, related attributes: colorTemperatureMireds, // colorTempPhysicalMinMireds, colorTempPhysicalMaxMireds - colorCapabilities: { id: 16394, type: ZCLDataTypes.map16('hueAndSaturation', 'enhancedHue', 'colorLoop', 'xy', 'colorTemperature') }, + colorCapabilities: { + id: 16394, + type: ZCLDataTypes.map16( + 'hueAndSaturation', + 'enhancedHue', + 'colorLoop', + 'xy', + 'colorTemperature', + ), + }, // Mandatory if colorCapabilities = colorTemperature // Read-only @@ -193,7 +202,10 @@ const COMMANDS = { // action, direction, time and startHue fields below are ignored by the device unless their // flag is set here. updateFlags: ZCLDataTypes.map8( - 'updateAction', 'updateDirection', 'updateTime', 'updateStartHue', + 'updateAction', + 'updateDirection', + 'updateTime', + 'updateStartHue', ), // Action to take for the color loop, only applied when updateFlags.updateAction is set @@ -222,7 +234,6 @@ const COMMANDS = { }; class ColorControlCluster extends Cluster { - static get ID() { return 768; // 0x0300 } @@ -238,7 +249,6 @@ class ColorControlCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(ColorControlCluster); diff --git a/lib/clusters/dehumidificationControl.js b/lib/clusters/dehumidificationControl.js index cae9ca67..a8338967 100644 --- a/lib/clusters/dehumidificationControl.js +++ b/lib/clusters/dehumidificationControl.js @@ -2,13 +2,11 @@ const Cluster = require('../Cluster'); -const ATTRIBUTES = { -}; +const ATTRIBUTES = {}; const COMMANDS = {}; class DehumidificationControlCluster extends Cluster { - static get ID() { return 515; } @@ -24,7 +22,6 @@ class DehumidificationControlCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(DehumidificationControlCluster); diff --git a/lib/clusters/deviceTemperature.js b/lib/clusters/deviceTemperature.js index 2b73ede0..62588bc1 100644 --- a/lib/clusters/deviceTemperature.js +++ b/lib/clusters/deviceTemperature.js @@ -8,7 +8,10 @@ const ATTRIBUTES = { minTempExperienced: { id: 1, type: ZCLDataTypes.int16 }, maxTempExperienced: { id: 2, type: ZCLDataTypes.int16 }, overTempTotalDwell: { id: 3, type: ZCLDataTypes.uint16 }, - deviceTempAlarmMask: { id: 16, type: ZCLDataTypes.map8('deviceTemperatureTooLow', 'deviceTemperatureTooHigh') }, + deviceTempAlarmMask: { + id: 16, + type: ZCLDataTypes.map8('deviceTemperatureTooLow', 'deviceTemperatureTooHigh'), + }, lowTempThreshold: { id: 17, type: ZCLDataTypes.int16 }, highTempThreshold: { id: 18, type: ZCLDataTypes.int16 }, lowTempDwellTripPoint: { id: 19, type: ZCLDataTypes.uint24 }, @@ -18,7 +21,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class DeviceTemperatureCluster extends Cluster { - static get ID() { return 2; } @@ -34,7 +36,6 @@ class DeviceTemperatureCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(DeviceTemperatureCluster); diff --git a/lib/clusters/diagnostics.js b/lib/clusters/diagnostics.js index f13d27bb..61cc151e 100644 --- a/lib/clusters/diagnostics.js +++ b/lib/clusters/diagnostics.js @@ -7,7 +7,6 @@ const ATTRIBUTES = {}; const COMMANDS = {}; class DiagnosticsCluster extends Cluster { - static get ID() { return 2821; // 0x0b05 } @@ -23,7 +22,6 @@ class DiagnosticsCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(DiagnosticsCluster); diff --git a/lib/clusters/doorLock.js b/lib/clusters/doorLock.js index 831fa88f..03beb134 100644 --- a/lib/clusters/doorLock.js +++ b/lib/clusters/doorLock.js @@ -5,7 +5,8 @@ const { ZCLDataTypes } = require('../zclTypes'); const ATTRIBUTES = { // Lock Information (0x0000 - 0x000F) - lockState: { // Mandatory + // Mandatory + lockState: { id: 0x0000, type: ZCLDataTypes.enum8({ notFullyLocked: 0, @@ -14,7 +15,8 @@ const ATTRIBUTES = { undefined: 255, }), }, - lockType: { // Mandatory + // Mandatory + lockType: { id: 0x0001, type: ZCLDataTypes.enum8({ deadBolt: 0, @@ -31,7 +33,8 @@ const ATTRIBUTES = { }), }, actuatorEnabled: { id: 0x0002, type: ZCLDataTypes.bool }, // Mandatory - doorState: { // Optional + // Optional + doorState: { id: 0x0003, type: ZCLDataTypes.enum8({ open: 0, @@ -51,11 +54,13 @@ const ATTRIBUTES = { numberOfTotalUsersSupported: { id: 0x0011, type: ZCLDataTypes.uint16 }, // 17, Optional numberOfPINUsersSupported: { id: 0x0012, type: ZCLDataTypes.uint16 }, // 18, Conditional¹ numberOfRFIDUsersSupported: { id: 0x0013, type: ZCLDataTypes.uint16 }, // 19, Conditional² - numberOfWeekDaySchedulesSupportedPerUser: { // Conditional³ + // Conditional³ + numberOfWeekDaySchedulesSupportedPerUser: { id: 0x0014, // 20 type: ZCLDataTypes.uint8, }, - numberOfYearDaySchedulesSupportedPerUser: { // Conditional⁴ + // Conditional⁴ + numberOfYearDaySchedulesSupportedPerUser: { id: 0x0015, // 21 type: ZCLDataTypes.uint8, }, @@ -63,7 +68,7 @@ const ATTRIBUTES = { maxPINCodeLength: { id: 0x0017, type: ZCLDataTypes.uint8 }, // 23, Conditional¹ minPINCodeLength: { id: 0x0018, type: ZCLDataTypes.uint8 }, // 24, Conditional¹ maxRFIDCodeLength: { id: 0x0019, type: ZCLDataTypes.uint8 }, // 25, Conditional² - minRFIDCodeLength: { id: 0x001A, type: ZCLDataTypes.uint8 }, // 26, Conditional² + minRFIDCodeLength: { id: 0x001a, type: ZCLDataTypes.uint8 }, // 26, Conditional² // ¹ PIN credential support // ² RFID credential support // ³ Week Day Schedule support @@ -76,7 +81,8 @@ const ATTRIBUTES = { ledSettings: { id: 0x0022, type: ZCLDataTypes.uint8 }, // 34, Optional autoRelockTime: { id: 0x0023, type: ZCLDataTypes.uint32 }, // 35, Optional soundVolume: { id: 0x0024, type: ZCLDataTypes.uint8 }, // 36, Optional - operatingMode: { // Optional + // Optional + operatingMode: { id: 0x0025, // 37 type: ZCLDataTypes.enum8({ normal: 0, @@ -86,17 +92,13 @@ const ATTRIBUTES = { passage: 4, }), }, - supportedOperatingModes: { // Mandatory + // Mandatory + supportedOperatingModes: { id: 0x0026, // 38 - type: ZCLDataTypes.map16( - 'normal', - 'vacation', - 'privacy', - 'noRFLockOrUnlock', - 'passage', - ), + type: ZCLDataTypes.map16('normal', 'vacation', 'privacy', 'noRFLockOrUnlock', 'passage'), }, - defaultConfigurationRegister: { // Optional + // Optional + defaultConfigurationRegister: { id: 0x0027, // 39 type: ZCLDataTypes.map16( 'enableLocalProgramming', @@ -111,15 +113,16 @@ const ATTRIBUTES = { }, enableLocalProgramming: { id: 0x0028, type: ZCLDataTypes.bool }, // 40, Optional enableOneTouchLocking: { id: 0x0029, type: ZCLDataTypes.bool }, // 41, Optional - enableInsideStatusLED: { id: 0x002A, type: ZCLDataTypes.bool }, // 42, Optional - enablePrivacyModeButton: { id: 0x002B, type: ZCLDataTypes.bool }, // 43, Optional + enableInsideStatusLED: { id: 0x002a, type: ZCLDataTypes.bool }, // 42, Optional + enablePrivacyModeButton: { id: 0x002b, type: ZCLDataTypes.bool }, // 43, Optional // Security Settings (0x0030 - 0x003F) wrongCodeEntryLimit: { id: 0x0030, type: ZCLDataTypes.uint8 }, // 48, Optional userCodeTemporaryDisableTime: { id: 0x0031, type: ZCLDataTypes.uint8 }, // 49, Optional sendPINOverTheAir: { id: 0x0032, type: ZCLDataTypes.bool }, // 50, Optional requirePINforRFOperation: { id: 0x0033, type: ZCLDataTypes.bool }, // 51, Optional - securityLevel: { // Optional + // Optional + securityLevel: { id: 0x0034, // 52 type: ZCLDataTypes.enum8({ network: 0, @@ -128,7 +131,8 @@ const ATTRIBUTES = { }, // Alarm and Event Masks (0x0040 - 0x004F) - alarmMask: { // Optional + // Optional + alarmMask: { id: 0x0040, // 64 type: ZCLDataTypes.map16( 'deadboltJammed', @@ -140,7 +144,8 @@ const ATTRIBUTES = { 'forcedDoorOpenUnderDoorLockedCondition', ), }, - keypadOperationEventMask: { // Optional + // Optional + keypadOperationEventMask: { id: 0x0041, // 65 type: ZCLDataTypes.map16( 'unknownOrManufacturerSpecificKeypadOperationEvent', @@ -153,7 +158,8 @@ const ATTRIBUTES = { 'nonAccessUserOperationEventSourceKeypad', ), }, - rfOperationEventMask: { // Optional + // Optional + rfOperationEventMask: { id: 0x0042, // 66 type: ZCLDataTypes.map16( 'unknownOrManufacturerSpecificKeypadOperationEvent', @@ -165,7 +171,8 @@ const ATTRIBUTES = { 'unlockSourceRFErrorInvalidSchedule', ), }, - manualOperationEventMask: { // Optional + // Optional + manualOperationEventMask: { id: 0x0043, // 67 type: ZCLDataTypes.map16( 'unknownOrManufacturerSpecificManualOperationEvent', @@ -181,7 +188,8 @@ const ATTRIBUTES = { 'manualUnlock', ), }, - rfidOperationEventMask: { // Optional + // Optional + rfidOperationEventMask: { id: 0x0044, // 68 type: ZCLDataTypes.map16( 'unknownOrManufacturerSpecificKeypadOperationEvent', @@ -193,7 +201,8 @@ const ATTRIBUTES = { 'unlockSourceRFIDErrorInvalidSchedule', ), }, - keypadProgrammingEventMask: { // Optional + // Optional + keypadProgrammingEventMask: { id: 0x0045, // 69 type: ZCLDataTypes.map16( 'unknownOrManufacturerSpecificKeypadProgrammingEvent', @@ -203,7 +212,8 @@ const ATTRIBUTES = { 'pinCodeChanged', ), }, - rfProgrammingEventMask: { // Optional + // Optional + rfProgrammingEventMask: { id: 0x0046, // 70 type: ZCLDataTypes.map16( 'unknownOrManufacturerSpecificRFProgrammingEvent', @@ -215,7 +225,8 @@ const ATTRIBUTES = { 'rfidCodeDeleted', ), }, - rfidProgrammingEventMask: { // Optional + // Optional + rfidProgrammingEventMask: { id: 0x0047, // 71 type: ZCLDataTypes.map16( 'unknownOrManufacturerSpecificRFIDProgrammingEvent', @@ -254,7 +265,8 @@ const COMMANDS = { // --- Client to Server Commands --- // Lock/Unlock Commands - lockDoor: { // Mandatory + // Mandatory + lockDoor: { id: 0x0000, args: { pinCode: ZCLDataTypes.octstr, @@ -264,7 +276,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - unlockDoor: { // Mandatory + // Mandatory + unlockDoor: { id: 0x0001, args: { pinCode: ZCLDataTypes.octstr, @@ -274,7 +287,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - toggle: { // Optional + // Optional + toggle: { id: 0x0002, args: { pinCode: ZCLDataTypes.octstr, @@ -284,7 +298,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - unlockWithTimeout: { // Optional + // Optional + unlockWithTimeout: { id: 0x0003, args: { timeout: ZCLDataTypes.uint16, @@ -297,7 +312,8 @@ const COMMANDS = { }, // Logging Commands - getLogRecord: { // Optional + // Optional + getLogRecord: { id: 0x0004, args: { logIndex: ZCLDataTypes.uint16, @@ -317,7 +333,8 @@ const COMMANDS = { }, // PIN Code Commands - setPINCode: { // Optional + // Optional + setPINCode: { id: 0x0005, args: { userId: ZCLDataTypes.uint16, @@ -330,7 +347,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - getPINCode: { // Optional + // Optional + getPINCode: { id: 0x0006, args: { userId: ZCLDataTypes.uint16, @@ -345,7 +363,8 @@ const COMMANDS = { }, }, }, - clearPINCode: { // Optional + // Optional + clearPINCode: { id: 0x0007, args: { userId: ZCLDataTypes.uint16, @@ -355,14 +374,16 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - clearAllPINCodes: { // Optional + // Optional + clearAllPINCodes: { id: 0x0008, response: { id: 0x0008, args: { status: ZCLDataTypes.uint8 }, }, }, - setUserStatus: { // Optional + // Optional + setUserStatus: { id: 0x0009, args: { userId: ZCLDataTypes.uint16, @@ -373,13 +394,14 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - getUserStatus: { // Optional - id: 0x000A, // 10 + // Optional + getUserStatus: { + id: 0x000a, // 10 args: { userId: ZCLDataTypes.uint16, }, response: { - id: 0x000A, // 10 + id: 0x000a, // 10 args: { userId: ZCLDataTypes.uint16, userStatus: USER_STATUS_ENUM, @@ -388,35 +410,53 @@ const COMMANDS = { }, // Schedule Commands - setWeekDaySchedule: { // Optional - id: 0x000B, // 11 + // Optional + setWeekDaySchedule: { + id: 0x000b, // 11 args: { scheduleId: ZCLDataTypes.uint8, userId: ZCLDataTypes.uint16, - daysMask: ZCLDataTypes.map8('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'), + daysMask: ZCLDataTypes.map8( + 'sunday', + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + ), startHour: ZCLDataTypes.uint8, startMinute: ZCLDataTypes.uint8, endHour: ZCLDataTypes.uint8, endMinute: ZCLDataTypes.uint8, }, response: { - id: 0x000B, // 11 + id: 0x000b, // 11 args: { status: ZCLDataTypes.uint8 }, }, }, - getWeekDaySchedule: { // Optional - id: 0x000C, // 12 + // Optional + getWeekDaySchedule: { + id: 0x000c, // 12 args: { scheduleId: ZCLDataTypes.uint8, userId: ZCLDataTypes.uint16, }, response: { - id: 0x000C, // 12 + id: 0x000c, // 12 args: { scheduleId: ZCLDataTypes.uint8, userId: ZCLDataTypes.uint16, status: ZCLDataTypes.uint8, - daysMask: ZCLDataTypes.map8('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'), + daysMask: ZCLDataTypes.map8( + 'sunday', + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + ), startHour: ZCLDataTypes.uint8, startMinute: ZCLDataTypes.uint8, endHour: ZCLDataTypes.uint8, @@ -424,19 +464,21 @@ const COMMANDS = { }, }, }, - clearWeekDaySchedule: { // Optional - id: 0x000D, // 13 + // Optional + clearWeekDaySchedule: { + id: 0x000d, // 13 args: { scheduleId: ZCLDataTypes.uint8, userId: ZCLDataTypes.uint16, }, response: { - id: 0x000D, // 13 + id: 0x000d, // 13 args: { status: ZCLDataTypes.uint8 }, }, }, - setYearDaySchedule: { // Optional - id: 0x000E, // 14 + // Optional + setYearDaySchedule: { + id: 0x000e, // 14 args: { scheduleId: ZCLDataTypes.uint8, userId: ZCLDataTypes.uint16, @@ -444,18 +486,19 @@ const COMMANDS = { localEndTime: ZCLDataTypes.uint32, }, response: { - id: 0x000E, // 14 + id: 0x000e, // 14 args: { status: ZCLDataTypes.uint8 }, }, }, - getYearDaySchedule: { // Optional - id: 0x000F, // 15 + // Optional + getYearDaySchedule: { + id: 0x000f, // 15 args: { scheduleId: ZCLDataTypes.uint8, userId: ZCLDataTypes.uint16, }, response: { - id: 0x000F, // 15 + id: 0x000f, // 15 args: { scheduleId: ZCLDataTypes.uint8, userId: ZCLDataTypes.uint16, @@ -465,7 +508,8 @@ const COMMANDS = { }, }, }, - clearYearDaySchedule: { // Optional + // Optional + clearYearDaySchedule: { id: 0x0010, // 16 args: { scheduleId: ZCLDataTypes.uint8, @@ -476,7 +520,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - setHolidaySchedule: { // Optional + // Optional + setHolidaySchedule: { id: 0x0011, // 17 args: { holidayScheduleId: ZCLDataTypes.uint8, @@ -489,7 +534,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - getHolidaySchedule: { // Optional + // Optional + getHolidaySchedule: { id: 0x0012, // 18 args: { holidayScheduleId: ZCLDataTypes.uint8, @@ -505,7 +551,8 @@ const COMMANDS = { }, }, }, - clearHolidaySchedule: { // Optional + // Optional + clearHolidaySchedule: { id: 0x0013, // 19 args: { holidayScheduleId: ZCLDataTypes.uint8, @@ -517,7 +564,8 @@ const COMMANDS = { }, // User Type Commands - setUserType: { // Optional + // Optional + setUserType: { id: 0x0014, // 20 args: { userId: ZCLDataTypes.uint16, @@ -528,7 +576,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - getUserType: { // Optional + // Optional + getUserType: { id: 0x0015, // 21 args: { userId: ZCLDataTypes.uint16, @@ -543,7 +592,8 @@ const COMMANDS = { }, // RFID Code Commands - setRFIDCode: { // Optional + // Optional + setRFIDCode: { id: 0x0016, // 22 args: { userId: ZCLDataTypes.uint16, @@ -556,7 +606,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - getRFIDCode: { // Optional + // Optional + getRFIDCode: { id: 0x0017, // 23 args: { userId: ZCLDataTypes.uint16, @@ -571,7 +622,8 @@ const COMMANDS = { }, }, }, - clearRFIDCode: { // Optional + // Optional + clearRFIDCode: { id: 0x0018, // 24 args: { userId: ZCLDataTypes.uint16, @@ -581,7 +633,8 @@ const COMMANDS = { args: { status: ZCLDataTypes.uint8 }, }, }, - clearAllRFIDCodes: { // Optional + // Optional + clearAllRFIDCodes: { id: 0x0019, // 25 response: { id: 0x0019, // 25 @@ -591,7 +644,8 @@ const COMMANDS = { // --- Server to Client Commands --- - operationEventNotification: { // Optional + // Optional + operationEventNotification: { id: 0x0020, // 32 direction: Cluster.DIRECTION_SERVER_TO_CLIENT, args: { @@ -603,7 +657,8 @@ const COMMANDS = { data: ZCLDataTypes.octstr, }, }, - programmingEventNotification: { // Optional + // Optional + programmingEventNotification: { id: 0x0021, // 33 direction: Cluster.DIRECTION_SERVER_TO_CLIENT, args: { @@ -620,7 +675,6 @@ const COMMANDS = { }; class DoorLockCluster extends Cluster { - static get ID() { return 0x0101; // 257 } @@ -636,7 +690,6 @@ class DoorLockCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(DoorLockCluster); diff --git a/lib/clusters/electricalMeasurement.js b/lib/clusters/electricalMeasurement.js index 8474f859..ccfa69b9 100644 --- a/lib/clusters/electricalMeasurement.js +++ b/lib/clusters/electricalMeasurement.js @@ -4,7 +4,20 @@ const Cluster = require('../Cluster'); const { ZCLDataTypes } = require('../zclTypes'); const ATTRIBUTES = { - measurementType: { id: 0, type: ZCLDataTypes.map32('activeMeasurementAC', 'reactiveMeasurementAC', 'apparentMeasurementAC', 'phaseAMeasurement', 'phaseBMeasurement', 'phaseCMeasurement', 'dcMeasurement', 'harmonicsMeasurement', 'powerQualityMeasurement') }, + measurementType: { + id: 0, + type: ZCLDataTypes.map32( + 'activeMeasurementAC', + 'reactiveMeasurementAC', + 'apparentMeasurementAC', + 'phaseAMeasurement', + 'phaseBMeasurement', + 'phaseCMeasurement', + 'dcMeasurement', + 'harmonicsMeasurement', + 'powerQualityMeasurement', + ), + }, acFrequency: { id: 768, type: ZCLDataTypes.uint16 }, measuredPhase1stHarmonicCurrent: { id: 781, type: ZCLDataTypes.int16 }, acFrequencyMultiplier: { id: 1024, type: ZCLDataTypes.uint16 }, @@ -20,7 +33,21 @@ const ATTRIBUTES = { acCurrentDivisor: { id: 1539, type: ZCLDataTypes.uint16 }, acPowerMultiplier: { id: 1540, type: ZCLDataTypes.uint16 }, acPowerDivisor: { id: 1541, type: ZCLDataTypes.uint16 }, - acAlarmsMask: { id: 2048, type: ZCLDataTypes.map16('voltageOverload', 'currentOverload', 'activePowerOverload', 'reactivePowerOverload', 'averageRMSOverVoltage', 'averageRMSUnderVoltage', 'rmsExtremeOverVoltage', 'rmsExtremeUnderVoltage', 'rmsVoltageSag', 'rmsVoltageSwell') }, + acAlarmsMask: { + id: 2048, + type: ZCLDataTypes.map16( + 'voltageOverload', + 'currentOverload', + 'activePowerOverload', + 'reactivePowerOverload', + 'averageRMSOverVoltage', + 'averageRMSUnderVoltage', + 'rmsExtremeOverVoltage', + 'rmsExtremeUnderVoltage', + 'rmsVoltageSag', + 'rmsVoltageSwell', + ), + }, acVoltageOverload: { id: 2049, type: ZCLDataTypes.uint16 }, acCurrentOverload: { id: 2050, type: ZCLDataTypes.uint16 }, acActivePowerOverload: { id: 2051, type: ZCLDataTypes.uint16 }, @@ -29,7 +56,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class ElectricalMeasurementCluster extends Cluster { - static get ID() { return 2820; // 0x0b04 } @@ -45,7 +71,6 @@ class ElectricalMeasurementCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(ElectricalMeasurementCluster); diff --git a/lib/clusters/fanControl.js b/lib/clusters/fanControl.js index 82c83bb9..367c217b 100644 --- a/lib/clusters/fanControl.js +++ b/lib/clusters/fanControl.js @@ -2,13 +2,11 @@ const Cluster = require('../Cluster'); -const ATTRIBUTES = { -}; +const ATTRIBUTES = {}; const COMMANDS = {}; class FanControlCluster extends Cluster { - static get ID() { return 514; } @@ -24,7 +22,6 @@ class FanControlCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(FanControlCluster); diff --git a/lib/clusters/flowMeasurement.js b/lib/clusters/flowMeasurement.js index 1e3936b8..79c25a29 100644 --- a/lib/clusters/flowMeasurement.js +++ b/lib/clusters/flowMeasurement.js @@ -13,7 +13,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class FlowMeasurementCluster extends Cluster { - static get ID() { return 1028; } @@ -29,7 +28,6 @@ class FlowMeasurementCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(FlowMeasurementCluster); diff --git a/lib/clusters/groups.js b/lib/clusters/groups.js index 878228fc..15863672 100644 --- a/lib/clusters/groups.js +++ b/lib/clusters/groups.js @@ -4,7 +4,10 @@ const Cluster = require('../Cluster'); const { ZCLDataTypes } = require('../zclTypes'); const ATTRIBUTES = { - nameSupport: { id: 0, type: ZCLDataTypes.map8(null, null, null, null, null, null, null, 'groupNames') }, + nameSupport: { + id: 0, + type: ZCLDataTypes.map8(null, null, null, null, null, null, null, 'groupNames'), + }, }; const COMMANDS = { @@ -73,7 +76,6 @@ const COMMANDS = { }; class GroupsCluster extends Cluster { - static get ID() { return 4; } @@ -89,7 +91,6 @@ class GroupsCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(GroupsCluster); diff --git a/lib/clusters/iasACE.js b/lib/clusters/iasACE.js index 250b1d84..43f0b2e7 100644 --- a/lib/clusters/iasACE.js +++ b/lib/clusters/iasACE.js @@ -2,13 +2,11 @@ const Cluster = require('../Cluster'); -const ATTRIBUTES = { -}; +const ATTRIBUTES = {}; const COMMANDS = {}; class IASACECluster extends Cluster { - static get ID() { return 1281; } @@ -24,7 +22,6 @@ class IASACECluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(IASACECluster); diff --git a/lib/clusters/iasWD.js b/lib/clusters/iasWD.js index 91ce66b3..ddef804e 100644 --- a/lib/clusters/iasWD.js +++ b/lib/clusters/iasWD.js @@ -2,13 +2,11 @@ const Cluster = require('../Cluster'); -const ATTRIBUTES = { -}; +const ATTRIBUTES = {}; const COMMANDS = {}; class IASWDCluster extends Cluster { - static get ID() { return 1282; } @@ -24,7 +22,6 @@ class IASWDCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(IASWDCluster); diff --git a/lib/clusters/iasZone.js b/lib/clusters/iasZone.js index fbb13287..c4079124 100644 --- a/lib/clusters/iasZone.js +++ b/lib/clusters/iasZone.js @@ -3,7 +3,18 @@ const Cluster = require('../Cluster'); const { ZCLDataTypes } = require('../zclTypes'); -const ZONE_STATUS_DATA_TYPE = ZCLDataTypes.map16('alarm1', 'alarm2', 'tamper', 'battery', 'supervisionReports', 'restoreReports', 'trouble', 'acMains', 'test', 'batteryDefect'); +const ZONE_STATUS_DATA_TYPE = ZCLDataTypes.map16( + 'alarm1', + 'alarm2', + 'tamper', + 'battery', + 'supervisionReports', + 'restoreReports', + 'trouble', + 'acMains', + 'test', + 'batteryDefect', +); const ZONE_TYPE_VALUES = { standardCIE: 0x0000, @@ -93,7 +104,6 @@ const COMMANDS = { }; class IASZoneCluster extends Cluster { - static get ID() { return 1280; } @@ -109,7 +119,6 @@ class IASZoneCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(IASZoneCluster); diff --git a/lib/clusters/identify.js b/lib/clusters/identify.js index 2da309d4..eb907cbd 100644 --- a/lib/clusters/identify.js +++ b/lib/clusters/identify.js @@ -40,7 +40,6 @@ const COMMANDS = { }; class IdentifyCluster extends Cluster { - static get ID() { return 3; } @@ -56,7 +55,6 @@ class IdentifyCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(IdentifyCluster); diff --git a/lib/clusters/illuminanceLevelSensing.js b/lib/clusters/illuminanceLevelSensing.js index 8b07c1ae..6f7fe862 100644 --- a/lib/clusters/illuminanceLevelSensing.js +++ b/lib/clusters/illuminanceLevelSensing.js @@ -27,7 +27,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class IlluminanceLevelSensingCluster extends Cluster { - static get ID() { return 1025; } @@ -43,7 +42,6 @@ class IlluminanceLevelSensingCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(IlluminanceLevelSensingCluster); diff --git a/lib/clusters/illuminanceMeasurement.js b/lib/clusters/illuminanceMeasurement.js index 656cc226..2bcdf1d8 100644 --- a/lib/clusters/illuminanceMeasurement.js +++ b/lib/clusters/illuminanceMeasurement.js @@ -21,7 +21,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class IlluminanceMeasurementCluster extends Cluster { - static get ID() { return 1024; // 0x0400 } @@ -37,7 +36,6 @@ class IlluminanceMeasurementCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(IlluminanceMeasurementCluster); diff --git a/lib/clusters/index.js b/lib/clusters/index.js index cf5718b3..25e81d72 100644 --- a/lib/clusters/index.js +++ b/lib/clusters/index.js @@ -57,11 +57,12 @@ const TouchLinkCluster = require('./touchlink'); * @returns {Readonly<{ATTRIBUTES: *, COMMANDS: *, ID: *, NAME: *}>} * @private */ -function destructConstProps({ - ID, NAME, ATTRIBUTES, COMMANDS, -}) { +function destructConstProps({ ID, NAME, ATTRIBUTES, COMMANDS }) { return Object.freeze({ - ID, NAME, ATTRIBUTES, COMMANDS, + ID, + NAME, + ATTRIBUTES, + COMMANDS, }); } diff --git a/lib/clusters/levelControl.js b/lib/clusters/levelControl.js index 04c20816..bd1bab0a 100644 --- a/lib/clusters/levelControl.js +++ b/lib/clusters/levelControl.js @@ -76,7 +76,6 @@ const COMMANDS = { }; class LevelControlCluster extends Cluster { - static get ID() { return 8; } @@ -92,7 +91,6 @@ class LevelControlCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(LevelControlCluster); diff --git a/lib/clusters/metering.js b/lib/clusters/metering.js index 93ededa5..5c3ea486 100644 --- a/lib/clusters/metering.js +++ b/lib/clusters/metering.js @@ -15,10 +15,10 @@ const ATTRIBUTES = { readingSnapShotTime: { id: 0x0007, type: ZCLDataTypes.uint32 }, // Optional currentMaxDemandDeliveredTime: { id: 0x0008, type: ZCLDataTypes.uint32 }, // Optional currentMaxDemandReceivedTime: { id: 0x0009, type: ZCLDataTypes.uint32 }, // Optional - defaultUpdatePeriod: { id: 0x000A, type: ZCLDataTypes.uint8 }, // 10, Optional - fastPollUpdatePeriod: { id: 0x000B, type: ZCLDataTypes.uint8 }, // 11, Optional - currentBlockPeriodConsumptionDelivered: { id: 0x000C, type: ZCLDataTypes.uint48 }, // 12, Optional - dailyConsumptionTarget: { id: 0x000D, type: ZCLDataTypes.uint24 }, // 13, Optional + defaultUpdatePeriod: { id: 0x000a, type: ZCLDataTypes.uint8 }, // 10, Optional + fastPollUpdatePeriod: { id: 0x000b, type: ZCLDataTypes.uint8 }, // 11, Optional + currentBlockPeriodConsumptionDelivered: { id: 0x000c, type: ZCLDataTypes.uint48 }, // 12, Optional + dailyConsumptionTarget: { id: 0x000d, type: ZCLDataTypes.uint24 }, // 13, Optional // currentBlock: { id: 0x000E, type: ZCLDataTypes.enum8({/* TODO fix */}) }, // 14, Optional // profileIntervalPeriod: { // Optional // id: 0x000F, // 15 @@ -36,7 +36,8 @@ const ATTRIBUTES = { currentTier4SummationReceived: { id: 0x0107, type: ZCLDataTypes.uint48 }, // 263, Optional // Meter Status (0x0200 - 0x02FF) - status: { // 512, Optional + // 512, Optional + status: { id: 0x0200, type: ZCLDataTypes.map8( 'checkMeter', @@ -75,8 +76,8 @@ const ATTRIBUTES = { // temperatureUnitOfMeasure: { id: 0x030C, type: ZCLDataTypes.enum8({/* TODO fix */}) }, // TODO: map8 packed fields — bits 0-2: right digits, 3-6: left digits, 7: suppress zeros // temperatureFormatting: { id: 0x030D, type: ZCLDataTypes.map8() }, // 781, Optional - moduleSerialNumber: { id: 0x030E, type: ZCLDataTypes.octstr }, // 782, Optional - operatingTariffLabelDelivered: { id: 0x030F, type: ZCLDataTypes.octstr }, // 783, Optional + moduleSerialNumber: { id: 0x030e, type: ZCLDataTypes.octstr }, // 782, Optional + operatingTariffLabelDelivered: { id: 0x030f, type: ZCLDataTypes.octstr }, // 783, Optional operatingTariffLabelReceived: { id: 0x0310, type: ZCLDataTypes.octstr }, // 784, Optional customerIdNumber: { id: 0x0311, type: ZCLDataTypes.octstr }, // 785, Optional /* 786, Optional */ @@ -91,29 +92,33 @@ const ATTRIBUTES = { currentDayConsumptionReceived: { id: 0x0402, type: ZCLDataTypes.uint24 }, // 1026, Optional previousDayConsumptionDelivered: { id: 0x0403, type: ZCLDataTypes.uint24 }, // 1027, Optional previousDayConsumptionReceived: { id: 0x0404, type: ZCLDataTypes.uint24 }, // 1028, Optional - currentPartialProfileIntervalStartTimeDelivered: { // Optional + // Optional + currentPartialProfileIntervalStartTimeDelivered: { id: 0x0405, // 1029 type: ZCLDataTypes.uint32, }, - currentPartialProfileIntervalStartTimeReceived: { // Optional + // Optional + currentPartialProfileIntervalStartTimeReceived: { id: 0x0406, // 1030 type: ZCLDataTypes.uint32, }, - currentPartialProfileIntervalValueDelivered: { // Optional + // Optional + currentPartialProfileIntervalValueDelivered: { id: 0x0407, // 1031 type: ZCLDataTypes.uint24, }, - currentPartialProfileIntervalValueReceived: { // Optional + // Optional + currentPartialProfileIntervalValueReceived: { id: 0x0408, // 1032 type: ZCLDataTypes.uint24, }, currentDayMaxPressure: { id: 0x0409, type: ZCLDataTypes.uint48 }, // 1033, Optional - currentDayMinPressure: { id: 0x040A, type: ZCLDataTypes.uint48 }, // 1034, Optional - previousDayMaxPressure: { id: 0x040B, type: ZCLDataTypes.uint48 }, // 1035, Optional - previousDayMinPressure: { id: 0x040C, type: ZCLDataTypes.uint48 }, // 1036, Optional - currentDayMaxDemand: { id: 0x040D, type: ZCLDataTypes.int24 }, // 1037, Optional - previousDayMaxDemand: { id: 0x040E, type: ZCLDataTypes.int24 }, // 1038, Optional - currentMonthMaxDemand: { id: 0x040F, type: ZCLDataTypes.int24 }, // 1039, Optional + currentDayMinPressure: { id: 0x040a, type: ZCLDataTypes.uint48 }, // 1034, Optional + previousDayMaxPressure: { id: 0x040b, type: ZCLDataTypes.uint48 }, // 1035, Optional + previousDayMinPressure: { id: 0x040c, type: ZCLDataTypes.uint48 }, // 1036, Optional + currentDayMaxDemand: { id: 0x040d, type: ZCLDataTypes.int24 }, // 1037, Optional + previousDayMaxDemand: { id: 0x040e, type: ZCLDataTypes.int24 }, // 1038, Optional + currentMonthMaxDemand: { id: 0x040f, type: ZCLDataTypes.int24 }, // 1039, Optional currentYearMaxDemand: { id: 0x0410, type: ZCLDataTypes.int24 }, // 1040, Optional currentDayMaxEnergyCarrierDemand: { id: 0x0411, type: ZCLDataTypes.int24 }, // 1041, Optional previousDayMaxEnergyCarrierDemand: { id: 0x0412, type: ZCLDataTypes.int24 }, // 1042, Optional @@ -144,12 +149,12 @@ const ATTRIBUTES = { currentNoTierBlock8SummationDelivered: { id: 0x0707, type: ZCLDataTypes.uint48 }, currentNoTierBlock9SummationDelivered: { id: 0x0708, type: ZCLDataTypes.uint48 }, currentNoTierBlock10SummationDelivered: { id: 0x0709, type: ZCLDataTypes.uint48 }, - currentNoTierBlock11SummationDelivered: { id: 0x070A, type: ZCLDataTypes.uint48 }, - currentNoTierBlock12SummationDelivered: { id: 0x070B, type: ZCLDataTypes.uint48 }, - currentNoTierBlock13SummationDelivered: { id: 0x070C, type: ZCLDataTypes.uint48 }, - currentNoTierBlock14SummationDelivered: { id: 0x070D, type: ZCLDataTypes.uint48 }, - currentNoTierBlock15SummationDelivered: { id: 0x070E, type: ZCLDataTypes.uint48 }, - currentNoTierBlock16SummationDelivered: { id: 0x070F, type: ZCLDataTypes.uint48 }, + currentNoTierBlock11SummationDelivered: { id: 0x070a, type: ZCLDataTypes.uint48 }, + currentNoTierBlock12SummationDelivered: { id: 0x070b, type: ZCLDataTypes.uint48 }, + currentNoTierBlock13SummationDelivered: { id: 0x070c, type: ZCLDataTypes.uint48 }, + currentNoTierBlock14SummationDelivered: { id: 0x070d, type: ZCLDataTypes.uint48 }, + currentNoTierBlock15SummationDelivered: { id: 0x070e, type: ZCLDataTypes.uint48 }, + currentNoTierBlock16SummationDelivered: { id: 0x070f, type: ZCLDataTypes.uint48 }, currentTier1Block1SummationDelivered: { id: 0x0710, type: ZCLDataTypes.uint48 }, currentTier1Block2SummationDelivered: { id: 0x0711, type: ZCLDataTypes.uint48 }, currentTier1Block3SummationDelivered: { id: 0x0712, type: ZCLDataTypes.uint48 }, @@ -160,12 +165,12 @@ const ATTRIBUTES = { currentTier1Block8SummationDelivered: { id: 0x0717, type: ZCLDataTypes.uint48 }, currentTier1Block9SummationDelivered: { id: 0x0718, type: ZCLDataTypes.uint48 }, currentTier1Block10SummationDelivered: { id: 0x0719, type: ZCLDataTypes.uint48 }, - currentTier1Block11SummationDelivered: { id: 0x071A, type: ZCLDataTypes.uint48 }, - currentTier1Block12SummationDelivered: { id: 0x071B, type: ZCLDataTypes.uint48 }, - currentTier1Block13SummationDelivered: { id: 0x071C, type: ZCLDataTypes.uint48 }, - currentTier1Block14SummationDelivered: { id: 0x071D, type: ZCLDataTypes.uint48 }, - currentTier1Block15SummationDelivered: { id: 0x071E, type: ZCLDataTypes.uint48 }, - currentTier1Block16SummationDelivered: { id: 0x071F, type: ZCLDataTypes.uint48 }, + currentTier1Block11SummationDelivered: { id: 0x071a, type: ZCLDataTypes.uint48 }, + currentTier1Block12SummationDelivered: { id: 0x071b, type: ZCLDataTypes.uint48 }, + currentTier1Block13SummationDelivered: { id: 0x071c, type: ZCLDataTypes.uint48 }, + currentTier1Block14SummationDelivered: { id: 0x071d, type: ZCLDataTypes.uint48 }, + currentTier1Block15SummationDelivered: { id: 0x071e, type: ZCLDataTypes.uint48 }, + currentTier1Block16SummationDelivered: { id: 0x071f, type: ZCLDataTypes.uint48 }, currentTier2Block1SummationDelivered: { id: 0x0720, type: ZCLDataTypes.uint48 }, currentTier2Block2SummationDelivered: { id: 0x0721, type: ZCLDataTypes.uint48 }, currentTier2Block3SummationDelivered: { id: 0x0722, type: ZCLDataTypes.uint48 }, @@ -176,12 +181,12 @@ const ATTRIBUTES = { currentTier2Block8SummationDelivered: { id: 0x0727, type: ZCLDataTypes.uint48 }, currentTier2Block9SummationDelivered: { id: 0x0728, type: ZCLDataTypes.uint48 }, currentTier2Block10SummationDelivered: { id: 0x0729, type: ZCLDataTypes.uint48 }, - currentTier2Block11SummationDelivered: { id: 0x072A, type: ZCLDataTypes.uint48 }, - currentTier2Block12SummationDelivered: { id: 0x072B, type: ZCLDataTypes.uint48 }, - currentTier2Block13SummationDelivered: { id: 0x072C, type: ZCLDataTypes.uint48 }, - currentTier2Block14SummationDelivered: { id: 0x072D, type: ZCLDataTypes.uint48 }, - currentTier2Block15SummationDelivered: { id: 0x072E, type: ZCLDataTypes.uint48 }, - currentTier2Block16SummationDelivered: { id: 0x072F, type: ZCLDataTypes.uint48 }, + currentTier2Block11SummationDelivered: { id: 0x072a, type: ZCLDataTypes.uint48 }, + currentTier2Block12SummationDelivered: { id: 0x072b, type: ZCLDataTypes.uint48 }, + currentTier2Block13SummationDelivered: { id: 0x072c, type: ZCLDataTypes.uint48 }, + currentTier2Block14SummationDelivered: { id: 0x072d, type: ZCLDataTypes.uint48 }, + currentTier2Block15SummationDelivered: { id: 0x072e, type: ZCLDataTypes.uint48 }, + currentTier2Block16SummationDelivered: { id: 0x072f, type: ZCLDataTypes.uint48 }, currentTier3Block1SummationDelivered: { id: 0x0730, type: ZCLDataTypes.uint48 }, currentTier3Block2SummationDelivered: { id: 0x0731, type: ZCLDataTypes.uint48 }, currentTier3Block3SummationDelivered: { id: 0x0732, type: ZCLDataTypes.uint48 }, @@ -192,12 +197,12 @@ const ATTRIBUTES = { currentTier3Block8SummationDelivered: { id: 0x0737, type: ZCLDataTypes.uint48 }, currentTier3Block9SummationDelivered: { id: 0x0738, type: ZCLDataTypes.uint48 }, currentTier3Block10SummationDelivered: { id: 0x0739, type: ZCLDataTypes.uint48 }, - currentTier3Block11SummationDelivered: { id: 0x073A, type: ZCLDataTypes.uint48 }, - currentTier3Block12SummationDelivered: { id: 0x073B, type: ZCLDataTypes.uint48 }, - currentTier3Block13SummationDelivered: { id: 0x073C, type: ZCLDataTypes.uint48 }, - currentTier3Block14SummationDelivered: { id: 0x073D, type: ZCLDataTypes.uint48 }, - currentTier3Block15SummationDelivered: { id: 0x073E, type: ZCLDataTypes.uint48 }, - currentTier3Block16SummationDelivered: { id: 0x073F, type: ZCLDataTypes.uint48 }, + currentTier3Block11SummationDelivered: { id: 0x073a, type: ZCLDataTypes.uint48 }, + currentTier3Block12SummationDelivered: { id: 0x073b, type: ZCLDataTypes.uint48 }, + currentTier3Block13SummationDelivered: { id: 0x073c, type: ZCLDataTypes.uint48 }, + currentTier3Block14SummationDelivered: { id: 0x073d, type: ZCLDataTypes.uint48 }, + currentTier3Block15SummationDelivered: { id: 0x073e, type: ZCLDataTypes.uint48 }, + currentTier3Block16SummationDelivered: { id: 0x073f, type: ZCLDataTypes.uint48 }, currentTier4Block1SummationDelivered: { id: 0x0740, type: ZCLDataTypes.uint48 }, currentTier4Block2SummationDelivered: { id: 0x0741, type: ZCLDataTypes.uint48 }, currentTier4Block3SummationDelivered: { id: 0x0742, type: ZCLDataTypes.uint48 }, @@ -208,12 +213,12 @@ const ATTRIBUTES = { currentTier4Block8SummationDelivered: { id: 0x0747, type: ZCLDataTypes.uint48 }, currentTier4Block9SummationDelivered: { id: 0x0748, type: ZCLDataTypes.uint48 }, currentTier4Block10SummationDelivered: { id: 0x0749, type: ZCLDataTypes.uint48 }, - currentTier4Block11SummationDelivered: { id: 0x074A, type: ZCLDataTypes.uint48 }, - currentTier4Block12SummationDelivered: { id: 0x074B, type: ZCLDataTypes.uint48 }, - currentTier4Block13SummationDelivered: { id: 0x074C, type: ZCLDataTypes.uint48 }, - currentTier4Block14SummationDelivered: { id: 0x074D, type: ZCLDataTypes.uint48 }, - currentTier4Block15SummationDelivered: { id: 0x074E, type: ZCLDataTypes.uint48 }, - currentTier4Block16SummationDelivered: { id: 0x074F, type: ZCLDataTypes.uint48 }, + currentTier4Block11SummationDelivered: { id: 0x074a, type: ZCLDataTypes.uint48 }, + currentTier4Block12SummationDelivered: { id: 0x074b, type: ZCLDataTypes.uint48 }, + currentTier4Block13SummationDelivered: { id: 0x074c, type: ZCLDataTypes.uint48 }, + currentTier4Block14SummationDelivered: { id: 0x074d, type: ZCLDataTypes.uint48 }, + currentTier4Block15SummationDelivered: { id: 0x074e, type: ZCLDataTypes.uint48 }, + currentTier4Block16SummationDelivered: { id: 0x074f, type: ZCLDataTypes.uint48 }, // Alarms (0x0800 - 0x08FF) // TODO: map16/32/48 alarm masks — bits map to alarm codes in their group (spec 10.4.2.2.9.1) @@ -238,49 +243,49 @@ const ATTRIBUTES = { currentNoTierBlock8SummationReceived: { id: 0x0907, type: ZCLDataTypes.uint48 }, currentNoTierBlock9SummationReceived: { id: 0x0908, type: ZCLDataTypes.uint48 }, currentNoTierBlock10SummationReceived: { id: 0x0909, type: ZCLDataTypes.uint48 }, - currentNoTierBlock11SummationReceived: { id: 0x090A, type: ZCLDataTypes.uint48 }, - currentNoTierBlock12SummationReceived: { id: 0x090B, type: ZCLDataTypes.uint48 }, - currentNoTierBlock13SummationReceived: { id: 0x090C, type: ZCLDataTypes.uint48 }, - currentNoTierBlock14SummationReceived: { id: 0x090D, type: ZCLDataTypes.uint48 }, - currentNoTierBlock15SummationReceived: { id: 0x090E, type: ZCLDataTypes.uint48 }, - currentNoTierBlock16SummationReceived: { id: 0x090F, type: ZCLDataTypes.uint48 }, + currentNoTierBlock11SummationReceived: { id: 0x090a, type: ZCLDataTypes.uint48 }, + currentNoTierBlock12SummationReceived: { id: 0x090b, type: ZCLDataTypes.uint48 }, + currentNoTierBlock13SummationReceived: { id: 0x090c, type: ZCLDataTypes.uint48 }, + currentNoTierBlock14SummationReceived: { id: 0x090d, type: ZCLDataTypes.uint48 }, + currentNoTierBlock15SummationReceived: { id: 0x090e, type: ZCLDataTypes.uint48 }, + currentNoTierBlock16SummationReceived: { id: 0x090f, type: ZCLDataTypes.uint48 }, // Meter Billing (0x0A00 - 0x0AFF) - billToDateDelivered: { id: 0x0A00, type: ZCLDataTypes.uint32 }, // 2560, Optional - billToDateTimeStampDelivered: { id: 0x0A01, type: ZCLDataTypes.uint32 }, // 2561, Optional - projectedBillDelivered: { id: 0x0A02, type: ZCLDataTypes.uint32 }, // 2562, Optional - projectedBillTimeStampDelivered: { id: 0x0A03, type: ZCLDataTypes.uint32 }, // 2563, Optional + billToDateDelivered: { id: 0x0a00, type: ZCLDataTypes.uint32 }, // 2560, Optional + billToDateTimeStampDelivered: { id: 0x0a01, type: ZCLDataTypes.uint32 }, // 2561, Optional + projectedBillDelivered: { id: 0x0a02, type: ZCLDataTypes.uint32 }, // 2562, Optional + projectedBillTimeStampDelivered: { id: 0x0a03, type: ZCLDataTypes.uint32 }, // 2563, Optional // TODO: map8 packed field — most significant nibble = digits right of decimal // billDeliveredTrailingDigit: { id: 0x0A04, type: ZCLDataTypes.map8() }, // 2564, Optional - billToDateReceived: { id: 0x0A10, type: ZCLDataTypes.uint32 }, // 2576, Optional - billToDateTimeStampReceived: { id: 0x0A11, type: ZCLDataTypes.uint32 }, // 2577, Optional - projectedBillReceived: { id: 0x0A12, type: ZCLDataTypes.uint32 }, // 2578, Optional - projectedBillTimeStampReceived: { id: 0x0A13, type: ZCLDataTypes.uint32 }, // 2579, Optional + billToDateReceived: { id: 0x0a10, type: ZCLDataTypes.uint32 }, // 2576, Optional + billToDateTimeStampReceived: { id: 0x0a11, type: ZCLDataTypes.uint32 }, // 2577, Optional + projectedBillReceived: { id: 0x0a12, type: ZCLDataTypes.uint32 }, // 2578, Optional + projectedBillTimeStampReceived: { id: 0x0a13, type: ZCLDataTypes.uint32 }, // 2579, Optional // TODO: map8 packed field — most significant nibble = digits right of decimal // billReceivedTrailingDigit: { id: 0x0A14, type: ZCLDataTypes.map8() }, // 2580, Optional // Supply Control (0x0B00 - 0x0BFF) - proposedChangeSupplyImplementationTime: { // Optional - id: 0x0B00, // 2816 + // Optional + proposedChangeSupplyImplementationTime: { + id: 0x0b00, // 2816 type: ZCLDataTypes.uint32, }, /* 2817, Optional */ // proposedChangeSupplyStatus: { id: 0x0B01, type: ZCLDataTypes.enum8({/* TODO fix */}) }, - uncontrolledFlowThreshold: { id: 0x0B10, type: ZCLDataTypes.uint16 }, // 2832, Optional + uncontrolledFlowThreshold: { id: 0x0b10, type: ZCLDataTypes.uint16 }, // 2832, Optional // uncontrolledFlowThresholdUnitOfMeasure: { // Optional // id: 0x0B11, // 2833 // type: ZCLDataTypes.enum8({/* TODO fix */}), // }, - uncontrolledFlowMultiplier: { id: 0x0B12, type: ZCLDataTypes.uint16 }, // 2834, Optional - uncontrolledFlowDivisor: { id: 0x0B13, type: ZCLDataTypes.uint16 }, // 2835, Optional - flowStabilisationPeriod: { id: 0x0B14, type: ZCLDataTypes.uint8 }, // 2836, Optional - flowMeasurementPeriod: { id: 0x0B15, type: ZCLDataTypes.uint16 }, // 2837, Optional + uncontrolledFlowMultiplier: { id: 0x0b12, type: ZCLDataTypes.uint16 }, // 2834, Optional + uncontrolledFlowDivisor: { id: 0x0b13, type: ZCLDataTypes.uint16 }, // 2835, Optional + flowStabilisationPeriod: { id: 0x0b14, type: ZCLDataTypes.uint8 }, // 2836, Optional + flowMeasurementPeriod: { id: 0x0b15, type: ZCLDataTypes.uint16 }, // 2837, Optional }; const COMMANDS = {}; class MeteringCluster extends Cluster { - static get ID() { return 0x0702; // 1794 } @@ -296,7 +301,6 @@ class MeteringCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(MeteringCluster); diff --git a/lib/clusters/multistateInput.js b/lib/clusters/multistateInput.js index 889c6eeb..882fce74 100644 --- a/lib/clusters/multistateInput.js +++ b/lib/clusters/multistateInput.js @@ -24,14 +24,16 @@ const ATTRIBUTES = { configurationError: 10, // CONFIGURATION-ERROR (10) }), }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class MultistateInputCluster extends Cluster { - static get ID() { return 18; // 0x12 } @@ -47,7 +49,6 @@ class MultistateInputCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(MultistateInputCluster); diff --git a/lib/clusters/multistateOutput.js b/lib/clusters/multistateOutput.js index b737fa42..636fc868 100644 --- a/lib/clusters/multistateOutput.js +++ b/lib/clusters/multistateOutput.js @@ -23,14 +23,16 @@ const ATTRIBUTES = { }), }, relinquishDefault: { id: 104, type: ZCLDataTypes.uint16 }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class MultistateOutputCluster extends Cluster { - static get ID() { return 19; } @@ -46,7 +48,6 @@ class MultistateOutputCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(MultistateOutputCluster); diff --git a/lib/clusters/multistateValue.js b/lib/clusters/multistateValue.js index 157bb6c6..d1f678ee 100644 --- a/lib/clusters/multistateValue.js +++ b/lib/clusters/multistateValue.js @@ -25,14 +25,16 @@ const ATTRIBUTES = { }), }, relinquishDefault: { id: 104, type: ZCLDataTypes.uint16 }, - statusFlags: { id: 111, type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService') }, + statusFlags: { + id: 111, + type: ZCLDataTypes.map8('inAlarm', 'fault', 'overridden', 'outOfService'), + }, applicationType: { id: 256, type: ZCLDataTypes.uint32 }, }; const COMMANDS = {}; class MultistateValueCluster extends Cluster { - static get ID() { return 20; } @@ -48,7 +50,6 @@ class MultistateValueCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(MultistateValueCluster); diff --git a/lib/clusters/occupancySensing.js b/lib/clusters/occupancySensing.js index 9a0aecce..905e2d52 100644 --- a/lib/clusters/occupancySensing.js +++ b/lib/clusters/occupancySensing.js @@ -6,7 +6,8 @@ const { ZCLDataTypes } = require('../zclTypes'); const ATTRIBUTES = { // Occupancy Sensor Information (0x0000 - 0x000F) occupancy: { id: 0x0000, type: ZCLDataTypes.map8('occupied') }, // Mandatory - occupancySensorType: { // Mandatory + // Mandatory + occupancySensorType: { id: 0x0001, type: ZCLDataTypes.enum8({ pir: 0, @@ -15,7 +16,8 @@ const ATTRIBUTES = { physicalContact: 3, }), }, - occupancySensorTypeBitmap: { // Mandatory + // Mandatory + occupancySensorTypeBitmap: { id: 0x0002, type: ZCLDataTypes.map8('pir', 'ultrasonic', 'physicalContact'), }, @@ -27,30 +29,36 @@ const ATTRIBUTES = { // ¹ PIR sensor type supported // Ultrasonic Configuration (0x0020 - 0x002F) - ultrasonicOccupiedToUnoccupiedDelay: { // 32, Conditional² + // 32, Conditional² + ultrasonicOccupiedToUnoccupiedDelay: { id: 0x0020, type: ZCLDataTypes.uint16, }, - ultrasonicUnoccupiedToOccupiedDelay: { // 33, Conditional² + // 33, Conditional² + ultrasonicUnoccupiedToOccupiedDelay: { id: 0x0021, type: ZCLDataTypes.uint16, }, - ultrasonicUnoccupiedToOccupiedThreshold: { // 34, Conditional² + // 34, Conditional² + ultrasonicUnoccupiedToOccupiedThreshold: { id: 0x0022, type: ZCLDataTypes.uint8, }, // ² Ultrasonic sensor type supported // Physical Contact Configuration (0x0030 - 0x003F) - physicalContactOccupiedToUnoccupiedDelay: { // 48, Conditional³ + // 48, Conditional³ + physicalContactOccupiedToUnoccupiedDelay: { id: 0x0030, type: ZCLDataTypes.uint16, }, - physicalContactUnoccupiedToOccupiedDelay: { // 49, Conditional³ + // 49, Conditional³ + physicalContactUnoccupiedToOccupiedDelay: { id: 0x0031, type: ZCLDataTypes.uint16, }, - physicalContactUnoccupiedToOccupiedThreshold: { // 50, Conditional³ + // 50, Conditional³ + physicalContactUnoccupiedToOccupiedThreshold: { id: 0x0032, type: ZCLDataTypes.uint8, }, @@ -60,7 +68,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class OccupancySensing extends Cluster { - static get ID() { return 0x0406; // 1030 } @@ -76,7 +83,6 @@ class OccupancySensing extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(OccupancySensing); diff --git a/lib/clusters/onOff.js b/lib/clusters/onOff.js index 038e2e91..4717e2bc 100644 --- a/lib/clusters/onOff.js +++ b/lib/clusters/onOff.js @@ -42,7 +42,6 @@ const COMMANDS = { }; class OnOffCluster extends Cluster { - static get ID() { return 6; } @@ -58,7 +57,6 @@ class OnOffCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(OnOffCluster); diff --git a/lib/clusters/onOffSwitch.js b/lib/clusters/onOffSwitch.js index 81a32197..bd5e9404 100644 --- a/lib/clusters/onOffSwitch.js +++ b/lib/clusters/onOffSwitch.js @@ -11,7 +11,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class OnOffSwitchCluster extends Cluster { - static get ID() { return 7; } @@ -27,7 +26,6 @@ class OnOffSwitchCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(OnOffSwitchCluster); diff --git a/lib/clusters/ota.js b/lib/clusters/ota.js index b7d31f59..5a2cc13a 100644 --- a/lib/clusters/ota.js +++ b/lib/clusters/ota.js @@ -37,7 +37,8 @@ const ATTRIBUTES = { // The upgrade status of the client device. The status indicates where the client // device is at in terms of the download and upgrade process. - imageUpgradeStatus: { // Mandatory + // Mandatory + imageUpgradeStatus: { id: 0x0006, type: ZCLDataTypes.enum8({ normal: 0x00, @@ -65,12 +66,13 @@ const ATTRIBUTES = { // A 32 bit value used as a second verification to identify the image. The value // must be consistent during the lifetime of the same image and unique for each // different build of the image. - imageStamp: { id: 0x000A, type: ZCLDataTypes.uint32 }, // 10, Optional + imageStamp: { id: 0x000a, type: ZCLDataTypes.uint32 }, // 10, Optional // Indicates what behavior the client device supports for activating a fully // downloaded but not installed upgrade image. - upgradeActivationPolicy: { // Optional - id: 0x000B, // 11 + // Optional + upgradeActivationPolicy: { + id: 0x000b, // 11 type: ZCLDataTypes.enum8({ otaServerActivationAllowed: 0x00, outOfBandActivationOnly: 0x01, @@ -79,8 +81,9 @@ const ATTRIBUTES = { // Dictates the behavior of the device in situations where an explicit activation // command cannot be retrieved. - upgradeTimeoutPolicy: { // Optional - id: 0x000C, // 12 + // Optional + upgradeTimeoutPolicy: { + id: 0x000c, // 12 type: ZCLDataTypes.enum8({ applyUpgradeAfterTimeout: 0x00, doNotApplyUpgradeAfterTimeout: 0x01, @@ -128,7 +131,8 @@ const COMMANDS = { // The purpose of sending Image Notify command is so the server has a way to // notify client devices of when the OTA upgrade images are available for them. - imageNotify: { // Optional + // Optional + imageNotify: { id: 0x0000, direction: Cluster.DIRECTION_SERVER_TO_CLIENT, encodeMissingFieldsBehavior: 'skip', @@ -153,7 +157,8 @@ const COMMANDS = { // --- Client to Server Commands --- // Client queries the server if new OTA upgrade image is available. - queryNextImageRequest: { // Mandatory + // Mandatory + queryNextImageRequest: { id: 0x0001, encodeMissingFieldsBehavior: 'skip', args: { @@ -179,14 +184,12 @@ const COMMANDS = { }, // Client requests a block of data from the OTA upgrade image. - imageBlockRequest: { // Mandatory + // Mandatory + imageBlockRequest: { id: 0x0003, encodeMissingFieldsBehavior: 'skip', args: { - fieldControl: ZCLDataTypes.map8( - 'requestNodeAddressPresent', - 'minimumBlockPeriodPresent', - ), + fieldControl: ZCLDataTypes.map8('requestNodeAddressPresent', 'minimumBlockPeriodPresent'), manufacturerCode: ZCLDataTypes.uint16, imageType: ZCLDataTypes.uint16, fileVersion: ZCLDataTypes.uint32, @@ -203,7 +206,8 @@ const COMMANDS = { // Client requests pages of data from the OTA upgrade image. Using Image Page // Request reduces the number of requests sent from the client to the upgrade // server, compared to using Image Block Request command. - imagePageRequest: { // Optional + // Optional + imagePageRequest: { id: 0x0004, encodeMissingFieldsBehavior: 'skip', args: { @@ -221,7 +225,8 @@ const COMMANDS = { response: IMAGE_BLOCK_RESPONSE, }, - imageBlockResponse: { // Mandatory + // Mandatory + imageBlockResponse: { ...IMAGE_BLOCK_RESPONSE, direction: Cluster.DIRECTION_SERVER_TO_CLIENT, frameControl: ['directionToClient', 'clusterSpecific', 'disableDefaultResponse'], @@ -229,7 +234,8 @@ const COMMANDS = { // Sent by the client when it has completed downloading an image. The status // value SHALL be SUCCESS, INVALID_IMAGE, REQUIRE_MORE_IMAGE, or ABORT. - upgradeEndRequest: { // Mandatory + // Mandatory + upgradeEndRequest: { id: 0x0006, args: { status: ZCLDataTypes.enum8Status, @@ -244,7 +250,8 @@ const COMMANDS = { // Response to the Upgrade End Request, indicating when the client should upgrade // to the new image. - upgradeEndResponse: { // Mandatory + // Mandatory + upgradeEndResponse: { ...UPGRADE_END_RESPONSE, direction: Cluster.DIRECTION_SERVER_TO_CLIENT, frameControl: ['directionToClient', 'clusterSpecific', 'disableDefaultResponse'], @@ -252,7 +259,8 @@ const COMMANDS = { // Client requests a device specific file such as security credential, // configuration or log. - queryDeviceSpecificFileRequest: { // Optional + // Optional + queryDeviceSpecificFileRequest: { id: 0x0008, args: { requestNodeAddress: ZCLDataTypes.EUI64, @@ -274,11 +282,9 @@ const COMMANDS = { }, }, }, - }; class OTACluster extends Cluster { - static get ID() { return 0x0019; // 25 } @@ -294,7 +300,6 @@ class OTACluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(OTACluster); diff --git a/lib/clusters/pollControl.js b/lib/clusters/pollControl.js index a29aa485..681f9f0d 100644 --- a/lib/clusters/pollControl.js +++ b/lib/clusters/pollControl.js @@ -30,7 +30,6 @@ const COMMANDS = { }; class PollControlCluster extends Cluster { - static get ID() { return 32; // 0x0020 } @@ -46,7 +45,6 @@ class PollControlCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(PollControlCluster); diff --git a/lib/clusters/powerConfiguration.js b/lib/clusters/powerConfiguration.js index 7123b598..5ce20a95 100644 --- a/lib/clusters/powerConfiguration.js +++ b/lib/clusters/powerConfiguration.js @@ -65,7 +65,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class PowerConfigurationCluster extends Cluster { - static get ID() { return 1; } @@ -81,7 +80,6 @@ class PowerConfigurationCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(PowerConfigurationCluster); diff --git a/lib/clusters/powerProfile.js b/lib/clusters/powerProfile.js index a26a098f..e386dd5b 100644 --- a/lib/clusters/powerProfile.js +++ b/lib/clusters/powerProfile.js @@ -2,13 +2,11 @@ const Cluster = require('../Cluster'); -const ATTRIBUTES = { -}; +const ATTRIBUTES = {}; const COMMANDS = {}; class PowerProfileCluster extends Cluster { - static get ID() { return 26; } @@ -24,7 +22,6 @@ class PowerProfileCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(PowerProfileCluster); diff --git a/lib/clusters/pressureMeasurement.js b/lib/clusters/pressureMeasurement.js index 4b8dbc47..92690122 100644 --- a/lib/clusters/pressureMeasurement.js +++ b/lib/clusters/pressureMeasurement.js @@ -18,7 +18,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class PressureMeasurementCluster extends Cluster { - static get ID() { return 1027; } @@ -34,7 +33,6 @@ class PressureMeasurementCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(PressureMeasurementCluster); diff --git a/lib/clusters/pumpConfigurationAndControl.js b/lib/clusters/pumpConfigurationAndControl.js index 3fd832c2..f69691e0 100644 --- a/lib/clusters/pumpConfigurationAndControl.js +++ b/lib/clusters/pumpConfigurationAndControl.js @@ -2,13 +2,11 @@ const Cluster = require('../Cluster'); -const ATTRIBUTES = { -}; +const ATTRIBUTES = {}; const COMMANDS = {}; class PumpConfigurationAndControlCluster extends Cluster { - static get ID() { return 512; } @@ -24,7 +22,6 @@ class PumpConfigurationAndControlCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(PumpConfigurationAndControlCluster); diff --git a/lib/clusters/relativeHumidity.js b/lib/clusters/relativeHumidity.js index 526947c8..8f18fcbf 100644 --- a/lib/clusters/relativeHumidity.js +++ b/lib/clusters/relativeHumidity.js @@ -13,7 +13,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class RelativeHumidityCluster extends Cluster { - static get ID() { return 1029; } @@ -29,7 +28,6 @@ class RelativeHumidityCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(RelativeHumidityCluster); diff --git a/lib/clusters/scenes.js b/lib/clusters/scenes.js index c15deaac..4c56bb19 100644 --- a/lib/clusters/scenes.js +++ b/lib/clusters/scenes.js @@ -7,7 +7,6 @@ const ATTRIBUTES = {}; const COMMANDS = {}; class ScenesCluster extends Cluster { - static get ID() { return 5; } @@ -23,7 +22,6 @@ class ScenesCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(ScenesCluster); diff --git a/lib/clusters/shadeConfiguration.js b/lib/clusters/shadeConfiguration.js index 3ede3b26..939308c8 100644 --- a/lib/clusters/shadeConfiguration.js +++ b/lib/clusters/shadeConfiguration.js @@ -2,13 +2,11 @@ const Cluster = require('../Cluster'); -const ATTRIBUTES = { -}; +const ATTRIBUTES = {}; const COMMANDS = {}; class ShadeConfigurationCluster extends Cluster { - static get ID() { return 256; } @@ -24,7 +22,6 @@ class ShadeConfigurationCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(ShadeConfigurationCluster); diff --git a/lib/clusters/temperatureMeasurement.js b/lib/clusters/temperatureMeasurement.js index c5f8510b..b0f8ac8d 100644 --- a/lib/clusters/temperatureMeasurement.js +++ b/lib/clusters/temperatureMeasurement.js @@ -12,7 +12,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class TemperatureMeasurement extends Cluster { - static get ID() { return 1026; // 0x0402 } @@ -28,7 +27,6 @@ class TemperatureMeasurement extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(TemperatureMeasurement); diff --git a/lib/clusters/thermostat.js b/lib/clusters/thermostat.js index e8d73ce7..47101aed 100644 --- a/lib/clusters/thermostat.js +++ b/lib/clusters/thermostat.js @@ -23,7 +23,10 @@ const ATTRIBUTES = { minCoolSetpointLimit: { id: 23, type: ZCLDataTypes.int16 }, maxCoolSetpointLimit: { id: 24, type: ZCLDataTypes.int16 }, minSetpointDeadBand: { id: 25, type: ZCLDataTypes.int8 }, - remoteSensing: { id: 26, type: ZCLDataTypes.map8('localTemperature', 'outdoorTemperature', 'occupancy') }, + remoteSensing: { + id: 26, + type: ZCLDataTypes.map8('localTemperature', 'outdoorTemperature', 'occupancy'), + }, controlSequenceOfOperation: { id: 27, type: ZCLDataTypes.enum8({ @@ -49,7 +52,10 @@ const ATTRIBUTES = { sleep: 9, }), }, - alarmMask: { id: 29, type: ZCLDataTypes.map8('initializationFailure', 'hardwareFailure', 'selfCalibrationFailure') }, + alarmMask: { + id: 29, + type: ZCLDataTypes.map8('initializationFailure', 'hardwareFailure', 'selfCalibrationFailure'), + }, runningMode: { id: 30, type: ZCLDataTypes.enum8({ @@ -75,7 +81,6 @@ const COMMANDS = { }; class ThermostatCluster extends Cluster { - static get ID() { return 513; } @@ -91,7 +96,6 @@ class ThermostatCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(ThermostatCluster); diff --git a/lib/clusters/thermostatUserInterfaceConfiguration.js b/lib/clusters/thermostatUserInterfaceConfiguration.js index 09531e39..74ae5c3f 100644 --- a/lib/clusters/thermostatUserInterfaceConfiguration.js +++ b/lib/clusters/thermostatUserInterfaceConfiguration.js @@ -34,7 +34,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class ThermostatUserInterfaceConfigurationCluster extends Cluster { - static get ID() { return 516; } @@ -50,7 +49,6 @@ class ThermostatUserInterfaceConfigurationCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(ThermostatUserInterfaceConfigurationCluster); diff --git a/lib/clusters/time.js b/lib/clusters/time.js index bf2d8242..2bf0283e 100644 --- a/lib/clusters/time.js +++ b/lib/clusters/time.js @@ -5,7 +5,10 @@ const { ZCLDataTypes } = require('../zclTypes'); const ATTRIBUTES = { time: { id: 0, type: ZCLDataTypes.UTC }, - timeStatus: { id: 1, type: ZCLDataTypes.map8('master', 'synchronized', 'masterZoneDst', 'superseding') }, + timeStatus: { + id: 1, + type: ZCLDataTypes.map8('master', 'synchronized', 'masterZoneDst', 'superseding'), + }, timeZone: { id: 2, type: ZCLDataTypes.int32 }, dstStart: { id: 3, type: ZCLDataTypes.uint32 }, dstEnd: { id: 4, type: ZCLDataTypes.uint32 }, @@ -19,7 +22,6 @@ const ATTRIBUTES = { const COMMANDS = {}; class TimeCluster extends Cluster { - static get ID() { return 10; // 0xA } @@ -35,7 +37,6 @@ class TimeCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(TimeCluster); diff --git a/lib/clusters/touchlink.js b/lib/clusters/touchlink.js index c548da4e..407aafed 100644 --- a/lib/clusters/touchlink.js +++ b/lib/clusters/touchlink.js @@ -16,17 +16,18 @@ const COMMANDS = { args: { total: ZCLDataTypes.uint8, startIndex: ZCLDataTypes.uint8, - groups: ZCLDataTypes.Array8(ZCLStruct('TLLGroupResponse', { - groupId: ZCLDataTypes.uint16, - groupType: ZCLDataTypes.uint8, - })), + groups: ZCLDataTypes.Array8( + ZCLStruct('TLLGroupResponse', { + groupId: ZCLDataTypes.uint16, + groupType: ZCLDataTypes.uint8, + }), + ), }, }, }, }; class TouchlinkCluster extends Cluster { - static get ID() { return 4096; // 0x1000 } @@ -42,7 +43,6 @@ class TouchlinkCluster extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(TouchlinkCluster); diff --git a/lib/clusters/windowCovering.js b/lib/clusters/windowCovering.js index 3e88aa9a..32f194e3 100644 --- a/lib/clusters/windowCovering.js +++ b/lib/clusters/windowCovering.js @@ -5,7 +5,8 @@ const { ZCLDataTypes } = require('../zclTypes'); const ATTRIBUTES = { // Window Covering Information (0x0000 - 0x000F) - windowCoveringType: { // Mandatory + // Mandatory + windowCoveringType: { id: 0x0000, type: ZCLDataTypes.enum8({ rollershade: 0, @@ -26,7 +27,8 @@ const ATTRIBUTES = { currentPositionTilt: { id: 0x0004, type: ZCLDataTypes.uint16 }, // Optional numberofActuationsLift: { id: 0x0005, type: ZCLDataTypes.uint16 }, // Optional numberofActuationsTilt: { id: 0x0006, type: ZCLDataTypes.uint16 }, // Optional - configStatus: { // Mandatory + // Mandatory + configStatus: { id: 0x0007, type: ZCLDataTypes.map8( 'operational', @@ -51,7 +53,8 @@ const ATTRIBUTES = { velocityLift: { id: 0x0014, type: ZCLDataTypes.uint16 }, // 20, Optional accelerationTimeLift: { id: 0x0015, type: ZCLDataTypes.uint16 }, // 21, Optional decelerationTimeLift: { id: 0x0016, type: ZCLDataTypes.uint16 }, // 22, Optional - mode: { // Mandatory + // Mandatory + mode: { id: 0x0017, // 23 type: ZCLDataTypes.map8( 'motorDirectionReversed', @@ -69,25 +72,29 @@ const COMMANDS = { upOpen: { id: 0x0000 }, // Mandatory downClose: { id: 0x0001 }, // Mandatory stop: { id: 0x0002 }, // Mandatory - goToLiftValue: { // Optional + // Optional + goToLiftValue: { id: 0x0004, args: { liftValue: ZCLDataTypes.uint16, }, }, - goToLiftPercentage: { // Optional + // Optional + goToLiftPercentage: { id: 0x0005, args: { percentageLiftValue: ZCLDataTypes.uint8, }, }, - goToTiltValue: { // Optional + // Optional + goToTiltValue: { id: 0x0007, args: { tiltValue: ZCLDataTypes.uint16, }, }, - goToTiltPercentage: { // Optional + // Optional + goToTiltPercentage: { id: 0x0008, args: { percentageTiltValue: ZCLDataTypes.uint8, @@ -96,7 +103,6 @@ const COMMANDS = { }; class WindowCovering extends Cluster { - static get ID() { return 0x0102; // 258 } @@ -112,7 +118,6 @@ class WindowCovering extends Cluster { static get COMMANDS() { return COMMANDS; } - } Cluster.addCluster(WindowCovering); diff --git a/lib/util/index.js b/lib/util/index.js index 6ab457f4..6d8d2a04 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -5,8 +5,10 @@ const debug = require('debug')('zigbee-clusters'); function getPropertyDescriptor(obj, name) { if (!obj) return; - return Object.getOwnPropertyDescriptor(obj, name) - || getPropertyDescriptor(Object.getPrototypeOf(obj), name); + return ( + Object.getOwnPropertyDescriptor(obj, name) || + getPropertyDescriptor(Object.getPrototypeOf(obj), name) + ); } /** @@ -21,12 +23,10 @@ function getLogId(endpointId, clusterName, clusterId) { } class ZCLError extends Error { - constructor(zclStatus = 'FAILURE') { super(`ZCL Error: ${zclStatus}`); this.zclStatus = zclStatus; } - } module.exports = { diff --git a/lib/zclFrames.js b/lib/zclFrames.js index 525155df..9b0c3949 100644 --- a/lib/zclFrames.js +++ b/lib/zclFrames.js @@ -2,7 +2,13 @@ const { ZCLDataType, ZCLDataTypes, ZCLStruct } = require('./zclTypes'); -const ZCLFrameControlBitmap = ZCLDataTypes.map8('clusterSpecific', null, 'manufacturerSpecific', 'directionToClient', 'disableDefaultResponse'); +const ZCLFrameControlBitmap = ZCLDataTypes.map8( + 'clusterSpecific', + null, + 'manufacturerSpecific', + 'directionToClient', + 'disableDefaultResponse', +); const ZCLStandardHeader = ZCLStruct('ZCLStandardHeader', { frameControl: ZCLFrameControlBitmap, @@ -32,241 +38,278 @@ const ZCLConfigureReportingRecordStruct = ZCLStruct('ConfigureReportingRecord', timeoutPeriod: ZCLDataTypes.uint16, }); -const ZCLAttributeDataRecord = (withStatus, attributes) => new ZCLDataType(NaN, 'attributeRecord', -3, ((buf, v, i) => { - const startByte = i; - i += ZCLDataTypes.uint16.toBuffer(buf, v.id, i); - if (withStatus) { - i += ZCLDataTypes.enum8Status.toBuffer(buf, v.status, i); - } - if (!withStatus || v.status === 'SUCCESS') { - i += ZCLDataTypes.uint8.toBuffer(buf, attributes[v.id].type.id, i); - i += attributes[v.id].type.toBuffer(buf, v.value, i); - } - return i - startByte; -}), function fromBuf(buf, i, returnLength) { - i = i || 0; - const startByte = i; - const res = {}; - if (buf.length >= i + Math.abs(this.length)) { - res.id = ZCLDataTypes.uint16.fromBuffer(buf, i); - i += ZCLDataTypes.uint16.length; - - if (withStatus) { - res.status = ZCLDataTypes.enum8Status.fromBuffer(buf, i); - i += ZCLDataTypes.enum8Status.length; - } - - if (!withStatus || res.status === 'SUCCESS') { - const dataTypeId = ZCLDataTypes.uint8.fromBuffer(buf, i); - i += ZCLDataTypes.uint8.length; - - // don't use the attribute's type if the type in the data is 0 (noData). - // This breaks when the attribute ID is 0 - const DataType = attributes[res.id] && dataTypeId !== 0 - ? attributes[res.id].type - : Object.values(ZCLDataTypes).find(type => type && type.id === dataTypeId); - if (!DataType) throw new TypeError(`Invalid Type for Attribute: ${res.id}`); - - if (attributes[res.id]) { - res.name = attributes[res.id].name || `unknown_attr_${res.id}`; +const ZCLAttributeDataRecord = (withStatus, attributes) => + new ZCLDataType( + NaN, + 'attributeRecord', + -3, + (buf, v, i) => { + const startByte = i; + i += ZCLDataTypes.uint16.toBuffer(buf, v.id, i); + if (withStatus) { + i += ZCLDataTypes.enum8Status.toBuffer(buf, v.status, i); } - - const entry = DataType.fromBuffer(buf, i, true); - if (DataType.length > 0) { - i += DataType.length; - res.value = entry; - } else { - res.value = entry.result; - i += entry.length; + if (!withStatus || v.status === 'SUCCESS') { + i += ZCLDataTypes.uint8.toBuffer(buf, attributes[v.id].type.id, i); + i += attributes[v.id].type.toBuffer(buf, v.value, i); } - } - } - if (returnLength) { - return { result: res, length: i - startByte }; - } - return res; -}); + return i - startByte; + }, + function fromBuf(buf, i, returnLength) { + i = i || 0; + const startByte = i; + const res = {}; + if (buf.length >= i + Math.abs(this.length)) { + res.id = ZCLDataTypes.uint16.fromBuffer(buf, i); + i += ZCLDataTypes.uint16.length; + + if (withStatus) { + res.status = ZCLDataTypes.enum8Status.fromBuffer(buf, i); + i += ZCLDataTypes.enum8Status.length; + } -const ZCLConfigureReportingRecords = ({ withStatus } = {}) => new ZCLDataType( - NaN, - 'configureReportingRecords', - -0, - ((buf, value, i) => { - // toBuffer - const startByte = i; + if (!withStatus || res.status === 'SUCCESS') { + const dataTypeId = ZCLDataTypes.uint8.fromBuffer(buf, i); + i += ZCLDataTypes.uint8.length; - // Loop each configure reporting record - for (const configureReportingRecord of value) { - // Remove the minChange property (because its size will vary) - const { minChange, direction } = configureReportingRecord; + // don't use the attribute's type if the type in the data is 0 (noData). + // This breaks when the attribute ID is 0 + const DataType = + attributes[res.id] && dataTypeId !== 0 + ? attributes[res.id].type + : Object.values(ZCLDataTypes).find((type) => type && type.id === dataTypeId); + if (!DataType) throw new TypeError(`Invalid Type for Attribute: ${res.id}`); - // Check if this record is expected to have a status field - if (withStatus) { - ZCLDataTypes.enum8Status.toBuffer(buf, configureReportingRecord.status, i); - i += ZCLDataTypes.enum8Status.length; - } + if (attributes[res.id]) { + res.name = attributes[res.id].name || `unknown_attr_${res.id}`; + } - ZCLConfigureReportingRecordStruct.fields.direction - .toBuffer(buf, configureReportingRecord.direction, i); - i += ZCLConfigureReportingRecordStruct.fields.direction.length; + const entry = DataType.fromBuffer(buf, i, true); + if (DataType.length > 0) { + i += DataType.length; + res.value = entry; + } else { + res.value = entry.result; + i += entry.length; + } + } + } + if (returnLength) { + return { result: res, length: i - startByte }; + } + return res; + }, + ); + +const ZCLConfigureReportingRecords = ({ withStatus } = {}) => + new ZCLDataType( + NaN, + 'configureReportingRecords', + -0, + (buf, value, i) => { + // toBuffer + const startByte = i; + + // Loop each configure reporting record + for (const configureReportingRecord of value) { + // Remove the minChange property (because its size will vary) + const { minChange, direction } = configureReportingRecord; + + // Check if this record is expected to have a status field + if (withStatus) { + ZCLDataTypes.enum8Status.toBuffer(buf, configureReportingRecord.status, i); + i += ZCLDataTypes.enum8Status.length; + } - ZCLConfigureReportingRecordStruct.fields.attributeId - .toBuffer(buf, configureReportingRecord.attributeId, i); - i += ZCLConfigureReportingRecordStruct.fields.attributeId.length; + ZCLConfigureReportingRecordStruct.fields.direction.toBuffer( + buf, + configureReportingRecord.direction, + i, + ); + i += ZCLConfigureReportingRecordStruct.fields.direction.length; + + ZCLConfigureReportingRecordStruct.fields.attributeId.toBuffer( + buf, + configureReportingRecord.attributeId, + i, + ); + i += ZCLConfigureReportingRecordStruct.fields.attributeId.length; + + // Nothing further to parse if it was not a success + if (withStatus && configureReportingRecord.status !== 'SUCCESS') { + continue; + } - // Nothing further to parse if it was not a success - if (withStatus && configureReportingRecord.status !== 'SUCCESS') { - continue; + if (direction === 'reported') { + // Determine the type of the minChange property (equal to attributeDataType of this record) + const minChangeDataType = Object.values(ZCLDataTypes).find( + (DataType) => DataType.id === configureReportingRecord.attributeDataType, + ); + + ZCLConfigureReportingRecordStruct.fields.attributeDataType.toBuffer( + buf, + configureReportingRecord.attributeDataType, + i, + ); + i += ZCLConfigureReportingRecordStruct.fields.attributeDataType.length; + + ZCLConfigureReportingRecordStruct.fields.minInterval.toBuffer( + buf, + configureReportingRecord.minInterval, + i, + ); + i += ZCLConfigureReportingRecordStruct.fields.minInterval.length; + + ZCLConfigureReportingRecordStruct.fields.maxInterval.toBuffer( + buf, + configureReportingRecord.maxInterval, + i, + ); + i += ZCLConfigureReportingRecordStruct.fields.maxInterval.length; + + if (minChangeDataType && typeof minChange === 'number') { + minChangeDataType.toBuffer(buf, minChange, i); + i += minChangeDataType.length; + } + } else { + ZCLConfigureReportingRecordStruct.fields.timeoutPeriod.toBuffer( + buf, + configureReportingRecord.timeoutPeriod, + i, + ); + i += ZCLConfigureReportingRecordStruct.fields.timeoutPeriod.length; + } } - if (direction === 'reported') { - // Determine the type of the minChange property (equal to attributeDataType of this record) - const minChangeDataType = Object.values(ZCLDataTypes) - .find(DataType => DataType.id === configureReportingRecord.attributeDataType); + return i - startByte; + }, + (buf, i, returnLength) => { + // fromBuffer + i = i || 0; + // Keep a reference to the current buffer index while reading it + const startByte = i; + const res = []; + + // Read the entire buffer + while (buf.length > i) { + // Parse status only if part of record struct (the read configuration response has a + // status field whereas the configure reporting command does not) + let status; + + // Check if this record is expected to have a status field + if (withStatus) { + status = ZCLDataTypes.enum8Status.fromBuffer(buf, i); + i += ZCLDataTypes.enum8Status.length; + } - ZCLConfigureReportingRecordStruct.fields.attributeDataType - .toBuffer(buf, configureReportingRecord.attributeDataType, i); - i += ZCLConfigureReportingRecordStruct.fields.attributeDataType.length; + // Parse direction + const direction = ZCLConfigureReportingRecordStruct.fields.direction.fromBuffer(buf, i); + i += 1; - ZCLConfigureReportingRecordStruct.fields.minInterval - .toBuffer(buf, configureReportingRecord.minInterval, i); - i += ZCLConfigureReportingRecordStruct.fields.minInterval.length; + // Parse attributeId + const attributeId = ZCLConfigureReportingRecordStruct.fields.attributeId.fromBuffer(buf, i); + i += 2; - ZCLConfigureReportingRecordStruct.fields.maxInterval - .toBuffer(buf, configureReportingRecord.maxInterval, i); - i += ZCLConfigureReportingRecordStruct.fields.maxInterval.length; + // Check if the status is success, only then we need to continue parsing for other values + if (withStatus && status !== 'SUCCESS') { + // Buffer parsing is complete + res.push({ status, direction, attributeId }); - if (minChangeDataType && typeof minChange === 'number') { - minChangeDataType.toBuffer(buf, minChange, i); - i += minChangeDataType.length; + // Continue reading the buffer in a next iteration + continue; } - } else { - ZCLConfigureReportingRecordStruct.fields.timeoutPeriod - .toBuffer(buf, configureReportingRecord.timeoutPeriod, i); - i += ZCLConfigureReportingRecordStruct.fields.timeoutPeriod.length; - } - } - - return i - startByte; - }), ((buf, i, returnLength) => { - // fromBuffer - i = i || 0; - // Keep a reference to the current buffer index while reading it - const startByte = i; - const res = []; - - // Read the entire buffer - while (buf.length > i) { - // Parse status only if part of record struct (the read configuration response has a - // status field whereas the configure reporting command does not) - let status; - - // Check if this record is expected to have a status field - if (withStatus) { - status = ZCLDataTypes.enum8Status.fromBuffer(buf, i); - i += ZCLDataTypes.enum8Status.length; - } - // Parse direction - const direction = ZCLConfigureReportingRecordStruct.fields.direction.fromBuffer(buf, i); - i += 1; + switch (direction) { + // Only if the direction is 'reported' are the minInterval/maxInterval and minChange + // properties available + case 'reported': { + // Parse attributeDataType, we need this to find the respective ZCLDataType in the + // next step + const attributeDataType = + ZCLConfigureReportingRecordStruct.fields.attributeDataType.fromBuffer(buf, i); + i += 1; + + // Find the respective ZCLDataType, we need it later on to determine if the data type + // is analog or discrete + const parsedAttributeDataType = Object.values(ZCLDataTypes).find( + (x) => x.id === attributeDataType, + ); + + // Parse minInterval + const minInterval = ZCLDataTypes.uint16.fromBuffer(buf, i); + i += 2; + + // Parse maxInterval + const maxInterval = ZCLDataTypes.uint16.fromBuffer(buf, i); + i += 2; + + // If the ZCLDataType is not analog there will be no minChange field + if (!parsedAttributeDataType.isAnalog) { + const resultRecord = { + direction, + attributeId, + attributeDataType, + minInterval, + maxInterval, + }; + if (withStatus) resultRecord.status = status; + res.push(resultRecord); + + // Continue reading the buffer in a next iteration + continue; + } + + // Apparently the ZCLDataType is analog so we should parse the minChange field + const minChange = ZCLDataTypes[parsedAttributeDataType.shortName].fromBuffer(buf, i); + i += parsedAttributeDataType.length; - // Parse attributeId - const attributeId = ZCLConfigureReportingRecordStruct.fields.attributeId.fromBuffer(buf, i); - i += 2; - - // Check if the status is success, only then we need to continue parsing for other values - if (withStatus && status !== 'SUCCESS') { - // Buffer parsing is complete - res.push({ status, direction, attributeId }); - - // Continue reading the buffer in a next iteration - continue; - } - - switch (direction) { - // Only if the direction is 'reported' are the minInterval/maxInterval and minChange - // properties available - case 'reported': { - // Parse attributeDataType, we need this to find the respective ZCLDataType in the - // next step - const attributeDataType = ZCLConfigureReportingRecordStruct.fields.attributeDataType - .fromBuffer(buf, i); - i += 1; - - // Find the respective ZCLDataType, we need it later on to determine if the data type - // is analog or discrete - const parsedAttributeDataType = Object.values(ZCLDataTypes) - .find(x => x.id === attributeDataType); - - // Parse minInterval - const minInterval = ZCLDataTypes.uint16.fromBuffer(buf, i); - i += 2; - - // Parse maxInterval - const maxInterval = ZCLDataTypes.uint16.fromBuffer(buf, i); - i += 2; - - // If the ZCLDataType is not analog there will be no minChange field - if (!parsedAttributeDataType.isAnalog) { const resultRecord = { - direction, attributeId, attributeDataType, minInterval, maxInterval, + direction, + attributeId, + attributeDataType, + minInterval, + maxInterval, + minChange, }; + if (withStatus) resultRecord.status = status; res.push(resultRecord); - // Continue reading the buffer in a next iteration - continue; + // Buffer parsing is complete + break; } - // Apparently the ZCLDataType is analog so we should parse the minChange field - const minChange = ZCLDataTypes[parsedAttributeDataType.shortName] - .fromBuffer(buf, i); - i += parsedAttributeDataType.length; - - const resultRecord = { - direction, - attributeId, - attributeDataType, - minInterval, - maxInterval, - minChange, - }; - - if (withStatus) resultRecord.status = status; - res.push(resultRecord); - - // Buffer parsing is complete - break; - } - - // If the direction is 'received' we only have to parse the - case 'received': { - // timeoutPeriod - // Parse timeout period - const timeoutPeriod = ZCLConfigureReportingRecordStruct.fields.timeoutPeriod - .fromBuffer(buf, i); - i += 2; + // If the direction is 'received' we only have to parse the + case 'received': { + // timeoutPeriod + // Parse timeout period + const timeoutPeriod = ZCLConfigureReportingRecordStruct.fields.timeoutPeriod.fromBuffer( + buf, + i, + ); + i += 2; - const resultRecord = { - direction, attributeId, timeoutPeriod, - }; + const resultRecord = { + direction, + attributeId, + timeoutPeriod, + }; - if (withStatus) resultRecord.status = status; - res.push(resultRecord); + if (withStatus) resultRecord.status = status; + res.push(resultRecord); - // Buffer parsing is complete - break; + // Buffer parsing is complete + break; + } } } - } - if (returnLength) { - return { result: res, length: i - startByte }; - } - return res; - }), -); + if (returnLength) { + return { result: res, length: i - startByte }; + } + return res; + }, + ); module.exports = { ZCLStandardHeader, diff --git a/lib/zclTypes.js b/lib/zclTypes.js index dee27bb1..e8706977 100644 --- a/lib/zclTypes.js +++ b/lib/zclTypes.js @@ -6,6 +6,7 @@ const ZCLDataType = DataType; const ZCLDataTypes = DataTypes; const ZCLStruct = Struct; +// prettier-ignore ZCLDataTypes.enum8Status = ZCLDataTypes.enum8({ SUCCESS : 0x00, FAILURE : 0x01, diff --git a/package-lock.json b/package-lock.json index 231a7fc6..c56e9e8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "jsdoc": "^4.0.5", "jsdoc-ts-utils": "^6.0.0", "mocha": "^12.0.3", + "prettier": "^3.9.10", "serve": "^14.0.1", "sinon": "^19.0.2", "typescript": "^5.9.3", @@ -3645,6 +3646,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 7b71286f..45508627 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "serve": "concurrently \"serve build/\" \"npm run build:watch\"", "build": "jsdoc --configure ./docs/jsdoc.json", "build:clean": "rm -rf ./build", - "build:watch": "watch \"npm run build:clean && npm run build\" lib docs \"node_modules/@athombv/jsdoc-template\"" + "build:watch": "watch \"npm run build:clean && npm run build\" lib docs \"node_modules/@athombv/jsdoc-template\"", + "format:check": "prettier --check .", + "format:write": "prettier --write ." }, "repository": { "type": "git", @@ -46,6 +48,7 @@ "jsdoc": "^4.0.5", "jsdoc-ts-utils": "^6.0.0", "mocha": "^12.0.3", + "prettier": "^3.9.10", "serve": "^14.0.1", "sinon": "^19.0.2", "typescript": "^5.9.3", diff --git a/scripts/generate-types.mts b/scripts/generate-types.mts index b480d340..02188170 100644 --- a/scripts/generate-types.mts +++ b/scripts/generate-types.mts @@ -1,7 +1,7 @@ -import type {Cluster, types} from "zigbee-clusters"; -import path from "node:path"; -import * as fs from "node:fs/promises"; -import type {DataType, DataTypes} from "@athombv/data-types"; +import type { Cluster, types } from 'zigbee-clusters'; +import path from 'node:path'; +import * as fs from 'node:fs/promises'; +import type { DataType, DataTypes } from '@athombv/data-types'; const INDENTATION_SPACES = 2; @@ -15,13 +15,13 @@ class StringBuilder { } public startLine(): void { - this.strings.push(" ".repeat(this.indentation)); + this.strings.push(' '.repeat(this.indentation)); } public print(...line: string[]): void { this.strings.push(...line); } public endLine(): void { - this.strings.push("\n"); + this.strings.push('\n'); } public printLine(...line: string[]): void { @@ -30,7 +30,6 @@ class StringBuilder { this.endLine(); } - public increaseIndent(levels = 1): void { this.indentation += INDENTATION_SPACES * levels; } @@ -45,7 +44,7 @@ class StringBuilder { } public toString(): string { - return this.strings.join(""); + return this.strings.join(''); } } @@ -62,12 +61,12 @@ async function main(): Promise { const stringBuilder = new StringBuilder(); // Write preamble and open module declaration - stringBuilder.printLine('declare module "zigbee-clusters" {') + stringBuilder.printLine('declare module "zigbee-clusters" {'); stringBuilder.increaseIndent(); - stringBuilder.printLine('import {Bitmap} from "@athombv/data-types";') + stringBuilder.printLine('import {Bitmap} from "@athombv/data-types";'); // @ts-expect-error No type declarations - const clustersModule = await import("../lib/clusters/index.js"); + const clustersModule = await import('../lib/clusters/index.js'); for (const [name, value] of Object.entries(clustersModule.default)) { if (name !== 'Cluster' && name !== 'CLUSTER') { await formatCluster(stringBuilder, name, value as typeof Cluster); @@ -75,7 +74,7 @@ async function main(): Promise { } // Open CLUSTER - stringBuilder.printLine("const CLUSTER: {"); + stringBuilder.printLine('const CLUSTER: {'); stringBuilder.increaseIndent(); // Collect (NAME, ClassName) pairs to emit a typed lookup map after the @@ -84,7 +83,7 @@ async function main(): Promise { const clusterClassByName: Array<{ name: string; className: string }> = []; for (const [key, value] of Object.entries(clustersModule.default.CLUSTER)) { - const definition = value as {ID: number, NAME: string}; + const definition = value as { ID: number; NAME: string }; // `lib/clusters/index.js` no longer re-exports `Cluster` (PR #182), so // reach the static `Cluster.clusters` registry via the top-level entry. const clusterClass = topModule.Cluster.clusters[definition.ID]; @@ -93,13 +92,13 @@ async function main(): Promise { if (clusterName === 'TouchlinkCluster') { clusterName = 'TouchLinkCluster'; } - if (!clusterName.endsWith("Cluster")) { + if (!clusterName.endsWith('Cluster')) { clusterName = `${clusterName}Cluster`; } clusterClassByName.push({ name: definition.NAME, className: clusterName }); stringBuilder.printLine(`${key}: {`); stringBuilder.increaseIndent(); - stringBuilder.printLine(`ID: 0x${definition.ID.toString(16).padStart(4, "0")},`); + stringBuilder.printLine(`ID: 0x${definition.ID.toString(16).padStart(4, '0')},`); stringBuilder.printLine(`NAME: ${JSON.stringify(definition.NAME)},`); stringBuilder.printLine(`ATTRIBUTES: Readonly<${clusterName}Attributes>,`); stringBuilder.printLine(`COMMANDS: Readonly<${clusterName}Commands>,`); @@ -109,20 +108,20 @@ async function main(): Promise { // Close CLUSTER stringBuilder.decreaseIndent(); - stringBuilder.printLine("};"); + stringBuilder.printLine('};'); // Emit a name->class lookup map used by `ZCLNodeEndpoint.clusters` for // typed access to known cluster classes. Keys are optional because an // endpoint may not implement every cluster; an unknown cluster name // (e.g. manufacturer-specific) falls through the intersection with // `Record>` in the template. - stringBuilder.printLine("type ClustersByName = {"); + stringBuilder.printLine('type ClustersByName = {'); stringBuilder.increaseIndent(); for (const { name, className } of clusterClassByName) { stringBuilder.printLine(`${JSON.stringify(name)}?: ${className};`); } stringBuilder.decreaseIndent(); - stringBuilder.printLine("};"); + stringBuilder.printLine('};'); const template = await fs.readFile(templatePath, 'utf8'); const templateHeader = "declare module 'zigbee-clusters' {"; @@ -133,19 +132,22 @@ async function main(): Promise { // Write the result to index.d.ts await fs.writeFile(filePath, stringBuilder.toString()); - console.log("Done!"); + console.log('Done!'); } - -async function formatCluster(stringBuilder: StringBuilder, className: string, classDefinition: typeof Cluster): Promise { +async function formatCluster( + stringBuilder: StringBuilder, + className: string, + classDefinition: typeof Cluster, +): Promise { stringBuilder.startLine(); stringBuilder.print(`type ${className}Attributes = `); if (Object.keys(classDefinition.ATTRIBUTES).length === 0) { - stringBuilder.print("Record;"); + stringBuilder.print('Record;'); stringBuilder.endLine(); } else { // Open attributes - stringBuilder.print("{"); + stringBuilder.print('{'); stringBuilder.endLine(); stringBuilder.increaseIndent(); for (const attribute in classDefinition.ATTRIBUTES) { @@ -160,11 +162,11 @@ async function formatCluster(stringBuilder: StringBuilder, className: string, cl stringBuilder.startLine(); stringBuilder.print(`type ${className}Commands = `); if (Object.keys(classDefinition.COMMANDS).length === 0) { - stringBuilder.print("Record;"); + stringBuilder.print('Record;'); stringBuilder.endLine(); } else { // Open commands - stringBuilder.print("{"); + stringBuilder.print('{'); stringBuilder.endLine(); stringBuilder.increaseIndent(); for (const command in classDefinition.COMMANDS) { @@ -177,7 +179,9 @@ async function formatCluster(stringBuilder: StringBuilder, className: string, cl } // Open class - stringBuilder.printLine(`class ${className} extends Cluster {`); + stringBuilder.printLine( + `class ${className} extends Cluster {`, + ); stringBuilder.increaseIndent(); for (const command in classDefinition.COMMANDS) { @@ -190,46 +194,58 @@ async function formatCluster(stringBuilder: StringBuilder, className: string, cl stringBuilder.printLine('}'); } - -function formatAttribute(stringBuilder: StringBuilder, className: string, name: string, definition: types.AttributeDefinition): void { +function formatAttribute( + stringBuilder: StringBuilder, + className: string, + name: string, + definition: types.AttributeDefinition, +): void { stringBuilder.startLine(); - stringBuilder.print(`${name}: { id: 0x${definition.id.toString(16).padStart(2, "0")}`); + stringBuilder.print(`${name}: { id: 0x${definition.id.toString(16).padStart(2, '0')}`); stringBuilder.print(', type: ZCLDataType<'); formatZCLDataTypeGeneric(stringBuilder, className, name, definition.type); - stringBuilder.print("> },"); + stringBuilder.print('> },'); stringBuilder.endLine(); } - -function formatCommand(stringBuilder: StringBuilder, className: string, name: string, definition: types.CommandDefinition): void { +function formatCommand( + stringBuilder: StringBuilder, + className: string, + name: string, + definition: types.CommandDefinition, +): void { stringBuilder.startLine(); - stringBuilder.print(`${name}: { id: 0x${definition.id.toString(16).padStart(2, "0")}`); - stringBuilder.print(`, direction: ${JSON.stringify(definition.direction ?? "DIRECTION_SERVER_TO_CLIENT")}`); + stringBuilder.print(`${name}: { id: 0x${definition.id.toString(16).padStart(2, '0')}`); + stringBuilder.print( + `, direction: ${JSON.stringify(definition.direction ?? 'DIRECTION_SERVER_TO_CLIENT')}`, + ); if (definition.frameControl !== undefined) { stringBuilder.print(`, frameControl: ${JSON.stringify(definition.frameControl)}`); } if (definition.encodeMissingFieldsBehavior !== undefined) { - stringBuilder.print(`, encodeMissingFieldsBehavior: ${JSON.stringify(definition.encodeMissingFieldsBehavior)}`); + stringBuilder.print( + `, encodeMissingFieldsBehavior: ${JSON.stringify(definition.encodeMissingFieldsBehavior)}`, + ); } if (definition.global !== undefined) { stringBuilder.print(`, global: ${definition.global}`); } if (definition.args !== undefined) { - stringBuilder.print(", args: "); + stringBuilder.print(', args: '); if (Object.keys(definition.args).length === 0) { - throw new Error(`Empty args definition for ${className} command ${name}`) + throw new Error(`Empty args definition for ${className} command ${name}`); } else { // Open args - stringBuilder.print("{"); + stringBuilder.print('{'); stringBuilder.endLine(); stringBuilder.increaseIndent(2); for (const [key, arg] of Object.entries(definition.args)) { stringBuilder.startLine(); stringBuilder.print(`${key}: ZCLDataType<`); formatZCLDataTypeGeneric(stringBuilder, className, name, arg); - stringBuilder.print(">,"); + stringBuilder.print('>,'); stringBuilder.endLine(); } // Close args @@ -241,15 +257,19 @@ function formatCommand(stringBuilder: StringBuilder, className: string, name: st } } else { // Space before closing bracket of attribute - stringBuilder.print(" "); + stringBuilder.print(' '); } stringBuilder.print('},'); stringBuilder.endLine(); } - -function formatCommandMethod(stringBuilder: StringBuilder, className: string, name: string, definition: types.CommandDefinition): void { +function formatCommandMethod( + stringBuilder: StringBuilder, + className: string, + name: string, + definition: types.CommandDefinition, +): void { // The runtime `Cluster.addCluster` installs a sendable method for every // command regardless of direction (see lib/Cluster.js around the // `Object.defineProperty(proto, cmdName, ...)` loop). Emit the sendable @@ -264,12 +284,17 @@ function formatCommandMethod(stringBuilder: StringBuilder, className: string, na } } -function emitSendableCommandMethod(stringBuilder: StringBuilder, className: string, name: string, definition: types.CommandDefinition): void { +function emitSendableCommandMethod( + stringBuilder: StringBuilder, + className: string, + name: string, + definition: types.CommandDefinition, +): void { stringBuilder.startLine(); stringBuilder.print(name); // Open method - stringBuilder.print("("); + stringBuilder.print('('); stringBuilder.endLine(); stringBuilder.increaseIndent(); @@ -292,7 +317,6 @@ function emitSendableCommandMethod(stringBuilder: StringBuilder, className: stri stringBuilder.decreaseIndent(); stringBuilder.printLine('},'); - // Opts stringBuilder.printLine('opts?: {'); stringBuilder.increaseIndent(); @@ -305,17 +329,22 @@ function emitSendableCommandMethod(stringBuilder: StringBuilder, className: stri closeCommandMethod(stringBuilder, className, name, definition); } -function emitCommandHandlerMethod(stringBuilder: StringBuilder, className: string, name: string, definition: types.CommandDefinition): void { +function emitCommandHandlerMethod( + stringBuilder: StringBuilder, + className: string, + name: string, + definition: types.CommandDefinition, +): void { stringBuilder.startLine(); - stringBuilder.print("on", name.charAt(0).toUpperCase(), name.slice(1)); + stringBuilder.print('on', name.charAt(0).toUpperCase(), name.slice(1)); // Open method - stringBuilder.print("("); + stringBuilder.print('('); stringBuilder.endLine(); stringBuilder.increaseIndent(); if (definition.args === undefined) { - stringBuilder.printLine('args: undefined,') + stringBuilder.printLine('args: undefined,'); } else { // Open args stringBuilder.printLine('args: {'); @@ -334,49 +363,58 @@ function emitCommandHandlerMethod(stringBuilder: StringBuilder, className: strin stringBuilder.printLine('},'); } - stringBuilder.printLine("meta: object,") - stringBuilder.printLine("frame: object,") - stringBuilder.printLine("rawFrame: Buffer,") + stringBuilder.printLine('meta: object,'); + stringBuilder.printLine('frame: object,'); + stringBuilder.printLine('rawFrame: Buffer,'); closeCommandMethod(stringBuilder, className, name, definition); } -function closeCommandMethod(stringBuilder: StringBuilder, className: string, name: string, definition: types.CommandDefinition): void { +function closeCommandMethod( + stringBuilder: StringBuilder, + className: string, + name: string, + definition: types.CommandDefinition, +): void { // Close method stringBuilder.decreaseIndent(); stringBuilder.startLine(); - stringBuilder.print("): Promise<"); + stringBuilder.print('): Promise<'); if (definition.response === undefined) { - stringBuilder.print("void"); + stringBuilder.print('void'); } else { // Open return object - stringBuilder.print("{") - stringBuilder.endLine() - stringBuilder.increaseIndent() + stringBuilder.print('{'); + stringBuilder.endLine(); + stringBuilder.increaseIndent(); for (let argName in definition.response.args) { const argType = definition.response.args[argName]; stringBuilder.startLine(); stringBuilder.print(argName); if (definition.response.encodeMissingFieldsBehavior === 'skip') { - stringBuilder.print("?") + stringBuilder.print('?'); } - stringBuilder.print(": ") + stringBuilder.print(': '); formatZCLDataTypeGeneric(stringBuilder, className, name, argType); - stringBuilder.print(",") + stringBuilder.print(','); stringBuilder.endLine(); } // Close return object stringBuilder.decreaseIndent(); stringBuilder.startLine(); - stringBuilder.print("}") + stringBuilder.print('}'); } - stringBuilder.print(">;") + stringBuilder.print('>;'); stringBuilder.endLine(); } - -function formatZCLDataTypeGeneric(stringBuilder: StringBuilder, className: string, command: string, definition: DataType): void { +function formatZCLDataTypeGeneric( + stringBuilder: StringBuilder, + className: string, + command: string, + definition: DataType, +): void { const typeName = definition.shortName as keyof typeof DataTypes; const typeArgs: Array = definition.args; @@ -385,21 +423,26 @@ function formatZCLDataTypeGeneric(stringBuilder: StringBuilder, className: strin stringBuilder.print('unknown'); } else if (typeName.startsWith('enum')) { if (definition === enum8Status) { - stringBuilder.print('types.ZCLEnum8Status') + stringBuilder.print('types.ZCLEnum8Status'); } else { formatEnumDataType(stringBuilder, className, command, typeName, typeArgs); } } else if (typeName.startsWith('map')) { formatMapDataType(stringBuilder, className, command, typeName, typeArgs); - } else if (typeName.startsWith("_Array")) { + } else if (typeName.startsWith('_Array')) { formatArrayDataType(stringBuilder, className, command, typeName, typeArgs); } else { stringBuilder.print(zclDataTypeToValueType(typeName)); } } - -function formatEnumDataType(stringBuilder: StringBuilder, className: string, field: string, typeName: string, typeArgs: Array): void { +function formatEnumDataType( + stringBuilder: StringBuilder, + className: string, + field: string, + typeName: string, + typeArgs: Array, +): void { const typeArg = typeArgs[0]; if (typeof typeArg !== 'object' || Array.isArray(typeArg) || typeArg === null) { throw new Error(`Invalid ${typeName} arg for ${className} ${field}`); @@ -409,12 +452,17 @@ function formatEnumDataType(stringBuilder: StringBuilder, className: string, fie if (entries.length === 0) { stringBuilder.print('unknown'); } else { - stringBuilder.print(entries.map(arg => JSON.stringify(arg)).join(" | ")); + stringBuilder.print(entries.map((arg) => JSON.stringify(arg)).join(' | ')); } } - -function formatMapDataType(stringBuilder: StringBuilder, className: string, field: string, typeName: string, typeArgs: Array): void { +function formatMapDataType( + stringBuilder: StringBuilder, + className: string, + field: string, + typeName: string, + typeArgs: Array, +): void { if (typeof typeArgs !== 'object' || !Array.isArray(typeArgs)) { throw new Error(`Invalid ${typeName} arg for ${className} ${field}`); } @@ -427,87 +475,92 @@ function formatMapDataType(stringBuilder: StringBuilder, className: string, fiel stringBuilder.print('Bitmap'); } else { stringBuilder.print('Bitmap<'); - stringBuilder.print(typeArgs.map(arg => JSON.stringify(arg)).join(" | ")); - stringBuilder.print(">"); + stringBuilder.print(typeArgs.map((arg) => JSON.stringify(arg)).join(' | ')); + stringBuilder.print('>'); } } -function formatArrayDataType(stringBuilder: StringBuilder, className: string, field: string, typeName: string, typeArgs: Array>): void { - stringBuilder.print('Array<') - formatZCLDataTypeGeneric(stringBuilder, className, field, typeArgs[0]) - stringBuilder.print('>') +function formatArrayDataType( + stringBuilder: StringBuilder, + className: string, + field: string, + typeName: string, + typeArgs: Array>, +): void { + stringBuilder.print('Array<'); + formatZCLDataTypeGeneric(stringBuilder, className, field, typeArgs[0]); + stringBuilder.print('>'); } - function zclDataTypeToValueType(type: keyof typeof DataTypes): string { switch (type) { - case "noData": - return "null"; - case "data8": - case "data16": - case "data24": - case "data32": - case "data40": - case "data48": - case "data56": - return "Buffer"; - case "bool": - return "boolean"; - case "map8": - case "map16": - case "map24": - case "map32": - case "map40": - case "map48": - case "map56": - case "map64": - throw new Error("Complex type"); - case "uint8": - case "uint16": - case "uint24": - case "uint32": - case "uint40": - case "uint48": - case "int8": - case "int16": - case "int24": - case "int32": - case "int40": - case "int48": - case "UTC": - return "number"; - case "enum8": - case "enum16": - case "enum32": - throw new Error("Complex type"); - case "single": - case "double": - return "number"; - case "octstr": - return "Buffer" - case "string": - case "EUI48": - case "EUI64": - case "key128": - return "string"; + case 'noData': + return 'null'; + case 'data8': + case 'data16': + case 'data24': + case 'data32': + case 'data40': + case 'data48': + case 'data56': + return 'Buffer'; + case 'bool': + return 'boolean'; + case 'map8': + case 'map16': + case 'map24': + case 'map32': + case 'map40': + case 'map48': + case 'map56': + case 'map64': + throw new Error('Complex type'); + case 'uint8': + case 'uint16': + case 'uint24': + case 'uint32': + case 'uint40': + case 'uint48': + case 'int8': + case 'int16': + case 'int24': + case 'int32': + case 'int40': + case 'int48': + case 'UTC': + return 'number'; + case 'enum8': + case 'enum16': + case 'enum32': + throw new Error('Complex type'); + case 'single': + case 'double': + return 'number'; + case 'octstr': + return 'Buffer'; + case 'string': + case 'EUI48': + case 'EUI64': + case 'key128': + return 'string'; } // Internal types switch (type as string) { - case "_map4": - throw new Error("Complex type"); - case "_uint4": - return "number"; - case "_enum4": - throw new Error("Complex type"); - case "_buffer": - case "_buffer8": - case "_buffer16": - return "Buffer"; - case "_Array0": - case "_Array8": - throw new Error("Complex type"); - case "_FixedString": - return "string"; + case '_map4': + throw new Error('Complex type'); + case '_uint4': + return 'number'; + case '_enum4': + throw new Error('Complex type'); + case '_buffer': + case '_buffer8': + case '_buffer16': + return 'Buffer'; + case '_Array0': + case '_Array8': + throw new Error('Complex type'); + case '_FixedString': + return 'string'; } throw new Error(`Unknown ZCL type ${type}`); } diff --git a/test/attributeReport.js b/test/attributeReport.js index 5a43456d..a595a89f 100644 --- a/test/attributeReport.js +++ b/test/attributeReport.js @@ -5,13 +5,15 @@ const OnOffCluster = require('../lib/clusters/onOff'); const { createMockNode } = require('./util'); const { ZCLStandardHeader } = require('../lib/zclFrames'); -describe('Attribute report', function() { - it('should emit the transaction sequence number with the attribute value', function(done) { +describe('Attribute report', function () { + it('should emit the transaction sequence number with the attribute value', function (done) { const node = createMockNode({ - endpoints: [{ - endpointId: 1, - inputClusters: [OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [OnOffCluster.ID], + }, + ], }); node.endpoints[1].clusters.onOff.on('attr.onOff', (value, meta) => { @@ -22,7 +24,7 @@ describe('Attribute report', function() { }); const frame = new ZCLStandardHeader(); - frame.cmdId = 0x0A; // reportAttributes + frame.cmdId = 0x0a; // reportAttributes frame.trxSequenceNumber = 42; frame.frameControl.directionToClient = true; // attributeId 0x0000 (onOff), dataType 0x10 (bool), value true diff --git a/test/boundClusterAttributeStatus.js b/test/boundClusterAttributeStatus.js index b576c824..338f7ce2 100644 --- a/test/boundClusterAttributeStatus.js +++ b/test/boundClusterAttributeStatus.js @@ -21,10 +21,10 @@ function readStatuses(response) { return BasicCluster.attributeArrayStatusDataType.fromBuffer(response.attributes, 0); } -describe('BoundCluster attribute status codes', function() { - describe('readAttributes', function() { - it('reports UNSUPPORTED_ATTRIBUTE for an id the cluster does not define', async function() { - const bound = bind(new class extends BoundCluster {}()); +describe('BoundCluster attribute status codes', function () { + describe('readAttributes', function () { + it('reports UNSUPPORTED_ATTRIBUTE for an id the cluster does not define', async function () { + const bound = bind(new (class extends BoundCluster {})()); const records = readStatuses(await bound.readAttributes({ attributes: [UNREGISTERED_ID] })); assert.equal(records.length, 1); @@ -34,49 +34,49 @@ describe('BoundCluster attribute status codes', function() { assert.equal(records[0].status, 'UNSUPPORTED_ATTRIBUTE'); }); - it('reports UNSUPPORTED_ATTRIBUTE for a defined but unimplemented attribute', async function() { - const bound = bind(new class extends BoundCluster {}()); + it('reports UNSUPPORTED_ATTRIBUTE for a defined but unimplemented attribute', async function () { + const bound = bind(new (class extends BoundCluster {})()); const records = readStatuses(await bound.readAttributes({ attributes: [MODEL_ID] })); assert.equal(records[0].status, 'UNSUPPORTED_ATTRIBUTE'); }); - it('still reports FAILURE when a getter throws', async function() { - const bound = bind(new class extends BoundCluster { - - get modelId() { - throw new Error('sensor offline'); - } - - }()); + it('still reports FAILURE when a getter throws', async function () { + const bound = bind( + new (class extends BoundCluster { + get modelId() { + throw new Error('sensor offline'); + } + })(), + ); const records = readStatuses(await bound.readAttributes({ attributes: [MODEL_ID] })); // The attribute exists, so this is a genuine failure and must not be masked as unsupported. assert.equal(records[0].status, 'FAILURE'); }); - it('reports FAILURE when the value cannot be serialized', async function() { - const bound = bind(new class extends BoundCluster { - - get modelId() { - // A string too long for the one-octet length prefix of a ZCL character string. - return 'x'.repeat(300); - } - - }()); + it('reports FAILURE when the value cannot be serialized', async function () { + const bound = bind( + new (class extends BoundCluster { + get modelId() { + // A string too long for the one-octet length prefix of a ZCL character string. + return 'x'.repeat(300); + } + })(), + ); const records = readStatuses(await bound.readAttributes({ attributes: [MODEL_ID] })); assert.equal(records[0].status, 'FAILURE'); }); - it('reports UNSUPPORTED_ATTRIBUTE for a write-only attribute, not 0x8f', async function() { - const bound = bind(new class extends BoundCluster { - - set modelId(value) { - this._modelId = value; - } - - }()); + it('reports UNSUPPORTED_ATTRIBUTE for a write-only attribute, not 0x8f', async function () { + const bound = bind( + new (class extends BoundCluster { + set modelId(value) { + this._modelId = value; + } + })(), + ); const records = readStatuses(await bound.readAttributes({ attributes: [MODEL_ID] })); // ZCL R8 deprecates WRITE_ONLY and reassigns 0x8f to NOT_AUTHORIZED ("a request has been @@ -86,14 +86,14 @@ describe('BoundCluster attribute status codes', function() { assert.equal(records[0].status, 'UNSUPPORTED_ATTRIBUTE'); }); - it('honours a ZCLError thrown by a getter instead of flattening it to FAILURE', async function() { - const bound = bind(new class extends BoundCluster { - - get modelId() { - throw new ZCLError('NOT_AUTHORIZED'); - } - - }()); + it('honours a ZCLError thrown by a getter instead of flattening it to FAILURE', async function () { + const bound = bind( + new (class extends BoundCluster { + get modelId() { + throw new ZCLError('NOT_AUTHORIZED'); + } + })(), + ); const records = readStatuses(await bound.readAttributes({ attributes: [MODEL_ID] })); // The per-attribute status is data on the error, so an implementation can pick its own rather @@ -101,28 +101,28 @@ describe('BoundCluster attribute status codes', function() { assert.equal(records[0].status, 'NOT_AUTHORIZED'); }); - it('still reports SUCCESS with a value for an implemented attribute', async function() { - const bound = bind(new class extends BoundCluster { - - get modelId() { - return 'Homey'; - } - - }()); + it('still reports SUCCESS with a value for an implemented attribute', async function () { + const bound = bind( + new (class extends BoundCluster { + get modelId() { + return 'Homey'; + } + })(), + ); const records = readStatuses(await bound.readAttributes({ attributes: [MODEL_ID] })); assert.equal(records[0].status, 'SUCCESS'); assert.equal(records[0].value, 'Homey'); }); - it('reports a per-attribute status, not one status for the whole request', async function() { - const bound = bind(new class extends BoundCluster { - - get modelId() { - return 'Homey'; - } - - }()); + it('reports a per-attribute status, not one status for the whole request', async function () { + const bound = bind( + new (class extends BoundCluster { + get modelId() { + return 'Homey'; + } + })(), + ); const records = readStatuses( await bound.readAttributes({ attributes: [MODEL_ID, UNREGISTERED_ID] }), ); @@ -133,7 +133,7 @@ describe('BoundCluster attribute status codes', function() { }); }); - describe('writeAttributes', function() { + describe('writeAttributes', function () { /** Build the wire payload writeAttributes expects. */ function writeBuffer(records) { const buf = Buffer.alloc(255); @@ -141,8 +141,8 @@ describe('BoundCluster attribute status codes', function() { return buf.slice(0, len); } - it('reports UNSUPPORTED_ATTRIBUTE for an attribute this device does not implement', async function() { - const bound = bind(new class extends BoundCluster {}()); + it('reports UNSUPPORTED_ATTRIBUTE for an attribute this device does not implement', async function () { + const bound = bind(new (class extends BoundCluster {})()); const { attributes } = await bound.writeAttributes({ attributes: writeBuffer([{ id: MODEL_ID, value: 'Homey' }]), }); @@ -150,14 +150,14 @@ describe('BoundCluster attribute status codes', function() { assert.equal(attributes[0].status, 'UNSUPPORTED_ATTRIBUTE'); }); - it('reports READ_ONLY for an attribute exposed with a getter but no setter', async function() { - const bound = bind(new class extends BoundCluster { - - get modelId() { - return 'Homey'; - } - - }()); + it('reports READ_ONLY for an attribute exposed with a getter but no setter', async function () { + const bound = bind( + new (class extends BoundCluster { + get modelId() { + return 'Homey'; + } + })(), + ); const { attributes } = await bound.writeAttributes({ attributes: writeBuffer([{ id: MODEL_ID, value: 'Other' }]), }); @@ -166,18 +166,18 @@ describe('BoundCluster attribute status codes', function() { assert.equal(attributes[0].status, 'READ_ONLY'); }); - it('honours a ZCLError thrown by a setter', async function() { - const bound = bind(new class extends BoundCluster { - - get modelId() { - return this._modelId; - } - - set modelId(value) { - throw new ZCLError('INVALID_VALUE'); - } - - }()); + it('honours a ZCLError thrown by a setter', async function () { + const bound = bind( + new (class extends BoundCluster { + get modelId() { + return this._modelId; + } + + set modelId(value) { + throw new ZCLError('INVALID_VALUE'); + } + })(), + ); const { attributes } = await bound.writeAttributes({ attributes: writeBuffer([{ id: MODEL_ID, value: 'Other' }]), }); @@ -185,18 +185,18 @@ describe('BoundCluster attribute status codes', function() { assert.equal(attributes[0].status, 'INVALID_VALUE'); }); - it('still reports SUCCESS for a writable attribute', async function() { - const bound = bind(new class extends BoundCluster { - - get modelId() { - return this._modelId; - } - - set modelId(value) { - this._modelId = value; - } - - }()); + it('still reports SUCCESS for a writable attribute', async function () { + const bound = bind( + new (class extends BoundCluster { + get modelId() { + return this._modelId; + } + + set modelId(value) { + this._modelId = value; + } + })(), + ); const { attributes } = await bound.writeAttributes({ attributes: writeBuffer([{ id: MODEL_ID, value: 'Other' }]), }); diff --git a/test/colorControl.js b/test/colorControl.js index 5aef9d33..967b46d1 100644 --- a/test/colorControl.js +++ b/test/colorControl.js @@ -15,19 +15,22 @@ const sandbox = sinon.createSandbox(); async function sendColorLoopSet(args) { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [ColorControlCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [ColorControlCluster.ID], + }, + ], }); - node.endpoints[1].bind('colorControl', new (class extends BoundCluster { - - async colorLoopSet() { - // Accept the command so no error response is generated - } - - })()); + node.endpoints[1].bind( + 'colorControl', + new (class extends BoundCluster { + async colorLoopSet() { + // Accept the command so no error response is generated + } + })(), + ); const sendFrameSpy = sandbox.spy(node, 'sendFrame'); await node.endpoints[1].clusters.colorControl.colorLoopSet(args); @@ -35,28 +38,31 @@ async function sendColorLoopSet(args) { return Endpoint.parseFrame(sendFrameSpy.getCall(0).args[2]); } -describe('Color Control', function() { - afterEach(function() { +describe('Color Control', function () { + afterEach(function () { sandbox.restore(); }); - it('should receive moveToColor', async function() { + it('should receive moveToColor', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [ColorControlCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [ColorControlCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('colorControl', new (class extends BoundCluster { - - async moveToColor(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'colorControl', + new (class extends BoundCluster { + async moveToColor(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.colorControl.moveToColor({ colorX: 0x5000, @@ -69,23 +75,26 @@ describe('Color Control', function() { assert.strictEqual(receivedData.transitionTime, 20); }); - it('should receive moveToColorTemperature', async function() { + it('should receive moveToColorTemperature', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [ColorControlCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [ColorControlCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('colorControl', new (class extends BoundCluster { - - async moveToColorTemperature(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'colorControl', + new (class extends BoundCluster { + async moveToColorTemperature(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.colorControl.moveToColorTemperature({ colorTemperature: 370, @@ -96,23 +105,26 @@ describe('Color Control', function() { assert.strictEqual(receivedData.transitionTime, 15); }); - it('should receive moveToHueAndSaturation', async function() { + it('should receive moveToHueAndSaturation', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [ColorControlCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [ColorControlCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('colorControl', new (class extends BoundCluster { - - async moveToHueAndSaturation(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'colorControl', + new (class extends BoundCluster { + async moveToHueAndSaturation(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.colorControl.moveToHueAndSaturation({ hue: 180, @@ -125,23 +137,26 @@ describe('Color Control', function() { assert.strictEqual(receivedData.transitionTime, 10); }); - it('should receive colorLoopSet', async function() { + it('should receive colorLoopSet', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [ColorControlCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [ColorControlCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('colorControl', new (class extends BoundCluster { - - async colorLoopSet(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'colorControl', + new (class extends BoundCluster { + async colorLoopSet(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.colorControl.colorLoopSet({ updateFlags: ['updateAction', 'updateDirection', 'updateTime', 'updateStartHue'], @@ -152,7 +167,10 @@ describe('Color Control', function() { }); assert.deepStrictEqual(receivedData.updateFlags.getBits(), [ - 'updateAction', 'updateDirection', 'updateTime', 'updateStartHue', + 'updateAction', + 'updateDirection', + 'updateTime', + 'updateStartHue', ]); assert.strictEqual(receivedData.action, 'activateFromColorLoopStartEnhancedHue'); assert.strictEqual(receivedData.direction, 'incrementHue'); @@ -164,7 +182,7 @@ describe('Color Control', function() { // that definition is wrong on the wire. These bytes come from the spec, not from our types: // ZCL r8 Figure 5-17 (payload layout), Figure 5-18 (updateFlags bits), Table 5.25 (action // values), Table 5.26 (direction values), Table 5.13 (command id). - it('should encode colorLoopSet as specified by the ZCL', async function() { + it('should encode colorLoopSet as specified by the ZCL', async function () { const frame = await sendColorLoopSet({ updateFlags: ['updateAction', 'updateStartHue'], // Bits 0 and 3, so 0x09 action: 'activateFromEnhancedCurrentHue', // 0x02 @@ -178,7 +196,7 @@ describe('Color Control', function() { }); // Setting every flag at once cannot tell the bit positions apart, so pin each one separately - it('should encode each colorLoopSet update flag on its own bit', async function() { + it('should encode each colorLoopSet update flag on its own bit', async function () { const flagBits = [ ['updateAction', 0x01], ['updateDirection', 0x02], diff --git a/test/defaultResponse.js b/test/defaultResponse.js index 43282a05..91cf82da 100644 --- a/test/defaultResponse.js +++ b/test/defaultResponse.js @@ -7,7 +7,7 @@ const DoorLockCluster = require('../lib/clusters/doorLock'); // Door Lock setWeekDaySchedule is cluster-specific command 0x0B, same id as global Default Response const SET_WEEK_DAY_SCHEDULE_REQUEST = Buffer.from([ - 0x01, 0x05, 0x0B, 0x01, 0x02, 0x00, 0x02, 0x08, 0x00, 0x17, 0x00, + 0x01, 0x05, 0x0b, 0x01, 0x02, 0x00, 0x02, 0x08, 0x00, 0x17, 0x00, ]); function createEndpoint() { @@ -23,23 +23,24 @@ function createEndpoint() { return { endpoint, sentFrames }; } -describe('Default Response', function() { - it('sends the cluster-specific response to a cluster-specific command 0x0B', async function() { +describe('Default Response', function () { + it('sends the cluster-specific response to a cluster-specific command 0x0B', async function () { const { endpoint, sentFrames } = createEndpoint(); - endpoint.bind('doorLock', new (class extends BoundCluster { - - async setWeekDaySchedule() { - return { status: 0 }; - } - - })()); + endpoint.bind( + 'doorLock', + new (class extends BoundCluster { + async setWeekDaySchedule() { + return { status: 0 }; + } + })(), + ); await endpoint.handleFrame(DoorLockCluster.ID, SET_WEEK_DAY_SCHEDULE_REQUEST); assert.deepEqual(sentFrames, ['19050b00']); }); - it('sends an error Default Response to an unhandled cluster-specific command 0x0B', async function() { + it('sends an error Default Response to an unhandled cluster-specific command 0x0B', async function () { const { endpoint, sentFrames } = createEndpoint(); endpoint.bind('doorLock', new BoundCluster()); @@ -53,10 +54,10 @@ describe('Default Response', function() { assert.match(sentFrames[0], /^18050b0b(?!00)[0-9a-f]{2}$/); }); - it('does not respond to a global Default Response', async function() { + it('does not respond to a global Default Response', async function () { const { endpoint, sentFrames } = createEndpoint(); - await endpoint.handleFrame(DoorLockCluster.ID, Buffer.from([0x08, 0x05, 0x0B, 0x00, 0x00])); + await endpoint.handleFrame(DoorLockCluster.ID, Buffer.from([0x08, 0x05, 0x0b, 0x00, 0x00])); await new Promise(setImmediate); assert.deepEqual(sentFrames, []); diff --git a/test/doorLock.js b/test/doorLock.js index 4c922c08..a3c5b65b 100644 --- a/test/doorLock.js +++ b/test/doorLock.js @@ -6,24 +6,27 @@ const DoorLockCluster = require('../lib/clusters/doorLock'); const { createMockNode } = require('./util'); const { ZCLStandardHeader } = require('../lib/zclFrames'); -describe('Door Lock', function() { - it('should receive lockDoor', async function() { +describe('Door Lock', function () { + it('should receive lockDoor', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [DoorLockCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [DoorLockCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('doorLock', new (class extends BoundCluster { - - async lockDoor(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'doorLock', + new (class extends BoundCluster { + async lockDoor(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.doorLock.lockDoor({ pinCode: Buffer.from([0x31, 0x32, 0x33, 0x34]), @@ -32,48 +35,54 @@ describe('Door Lock', function() { assert.deepStrictEqual(receivedData.pinCode, Buffer.from([0x31, 0x32, 0x33, 0x34])); }); - it('should receive lockDoor without arguments', async function() { + it('should receive lockDoor without arguments', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [DoorLockCluster.ID], - }], + 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([])); - } - - })()); + 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() { + it('should receive unlockDoor', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [DoorLockCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [DoorLockCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('doorLock', new (class extends BoundCluster { - - async unlockDoor(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'doorLock', + new (class extends BoundCluster { + async unlockDoor(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.doorLock.unlockDoor({ pinCode: Buffer.from([0x30, 0x30, 0x30, 0x30]), @@ -82,23 +91,26 @@ describe('Door Lock', function() { assert.deepStrictEqual(receivedData.pinCode, Buffer.from([0x30, 0x30, 0x30, 0x30])); }); - it('should receive setPINCode', async function() { + it('should receive setPINCode', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [DoorLockCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [DoorLockCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('doorLock', new (class extends BoundCluster { - - async setPINCode(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'doorLock', + new (class extends BoundCluster { + async setPINCode(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.doorLock.setPINCode({ userId: 1, @@ -115,16 +127,18 @@ describe('Door Lock', function() { // Server-to-client notifications require manual frame construction // since they are sent BY the device TO the controller - it('should receive operationEventNotification', function(done) { + it('should receive operationEventNotification', function (done) { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [DoorLockCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [DoorLockCluster.ID], + }, + ], }); - node.endpoints[1].clusters.doorLock.onOperationEventNotification = data => { + node.endpoints[1].clusters.doorLock.onOperationEventNotification = (data) => { assert.strictEqual(data.operationEventSource, 1); // Keypad assert.strictEqual(data.operationEventCode, 2); // Unlock assert.strictEqual(data.userId, 3); @@ -139,32 +153,31 @@ describe('Door Lock', function() { frame.frameControl.directionToClient = true; frame.frameControl.clusterSpecific = true; frame.data = Buffer.from([ - 0x01, 0x02, 0x03, 0x00, - 0x04, 0x31, 0x32, 0x33, 0x34, - 0x78, 0x56, 0x34, 0x12, - 0x00, + 0x01, 0x02, 0x03, 0x00, 0x04, 0x31, 0x32, 0x33, 0x34, 0x78, 0x56, 0x34, 0x12, 0x00, ]); node.handleFrame(1, DoorLockCluster.ID, frame.toBuffer(), {}); }); - it('should receive programmingEventNotification', function(done) { + it('should receive programmingEventNotification', function (done) { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [DoorLockCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [DoorLockCluster.ID], + }, + ], }); - node.endpoints[1].clusters.doorLock.onProgrammingEventNotification = data => { + node.endpoints[1].clusters.doorLock.onProgrammingEventNotification = (data) => { assert.strictEqual(data.programEventSource, 2); // RF assert.strictEqual(data.programEventCode, 3); // PIN added assert.strictEqual(data.userId, 7); assert.deepStrictEqual(data.pin, Buffer.from([0x35, 0x36, 0x37, 0x38])); assert.strictEqual(data.userType, 'unrestricted'); assert.strictEqual(data.userStatus, 'occupiedEnabled'); - assert.strictEqual(data.zigBeeLocalTime, 0xAABBCCDD); + assert.strictEqual(data.zigBeeLocalTime, 0xaabbccdd); assert.deepStrictEqual(data.data, Buffer.from([])); done(); }; @@ -174,10 +187,7 @@ describe('Door Lock', function() { frame.frameControl.directionToClient = true; frame.frameControl.clusterSpecific = true; frame.data = Buffer.from([ - 0x02, 0x03, 0x07, 0x00, - 0x04, 0x35, 0x36, 0x37, 0x38, - 0x00, 0x01, - 0xDD, 0xCC, 0xBB, 0xAA, + 0x02, 0x03, 0x07, 0x00, 0x04, 0x35, 0x36, 0x37, 0x38, 0x00, 0x01, 0xdd, 0xcc, 0xbb, 0xaa, 0x00, ]); diff --git a/test/endpointStandalone.js b/test/endpointStandalone.js index 51bb88f7..0593f3f6 100644 --- a/test/endpointStandalone.js +++ b/test/endpointStandalone.js @@ -8,15 +8,13 @@ const BASIC_CLUSTER_ID = 0x0000; const MODEL_ID = 0x0005; class Basic extends BoundCluster { - get modelId() { return 'Homey Bridge'; } - } -describe('Endpoint standalone', function() { - it('serves bound clusters with nothing but a sendFrame owner', async function() { +describe('Endpoint standalone', function () { + it('serves bound clusters with nothing but a sendFrame owner', async function () { // The whole point of the export: a device answering incoming commands is not a peer, so it // should not have to construct a ZCLNode - which models one remote node and bakes that node's // address into its own sendFrame. Endpoint only needs sendFrame from its owner (nextSeqNr is @@ -38,7 +36,7 @@ describe('Endpoint standalone', function() { assert.equal(sent.toString('hex'), '180701050000420c486f6d657920427269646765'); }); - it('allocates transaction sequence numbers without a node counter', function() { + it('allocates transaction sequence numbers without a node counter', function () { const endpoint = new Endpoint( { sendFrame: async () => {} }, { endpointId: 1, inputClusters: [BASIC_CLUSTER_ID], outputClusters: [] }, diff --git a/test/iasZone.js b/test/iasZone.js index a551210a..514e5d95 100644 --- a/test/iasZone.js +++ b/test/iasZone.js @@ -6,18 +6,20 @@ const IASZoneCluster = require('../lib/clusters/iasZone'); const { createMockNode, MOCK_DEVICES } = require('./util'); const { ZCLStandardHeader } = require('../lib/zclFrames'); -describe('IAS Zone', function() { +describe('IAS Zone', function () { // Server-to-client notification - requires manual frame - it('should receive onZoneEnrollRequest', function(done) { + it('should receive onZoneEnrollRequest', function (done) { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [IASZoneCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [IASZoneCluster.ID], + }, + ], }); - node.endpoints[1].clusters.iasZone.onZoneEnrollRequest = data => { + node.endpoints[1].clusters.iasZone.onZoneEnrollRequest = (data) => { assert.strictEqual(data.zoneType, 'keypad'); assert.strictEqual(data.manufacturerCode, 4117); done(); @@ -33,23 +35,26 @@ describe('IAS Zone', function() { }); // Client-to-server command - can use loopback - it('should send zoneEnrollResponse', async function() { + it('should send zoneEnrollResponse', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [IASZoneCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [IASZoneCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('iasZone', new (class extends BoundCluster { - - async zoneEnrollResponse(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'iasZone', + new (class extends BoundCluster { + async zoneEnrollResponse(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.iasZone.zoneEnrollResponse({ enrollResponseCode: 'success', @@ -61,16 +66,18 @@ describe('IAS Zone', function() { }); // Server-to-client notification - requires manual frame - it('should receive zoneStatusChangeNotification', function(done) { + it('should receive zoneStatusChangeNotification', function (done) { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [IASZoneCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [IASZoneCluster.ID], + }, + ], }); - node.endpoints[1].clusters.iasZone.onZoneStatusChangeNotification = data => { + node.endpoints[1].clusters.iasZone.onZoneStatusChangeNotification = (data) => { assert.strictEqual(data.zoneStatus.supervisionReports, true); assert.strictEqual(data.extendedStatus, 0); assert.strictEqual(data.zoneId, 10); @@ -82,27 +89,27 @@ describe('IAS Zone', function() { frame.cmdId = IASZoneCluster.COMMANDS.zoneStatusChangeNotification.id; frame.frameControl.directionToClient = true; frame.frameControl.clusterSpecific = true; - frame.data = Buffer.from([0x10, 0x00, 0x00, 0x0A, 0x6C, 0x00]); + frame.data = Buffer.from([0x10, 0x00, 0x00, 0x0a, 0x6c, 0x00]); node.handleFrame(1, IASZoneCluster.ID, frame.toBuffer(), {}); }); - describe('Mock Device Factory', function() { - it('should create a motion sensor with correct zone type', function() { + describe('Mock Device Factory', function () { + it('should create a motion sensor with correct zone type', function () { const sensor = MOCK_DEVICES.motionSensor(); const boundCluster = sensor.endpoints[1].bindings.iasZone; - assert.strictEqual(boundCluster.zoneType, 0x000D, 'Should be motion sensor type'); + assert.strictEqual(boundCluster.zoneType, 0x000d, 'Should be motion sensor type'); assert.strictEqual(boundCluster.zoneState, 1, 'Should be enrolled'); }); - it('should create a contact sensor with correct zone type', function() { + it('should create a contact sensor with correct zone type', function () { const sensor = MOCK_DEVICES.contactSensor(); const boundCluster = sensor.endpoints[1].bindings.iasZone; assert.strictEqual(boundCluster.zoneType, 0x0015, 'Should be contact switch type'); assert.strictEqual(boundCluster.zoneState, 1, 'Should be enrolled'); }); - it('should allow attribute overrides', function() { + it('should allow attribute overrides', function () { const sensor = MOCK_DEVICES.motionSensor({ iasZone: { zoneStatus: 0x0001 }, // Alarm1 active }); @@ -110,7 +117,7 @@ describe('IAS Zone', function() { assert.strictEqual(boundCluster.zoneStatus, 0x0001, 'Should have alarm1 bit set'); }); - it('should create temp/humidity sensor with measurement values', function() { + it('should create temp/humidity sensor with measurement values', function () { const sensor = MOCK_DEVICES.tempHumiditySensor(); const tempCluster = sensor.endpoints[1].bindings.temperatureMeasurement; const humCluster = sensor.endpoints[1].bindings.relativeHumidity; diff --git a/test/levelControl.js b/test/levelControl.js index 98daf598..cc557629 100644 --- a/test/levelControl.js +++ b/test/levelControl.js @@ -5,24 +5,27 @@ const BoundCluster = require('../lib/BoundCluster'); const LevelControlCluster = require('../lib/clusters/levelControl'); const { createMockNode } = require('./util'); -describe('Level Control', function() { - it('should receive moveToLevel', async function() { +describe('Level Control', function () { + it('should receive moveToLevel', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [LevelControlCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [LevelControlCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('levelControl', new (class extends BoundCluster { - - async moveToLevel(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'levelControl', + new (class extends BoundCluster { + async moveToLevel(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.levelControl.moveToLevel({ level: 128, @@ -33,23 +36,26 @@ describe('Level Control', function() { assert.strictEqual(receivedData.transitionTime, 10); }); - it('should receive step', async function() { + it('should receive step', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [LevelControlCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [LevelControlCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('levelControl', new (class extends BoundCluster { - - async step(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'levelControl', + new (class extends BoundCluster { + async step(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.levelControl.step({ mode: 'up', @@ -62,23 +68,26 @@ describe('Level Control', function() { assert.strictEqual(receivedData.transitionTime, 5); }); - it('should receive stop', async function() { + it('should receive stop', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [LevelControlCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [LevelControlCluster.ID], + }, + ], }); let called = false; - node.endpoints[1].bind('levelControl', new (class extends BoundCluster { - - async stop() { - called = true; - } - - })()); + node.endpoints[1].bind( + 'levelControl', + new (class extends BoundCluster { + async stop() { + called = true; + } + })(), + ); await node.endpoints[1].clusters.levelControl.stop(); assert.strictEqual(called, true); diff --git a/test/manual/xiaomiHacks.js b/test/manual/xiaomiHacks.js index d08177c5..90ef1a82 100644 --- a/test/manual/xiaomiHacks.js +++ b/test/manual/xiaomiHacks.js @@ -24,59 +24,70 @@ const attributes = { }; // custom data type parser with dynamic length with minimal 3 bytes, and no declared ZCL id. -const XiaomiLifelineDataRecord = new ZCLDataType(NaN, 'XiaomiLifelineDataRecord', -3, function(_buf, _v, _i) { - throw new Error('generating xiaomi structs is not implemented'); -}, function(buf, i, returnLength) { - i = i || 0; - const startByte = i; - const res = {}; - res.id = ZCLDataTypes.uint8.fromBuffer(buf, i); - i += ZCLDataTypes.uint8.length; - - const dataTypeId = ZCLDataTypes.uint8.fromBuffer(buf, i); - i += ZCLDataTypes.uint8.length; - - const DataType = attributes[res.id] && attributes[res.id].type - || Object.values(ZCLDataTypes).find(type => type && type.id === dataTypeId); - if (!DataType) throw new TypeError(`Invalid Type for Attribute: ${res.id}`); - - res.name = attributes[res.id] && attributes[res.id].name || `unknown_attr_${res.id}`; - - const entry = DataType.fromBuffer(buf, i, true); - if (DataType.length > 0) { - i += DataType.length; - res.value = entry; - } else { - res.value = entry.result; - i += entry.length; - } - if (returnLength) { - return { result: res, length: i - startByte }; - } - return res; -}); - -const XiaomiLifelineDataRecordArray = new ZCLDataType(NaN, 'XiaomiLifelineDataRecordArray', -1, function(_buf, _v, _i) { - throw new Error('not_supported'); -}, function(buf, i, returnLength) { - let { result, length } = ZCLDataTypes.buffer8.fromBuffer(buf, i, true); - result = ZCLDataTypes.Array0(XiaomiLifelineDataRecord).fromBuffer(result, 0); - result = result.reduce((r, { name, value }) => Object.assign(r, { [name]: value }), {}); - if (returnLength) { - return { result, length }; - } - return result; -}); +const XiaomiLifelineDataRecord = new ZCLDataType( + NaN, + 'XiaomiLifelineDataRecord', + -3, + function (_buf, _v, _i) { + throw new Error('generating xiaomi structs is not implemented'); + }, + function (buf, i, returnLength) { + i = i || 0; + const startByte = i; + const res = {}; + res.id = ZCLDataTypes.uint8.fromBuffer(buf, i); + i += ZCLDataTypes.uint8.length; + + const dataTypeId = ZCLDataTypes.uint8.fromBuffer(buf, i); + i += ZCLDataTypes.uint8.length; + + const DataType = + (attributes[res.id] && attributes[res.id].type) || + Object.values(ZCLDataTypes).find((type) => type && type.id === dataTypeId); + if (!DataType) throw new TypeError(`Invalid Type for Attribute: ${res.id}`); + + res.name = (attributes[res.id] && attributes[res.id].name) || `unknown_attr_${res.id}`; + + const entry = DataType.fromBuffer(buf, i, true); + if (DataType.length > 0) { + i += DataType.length; + res.value = entry; + } else { + res.value = entry.result; + i += entry.length; + } + if (returnLength) { + return { result: res, length: i - startByte }; + } + return res; + }, +); + +const XiaomiLifelineDataRecordArray = new ZCLDataType( + NaN, + 'XiaomiLifelineDataRecordArray', + -1, + function (_buf, _v, _i) { + throw new Error('not_supported'); + }, + function (buf, i, returnLength) { + let { result, length } = ZCLDataTypes.buffer8.fromBuffer(buf, i, true); + result = ZCLDataTypes.Array0(XiaomiLifelineDataRecord).fromBuffer(result, 0); + result = result.reduce((r, { name, value }) => Object.assign(r, { [name]: value }), {}); + if (returnLength) { + return { result, length }; + } + return result; + }, +); class XiaomiHackedBasicCluster extends BasicCluster { - static get ATTRIBUTES() { return { ...super.ATTRIBUTES, - xiaomiLifeline: { id: 0xFF01, type: XiaomiLifelineDataRecordArray }, + xiaomiLifeline: { id: 0xff01, type: XiaomiLifelineDataRecordArray }, }; } - } // Replace basic cluster with our patched version diff --git a/test/onOff.js b/test/onOff.js index 5306f556..803d15e1 100644 --- a/test/onOff.js +++ b/test/onOff.js @@ -5,90 +5,102 @@ const BoundCluster = require('../lib/BoundCluster'); const OnOffCluster = require('../lib/clusters/onOff'); const { createMockNode } = require('./util'); -describe('On/Off', function() { - it('should receive setOn', async function() { +describe('On/Off', function () { + it('should receive setOn', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [OnOffCluster.ID], + }, + ], }); let called = false; - node.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async setOn() { - called = true; - } - - })()); + node.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async setOn() { + called = true; + } + })(), + ); await node.endpoints[1].clusters.onOff.setOn(); assert.strictEqual(called, true); }); - it('should receive setOff', async function() { + it('should receive setOff', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [OnOffCluster.ID], + }, + ], }); let called = false; - node.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async setOff() { - called = true; - } - - })()); + node.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async setOff() { + called = true; + } + })(), + ); await node.endpoints[1].clusters.onOff.setOff(); assert.strictEqual(called, true); }); - it('should receive toggle', async function() { + it('should receive toggle', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [OnOffCluster.ID], + }, + ], }); let called = false; - node.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async toggle() { - called = true; - } - - })()); + node.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async toggle() { + called = true; + } + })(), + ); await node.endpoints[1].clusters.onOff.toggle(); assert.strictEqual(called, true); }); - it('should receive onWithTimedOff', async function() { + it('should receive onWithTimedOff', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [OnOffCluster.ID], + }, + ], }); let receivedData = null; - node.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async onWithTimedOff(data) { - receivedData = data; - } - - })()); + node.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async onWithTimedOff(data) { + receivedData = data; + } + })(), + ); await node.endpoints[1].clusters.onOff.onWithTimedOff({ onOffControl: 0x01, diff --git a/test/ota.js b/test/ota.js index fd1e697b..74eafa38 100644 --- a/test/ota.js +++ b/test/ota.js @@ -25,32 +25,35 @@ function createCapturingNode() { capturedFrames.push(buffer); return node.handleFrame(endpointId, clusterId, buffer, {}); }, - endpointDescriptors: [{ - endpointId: 1, - inputClusters: [OTACluster.ID], - outputClusters: [], - }], + endpointDescriptors: [ + { + endpointId: 1, + inputClusters: [OTACluster.ID], + outputClusters: [], + }, + ], }; node = new Node(mockNode); return { node, capturedFrames }; } -describe('OTA', function() { +describe('OTA', function () { // --- queryNextImageRequest: encodeMissingFieldsBehavior on the request --- - it('should NOT encode hardwareVersion when omitted from queryNextImageRequest', async function() { + it('should NOT encode hardwareVersion when omitted from queryNextImageRequest', async function () { const { node, capturedFrames } = createCapturingNode(); let receivedData = null; - node.endpoints[1].bind('ota', new (class extends BoundCluster { - - async queryNextImageRequest(data) { - receivedData = data; - return { status: 'NO_IMAGE_AVAILABLE' }; - } - - })()); + node.endpoints[1].bind( + 'ota', + new (class extends BoundCluster { + async queryNextImageRequest(data) { + receivedData = data; + return { status: 'NO_IMAGE_AVAILABLE' }; + } + })(), + ); await node.endpoints[1].clusters.ota.queryNextImageRequest({ fieldControl: 0, @@ -60,7 +63,7 @@ describe('OTA', function() { // hardwareVersion intentionally omitted }); - const requestFrame = capturedFrames.find(buf => buf[2] === CMD_QUERY_NEXT_IMAGE_REQUEST); + const requestFrame = capturedFrames.find((buf) => buf[2] === CMD_QUERY_NEXT_IMAGE_REQUEST); assert.ok(requestFrame, 'request frame not captured'); // fieldControl(1) + manufacturerCode(2) + imageType(2) + fileVersion(4) = 9 bytes @@ -79,16 +82,17 @@ describe('OTA', function() { assert.strictEqual(receivedData.hardwareVersion, 0); }); - it('should encode hardwareVersion when provided in queryNextImageRequest', async function() { + it('should encode hardwareVersion when provided in queryNextImageRequest', async function () { const { node, capturedFrames } = createCapturingNode(); - node.endpoints[1].bind('ota', new (class extends BoundCluster { - - async queryNextImageRequest() { - return { status: 'NO_IMAGE_AVAILABLE' }; - } - - })()); + node.endpoints[1].bind( + 'ota', + new (class extends BoundCluster { + async queryNextImageRequest() { + return { status: 'NO_IMAGE_AVAILABLE' }; + } + })(), + ); await node.endpoints[1].clusters.ota.queryNextImageRequest({ fieldControl: ['hardwareVersionPresent'], @@ -98,7 +102,7 @@ describe('OTA', function() { hardwareVersion: 0x0002, }); - const requestFrame = capturedFrames.find(buf => buf[2] === CMD_QUERY_NEXT_IMAGE_REQUEST); + const requestFrame = capturedFrames.find((buf) => buf[2] === CMD_QUERY_NEXT_IMAGE_REQUEST); assert.ok(requestFrame, 'request frame not captured'); // fieldControl(1) + manufacturerCode(2) + imageType(2) + fileVersion(4) @@ -116,22 +120,23 @@ describe('OTA', function() { // --- queryNextImageResponse: encodeMissingFieldsBehavior on the response --- - it('should encode all fields in queryNextImageResponse when status is SUCCESS', async function() { + it('should encode all fields in queryNextImageResponse when status is SUCCESS', async function () { const { node, capturedFrames } = createCapturingNode(); - node.endpoints[1].bind('ota', new (class extends BoundCluster { - - async queryNextImageRequest() { - return { - status: 'SUCCESS', - manufacturerCode: 0x1234, - imageType: 0x0001, - fileVersion: 0x00000020, - imageSize: 0x00010000, - }; - } - - })()); + node.endpoints[1].bind( + 'ota', + new (class extends BoundCluster { + async queryNextImageRequest() { + return { + status: 'SUCCESS', + manufacturerCode: 0x1234, + imageType: 0x0001, + fileVersion: 0x00000020, + imageSize: 0x00010000, + }; + } + })(), + ); const response = await node.endpoints[1].clusters.ota.queryNextImageRequest({ fieldControl: 0, @@ -140,7 +145,7 @@ describe('OTA', function() { fileVersion: 0x00000010, }); - const responseFrame = capturedFrames.find(buf => buf[2] === CMD_QUERY_NEXT_IMAGE_RESPONSE); + const responseFrame = capturedFrames.find((buf) => buf[2] === CMD_QUERY_NEXT_IMAGE_RESPONSE); assert.ok(responseFrame, 'response frame not captured'); // status(1) + manufacturerCode(2) + imageType(2) + fileVersion(4) + imageSize(4) = 13 bytes @@ -158,42 +163,42 @@ describe('OTA', function() { assert.strictEqual(response.imageSize, 0x00010000); }); - it('should encode only the status byte in queryNextImageResponse when status is NO_IMAGE_AVAILABLE', - async function() { - const { node, capturedFrames } = createCapturingNode(); - - node.endpoints[1].bind('ota', new (class extends BoundCluster { + it('should encode only the status byte in queryNextImageResponse when status is NO_IMAGE_AVAILABLE', async function () { + const { node, capturedFrames } = createCapturingNode(); + node.endpoints[1].bind( + 'ota', + new (class extends BoundCluster { async queryNextImageRequest() { return { status: 'NO_IMAGE_AVAILABLE' }; } + })(), + ); - })()); - - const response = await node.endpoints[1].clusters.ota.queryNextImageRequest({ - fieldControl: 0, - manufacturerCode: 0x1234, - imageType: 0x0001, - fileVersion: 0x00000010, - }); - - const responseFrame = capturedFrames.find(buf => buf[2] === CMD_QUERY_NEXT_IMAGE_RESPONSE); - assert.ok(responseFrame, 'response frame not captured'); - - // Only status(1) — all other fields must be absent - const payload = responseFrame.slice(ZCL_HEADER_SIZE); - assert.strictEqual( - payload.length, - 1, - `payload must be 1 byte (status only), got ${payload.length}`, - ); - assert.strictEqual(payload[0], 0x98, 'status byte must be NO_IMAGE_AVAILABLE (0x98)'); - - assert.strictEqual(response.status, 'NO_IMAGE_AVAILABLE'); - // Absent fields decode as 0 (decoder reads from exhausted buffer) - assert.strictEqual(response.manufacturerCode, 0); - assert.strictEqual(response.imageType, 0); - assert.strictEqual(response.fileVersion, 0); - assert.strictEqual(response.imageSize, 0); + const response = await node.endpoints[1].clusters.ota.queryNextImageRequest({ + fieldControl: 0, + manufacturerCode: 0x1234, + imageType: 0x0001, + fileVersion: 0x00000010, }); + + const responseFrame = capturedFrames.find((buf) => buf[2] === CMD_QUERY_NEXT_IMAGE_RESPONSE); + assert.ok(responseFrame, 'response frame not captured'); + + // Only status(1) — all other fields must be absent + const payload = responseFrame.slice(ZCL_HEADER_SIZE); + assert.strictEqual( + payload.length, + 1, + `payload must be 1 byte (status only), got ${payload.length}`, + ); + assert.strictEqual(payload[0], 0x98, 'status byte must be NO_IMAGE_AVAILABLE (0x98)'); + + assert.strictEqual(response.status, 'NO_IMAGE_AVAILABLE'); + // Absent fields decode as 0 (decoder reads from exhausted buffer) + assert.strictEqual(response.manufacturerCode, 0); + assert.strictEqual(response.imageType, 0); + assert.strictEqual(response.fileVersion, 0); + assert.strictEqual(response.imageSize, 0); + }); }); diff --git a/test/testClusterResponseTimeout.js b/test/testClusterResponseTimeout.js index 4ad7d97a..2e20ac70 100644 --- a/test/testClusterResponseTimeout.js +++ b/test/testClusterResponseTimeout.js @@ -13,11 +13,11 @@ const IasZoneCluster = require('../lib/clusters/iasZone'); const sandbox = sinon.createSandbox(); -describe('Cluster Response Timeout', function() { +describe('Cluster Response Timeout', function () { let receivingNode; let sendingNode; - beforeEach(function() { + beforeEach(function () { sendingNode = new Node({ // Forward frames to receiving node sendFrame: (...args) => { @@ -45,42 +45,44 @@ describe('Cluster Response Timeout', function() { }); }); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); - it('should only start timeout after frame is sent', async function() { + it('should only start timeout after frame is sent', async function () { const originalSendFrame = sendingNode.endpoints[1].sendFrame.bind(sendingNode.endpoints[1]); sandbox.stub(sendingNode.endpoints[1], 'sendFrame').callsFake(async (...args) => { // Delay sendFrame by 150ms (longer than timeout) - await new Promise(resolve => setTimeout(resolve, 150)); + await new Promise((resolve) => setTimeout(resolve, 150)); return originalSendFrame(...args); }); - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() { - // Respond immediately - } - - }()); + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() { + // Respond immediately + } + })(), + ); // Start the command with 100ms timeout, if timeout starts immediately it will fire before // sendFrame completes and cause the command to fail await sendingNode.endpoints[1].clusters.onOff.toggle(undefined, { timeout: 100 }); }); - it('should clear timeout when response is received', async function() { + it('should clear timeout when response is received', async function () { const clearTimeoutSpy = sandbox.spy(global, 'clearTimeout'); - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - async toggle() { - // Respond after sendFrame resolved - return new Promise(resolve => setImmediate(resolve)); - } - - }()); + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async toggle() { + // Respond after sendFrame resolved + return new Promise((resolve) => setImmediate(resolve)); + } + })(), + ); await sendingNode.endpoints[1].clusters.onOff.toggle(undefined, { timeout: 5000 }); @@ -88,17 +90,17 @@ describe('Cluster Response Timeout', function() { assert(clearTimeoutSpy.called, 'clearTimeout should be called when response received'); }); - it('should not wait for response when waitForResponse is false', async function() { + it('should not wait for response when waitForResponse is false', async function () { const sendingNodeSendFrameSpy = sandbox.spy(sendingNode, 'sendFrame'); - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() { - - // Respond with default response - } - - }()); + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() { + // Respond with default response + } + })(), + ); // Send command without waiting for response const result = await sendingNode.endpoints[1].clusters.onOff.toggle({ waitForResponse: false }); @@ -110,20 +112,23 @@ describe('Cluster Response Timeout', function() { assert.strictEqual(sendingNodeSendFrameSpy.callCount, 1); }); - it('should not resolve with a device-initiated frame that reuses the pending TSN', async function() { + it('should not resolve with a device-initiated frame that reuses the pending TSN', async function () { const reportedValues = []; - sendingNode.endpoints[1].clusters.onOff.on('attr.onOff', value => reportedValues.push(value)); - - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - async toggle(args, meta, frame) { - // Device sends an attribute report numbered from its own counter, colliding with the TSN - await sendingNode.handleFrame(1, OnOffCluster.ID, Buffer.from([ - 0x18, frame.trxSequenceNumber, 0x0A, 0x00, 0x00, 0x10, 0x01, - ])); - } - - }()); + sendingNode.endpoints[1].clusters.onOff.on('attr.onOff', (value) => reportedValues.push(value)); + + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async toggle(args, meta, frame) { + // Device sends an attribute report numbered from its own counter, colliding with the TSN + await sendingNode.handleFrame( + 1, + OnOffCluster.ID, + Buffer.from([0x18, frame.trxSequenceNumber, 0x0a, 0x00, 0x00, 0x10, 0x01]), + ); + } + })(), + ); const result = await sendingNode.endpoints[1].clusters.onOff.toggle(); @@ -131,7 +136,7 @@ describe('Cluster Response Timeout', function() { assert.strictEqual(result, undefined); }); - it('should resolve getAlarm with a Get Alarm Response alone', async function() { + it('should resolve getAlarm with a Get Alarm Response alone', async function () { const pair = [AlarmsCluster.ID]; const sender = new Node({ sendFrame: (...args) => receiver.handleFrame(...args), @@ -141,15 +146,19 @@ describe('Cluster Response Timeout', function() { sendFrame: (...args) => sender.handleFrame(...args), endpointDescriptors: [{ endpointId: 1, inputClusters: pair }], }); - receiver.endpoints[1].bind('alarms', new class extends BoundCluster { - - getAlarm() { - return { - status: 'SUCCESS', alarmCode: 2, clusterId: 6, timeStamp: 1234, - }; - } - - }()); + receiver.endpoints[1].bind( + 'alarms', + new (class extends BoundCluster { + getAlarm() { + return { + status: 'SUCCESS', + alarmCode: 2, + clusterId: 6, + timeStamp: 1234, + }; + } + })(), + ); const { alarms } = sender.endpoints[1].clusters; const response = await alarms.getAlarm(undefined, { timeout: 1000 }); @@ -158,11 +167,10 @@ describe('Cluster Response Timeout', function() { assert.strictEqual(response.timeStamp, 1234); }); - it('should resolve a command without declared response on a cluster-specific reply', async function() { + it('should resolve a command without declared response on a cluster-specific reply', async function () { class TuyaLikeCluster extends Cluster { - static get ID() { - return 0xEF00; + return 0xef00; } static get NAME() { @@ -179,7 +187,6 @@ describe('Cluster Response Timeout', function() { }, }; } - } Cluster.addCluster(TuyaLikeCluster); @@ -193,20 +200,24 @@ describe('Cluster Response Timeout', function() { sendFrame: (...args) => sender.handleFrame(...args), endpointDescriptors: [{ endpointId: 1, inputClusters: pair }], }); - receiver.endpoints[1].bind('tuyaLike', new class extends BoundCluster { - - // Reply like a Tuya MCU: its own command with the request's TSN, no Default Response - async datapoint({ value }, meta, frame) { - await sender.handleFrame(1, TuyaLikeCluster.ID, Buffer.from([ - 0x19, frame.trxSequenceNumber, 0x01, value, - ])); - } - - }()); + receiver.endpoints[1].bind( + 'tuyaLike', + new (class extends BoundCluster { + // Reply like a Tuya MCU: its own command with the request's TSN, no Default Response + async datapoint({ value }, meta, frame) { + await sender.handleFrame( + 1, + TuyaLikeCluster.ID, + Buffer.from([0x19, frame.trxSequenceNumber, 0x01, value]), + ); + } + })(), + ); const { tuyaLike } = sender.endpoints[1].clusters; const response = await tuyaLike.datapoint( - { value: 7 }, { disableDefaultResponse: true, timeout: 1000 }, + { value: 7 }, + { disableDefaultResponse: true, timeout: 1000 }, ); assert.strictEqual(response.value, 7); @@ -215,11 +226,10 @@ describe('Cluster Response Timeout', function() { } }); - it('should treat a declared response without id as the command id', async function() { + it('should treat a declared response without id as the command id', async function () { class EchoCluster extends Cluster { - static get ID() { - return 0xFC01; + return 0xfc01; } static get NAME() { @@ -240,7 +250,6 @@ describe('Cluster Response Timeout', function() { }, }; } - } Cluster.addCluster(EchoCluster); @@ -254,17 +263,20 @@ describe('Cluster Response Timeout', function() { sendFrame: (...args) => sender.handleFrame(...args), endpointDescriptors: [{ endpointId: 1, inputClusters: pair }], }); - receiver.endpoints[1].bind('echo', new class extends BoundCluster { - - // An unrelated device command reuses the TSN first, then the real response (id 0x05) - async ping({ value }, meta, frame) { - await sender.handleFrame(1, EchoCluster.ID, Buffer.from([ - 0x19, frame.trxSequenceNumber, 0x06, 99, - ])); - return { value }; - } - - }()); + receiver.endpoints[1].bind( + 'echo', + new (class extends BoundCluster { + // An unrelated device command reuses the TSN first, then the real response (id 0x05) + async ping({ value }, meta, frame) { + await sender.handleFrame( + 1, + EchoCluster.ID, + Buffer.from([0x19, frame.trxSequenceNumber, 0x06, 99]), + ); + return { value }; + } + })(), + ); const { echo } = sender.endpoints[1].clusters; const response = await echo.ping({ value: 7 }, { timeout: 1000 }); @@ -275,7 +287,7 @@ describe('Cluster Response Timeout', function() { } }); - describe('pending response matching', function() { + describe('pending response matching', function () { // Node pair where the receiving side first sends `colliding(tsn)` with the request's TSN, // then its real reply function createCollidingPair(clusterId, colliding) { @@ -294,18 +306,19 @@ describe('Cluster Response Timeout', function() { return { sender, receiver }; } - it('should not resolve a global command on a cluster-specific frame with its response id', async function() { + it('should not resolve a global command on a cluster-specific frame with its response id', async function () { // IAS Zone Enroll Request is cluster-specific 0x01, the same id as Read Attributes Response - const { sender, receiver } = createCollidingPair(IasZoneCluster.ID, tsn => Buffer.from([ - 0x19, tsn, 0x01, 0x0D, 0x00, 0x00, 0x00, - ])); - receiver.endpoints[1].bind('iasZone', new class extends BoundCluster { - - get zoneState() { - return 'enrolled'; - } - - }()); + const { sender, receiver } = createCollidingPair(IasZoneCluster.ID, (tsn) => + Buffer.from([0x19, tsn, 0x01, 0x0d, 0x00, 0x00, 0x00]), + ); + receiver.endpoints[1].bind( + 'iasZone', + new (class extends BoundCluster { + get zoneState() { + return 'enrolled'; + } + })(), + ); const { iasZone } = sender.endpoints[1].clusters; const result = await iasZone.readAttributes(['zoneState'], { timeout: 1000 }); @@ -313,27 +326,29 @@ describe('Cluster Response Timeout', function() { assert.deepStrictEqual(result, { zoneState: 'enrolled' }); }); - it('should not resolve on a Default Response for another command', async function() { + it('should not resolve on a Default Response for another command', async function () { // Default Response with FAILURE for cmdId 0x01 (on), while toggle (0x02) is pending - const { sender, receiver } = createCollidingPair(OnOffCluster.ID, tsn => Buffer.from([ - 0x18, tsn, 0x0B, 0x01, 0x01, - ])); - receiver.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() {} - - }()); + const { sender, receiver } = createCollidingPair(OnOffCluster.ID, (tsn) => + Buffer.from([0x18, tsn, 0x0b, 0x01, 0x01]), + ); + receiver.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() {} + })(), + ); await sender.endpoints[1].clusters.onOff.toggle(undefined, { timeout: 1000 }); }); }); - it('should resolve when the response arrives before sendFrame rejects', async function() { - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() {} - - }()); + it('should resolve when the response arrives before sendFrame rejects', async function () { + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() {} + })(), + ); sendingNode.sendFrame = async (endpointId, clusterId, frame) => { await receivingNode.handleFrame(endpointId, clusterId, frame); throw new Error('device not responding'); @@ -342,7 +357,7 @@ describe('Cluster Response Timeout', function() { await sendingNode.endpoints[1].clusters.onOff.toggle(); }); - it('should reject when sendFrame rejects without a response', async function() { + it('should reject when sendFrame rejects without a response', async function () { sendingNode.sendFrame = async () => { throw new Error('device not responding'); }; diff --git a/test/testConfigureReportingRecordsStruct.js b/test/testConfigureReportingRecordsStruct.js index 016110b1..588c64c9 100644 --- a/test/testConfigureReportingRecordsStruct.js +++ b/test/testConfigureReportingRecordsStruct.js @@ -2,9 +2,7 @@ const assert = require('assert'); -const { - ZCLConfigureReportingRecords, -} = require('../lib/zclFrames'); +const { ZCLConfigureReportingRecords } = require('../lib/zclFrames'); const CONFIGURE_REPORTING_RECORDS_MOCK = [ { @@ -80,8 +78,8 @@ const CONFIGURE_REPORTING_RECORDS_WITH_STATUS_BUFFER_MOCK = Buffer.from( 'hex', ); -describe('command configure reporting', function() { - it('should parse multiple records to buffer', function() { +describe('command configure reporting', function () { + it('should parse multiple records to buffer', function () { // Create type instance const ZCLConfigureReportingRecordsType = ZCLConfigureReportingRecords(); @@ -95,13 +93,15 @@ describe('command configure reporting', function() { assert(buf.equals(CONFIGURE_REPORTING_RECORDS_BUFFER_MOCK)); }); - it('should parse buffer to multiple records', function() { + it('should parse buffer to multiple records', function () { // Create type instance const ZCLConfigureReportingRecordsType = ZCLConfigureReportingRecords(); // Allocate buffer to be filled with configure reporting records - const parsedRecords = ZCLConfigureReportingRecordsType - .fromBuffer(CONFIGURE_REPORTING_RECORDS_BUFFER_MOCK, 0); + const parsedRecords = ZCLConfigureReportingRecordsType.fromBuffer( + CONFIGURE_REPORTING_RECORDS_BUFFER_MOCK, + 0, + ); // Compare the parsed records against the initial records `configureReportingRecordsBuffer` for (let i = 0; i < CONFIGURE_REPORTING_RECORDS_MOCK.length; i++) { @@ -110,8 +110,8 @@ describe('command configure reporting', function() { }); }); -describe('command read configure reporting', function() { - it('should parse multiple records to buffer', function() { +describe('command read configure reporting', function () { + it('should parse multiple records to buffer', function () { // Create type instance const ZCLConfigureReportingRecordsWithStatusType = ZCLConfigureReportingRecords({ withStatus: true, @@ -129,15 +129,17 @@ describe('command read configure reporting', function() { assert(buf.equals(CONFIGURE_REPORTING_RECORDS_WITH_STATUS_BUFFER_MOCK)); }); - it('should parse buffer to multiple records', function() { + it('should parse buffer to multiple records', function () { // Create type instance const ZCLConfigureReportingRecordsWithStatusType = ZCLConfigureReportingRecords({ withStatus: true, }); // Allocate buffer to be filled with configure reporting records - const parsedRecords = ZCLConfigureReportingRecordsWithStatusType - .fromBuffer(CONFIGURE_REPORTING_RECORDS_WITH_STATUS_BUFFER_MOCK, 0); + const parsedRecords = ZCLConfigureReportingRecordsWithStatusType.fromBuffer( + CONFIGURE_REPORTING_RECORDS_WITH_STATUS_BUFFER_MOCK, + 0, + ); // Compare the parsed records against the initial records `configureReportingRecordsBuffer` for (let i = 0; i < CONFIGURE_REPORTING_RECORDS_WITH_STATUS_MOCK.length; i++) { diff --git a/test/testMfgSpecificCommands.js b/test/testMfgSpecificCommands.js index d29ea2bb..35a3830a 100644 --- a/test/testMfgSpecificCommands.js +++ b/test/testMfgSpecificCommands.js @@ -10,7 +10,6 @@ const MANUFACTURER_ID_1 = 0x1234; const MANUFACTURER_ID_2 = 0x4321; class IkeaSceneCluster extends SceneCluster { - static get ATTRIBUTES() { return { sceneCount: { @@ -40,7 +39,7 @@ class IkeaSceneCluster extends SceneCluster { ...super.COMMANDS, ikeaSceneStep: { id: 0x07, - manufacturerId: 0x117C, + manufacturerId: 0x117c, args: { mode: ZCLDataTypes.enum8({ up: 0, @@ -52,7 +51,7 @@ class IkeaSceneCluster extends SceneCluster { }, ikeaSceneMove: { id: 0x08, - manufacturerId: 0x117C, + manufacturerId: 0x117c, args: { mode: ZCLDataTypes.enum8({ up: 0, @@ -63,14 +62,13 @@ class IkeaSceneCluster extends SceneCluster { }, ikeaSceneMoveStop: { id: 0x09, - manufacturerId: 0x117C, + manufacturerId: 0x117c, args: { duration: ZCLDataTypes.uint16, }, }, }; } - } Cluster.addCluster(IkeaSceneCluster); @@ -78,14 +76,15 @@ const { createMockNode } = require('./util'); const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [SceneCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [SceneCluster.ID], + }, + ], }); class IkeaBoundCluster extends BoundCluster { - constructor({ onReadAttributes, onWriteAttributes, @@ -106,11 +105,10 @@ class IkeaBoundCluster extends BoundCluster { if (onIkeaSceneMove) this.ikeaSceneMove = onIkeaSceneMove; if (onIkeaSceneMoveStop) this.ikeaSceneMoveStop = onIkeaSceneMoveStop; } - } -describe('manufacturer specific commands', function() { - it('should execute custom commands', function(done) { +describe('manufacturer specific commands', function () { + it('should execute custom commands', function (done) { let commandsExecuted = 0; const ikeaSceneStepCommand = { mode: 'up', @@ -124,147 +122,182 @@ describe('manufacturer specific commands', function() { const ikeaSceneMoveStop = { duration: 1000, }; - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onIkeaSceneStep: payload => { - commandsExecuted++; - assert.deepStrictEqual({ ...payload }, ikeaSceneStepCommand); - if (commandsExecuted === 3) done(); - }, - onIkeaSceneMove: payload => { - commandsExecuted++; - assert.deepStrictEqual({ ...payload }, ikeaSceneMoveCommand); - if (commandsExecuted === 3) done(); - }, - onIkeaSceneMoveStop: payload => { - commandsExecuted++; - assert.deepStrictEqual({ ...payload }, ikeaSceneMoveStop); - if (commandsExecuted === 3) done(); - }, - })); + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onIkeaSceneStep: (payload) => { + commandsExecuted++; + assert.deepStrictEqual({ ...payload }, ikeaSceneStepCommand); + if (commandsExecuted === 3) done(); + }, + onIkeaSceneMove: (payload) => { + commandsExecuted++; + assert.deepStrictEqual({ ...payload }, ikeaSceneMoveCommand); + if (commandsExecuted === 3) done(); + }, + onIkeaSceneMoveStop: (payload) => { + commandsExecuted++; + assert.deepStrictEqual({ ...payload }, ikeaSceneMoveStop); + if (commandsExecuted === 3) done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].ikeaSceneStep(ikeaSceneStepCommand); node.endpoints[1].clusters['scenes'].ikeaSceneMove(ikeaSceneMoveCommand); node.endpoints[1].clusters['scenes'].ikeaSceneMoveStop(ikeaSceneMoveStop); }); - describe('global commands', function() { - describe('readAttributes', function() { - it('should set correct manufacturerId', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onReadAttributes: (payload, meta, rawPayload) => { - assert.strictEqual(MANUFACTURER_ID_1, rawPayload.manufacturerId); - done(); - }, - })); + describe('global commands', function () { + describe('readAttributes', function () { + it('should set correct manufacturerId', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onReadAttributes: (payload, meta, rawPayload) => { + assert.strictEqual(MANUFACTURER_ID_1, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].readAttributes(['sceneCount']); }); - it('should throw when different manufacturerIds are found', function(done) { - node.endpoints[1].clusters['scenes'].readAttributes(['sceneCount', 'currentScene']) - .catch(err => { + it('should throw when different manufacturerIds are found', function (done) { + node.endpoints[1].clusters['scenes'] + .readAttributes(['sceneCount', 'currentScene']) + .catch((err) => { assert(err instanceof Error); done(); }); }); - it('should set correct manufacturerId for multiple attributes', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onReadAttributes: (payload, meta, rawPayload) => { - assert.strictEqual(MANUFACTURER_ID_2, rawPayload.manufacturerId); - done(); - }, - })); + it('should set correct manufacturerId for multiple attributes', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onReadAttributes: (payload, meta, rawPayload) => { + assert.strictEqual(MANUFACTURER_ID_2, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].readAttributes(['currentScene', 'currentGroup']); }); - it('should not set manufacturerId when zcl attribute is present', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onReadAttributes: (payload, meta, rawPayload) => { - assert.strictEqual(undefined, rawPayload.manufacturerId); - done(); - }, - })); + it('should not set manufacturerId when zcl attribute is present', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onReadAttributes: (payload, meta, rawPayload) => { + assert.strictEqual(undefined, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].readAttributes(['sceneValid']); }); }); - describe('writeAttributes', function() { - it('should set correct manufacturerId', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onWriteAttributes: (payload, meta, rawPayload) => { - assert.strictEqual(MANUFACTURER_ID_1, rawPayload.manufacturerId); - done(); - }, - })); + describe('writeAttributes', function () { + it('should set correct manufacturerId', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onWriteAttributes: (payload, meta, rawPayload) => { + assert.strictEqual(MANUFACTURER_ID_1, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].writeAttributes({ sceneCount: 1 }); }); - it('should throw when different manufacturerIds are found', function(done) { - node.endpoints[1].clusters['scenes'].writeAttributes({ sceneCount: 1, currentScene: 2 }) - .catch(err => { + it('should throw when different manufacturerIds are found', function (done) { + node.endpoints[1].clusters['scenes'] + .writeAttributes({ sceneCount: 1, currentScene: 2 }) + .catch((err) => { assert(err instanceof Error); done(); }); }); - it('should set correct manufacturerId for multiple attributes', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onWriteAttributes: (payload, meta, rawPayload) => { - assert.strictEqual(MANUFACTURER_ID_2, rawPayload.manufacturerId); - done(); - }, - })); + it('should set correct manufacturerId for multiple attributes', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onWriteAttributes: (payload, meta, rawPayload) => { + assert.strictEqual(MANUFACTURER_ID_2, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].writeAttributes({ currentScene: 1, currentGroup: 2 }); }); - it('should not set manufacturerId when zcl attribute is present', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onWriteAttributes: (payload, meta, rawPayload) => { - assert.strictEqual(undefined, rawPayload.manufacturerId); - done(); - }, - })); + it('should not set manufacturerId when zcl attribute is present', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onWriteAttributes: (payload, meta, rawPayload) => { + assert.strictEqual(undefined, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].writeAttributes({ sceneValid: true }); }); }); - describe('configureReporting', function() { - it('should set correct manufacturerId', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onConfigureReporting: (payload, meta, rawPayload) => { - assert.strictEqual(MANUFACTURER_ID_1, rawPayload.manufacturerId); - done(); - }, - })); - node.endpoints[1].clusters['scenes'].configureReporting({ sceneCount: { minInterval: 0, maxInterval: 300, minChange: 1 } }); - }); - it('should throw when different manufacturerIds are found', function(done) { + describe('configureReporting', function () { + it('should set correct manufacturerId', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onConfigureReporting: (payload, meta, rawPayload) => { + assert.strictEqual(MANUFACTURER_ID_1, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].configureReporting({ sceneCount: { minInterval: 0, maxInterval: 300, minChange: 1 }, - currentScene: { minInterval: 0, maxInterval: 300, minChange: 1 }, - }) - .catch(err => { + }); + }); + it('should throw when different manufacturerIds are found', function (done) { + node.endpoints[1].clusters['scenes'] + .configureReporting({ + sceneCount: { minInterval: 0, maxInterval: 300, minChange: 1 }, + currentScene: { minInterval: 0, maxInterval: 300, minChange: 1 }, + }) + .catch((err) => { assert(err instanceof Error); done(); }); }); - it('should set correct manufacturerId for multiple attributes', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onConfigureReporting: (payload, meta, rawPayload) => { - assert.strictEqual(MANUFACTURER_ID_2, rawPayload.manufacturerId); - done(); - }, - })); + it('should set correct manufacturerId for multiple attributes', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onConfigureReporting: (payload, meta, rawPayload) => { + assert.strictEqual(MANUFACTURER_ID_2, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].configureReporting({ currentScene: { minInterval: 0, maxInterval: 300, minChange: 1 }, currentGroup: { minInterval: 0, maxInterval: 300, minChange: 1 }, }); }); - it('should not set manufacturerId when zcl attribute is present', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onConfigureReporting: (payload, meta, rawPayload) => { - assert.strictEqual(undefined, rawPayload.manufacturerId); - done(); - }, - })); + it('should not set manufacturerId when zcl attribute is present', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onConfigureReporting: (payload, meta, rawPayload) => { + assert.strictEqual(undefined, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].configureReporting({ sceneValid: { minInterval: 0, maxInterval: 300, minChange: 1 }, @@ -273,41 +306,57 @@ describe('manufacturer specific commands', function() { }); }); - describe('readReportingConfiguration', function() { - it('should set correct manufacturerId', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onReadReportingConfiguration: (payload, meta, rawPayload) => { - assert.strictEqual(MANUFACTURER_ID_1, rawPayload.manufacturerId); - done(); - }, - })); + describe('readReportingConfiguration', function () { + it('should set correct manufacturerId', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onReadReportingConfiguration: (payload, meta, rawPayload) => { + assert.strictEqual(MANUFACTURER_ID_1, rawPayload.manufacturerId); + done(); + }, + }), + ); node.endpoints[1].clusters['scenes'].readReportingConfiguration(['sceneCount']); }); - it('should throw when different manufacturerIds are found', function(done) { - node.endpoints[1].clusters['scenes'].readReportingConfiguration(['sceneCount', 'currentScene']) - .catch(err => { + it('should throw when different manufacturerIds are found', function (done) { + node.endpoints[1].clusters['scenes'] + .readReportingConfiguration(['sceneCount', 'currentScene']) + .catch((err) => { assert(err instanceof Error); done(); }); }); - it('should set correct manufacturerId for multiple attributes', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onReadReportingConfiguration: (payload, meta, rawPayload) => { - assert.strictEqual(MANUFACTURER_ID_2, rawPayload.manufacturerId); - done(); - }, - })); - node.endpoints[1].clusters['scenes'].readReportingConfiguration(['currentGroup', 'currentScene']); + it('should set correct manufacturerId for multiple attributes', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onReadReportingConfiguration: (payload, meta, rawPayload) => { + assert.strictEqual(MANUFACTURER_ID_2, rawPayload.manufacturerId); + done(); + }, + }), + ); + node.endpoints[1].clusters['scenes'].readReportingConfiguration([ + 'currentGroup', + 'currentScene', + ]); }); - it('should not set manufacturerId when zcl attribute is present', function(done) { - node.endpoints[1].bind('scenes', new IkeaBoundCluster({ - onReadReportingConfiguration: (payload, meta, rawPayload) => { - assert.strictEqual(undefined, rawPayload.manufacturerId); - done(); - }, - })); + it('should not set manufacturerId when zcl attribute is present', function (done) { + node.endpoints[1].bind( + 'scenes', + new IkeaBoundCluster({ + onReadReportingConfiguration: (payload, meta, rawPayload) => { + assert.strictEqual(undefined, rawPayload.manufacturerId); + done(); + }, + }), + ); - node.endpoints[1].clusters['scenes'].readReportingConfiguration(['currentGroup', 'sceneValid']); + node.endpoints[1].clusters['scenes'].readReportingConfiguration([ + 'currentGroup', + 'sceneValid', + ]); }); }); }); diff --git a/test/testNode.js b/test/testNode.js index 1826cca7..f1ad27c2 100644 --- a/test/testNode.js +++ b/test/testNode.js @@ -16,31 +16,37 @@ debug = debug.extend('test-node'); const sandbox = sinon.createSandbox(); -describe('Node', function() { +describe('Node', function () { let mockNode; let receivingNode; let sendingNode; - before(function() { + before(function () { mockNode = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [BasicCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [BasicCluster.ID], + }, + ], }); [sendingNode, receivingNode] = createConnectedNodePair( { - endpoints: [{ - endpointId: 1, - inputClusters: [BasicCluster.ID, OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [BasicCluster.ID, OnOffCluster.ID], + }, + ], }, { - endpoints: [{ - endpointId: 1, - inputClusters: [BasicCluster.ID, OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [BasicCluster.ID, OnOffCluster.ID], + }, + ], }, ); // Override log id for sending node @@ -52,10 +58,10 @@ describe('Node', function() { receivingNode.getLogId = receivingNodeLog; receivingNode.endpoints[1].getLogId = receivingNodeLog; }); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); - it('should fail for unbound cluster', async function() { + it('should fail for unbound cluster', async function () { try { await mockNode.endpoints[1].clusters['basic'].configureReporting({ zclVersion: { @@ -66,10 +72,10 @@ describe('Node', function() { } catch { return; } - throw new Error('didn\'t throw'); + throw new Error("didn't throw"); }); - it('should fail for unimplemented command', async function() { + it('should fail for unimplemented command', async function () { mockNode.endpoints[1].bind('basic', new BoundCluster()); try { @@ -77,29 +83,31 @@ describe('Node', function() { } catch { return; } - throw new Error('didn\'t throw'); + throw new Error("didn't throw"); }); - it('should invoke command', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - async factoryReset() { - debug('factory reset'); - } - - }()); + it('should invoke command', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + async factoryReset() { + debug('factory reset'); + } + })(), + ); await mockNode.endpoints[1].clusters['basic'].factoryReset(); }); - it('should configure reporting', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - async configureReporting({ reports }) { - assert.equal(reports.length, 1, 'exactly 1 report'); - } - - }()); + it('should configure reporting', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + async configureReporting({ reports }) { + assert.equal(reports.length, 1, 'exactly 1 report'); + } + })(), + ); await mockNode.endpoints[1].clusters['basic'].configureReporting({ zclVersion: { @@ -109,25 +117,32 @@ describe('Node', function() { }); }); - it('should read attributes', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - constructor() { - super(); - this.dateCode = '1234'; - } - - get modelId() { - return 'test'; - } - - get manufacturerName() { - return 'Athom'; - } - - }()); + it('should read attributes', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + constructor() { + super(); + this.dateCode = '1234'; + } + + get modelId() { + return 'test'; + } + + get manufacturerName() { + return 'Athom'; + } + })(), + ); - const res = await mockNode.endpoints[1].clusters['basic'].readAttributes(['modelId', 'manufacturerName', 'clusterRevision', 'zclVersion', 'dateCode']); + const res = await mockNode.endpoints[1].clusters['basic'].readAttributes([ + 'modelId', + 'manufacturerName', + 'clusterRevision', + 'zclVersion', + 'dateCode', + ]); assert.equal(res.modelId, 'test', 'modelId should be test'); assert.equal(res.manufacturerName, 'Athom', 'manufacturerName should be test'); assert.equal(res.clusterRevision, 1, 'clusterRevision should be test'); @@ -135,108 +150,117 @@ describe('Node', function() { assert.equal(res.dateCode, '1234', 'dateCode should not be present'); }); - it('should read attributes by numeric ID', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - get modelId() { - return 'numericTest'; - } - - }()); + it('should read attributes by numeric ID', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + get modelId() { + return 'numericTest'; + } + })(), + ); // modelId has attribute ID 5 const res = await mockNode.endpoints[1].clusters['basic'].readAttributes([5]); assert.equal(res[5], 'numericTest', 'numeric ID 5 should return modelId value'); }); - it('should read attributes with mixed strings and numeric IDs', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - get modelId() { - return 'mixedTest'; - } - - get manufacturerName() { - return 'Athom'; - } - - }()); + it('should read attributes with mixed strings and numeric IDs', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + get modelId() { + return 'mixedTest'; + } + + get manufacturerName() { + return 'Athom'; + } + })(), + ); // manufacturerName by name, modelId (ID 5) by numeric ID - const res = await mockNode.endpoints[1].clusters['basic'].readAttributes(['manufacturerName', 5]); + const res = await mockNode.endpoints[1].clusters['basic'].readAttributes([ + 'manufacturerName', + 5, + ]); assert.equal(res.manufacturerName, 'Athom', 'manufacturerName should resolve by name'); assert.equal(res[5], 'mixedTest', 'numeric ID 5 should return modelId value'); }); - it('should return both name and numeric key when same attribute requested both ways', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - get modelId() { - return 'both'; - } - - }()); + it('should return both name and numeric key when same attribute requested both ways', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + get modelId() { + return 'both'; + } + })(), + ); const res = await mockNode.endpoints[1].clusters['basic'].readAttributes(['modelId', 5]); assert.equal(res.modelId, 'both', 'modelId key should exist'); assert.equal(res[5], 'both', 'numeric key 5 should also exist'); }); - it('should read an unregistered attribute by numeric ID', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster {}()); + it('should read an unregistered attribute by numeric ID', async function () { + mockNode.endpoints[1].bind('basic', new (class extends BoundCluster {})()); // 0x1234 is not registered in the basic cluster, expect no error and empty/missing result const res = await mockNode.endpoints[1].clusters['basic'].readAttributes([0x1234]); assert.equal(res[0x1234], undefined, 'unregistered attribute should not have a value'); }); - it('should throw for invalid numeric attribute IDs', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster {}()); + it('should throw for invalid numeric attribute IDs', async function () { + mockNode.endpoints[1].bind('basic', new (class extends BoundCluster {})()); - await assert.rejects( - () => mockNode.endpoints[1].clusters['basic'].readAttributes([-1]), - { name: 'TypeError' }, - ); - await assert.rejects( - () => mockNode.endpoints[1].clusters['basic'].readAttributes([0x10000]), - { name: 'TypeError' }, - ); - await assert.rejects( - () => mockNode.endpoints[1].clusters['basic'].readAttributes([1.5]), - { name: 'TypeError' }, - ); - await assert.rejects( - () => mockNode.endpoints[1].clusters['basic'].readAttributes([NaN]), - { name: 'TypeError' }, - ); + await assert.rejects(() => mockNode.endpoints[1].clusters['basic'].readAttributes([-1]), { + name: 'TypeError', + }); + await assert.rejects(() => mockNode.endpoints[1].clusters['basic'].readAttributes([0x10000]), { + name: 'TypeError', + }); + await assert.rejects(() => mockNode.endpoints[1].clusters['basic'].readAttributes([1.5]), { + name: 'TypeError', + }); + await assert.rejects(() => mockNode.endpoints[1].clusters['basic'].readAttributes([NaN]), { + name: 'TypeError', + }); }); - it('should respond with read attribute response when disableDefaultResponse is true', async function() { - receivingNode.endpoints[1].bind('basic', new class extends BoundCluster { - - get modelId() { - return 'test'; - } - - }()); + it('should respond with read attribute response when disableDefaultResponse is true', async function () { + receivingNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + get modelId() { + return 'test'; + } + })(), + ); - const readAttributesResponse = await sendingNode.endpoints[1].clusters['basic'].readAttributes(['modelId'], { disableDefaultResponse: true }); + const readAttributesResponse = await sendingNode.endpoints[1].clusters['basic'].readAttributes( + ['modelId'], + { disableDefaultResponse: true }, + ); assert.equal(readAttributesResponse.modelId, 'test'); }); - it('should not respond with a default response when disableDefaultResponse is true', async function() { - receivingNode.endpoints[1].bind('basic', new class extends BoundCluster { - - get modelId() { - return 'test'; - } - - }()); + it('should not respond with a default response when disableDefaultResponse is true', async function () { + receivingNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + get modelId() { + return 'test'; + } + })(), + ); const sendingNodeSendFrameSpy = sandbox.spy(sendingNode, 'sendFrame'); const receivingNodeSendFrameSpy = sandbox.spy(receivingNode, 'sendFrame'); - await sendingNode.endpoints[1].clusters['basic'].readAttributes(['modelId'], { disableDefaultResponse: true }); + await sendingNode.endpoints[1].clusters['basic'].readAttributes(['modelId'], { + disableDefaultResponse: true, + }); // 1 frame sent by sending node, the read attributes command assert.equal(sendingNodeSendFrameSpy.callCount, 1); @@ -250,14 +274,15 @@ describe('Node', function() { assert.equal(frame.cmdId, 1); }); - it('should not respond to a default response frame', async function() { - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() { - debug('toggled'); - } - - }()); + it('should not respond to a default response frame', async function () { + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() { + debug('toggled'); + } + })(), + ); const sendingNodeSendFrameSpy = sandbox.spy(sendingNode, 'sendFrame'); const receivingNodeSendFrameSpy = sandbox.spy(receivingNode, 'sendFrame'); @@ -275,32 +300,35 @@ describe('Node', function() { assert.equal(frame.cmdId, 11); }); - it('should respond with default response error when frame could not be handled', async function() { - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster {}()); + it('should respond with default response error when frame could not be handled', async function () { + receivingNode.endpoints[1].bind('onOff', new (class extends BoundCluster {})()); const sendingNodeHandleFrameSpy = sandbox.spy(sendingNode, 'handleFrame'); // Execute toggle and expect UNSUP_COMMAND as the command is not implemented (ZCL R8 2.5.12.2) - await assert.rejects(sendingNode.endpoints[1].clusters['onOff'].toggle(), { message: 'UNSUP_CLUSTER_COMMAND' }); + await assert.rejects(sendingNode.endpoints[1].clusters['onOff'].toggle(), { + message: 'UNSUP_CLUSTER_COMMAND', + }); // 1 frame received by sending node, the default response error assert.equal(sendingNodeHandleFrameSpy.callCount, 1); // Parse the received frame const frame = Endpoint.parseFrame(sendingNodeHandleFrameSpy.getCall(0).args[2]); - assert.equal(frame.cmdId, 0x0B); + assert.equal(frame.cmdId, 0x0b); assert.equal(frame.data[1], 0x81); // UNSUP_COMMAND }); - it('should not respond to a group cast frame', async function() { + it('should not respond to a group cast frame', async function () { const toggledSpy = sandbox.spy(); - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() { - toggledSpy(); - } - - }()); + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() { + toggledSpy(); + } + })(), + ); const receivingNodeSendFrameSpy = sandbox.spy(receivingNode, 'sendFrame'); @@ -311,15 +339,16 @@ describe('Node', function() { assert.equal(receivingNodeSendFrameSpy.callCount, 0); }); - it('should not send a default response to a broadcast frame', async function() { + it('should not send a default response to a broadcast frame', async function () { const toggledSpy = sandbox.spy(); - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() { - toggledSpy(); - } - - }()); + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() { + toggledSpy(); + } + })(), + ); const receivingNodeSendFrameSpy = sandbox.spy(receivingNode, 'sendFrame'); @@ -329,8 +358,8 @@ describe('Node', function() { assert.equal(receivingNodeSendFrameSpy.callCount, 0); }); - it('should not send a default response error to a broadcast frame', async function() { - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster {}()); + it('should not send a default response error to a broadcast frame', async function () { + receivingNode.endpoints[1].bind('onOff', new (class extends BoundCluster {})()); const receivingNodeSendFrameSpy = sandbox.spy(receivingNode, 'sendFrame'); @@ -339,79 +368,82 @@ describe('Node', function() { assert.equal(receivingNodeSendFrameSpy.callCount, 0); }); - it('should send a default response to a unicast frame for the broadcast endpoint', async function() { - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() {} - - }()); + it('should send a default response to a unicast frame for the broadcast endpoint', async function () { + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() {} + })(), + ); const receivingNodeSendFrameSpy = sandbox.spy(receivingNode, 'sendFrame'); - await receivingNode.handleFrame(1, 6, Buffer.from([0x01, 0x01, 0x02]), { dstEndpoint: 0xFF }); + await receivingNode.handleFrame(1, 6, Buffer.from([0x01, 0x01, 0x02]), { dstEndpoint: 0xff }); assert.equal(receivingNodeSendFrameSpy.callCount, 1); }); - it('should write attributes', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - get modelId() { - return 'test'; - } - - get manufacturerName() { - return 'Athom'; - } - - set modelId(val) { - assert.equal(val, 'test1'); - } - - }()); + it('should write attributes', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + get modelId() { + return 'test'; + } + + get manufacturerName() { + return 'Athom'; + } + + set modelId(val) { + assert.equal(val, 'test1'); + } + })(), + ); await mockNode.endpoints[1].clusters['basic'].writeAttributes({ modelId: 'test1', }); }); - it('should discover attributes', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - get modelId() { - return 'test'; - } - - get manufacturerName() { - return 'Athom'; - } - - set modelId(val) { - assert.equal(val, 'test1'); - } - - }()); + it('should discover attributes', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + get modelId() { + return 'test'; + } + + get manufacturerName() { + return 'Athom'; + } + + set modelId(val) { + assert.equal(val, 'test1'); + } + })(), + ); const attrs = await mockNode.endpoints[1].clusters['basic'].discoverAttributes(); - ['modelId', 'manufacturerName'].forEach(a => { + ['modelId', 'manufacturerName'].forEach((a) => { assert(attrs.includes(a), `${a} is missing`); }); }); - it('should discover received commands', async function() { - mockNode.endpoints[1].bind('basic', new class extends BoundCluster { - - async factoryReset() { - debug('factory reset'); - } - - }()); + it('should discover received commands', async function () { + mockNode.endpoints[1].bind( + 'basic', + new (class extends BoundCluster { + async factoryReset() { + debug('factory reset'); + } + })(), + ); const cmds = await mockNode.endpoints[1].clusters['basic'].discoverCommandsReceived(); assert(cmds.includes('factoryReset')); }); - it('should support adding a Cluster with string attribute with 0x0000 ID', async function() { + it('should support adding a Cluster with string attribute with 0x0000 ID', async function () { class MyCluster extends Cluster { - static get ID() { return 0x1234; } @@ -421,7 +453,6 @@ describe('Node', function() { priceOption: { id: 0x0000, type: ZCLDataTypes.string }, }; } - } Cluster.addCluster(MyCluster); diff --git a/test/testTrxSequenceNumber.js b/test/testTrxSequenceNumber.js index 430857d2..cdee59b7 100644 --- a/test/testTrxSequenceNumber.js +++ b/test/testTrxSequenceNumber.js @@ -10,12 +10,12 @@ const OnOffCluster = require('../lib/clusters/onOff'); const sandbox = sinon.createSandbox(); -describe('Transaction sequence number', function() { +describe('Transaction sequence number', function () { let receivingNode; let sendingNode; let sentTrxSequenceNumbers; - beforeEach(function() { + beforeEach(function () { sentTrxSequenceNumbers = []; sendingNode = new Node({ sendFrame: (endpointId, clusterId, frame) => { @@ -37,18 +37,19 @@ describe('Transaction sequence number', function() { { endpointId: 2, inputClusters: [BasicCluster.ID] }, ], }); - receivingNode.endpoints[1].bind('onOff', new class extends BoundCluster { - - toggle() {} - - }()); + receivingNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + toggle() {} + })(), + ); }); - afterEach(function() { + afterEach(function () { sandbox.restore(); }); - it('should share one counter across endpoints and clusters', async function() { + it('should share one counter across endpoints and clusters', async function () { await sendingNode.endpoints[1].clusters.onOff.toggle(); const { basic } = sendingNode.endpoints[2].clusters; await basic.factoryReset(undefined, { waitForResponse: false }); @@ -58,7 +59,7 @@ describe('Transaction sequence number', function() { assert.deepStrictEqual(sentTrxSequenceNumbers, [first, (first + 1) % 256, (first + 2) % 256]); }); - it('should start at a random value and wrap around', function() { + it('should start at a random value and wrap around', function () { sandbox.stub(Math, 'random').returns(0.999); const node = new Node({ sendFrame: () => {}, endpointDescriptors: [] }); diff --git a/test/testZCLError.js b/test/testZCLError.js index 4c6afa51..8b00be3e 100644 --- a/test/testZCLError.js +++ b/test/testZCLError.js @@ -47,204 +47,238 @@ function makeToClientFrame(cmdId) { return header.toBuffer(); } -describe('ZCLError handling', function() { - it('should return ZCLError status code in Default Response when BoundCluster throws ZCLError', async function() { +describe('ZCLError handling', function () { + it('should return ZCLError status code in Default Response when BoundCluster throws ZCLError', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [OnOffCluster.ID], + }, + ], }); - node.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async setOn() { - throw new ZCLError('NOT_AUTHORIZED'); - } - - })()); + node.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async setOn() { + throw new ZCLError('NOT_AUTHORIZED'); + } + })(), + ); await assert.rejects( () => node.endpoints[1].clusters.onOff.setOn(), - err => { + (err) => { assert.strictEqual(err.message, 'NOT_AUTHORIZED'); return true; }, ); }); - it('should return FAILURE status in Default Response when BoundCluster throws a generic Error', async function() { + it('should return FAILURE status in Default Response when BoundCluster throws a generic Error', async function () { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [OnOffCluster.ID], + }, + ], }); - node.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async setOn() { - throw new Error('something went wrong'); - } - - })()); + node.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async setOn() { + throw new Error('something went wrong'); + } + })(), + ); await assert.rejects( () => node.endpoints[1].clusters.onOff.setOn(), - err => { + (err) => { assert.strictEqual(err.message, 'FAILURE'); return true; }, ); }); - it('should encode ZCLError status code in Default Response frame bytes', async function() { + it('should encode ZCLError status code in Default Response frame bytes', async function () { // NOT_AUTHORIZED = 0x7e per ZCL spec const NOT_AUTHORIZED_STATUS_BYTE = 0x7e; const SET_ON_CMD_ID = OnOffCluster.COMMANDS.setOn.id; - const DEFAULT_RESPONSE_CMD_ID = 0x0B; + const DEFAULT_RESPONSE_CMD_ID = 0x0b; const { controllerNode, deviceNode, capturedFrames } = createSpyNodePair(); - deviceNode.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async setOn() { - throw new ZCLError('NOT_AUTHORIZED'); - } - - })()); + deviceNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async setOn() { + throw new ZCLError('NOT_AUTHORIZED'); + } + })(), + ); await assert.rejects(() => controllerNode.endpoints[1].clusters.onOff.setOn()); // capturedFrames only contains frames sent from the device back to the controller. // Find the Default Response frame among the captured device → controller frames. const defaultResponseFrame = capturedFrames - .map(f => ZCLStandardHeader.fromBuffer(f.data)) - .find(frame => frame.cmdId === DEFAULT_RESPONSE_CMD_ID); + .map((f) => ZCLStandardHeader.fromBuffer(f.data)) + .find((frame) => frame.cmdId === DEFAULT_RESPONSE_CMD_ID); assert.ok(defaultResponseFrame, 'Default Response frame was sent'); - assert.strictEqual(defaultResponseFrame.data[0], SET_ON_CMD_ID, - 'Default Response should reference the received setOn command ID'); - assert.strictEqual(defaultResponseFrame.data[1], NOT_AUTHORIZED_STATUS_BYTE, - 'Default Response should encode NOT_AUTHORIZED (0x7e) as the status byte'); + assert.strictEqual( + defaultResponseFrame.data[0], + SET_ON_CMD_ID, + 'Default Response should reference the received setOn command ID', + ); + assert.strictEqual( + defaultResponseFrame.data[1], + NOT_AUTHORIZED_STATUS_BYTE, + 'Default Response should encode NOT_AUTHORIZED (0x7e) as the status byte', + ); }); - it('should encode FAILURE (0x01) in Default Response frame bytes when a generic Error is thrown', async function() { + it('should encode FAILURE (0x01) in Default Response frame bytes when a generic Error is thrown', async function () { const FAILURE_STATUS_BYTE = 0x01; const SET_ON_CMD_ID = OnOffCluster.COMMANDS.setOn.id; - const DEFAULT_RESPONSE_CMD_ID = 0x0B; + const DEFAULT_RESPONSE_CMD_ID = 0x0b; const { controllerNode, deviceNode, capturedFrames } = createSpyNodePair(); - deviceNode.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async setOn() { - throw new Error('something went wrong'); - } - - })()); + deviceNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async setOn() { + throw new Error('something went wrong'); + } + })(), + ); await assert.rejects(() => controllerNode.endpoints[1].clusters.onOff.setOn()); const defaultResponseFrame = capturedFrames - .map(f => ZCLStandardHeader.fromBuffer(f.data)) - .find(frame => frame.cmdId === DEFAULT_RESPONSE_CMD_ID); + .map((f) => ZCLStandardHeader.fromBuffer(f.data)) + .find((frame) => frame.cmdId === DEFAULT_RESPONSE_CMD_ID); assert.ok(defaultResponseFrame, 'Default Response frame was sent'); - assert.strictEqual(defaultResponseFrame.data[0], SET_ON_CMD_ID, - 'Default Response should reference the received setOn command ID'); - assert.strictEqual(defaultResponseFrame.data[1], FAILURE_STATUS_BYTE, - 'Default Response should encode FAILURE (0x01) as the status byte'); + assert.strictEqual( + defaultResponseFrame.data[0], + SET_ON_CMD_ID, + 'Default Response should reference the received setOn command ID', + ); + assert.strictEqual( + defaultResponseFrame.data[1], + FAILURE_STATUS_BYTE, + 'Default Response should encode FAILURE (0x01) as the status byte', + ); }); - it('should send SUCCESS status byte when ZCLError("SUCCESS") is thrown', async function() { + it('should send SUCCESS status byte when ZCLError("SUCCESS") is thrown', async function () { const SUCCESS_STATUS_BYTE = 0x00; const SET_ON_CMD_ID = OnOffCluster.COMMANDS.setOn.id; - const DEFAULT_RESPONSE_CMD_ID = 0x0B; + const DEFAULT_RESPONSE_CMD_ID = 0x0b; const { controllerNode, deviceNode, capturedFrames } = createSpyNodePair(); - deviceNode.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async setOn() { - throw new ZCLError('SUCCESS'); - } - - })()); + deviceNode.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async setOn() { + throw new ZCLError('SUCCESS'); + } + })(), + ); // The controller should not reject — a SUCCESS default response resolves the command await controllerNode.endpoints[1].clusters.onOff.setOn(); const defaultResponseFrame = capturedFrames - .map(f => ZCLStandardHeader.fromBuffer(f.data)) - .find(frame => frame.cmdId === DEFAULT_RESPONSE_CMD_ID); + .map((f) => ZCLStandardHeader.fromBuffer(f.data)) + .find((frame) => frame.cmdId === DEFAULT_RESPONSE_CMD_ID); assert.ok(defaultResponseFrame, 'Default Response frame was sent'); - assert.strictEqual(defaultResponseFrame.data[0], SET_ON_CMD_ID, - 'Default Response should reference the received setOn command ID'); - assert.strictEqual(defaultResponseFrame.data[1], SUCCESS_STATUS_BYTE, - 'Default Response should encode SUCCESS (0x00) as the status byte'); + assert.strictEqual( + defaultResponseFrame.data[0], + SET_ON_CMD_ID, + 'Default Response should reference the received setOn command ID', + ); + assert.strictEqual( + defaultResponseFrame.data[1], + SUCCESS_STATUS_BYTE, + 'Default Response should encode SUCCESS (0x00) as the status byte', + ); }); // ZCL R8 2.5.12.2 - it('should send UNSUP_COMMAND (0x81) for a command the BoundCluster does not implement', async function() { + it('should send UNSUP_COMMAND (0x81) for a command the BoundCluster does not implement', async function () { const { controllerNode, deviceNode, capturedFrames } = createSpyNodePair(); deviceNode.endpoints[1].bind('onOff', new BoundCluster()); - await assert.rejects(() => controllerNode.endpoints[1].clusters.onOff.setOn(), { message: 'UNSUP_CLUSTER_COMMAND' }); + await assert.rejects(() => controllerNode.endpoints[1].clusters.onOff.setOn(), { + message: 'UNSUP_CLUSTER_COMMAND', + }); const defaultResponseFrame = ZCLStandardHeader.fromBuffer(capturedFrames[0].data); - assert.strictEqual(defaultResponseFrame.cmdId, 0x0B); + assert.strictEqual(defaultResponseFrame.cmdId, 0x0b); assert.strictEqual(defaultResponseFrame.data[1], 0x81); }); - it('should send UNSUP_COMMAND (0x81) for an unknown command id on a client cluster', async function() { + it('should send UNSUP_COMMAND (0x81) for an unknown command id on a client cluster', async function () { const { deviceNode, capturedFrames } = createSpyNodePair(); await deviceNode.handleFrame(1, OnOffCluster.ID, makeToClientFrame(0x7f), {}); assert.strictEqual(ZCLStandardHeader.fromBuffer(capturedFrames[0].data).data[1], 0x81); }); - it('should send UNSUPPORTED_CLUSTER (0xc3) for a server-to-client frame on a cluster the endpoint lacks', async function() { + it('should send UNSUPPORTED_CLUSTER (0xc3) for a server-to-client frame on a cluster the endpoint lacks', async function () { const { deviceNode, capturedFrames } = createSpyNodePair(); await deviceNode.handleFrame(1, LevelControlCluster.ID, makeToClientFrame(0x00), {}); assert.strictEqual(ZCLStandardHeader.fromBuffer(capturedFrames[0].data).data[1], 0xc3); }); - it('should send UNSUPPORTED_CLUSTER (0xc3) when the endpoint has no binding for the cluster', async function() { + it('should send UNSUPPORTED_CLUSTER (0xc3) when the endpoint has no binding for the cluster', async function () { const { controllerNode, capturedFrames } = createSpyNodePair(); - await assert.rejects(() => controllerNode.endpoints[1].clusters.onOff.setOn(), { message: 'UNSUPPORTED_CLUSTER' }); + await assert.rejects(() => controllerNode.endpoints[1].clusters.onOff.setOn(), { + message: 'UNSUPPORTED_CLUSTER', + }); const defaultResponseFrame = ZCLStandardHeader.fromBuffer(capturedFrames[0].data); - assert.strictEqual(defaultResponseFrame.cmdId, 0x0B); + assert.strictEqual(defaultResponseFrame.cmdId, 0x0b); assert.strictEqual(defaultResponseFrame.data[1], 0xc3); }); - it('should propagate different ZCLError status codes', async function() { + it('should propagate different ZCLError status codes', async function () { const statuses = ['NOT_AUTHORIZED', 'INVALID_VALUE', 'INSUFFICIENT_SPACE']; for (const status of statuses) { const node = createMockNode({ loopback: true, - endpoints: [{ - endpointId: 1, - inputClusters: [OnOffCluster.ID], - }], + endpoints: [ + { + endpointId: 1, + inputClusters: [OnOffCluster.ID], + }, + ], }); - node.endpoints[1].bind('onOff', new (class extends BoundCluster { - - async setOn() { - throw new ZCLError(status); - } - - })()); + node.endpoints[1].bind( + 'onOff', + new (class extends BoundCluster { + async setOn() { + throw new ZCLError(status); + } + })(), + ); await assert.rejects( () => node.endpoints[1].clusters.onOff.setOn(), - err => { + (err) => { assert.strictEqual(err.message, status); return true; }, diff --git a/test/util/mockNode.js b/test/util/mockNode.js index a303794e..59e5f47c 100644 --- a/test/util/mockNode.js +++ b/test/util/mockNode.js @@ -21,17 +21,15 @@ const ElectricalMeasurementCluster = require('../../lib/clusters/electricalMeasu */ function createBoundClusterWithAttributes(attributes) { const ClusterClass = class extends BoundCluster { - constructor() { super(); // Store mutable attributes this._attributes = { ...attributes }; } - }; // Define getters/setters for each attribute - Object.keys(attributes).forEach(attrName => { + Object.keys(attributes).forEach((attrName) => { Object.defineProperty(ClusterClass.prototype, attrName, { get() { return this._attributes[attrName]; @@ -78,7 +76,7 @@ function createBoundClusterWithAttributes(attributes) { * @returns {Node} Configured mock Node */ function createMockNode(config) { - const endpointDescriptors = config.endpoints.map(ep => ({ + const endpointDescriptors = config.endpoints.map((ep) => ({ endpointId: ep.endpointId, inputClusters: ep.inputClusters || [], outputClusters: ep.outputClusters || [], @@ -87,16 +85,14 @@ function createMockNode(config) { // Create node reference first so sendFrame can reference it let node; const mockNode = { - sendFrame: config.loopback - ? (...args) => node.handleFrame(...args) - : () => Promise.resolve(), + sendFrame: config.loopback ? (...args) => node.handleFrame(...args) : () => Promise.resolve(), endpointDescriptors, }; node = new Node(mockNode); // Bind clusters with preset attribute values - config.endpoints.forEach(ep => { + config.endpoints.forEach((ep) => { if (!ep.clusters) return; Object.entries(ep.clusters).forEach(([clusterName, attributes]) => { @@ -116,13 +112,13 @@ function createMockNode(config) { * @returns {[Node, Node]} Tuple of connected nodes */ function createConnectedNodePair(config1, config2) { - const endpointDescriptors1 = config1.endpoints.map(ep => ({ + const endpointDescriptors1 = config1.endpoints.map((ep) => ({ endpointId: ep.endpointId, inputClusters: ep.inputClusters || [], outputClusters: ep.outputClusters || [], })); - const endpointDescriptors2 = config2.endpoints.map(ep => ({ + const endpointDescriptors2 = config2.endpoints.map((ep) => ({ endpointId: ep.endpointId, inputClusters: ep.inputClusters || [], outputClusters: ep.outputClusters || [], @@ -145,7 +141,7 @@ function createConnectedNodePair(config1, config2) { node2 = new Node(mockNode2); // Bind clusters for node1 - config1.endpoints.forEach(ep => { + config1.endpoints.forEach((ep) => { if (!ep.clusters) return; Object.entries(ep.clusters).forEach(([clusterName, attributes]) => { const boundCluster = createBoundClusterWithAttributes(attributes); @@ -154,7 +150,7 @@ function createConnectedNodePair(config1, config2) { }); // Bind clusters for node2 - config2.endpoints.forEach(ep => { + config2.endpoints.forEach((ep) => { if (!ep.clusters) return; Object.entries(ep.clusters).forEach(([clusterName, attributes]) => { const boundCluster = createBoundClusterWithAttributes(attributes); @@ -172,140 +168,152 @@ const MOCK_DEVICES = { /** * IAS Zone Motion Sensor */ - motionSensor: (overrides = {}) => createMockNode({ - endpoints: [{ - endpointId: 1, - inputClusters: [BasicCluster.ID, PowerConfigurationCluster.ID, IASZoneCluster.ID], - clusters: { - basic: { - zclVersion: 3, - manufacturerName: 'MockDevice', - modelId: 'MockMotionSensor', - ...overrides.basic, - }, - powerConfiguration: { - batteryPercentageRemaining: 200, // 100% - ...overrides.powerConfiguration, + motionSensor: (overrides = {}) => + createMockNode({ + endpoints: [ + { + endpointId: 1, + inputClusters: [BasicCluster.ID, PowerConfigurationCluster.ID, IASZoneCluster.ID], + clusters: { + basic: { + zclVersion: 3, + manufacturerName: 'MockDevice', + modelId: 'MockMotionSensor', + ...overrides.basic, + }, + powerConfiguration: { + batteryPercentageRemaining: 200, // 100% + ...overrides.powerConfiguration, + }, + iasZone: { + zoneState: 1, // Enrolled + zoneType: 0x000d, // Motion sensor + zoneStatus: 0, + iasCIEAddress: '0x0000000000000000', + zoneId: 1, + ...overrides.iasZone, + }, + }, }, - iasZone: { - zoneState: 1, // Enrolled - zoneType: 0x000D, // Motion sensor - zoneStatus: 0, - iasCIEAddress: '0x0000000000000000', - zoneId: 1, - ...overrides.iasZone, - }, - }, - }], - }), + ], + }), /** * IAS Zone Contact Sensor (door/window) */ - contactSensor: (overrides = {}) => createMockNode({ - endpoints: [{ - endpointId: 1, - inputClusters: [BasicCluster.ID, PowerConfigurationCluster.ID, IASZoneCluster.ID], - clusters: { - basic: { - zclVersion: 3, - manufacturerName: 'MockDevice', - modelId: 'MockContactSensor', - ...overrides.basic, + contactSensor: (overrides = {}) => + createMockNode({ + endpoints: [ + { + endpointId: 1, + inputClusters: [BasicCluster.ID, PowerConfigurationCluster.ID, IASZoneCluster.ID], + clusters: { + basic: { + zclVersion: 3, + manufacturerName: 'MockDevice', + modelId: 'MockContactSensor', + ...overrides.basic, + }, + powerConfiguration: { + batteryPercentageRemaining: 200, + ...overrides.powerConfiguration, + }, + iasZone: { + zoneState: 1, + zoneType: 0x0015, // Contact switch + zoneStatus: 0, + iasCIEAddress: '0x0000000000000000', + zoneId: 1, + ...overrides.iasZone, + }, + }, }, - powerConfiguration: { - batteryPercentageRemaining: 200, - ...overrides.powerConfiguration, - }, - iasZone: { - zoneState: 1, - zoneType: 0x0015, // Contact switch - zoneStatus: 0, - iasCIEAddress: '0x0000000000000000', - zoneId: 1, - ...overrides.iasZone, - }, - }, - }], - }), + ], + }), /** * Temperature + Humidity Sensor */ - tempHumiditySensor: (overrides = {}) => createMockNode({ - endpoints: [{ - endpointId: 1, - inputClusters: [ - BasicCluster.ID, - PowerConfigurationCluster.ID, - TemperatureMeasurementCluster.ID, - RelativeHumidityCluster.ID, - ], - clusters: { - basic: { - zclVersion: 3, - manufacturerName: 'MockDevice', - modelId: 'MockTempHumidity', - ...overrides.basic, + tempHumiditySensor: (overrides = {}) => + createMockNode({ + endpoints: [ + { + endpointId: 1, + inputClusters: [ + BasicCluster.ID, + PowerConfigurationCluster.ID, + TemperatureMeasurementCluster.ID, + RelativeHumidityCluster.ID, + ], + clusters: { + basic: { + zclVersion: 3, + manufacturerName: 'MockDevice', + modelId: 'MockTempHumidity', + ...overrides.basic, + }, + powerConfiguration: { + batteryPercentageRemaining: 200, + ...overrides.powerConfiguration, + }, + temperatureMeasurement: { + measuredValue: 2150, // 21.50°C + minMeasuredValue: -4000, + maxMeasuredValue: 8500, + ...overrides.temperatureMeasurement, + }, + relativeHumidity: { + measuredValue: 6500, // 65.00% + minMeasuredValue: 0, + maxMeasuredValue: 10000, + ...overrides.relativeHumidity, + }, + }, }, - powerConfiguration: { - batteryPercentageRemaining: 200, - ...overrides.powerConfiguration, - }, - temperatureMeasurement: { - measuredValue: 2150, // 21.50°C - minMeasuredValue: -4000, - maxMeasuredValue: 8500, - ...overrides.temperatureMeasurement, - }, - relativeHumidity: { - measuredValue: 6500, // 65.00% - minMeasuredValue: 0, - maxMeasuredValue: 10000, - ...overrides.relativeHumidity, - }, - }, - }], - }), + ], + }), /** * Smart Plug with Power Metering */ - smartPlug: (overrides = {}) => createMockNode({ - endpoints: [{ - endpointId: 1, - inputClusters: [ - BasicCluster.ID, - OnOffCluster.ID, - MeteringCluster.ID, - ElectricalMeasurementCluster.ID, - ], - clusters: { - basic: { - zclVersion: 3, - manufacturerName: 'MockDevice', - modelId: 'MockSmartPlug', - ...overrides.basic, + smartPlug: (overrides = {}) => + createMockNode({ + endpoints: [ + { + endpointId: 1, + inputClusters: [ + BasicCluster.ID, + OnOffCluster.ID, + MeteringCluster.ID, + ElectricalMeasurementCluster.ID, + ], + clusters: { + basic: { + zclVersion: 3, + manufacturerName: 'MockDevice', + modelId: 'MockSmartPlug', + ...overrides.basic, + }, + onOff: { + onOff: false, + ...overrides.onOff, + }, + metering: { + currentSummationDelivered: 12345678, + multiplier: 1, + divisor: 1000, + ...overrides.metering, + }, + electricalMeasurement: { + activePower: 1500, // 150.0W + rmsVoltage: 2300, // 230.0V + rmsCurrent: 652, // 0.652A + ...overrides.electricalMeasurement, + }, + }, }, - onOff: { - onOff: false, - ...overrides.onOff, - }, - metering: { - currentSummationDelivered: 12345678, - multiplier: 1, - divisor: 1000, - ...overrides.metering, - }, - electricalMeasurement: { - activePower: 1500, // 150.0W - rmsVoltage: 2300, // 230.0V - rmsCurrent: 652, // 0.652A - ...overrides.electricalMeasurement, - }, - }, - }], - }), + ], + }), }; module.exports = { diff --git a/tsconfig.json b/tsconfig.json index 82403b49..a7c74bd0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,6 @@ "forceConsistentCasingInFileNames": true, "moduleResolution": "node", "declaration": false, - "skipLibCheck": false, + "skipLibCheck": false } }