From 1e5839f8f40bb41489f5d4e7e1ddc729c7336c9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 02:38:24 +0000 Subject: [PATCH 01/19] chore(deps-dev): bump @microsoft/api-extractor from 7.36.4 to 7.37.3 (#6347) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- packages/cc/package.json | 2 +- packages/config/package.json | 2 +- packages/core/package.json | 2 +- packages/host/package.json | 2 +- packages/nvmedit/package.json | 2 +- packages/serial/package.json | 2 +- packages/shared/package.json | 2 +- packages/testing/package.json | 2 +- packages/zwave-js/package.json | 2 +- yarn.lock | 70 +++++++++++++++++----------------- 11 files changed, 45 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 8abbe45643f3..108a1d6f79f1 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@dprint/json": "^0.17.4", "@dprint/markdown": "^0.16.0", "@dprint/typescript": "^0.87.1", - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@monorepo-utils/workspaces-to-typescript-project-references": "^2.10.2", "@tsconfig/node18": "^18.2.1", "@types/fs-extra": "^11.0.1", diff --git a/packages/cc/package.json b/packages/cc/package.json index a5a9eda89998..84c6aced429b 100644 --- a/packages/cc/package.json +++ b/packages/cc/package.json @@ -71,7 +71,7 @@ "reflect-metadata": "^0.1.13" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@types/fs-extra": "^11.0.1", "@types/node": "^18.17.14", "@zwave-js/maintenance": "workspace:*", diff --git a/packages/config/package.json b/packages/config/package.json index 5383a4c00463..6227a524dc4c 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -66,7 +66,7 @@ "winston": "^3.10.0" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@types/fs-extra": "^11.0.1", "@types/js-levenshtein": "^1.1.1", "@types/json-logic-js": "^2.0.2", diff --git a/packages/core/package.json b/packages/core/package.json index 11f52548323e..424c5fabcd9f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -69,7 +69,7 @@ "winston-transport": "^4.5.0" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@types/node": "^18.17.14", "@types/sinon": "^10.0.16", "@types/triple-beam": "^1.3.2", diff --git a/packages/host/package.json b/packages/host/package.json index 2675f5e6fa29..80cf8037e31f 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -55,7 +55,7 @@ "alcalzone-shared": "^4.0.8" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@types/node": "^18.17.14", "del-cli": "^5.1.0", "typescript": "5.2.2" diff --git a/packages/nvmedit/package.json b/packages/nvmedit/package.json index 88d2c461b90d..ae6552865455 100644 --- a/packages/nvmedit/package.json +++ b/packages/nvmedit/package.json @@ -62,7 +62,7 @@ "test:dirty": "node -r ../../maintenance/esbuild-register.js ../maintenance/src/resolveDirtyTests.ts --run" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@types/fs-extra": "^11.0.1", "@types/node": "^18.17.14", "@types/semver": "^7.5.1", diff --git a/packages/serial/package.json b/packages/serial/package.json index d07462e416da..4011c31e2580 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -64,7 +64,7 @@ "winston": "^3.10.0" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@serialport/binding-mock": "^10.2.2", "@serialport/bindings-interface": "*", "@types/node": "^18.17.14", diff --git a/packages/shared/package.json b/packages/shared/package.json index 2c352923b88e..8c7bd4c4f855 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -55,7 +55,7 @@ "test:dirty": "node -r ../../maintenance/esbuild-register.js ../maintenance/src/resolveDirtyTests.ts --run" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@types/fs-extra": "^11.0.1", "@types/node": "^18.17.14", "@types/sinon": "^10.0.16", diff --git a/packages/testing/package.json b/packages/testing/package.json index f0d818aa7c91..f838a2c4674e 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -46,7 +46,7 @@ "ansi-colors": "^4.1.3" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@types/node": "^18.17.14", "@types/triple-beam": "^1.3.2", "del-cli": "^5.1.0", diff --git a/packages/zwave-js/package.json b/packages/zwave-js/package.json index 716533e9f3e8..0ea9d293f0e6 100644 --- a/packages/zwave-js/package.json +++ b/packages/zwave-js/package.json @@ -107,7 +107,7 @@ "xstate": "4.38.2" }, "devDependencies": { - "@microsoft/api-extractor": "^7.36.4", + "@microsoft/api-extractor": "^7.37.3", "@types/fs-extra": "^11.0.1", "@types/node": "^18.17.14", "@types/proper-lockfile": "^4.1.2", diff --git a/yarn.lock b/yarn.lock index 3de9f418aca7..6ec501842528 100644 --- a/yarn.lock +++ b/yarn.lock @@ -838,27 +838,27 @@ __metadata: languageName: node linkType: hard -"@microsoft/api-extractor-model@npm:7.27.6": - version: 7.27.6 - resolution: "@microsoft/api-extractor-model@npm:7.27.6" +"@microsoft/api-extractor-model@npm:7.28.2": + version: 7.28.2 + resolution: "@microsoft/api-extractor-model@npm:7.28.2" dependencies: "@microsoft/tsdoc": 0.14.2 "@microsoft/tsdoc-config": ~0.16.1 - "@rushstack/node-core-library": 3.59.7 - checksum: 7867feaf3a0e5accfcce3a77681248a319952a266cffc644e4f8f7df1c9e1d55adb5124df901e8cca594bb3e12d361d1fcb2bffbdbb4b20fe3113928f6535975 + "@rushstack/node-core-library": 3.61.0 + checksum: 0eb1cb511414813eeb890778af7dc57e5adcd078ba040a91a736a63964b306a1d31f8b97a76286884432a7884808960a16160d49720c46e23472124f035b9023 languageName: node linkType: hard -"@microsoft/api-extractor@npm:^7.36.4": - version: 7.36.4 - resolution: "@microsoft/api-extractor@npm:7.36.4" +"@microsoft/api-extractor@npm:^7.37.3": + version: 7.37.3 + resolution: "@microsoft/api-extractor@npm:7.37.3" dependencies: - "@microsoft/api-extractor-model": 7.27.6 + "@microsoft/api-extractor-model": 7.28.2 "@microsoft/tsdoc": 0.14.2 "@microsoft/tsdoc-config": ~0.16.1 - "@rushstack/node-core-library": 3.59.7 - "@rushstack/rig-package": 0.4.1 - "@rushstack/ts-command-line": 4.15.2 + "@rushstack/node-core-library": 3.61.0 + "@rushstack/rig-package": 0.5.1 + "@rushstack/ts-command-line": 4.16.1 colors: ~1.2.1 lodash: ~4.17.15 resolve: ~1.22.1 @@ -867,7 +867,7 @@ __metadata: typescript: ~5.0.4 bin: api-extractor: bin/api-extractor - checksum: 92559325cf2407fa27cb9675772956511fa35005f295cdb4dc47abd7ef9c77ba61b0f684c2e952301a76dd2cfa9e398840c8f3d9117d621300e12b0ecfbf8147 + checksum: 6d17df06316508ddbfa0cc2ec6fbba7d63e498cb7d9816be2d993c98507645254ae852d1b536197586e4023777f510242a185f3b1d341d267962c5aad15b2740 languageName: node linkType: hard @@ -1111,9 +1111,9 @@ __metadata: languageName: node linkType: hard -"@rushstack/node-core-library@npm:3.59.7": - version: 3.59.7 - resolution: "@rushstack/node-core-library@npm:3.59.7" +"@rushstack/node-core-library@npm:3.61.0": + version: 3.61.0 + resolution: "@rushstack/node-core-library@npm:3.61.0" dependencies: colors: ~1.2.1 fs-extra: ~7.0.1 @@ -1127,29 +1127,29 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true - checksum: 57819d62fd662a6cf3306bf7d39c11204e094a2d5c2210639c2ac5baee58c183c02023203963cd0484a5623fd9f5dea7a223df843fb52b46a18508e6118cdc19 + checksum: a6f790cd521ca5b0b10ee918d8352c7dd7a0b2457aaf6a4f37d8f7bedee680d7d0126476f5ee5147952e08b11dea37926acb45f7432cd16c828690d3b9bfd34b languageName: node linkType: hard -"@rushstack/rig-package@npm:0.4.1": - version: 0.4.1 - resolution: "@rushstack/rig-package@npm:0.4.1" +"@rushstack/rig-package@npm:0.5.1": + version: 0.5.1 + resolution: "@rushstack/rig-package@npm:0.5.1" dependencies: resolve: ~1.22.1 strip-json-comments: ~3.1.1 - checksum: 68c5ec6c446c35939fca0444fa48e5beda736e3a5816e8b44d83df6ba8b9a2caf0ceddbdc866cd8ad3b523e42877cf6ecd467bc7839e3d618a9bb1c4b3e0b5a5 + checksum: 2d45af13568590cc7f6396b7a075fa27f9676bc04deb39a3867a6f912d43cad45481d8d44482ff6a49c7bd9d428499c2701032602a8241740fc10b19c45dec0f languageName: node linkType: hard -"@rushstack/ts-command-line@npm:4.15.2": - version: 4.15.2 - resolution: "@rushstack/ts-command-line@npm:4.15.2" +"@rushstack/ts-command-line@npm:4.16.1": + version: 4.16.1 + resolution: "@rushstack/ts-command-line@npm:4.16.1" dependencies: "@types/argparse": 1.0.38 argparse: ~1.0.9 colors: ~1.2.1 string-argv: ~0.3.1 - checksum: c80dcfc99630ee51c6654c58ff41f69a3bd89c38e41d9871692bc73ee3c938ced79f8b75e182e492cafb2f6ddeb0628606856af494a0259ff6fac5b248996bed + checksum: f8309a274bdc9d9c87258f5f56b3905b8467319c87cdc757d98bf582b7c4a6925b389bce0ce4125a625a402335f195668dc55547b754f0e9a5d0014154c32d2d languageName: node linkType: hard @@ -1927,7 +1927,7 @@ __metadata: version: 0.0.0-use.local resolution: "@zwave-js/cc@workspace:packages/cc" dependencies: - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@types/fs-extra": ^11.0.1 "@types/node": ^18.17.14 "@zwave-js/core": "workspace:*" @@ -1951,7 +1951,7 @@ __metadata: version: 0.0.0-use.local resolution: "@zwave-js/config@workspace:packages/config" dependencies: - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@types/fs-extra": ^11.0.1 "@types/js-levenshtein": ^1.1.1 "@types/json-logic-js": ^2.0.2 @@ -1992,7 +1992,7 @@ __metadata: resolution: "@zwave-js/core@workspace:packages/core" dependencies: "@alcalzone/jsonl-db": ^3.1.0 - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@types/node": ^18.17.14 "@types/sinon": ^10.0.16 "@types/triple-beam": ^1.3.2 @@ -2063,7 +2063,7 @@ __metadata: version: 0.0.0-use.local resolution: "@zwave-js/host@workspace:packages/host" dependencies: - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@types/node": ^18.17.14 "@zwave-js/config": "workspace:*" "@zwave-js/core": "workspace:*" @@ -2111,7 +2111,7 @@ __metadata: version: 0.0.0-use.local resolution: "@zwave-js/nvmedit@workspace:packages/nvmedit" dependencies: - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@types/fs-extra": ^11.0.1 "@types/node": ^18.17.14 "@types/semver": ^7.5.1 @@ -2149,7 +2149,7 @@ __metadata: "@dprint/json": ^0.17.4 "@dprint/markdown": ^0.16.0 "@dprint/typescript": ^0.87.1 - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@monorepo-utils/workspaces-to-typescript-project-references": ^2.10.2 "@tsconfig/node18": ^18.2.1 "@types/fs-extra": ^11.0.1 @@ -2205,7 +2205,7 @@ __metadata: version: 0.0.0-use.local resolution: "@zwave-js/serial@workspace:packages/serial" dependencies: - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@serialport/binding-mock": ^10.2.2 "@serialport/bindings-interface": "*" "@serialport/stream": ^12.0.0 @@ -2231,7 +2231,7 @@ __metadata: version: 0.0.0-use.local resolution: "@zwave-js/shared@workspace:packages/shared" dependencies: - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@types/fs-extra": ^11.0.1 "@types/node": ^18.17.14 "@types/sinon": ^10.0.16 @@ -2250,7 +2250,7 @@ __metadata: version: 0.0.0-use.local resolution: "@zwave-js/testing@workspace:packages/testing" dependencies: - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@types/node": ^18.17.14 "@types/triple-beam": ^1.3.2 "@zwave-js/core": "workspace:*" @@ -9267,7 +9267,7 @@ __metadata: dependencies: "@alcalzone/jsonl-db": ^3.1.0 "@alcalzone/pak": ^0.9.0 - "@microsoft/api-extractor": ^7.36.4 + "@microsoft/api-extractor": ^7.37.3 "@types/fs-extra": ^11.0.1 "@types/node": ^18.17.14 "@types/proper-lockfile": ^4.1.2 From 6e356dba0898e332f5256ff23b8996760df44953 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 02:40:01 +0000 Subject: [PATCH 02/19] chore(deps-dev): bump xml2js from 0.5.0 to 0.6.2 (#6349) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/config/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/config/package.json b/packages/config/package.json index 6227a524dc4c..9e103c830dd4 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -88,7 +88,7 @@ "sinon": "^15.2.0", "ts-pegjs": "^0.3.1", "typescript": "5.2.2", - "xml2js": "^0.5.0", + "xml2js": "^0.6.2", "yargs": "^17.7.2" } } diff --git a/yarn.lock b/yarn.lock index 6ec501842528..9116e8c7de05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1982,7 +1982,7 @@ __metadata: ts-pegjs: ^0.3.1 typescript: 5.2.2 winston: ^3.10.0 - xml2js: ^0.5.0 + xml2js: ^0.6.2 yargs: ^17.7.2 languageName: unknown linkType: soft @@ -9073,13 +9073,13 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:^0.5.0": - version: 0.5.0 - resolution: "xml2js@npm:0.5.0" +"xml2js@npm:^0.6.2": + version: 0.6.2 + resolution: "xml2js@npm:0.6.2" dependencies: sax: ">=0.6.0" xmlbuilder: ~11.0.0 - checksum: 1aa71d62e5bc2d89138e3929b9ea46459157727759cbc62ef99484b778641c0cd21fb637696c052d901a22f82d092a3e740a16b4ce218e81ac59b933535124ea + checksum: 458a83806193008edff44562c0bdb982801d61ee7867ae58fd35fab781e69e17f40dfeb8fc05391a4648c9c54012066d3955fe5d993ffbe4dc63399023f32ac2 languageName: node linkType: hard From 715a0d425c9d88a3dcd8fa1dea88ca3999698cc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 02:45:49 +0000 Subject: [PATCH 03/19] chore(deps-dev): bump @serialport/bindings-interface from 1.2.1 to 1.2.2 (#6350) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9116e8c7de05..a246bfa74ecf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1177,14 +1177,7 @@ __metadata: languageName: node linkType: hard -"@serialport/bindings-interface@npm:*, @serialport/bindings-interface@npm:^1.2.1": - version: 1.2.1 - resolution: "@serialport/bindings-interface@npm:1.2.1" - checksum: 9d3b8231ecbcf67cc85eb0d4f1db617de3353d71f6ab8124c68ec7009a18ef47bdf9c226e8174e393120def37941a7f61a997bd806370a737c63c5e6ec13a0de - languageName: node - linkType: hard - -"@serialport/bindings-interface@npm:1.2.2": +"@serialport/bindings-interface@npm:*, @serialport/bindings-interface@npm:1.2.2, @serialport/bindings-interface@npm:^1.2.1": version: 1.2.2 resolution: "@serialport/bindings-interface@npm:1.2.2" checksum: 66154b9abe8b3cfc466431f2197ca1d6ed832e121bab15aed9ffcbda5c1d3bc0d11be3cb5c343d3cfc2b0ea69a393900c5be6a77fe9ec646e3004bf6d6a3756d From 05ac6fe102dff45b0290b304423ff7fd79e84cdd Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Wed, 4 Oct 2023 13:35:54 +0200 Subject: [PATCH 04/19] fix: limit number of retries when sending a command results in the transmit status `Fail` (#6361) --- docs/api/driver.md | 6 + packages/zwave-js/src/lib/driver/Driver.ts | 63 ++++++++- .../zwave-js/src/lib/driver/ZWaveOptions.ts | 6 + .../lib/test/driver/controllerJammed.test.ts | 125 +++++++++++++++++- 4 files changed, 192 insertions(+), 8 deletions(-) diff --git a/docs/api/driver.md b/docs/api/driver.md index 40149d8fc604..cbab3b66160f 100644 --- a/docs/api/driver.md +++ b/docs/api/driver.md @@ -698,6 +698,9 @@ interface ZWaveOptions extends ZWaveHostOptions { /** How long generated nonces are valid */ nonce: number; // [3000...20000], default: 5000 ms + /** How long to wait before retrying a command when the controller is jammed */ + retryJammed: number; // [10...5000], default: 1000 ms + /** * How long to wait without pending commands before sending a node back to sleep. * Should be as short as possible to save battery, but long enough to give applications time to react. @@ -736,6 +739,9 @@ interface ZWaveOptions extends ZWaveHostOptions { /** How often the driver should try sending SendData commands before giving up */ sendData: number; // [1...5], default: 3 + /** How often the driver should retry SendData commands while the controller is jammed */ + sendDataJammed: number; // [1...10], default: 5 + /** * How many attempts should be made for each node interview before giving up */ diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index af8c4cca3219..75b843aa94fa 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -254,6 +254,7 @@ const defaultOptions: ZWaveOptions = { nonce: 5000, sendDataCallback: 65000, // as defined in INS13954 sendToSleep: 250, // The default should be enough time for applications to react to devices waking up + retryJammed: 1000, refreshValue: 5000, // Default should handle most slow devices until we have a better solution refreshValueAfterTransition: 1000, // To account for delays in the device serialAPIStarted: 5000, @@ -262,6 +263,7 @@ const defaultOptions: ZWaveOptions = { openSerialPort: 10, controller: 3, sendData: 3, + sendDataJammed: 5, nodeInterview: 5, }, disableOptimisticValueUpdate: false, @@ -313,6 +315,14 @@ function checkOptions(options: ZWaveOptions): void { ZWaveErrorCodes.Driver_InvalidOptions, ); } + if ( + options.timeouts.retryJammed < 10 || options.timeouts.retryJammed > 5000 + ) { + throw new ZWaveError( + `The timeout for retrying while jammed must be between 10 and 5000 milliseconds!`, + ZWaveErrorCodes.Driver_InvalidOptions, + ); + } if ( options.timeouts.sendToSleep < 10 || options.timeouts.sendToSleep > 5000 ) { @@ -369,6 +379,15 @@ function checkOptions(options: ZWaveOptions): void { ZWaveErrorCodes.Driver_InvalidOptions, ); } + if ( + options.attempts.sendDataJammed < 1 + || options.attempts.sendDataJammed > 10 + ) { + throw new ZWaveError( + `The SendData attempts while jammed must be between 1 and 10!`, + ZWaveErrorCodes.Driver_InvalidOptions, + ); + } if ( options.attempts.nodeInterview < 1 || options.attempts.nodeInterview > 10 @@ -4629,7 +4648,8 @@ ${handlers.length} left`, // Step through the transaction as long as it gives us a next message while ((msg = await transaction.generateNextMessage(prevResult))) { - // TODO: refactor this nested loop or make it part of executeSerialAPICommand + // Keep track of how often the controller failed to send a command, to prevent ending up in an infinite loop + let jammedAttempts = 0; attemptMessage: for (let attemptNumber = 1;; attemptNumber++) { try { prevResult = await this.queueSerialAPICommand( @@ -4647,11 +4667,34 @@ ${handlers.length} left`, // Ensure the controller didn't actually transmit && prevResult.txReport?.txTicks === 0 ) { - // The controller is jammed. Wait a second, then try again. - this.controller.setStatus(ControllerStatus.Jammed); - await wait(1000, true); - - continue attemptMessage; + jammedAttempts++; + if ( + jammedAttempts + < this.options.attempts.sendDataJammed + ) { + // The controller is jammed. Wait a bit, then try again. + this.controller.setStatus( + ControllerStatus.Jammed, + ); + await wait( + this.options.timeouts.retryJammed, + true, + ); + + continue attemptMessage; + } else { + // Maybe this isn't actually the controller being jammed. Give up on this command. + this.controller.setStatus( + ControllerStatus.Ready, + ); + + throw new ZWaveError( + `Failed to send the command after ${jammedAttempts} attempts`, + ZWaveErrorCodes.Controller_MessageDropped, + prevResult, + transaction.stack, + ); + } } if ( @@ -4699,12 +4742,18 @@ ${handlers.length} left`, } else if (isMissingControllerACK(e)) { // The controller is unresponsive. Reject the transaction, so we can attempt to recover throw e; + } else if ( + e.code === ZWaveErrorCodes.Controller_MessageDropped + ) { + // We gave up on this command, so don't retry it + throw e; } if ( this.mayRetrySerialAPICommand( msg, - attemptNumber, + // Ignore the number of attempts while jammed + attemptNumber - jammedAttempts, e.code, ) ) { diff --git a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts index 646e7227c618..901a133b20e2 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -29,6 +29,9 @@ export interface ZWaveOptions extends ZWaveHostOptions { /** How long generated nonces are valid */ nonce: number; // [3000...20000], default: 5000 ms + /** How long to wait before retrying a command when the controller is jammed */ + retryJammed: number; // [10...5000], default: 1000 ms + /** * How long to wait without pending commands before sending a node back to sleep. * Should be as short as possible to save battery, but long enough to give applications time to react. @@ -73,6 +76,9 @@ export interface ZWaveOptions extends ZWaveHostOptions { /** How often the driver should try sending SendData commands before giving up */ sendData: number; // [1...5], default: 3 + /** How often the driver should retry SendData commands while the controller is jammed */ + sendDataJammed: number; // [1...10], default: 5 + /** * How many attempts should be made for each node interview before giving up */ diff --git a/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts b/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts index 17c4707ccd5e..8f9b08aaf7be 100644 --- a/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts +++ b/packages/zwave-js/src/lib/test/driver/controllerJammed.test.ts @@ -1,4 +1,10 @@ -import { ControllerStatus, NodeStatus, TransmitStatus } from "@zwave-js/core"; +import { + ControllerStatus, + NodeStatus, + TransmitStatus, + ZWaveErrorCodes, + assertZWaveError, +} from "@zwave-js/core"; import { type MockControllerBehavior } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; import sinon from "sinon"; @@ -13,6 +19,7 @@ import { SendDataResponse, } from "../../serialapi/transport/SendDataMessages"; import { integrationTest } from "../integrationTestSuite"; +import { integrationTest as integrationTestMulti } from "../integrationTestSuiteMulti"; let shouldFail = false; @@ -130,3 +137,119 @@ integrationTest("update the controller status and wait if TX status is Fail", { ]); }, }); + +integrationTestMulti( + "Prevent an infinite loop when the controller is unable to transmit a command to a specific node", + { + // debug: true, + // provisioningDirectory: path.join( + // __dirname, + // "__fixtures/supervision_binary_switch", + // ), + + additionalDriverOptions: { + testingHooks: { + skipNodeInterview: true, + }, + }, + + nodeCapabilities: [ + { + id: 2, + capabilities: { + isListening: true, + }, + }, + { + id: 3, + capabilities: { + isListening: true, + }, + }, + ], + + customSetup: async (driver, controller, mockNodes) => { + // Return a TX status of Fail when desired + const handleSendData: MockControllerBehavior = { + async onHostMessage(host, controller, msg) { + if (msg instanceof SendDataRequest) { + // Commands to node 3 work normally + if (msg.getNodeId() === 3) { + // Defer to the default behavior + return false; + } + + // Check if this command is legal right now + const state = controller.state.get( + MockControllerStateKeys.CommunicationState, + ) as MockControllerCommunicationState | undefined; + if ( + state != undefined + && state !== MockControllerCommunicationState.Idle + ) { + throw new Error( + "Received SendDataRequest while not idle", + ); + } + + // Put the controller into sending state + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.Sending, + ); + + // Notify the host that the message was sent + const res = new SendDataResponse(host, { + wasSent: true, + }); + await controller.sendToHost(res.serialize()); + + await wait(100); + + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.Idle, + ); + + const cb = new SendDataRequestTransmitReport(host, { + callbackId: msg.callbackId, + transmitStatus: TransmitStatus.Fail, + txReport: { + txTicks: 0, + routeSpeed: 0 as any, + routingAttempts: 0, + ackRSSI: 0, + }, + }); + await controller.sendToHost(cb.serialize()); + + return true; + } else if (msg instanceof SendDataAbort) { + // Put the controller into idle state + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.Idle, + ); + } + }, + }; + controller.defineBehavior(handleSendData); + }, + testBody: async (t, driver, nodes, mockController, mockNodes) => { + const [node2, node3] = nodes; + node2.markAsAlive(); + node3.markAsAlive(); + + driver.options.timeouts.retryJammed = 100; + + t.true(await node3.ping()); + await assertZWaveError( + t, + () => node2.commandClasses.Basic.set(99), + { + errorCode: ZWaveErrorCodes.Controller_MessageDropped, + }, + ); + }, + }, +); From 2da6d9e1c6f3253df0d4dc95e2b8b62a6ca48175 Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Wed, 4 Oct 2023 14:13:32 +0200 Subject: [PATCH 05/19] fix: avoid calling `assignReturnRoutes` with controller as target (#6362) --- .../zwave-js/src/lib/controller/Controller.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/zwave-js/src/lib/controller/Controller.ts b/packages/zwave-js/src/lib/controller/Controller.ts index d8d4a47dd04b..ad18a70e012e 100644 --- a/packages/zwave-js/src/lib/controller/Controller.ts +++ b/packages/zwave-js/src/lib/controller/Controller.ts @@ -4175,7 +4175,7 @@ supported CCs: ${ } // 2. re-create the SUC return route, just in case - node.hasSUCReturnRoute ||= await this.assignSUCReturnRoutes(nodeId); + node.hasSUCReturnRoute = await this.assignSUCReturnRoutes(nodeId); // 3. delete all return routes to get rid of potential priority return routes for (let attempt = 1; attempt <= maxAttempts; attempt++) { @@ -4199,7 +4199,7 @@ supported CCs: ${ } } - // 4. Assign return routes to all association destinations. + // 4. Assign return routes to all association destinations... let associatedNodes: number[] = []; try { associatedNodes = distinct( @@ -4208,14 +4208,14 @@ supported CCs: ${ (assocs: AssociationAddress[]) => assocs.map((a) => a.nodeId), ), - ).sort(); + ) + // ...except the controller itself, which was handled by step 2 + .filter((id) => id !== this._ownNodeId!) + .sort(); } catch { /* ignore */ } - // One of those should probably be the controller. Not sure if the SUC return route is enough. - if (!associatedNodes.includes(this._ownNodeId!)) { - associatedNodes.unshift(this._ownNodeId!); - } + this.driver.controllerLog.logNode(nodeId, { message: `assigning return routes to the following nodes: ${associatedNodes.join(", ")}`, @@ -5244,7 +5244,11 @@ ${associatedNodes.join(", ")}`, // Except to the controller itself - this route is already known ).filter((id) => id !== this.ownNodeId); for (const id of destinationNodeIDs) { - await this.assignReturnRoutes(source.nodeId, id); + if (id === this._ownNodeId) { + await this.assignSUCReturnRoutes(source.nodeId); + } else { + await this.assignReturnRoutes(source.nodeId, id); + } } } From ae9a87bf3a2e9220870f64ff8799a214ceebae0e Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Thu, 5 Oct 2023 20:43:51 +0200 Subject: [PATCH 06/19] fix: implement workaround for some 500 series controllers replying with invalid callbacks in case of SUC route assignment failure (#6370) --- docs/config-files/file-format.md | 6 + maintenance/schemas/device-config.json | 8 + .../config/devices/0x0000/husbzb-1.json | 8 + .../config/config/devices/0x0086/zw090.json | 8 + packages/config/src/devices/CompatConfig.ts | 19 ++ packages/serial/src/message/Message.ts | 20 +- .../testing/src/MockControllerCapabilities.ts | 26 +- packages/testing/src/index.ts | 1 + .../zwave-js/src/lib/controller/Controller.ts | 34 ++- .../AssignSUCReturnRouteMessages.ts | 19 +- .../DeleteSUCReturnRouteMessages.ts | 94 +++++-- .../invalidCallbackFunctionTypes.test.ts | 241 ++++++++++++++++++ .../src/lib/test/integrationTestSuite.ts | 23 +- .../src/lib/test/integrationTestSuiteMulti.ts | 11 +- .../lib/test/integrationTestSuiteShared.ts | 5 +- 15 files changed, 466 insertions(+), 57 deletions(-) create mode 100644 packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts diff --git a/docs/config-files/file-format.md b/docs/config-files/file-format.md index a23174ba8b24..90fc592bc6f1 100644 --- a/docs/config-files/file-format.md +++ b/docs/config-files/file-format.md @@ -364,6 +364,12 @@ Several command classes are refreshed regularly (every couple of hours) if they By default, received `Basic CC::Report` commands are mapped to a more appropriate CC. Setting `disableBasicMapping` to `true` disables this feature. +### `disableCallbackFunctionTypeCheck` + +By default, responses or callbacks for Serial API commands must have the same function type (command identifier) in order to be recognized. However, in some situations, certain controllers send a callback with an invalid function type. In this case, the faulty commands may be listed in the `disableCallbackFunctionTypeCheck` array to disable the check for a matching function type. + +> [!NOTE] This compat flag requires command-specific support and is not a generic escape hatch. + ### `disableStrictEntryControlDataValidation` The specifications mandate strict rules for the data and sequence numbers in `Entry Control CC Notifications`, which some devices do not follow, causing the notifications to get dropped. Setting `disableStrictEntryControlDataValidation` to `true` disables these strict checks. diff --git a/maintenance/schemas/device-config.json b/maintenance/schemas/device-config.json index cc41d9a053e4..85141039e110 100644 --- a/maintenance/schemas/device-config.json +++ b/maintenance/schemas/device-config.json @@ -568,6 +568,14 @@ "disableBasicMapping": { "const": true }, + "disableCallbackFunctionTypeCheck": { + "type": "array", + "items": { + "type": "integer", + "minimum": 1 + }, + "minItems": 1 + }, "disableStrictEntryControlDataValidation": { "const": true }, diff --git a/packages/config/config/devices/0x0000/husbzb-1.json b/packages/config/config/devices/0x0000/husbzb-1.json index a037369ffe78..5be87d4123e0 100644 --- a/packages/config/config/devices/0x0000/husbzb-1.json +++ b/packages/config/config/devices/0x0000/husbzb-1.json @@ -12,5 +12,13 @@ "firmwareVersion": { "min": "0.0", "max": "255.255" + }, + "compat": { + // In some situations, AssignSUCReturnRoute and DeleteSUCReturnRoute get answered with a wrong callback function type, + // triggering Z-Wave JS's unresponsive controller check. + "disableCallbackFunctionTypeCheck": [ + 81, // AssignSUCReturnRoute + 85 // DeleteSUCReturnRoute + ] } } diff --git a/packages/config/config/devices/0x0086/zw090.json b/packages/config/config/devices/0x0086/zw090.json index ffde9637b1b3..4dfde0af7952 100644 --- a/packages/config/config/devices/0x0086/zw090.json +++ b/packages/config/config/devices/0x0086/zw090.json @@ -58,5 +58,13 @@ "metadata": { "reset": "Use this procedure only in the event that the primary controller is missing or otherwise inoperable.\n\nPress and hold the Action Button on Z-Stick for 20 seconds and then release", "manual": "https://products.z-wavealliance.org/ProductManual/File?folder=&filename=MarketCertificationFiles/1345/Z%20Stick%20Gen5%20manual%201.pdf" + }, + "compat": { + // In some situations, AssignSUCReturnRoute and DeleteSUCReturnRoute get answered with a wrong callback function type, + // triggering Z-Wave JS's unresponsive controller check. + "disableCallbackFunctionTypeCheck": [ + 81, // AssignSUCReturnRoute + 85 // DeleteSUCReturnRoute + ] } } diff --git a/packages/config/src/devices/CompatConfig.ts b/packages/config/src/devices/CompatConfig.ts index 3a513d5fdbf3..ce20fa61028f 100644 --- a/packages/config/src/devices/CompatConfig.ts +++ b/packages/config/src/devices/CompatConfig.ts @@ -88,6 +88,23 @@ compat option disableBasicMapping must be true or omitted`, this.disableBasicMapping = definition.disableBasicMapping; } + if (definition.disableCallbackFunctionTypeCheck != undefined) { + if ( + !isArray(definition.disableCallbackFunctionTypeCheck) + || !definition.disableCallbackFunctionTypeCheck.every( + (d: any) => typeof d === "number" && d % 1 === 0 && d > 0, + ) + ) { + throwInvalidConfig( + "devices", + `config/devices/${filename}: +when present, compat option disableCallbackFunctionTypeCheck msut be an array of positive integers`, + ); + } + this.disableCallbackFunctionTypeCheck = + definition.disableCallbackFunctionTypeCheck; + } + if (definition.disableStrictEntryControlDataValidation != undefined) { if (definition.disableStrictEntryControlDataValidation !== true) { throwInvalidConfig( @@ -568,6 +585,7 @@ compat option overrideQueries must be an object!`, public readonly disableBasicMapping?: boolean; public readonly disableStrictEntryControlDataValidation?: boolean; public readonly disableStrictMeasurementValidation?: boolean; + public readonly disableCallbackFunctionTypeCheck?: number[]; public readonly enableBasicSetMapping?: boolean; public readonly forceNotificationIdleReset?: boolean; public readonly forceSceneControllerGroupCount?: number; @@ -608,6 +626,7 @@ compat option overrideQueries must be an object!`, "removeCCs", "disableAutoRefresh", "disableBasicMapping", + "disableCallbackFunctionTypeCheck", "disableStrictEntryControlDataValidation", "disableStrictMeasurementValidation", "enableBasicSetMapping", diff --git a/packages/serial/src/message/Message.ts b/packages/serial/src/message/Message.ts index bd6a61d419db..4ef2e7d83240 100644 --- a/packages/serial/src/message/Message.ts +++ b/packages/serial/src/message/Message.ts @@ -70,7 +70,7 @@ export type MessageOptions = */ export class Message { public constructor( - protected host: ZWaveHost, + public readonly host: ZWaveHost, options: MessageOptions = {}, ) { // decide which implementation we follow @@ -355,12 +355,18 @@ export class Message { /** Checks if a message is an expected callback for this message */ public isExpectedCallback(msg: Message): boolean { if (msg.type !== MessageType.Request) return false; - // If a received request included a callback id, enforce that the response contains the same - if ( - this.hasCallbackId() - && (!msg.hasCallbackId() || this._callbackId !== msg._callbackId) - ) { - return false; + + // Some controllers have a bug causing them to send a callback with a function type of 0 and no callback ID + // To prevent this from triggering the unresponsive controller detection we need to forward these messages as if they were correct + if (msg.functionType !== 0 as any) { + // If a received request included a callback id, enforce that the response contains the same + if ( + this.hasCallbackId() + && (!msg.hasCallbackId() + || this._callbackId !== msg._callbackId) + ) { + return false; + } } return this.testMessage(msg, this.expectedCallback); diff --git a/packages/testing/src/MockControllerCapabilities.ts b/packages/testing/src/MockControllerCapabilities.ts index f4c0a4a99b9f..19700d231b24 100644 --- a/packages/testing/src/MockControllerCapabilities.ts +++ b/packages/testing/src/MockControllerCapabilities.ts @@ -31,23 +31,27 @@ export interface MockControllerCapabilities { watchdogEnabled: boolean; } +export function getDefaultSupportedFunctionTypes(): FunctionType[] { + return [ + FunctionType.GetSerialApiInitData, + FunctionType.GetControllerCapabilities, + FunctionType.SendData, + FunctionType.SendDataMulticast, + FunctionType.GetControllerVersion, + FunctionType.GetControllerId, + FunctionType.GetNodeProtocolInfo, + FunctionType.RequestNodeInfo, + FunctionType.AssignSUCReturnRoute, + ]; +} + export function getDefaultMockControllerCapabilities(): MockControllerCapabilities { return { firmwareVersion: "1.0", manufacturerId: 0xffff, productType: 0xffff, productId: 0xfffe, - supportedFunctionTypes: [ - FunctionType.GetSerialApiInitData, - FunctionType.GetControllerCapabilities, - FunctionType.SendData, - FunctionType.SendDataMulticast, - FunctionType.GetControllerVersion, - FunctionType.GetControllerId, - FunctionType.GetNodeProtocolInfo, - FunctionType.RequestNodeInfo, - FunctionType.AssignSUCReturnRoute, - ], + supportedFunctionTypes: getDefaultSupportedFunctionTypes(), controllerType: ZWaveLibraryTypes["Static Controller"], libraryVersion: "Z-Wave 7.17.99", diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index 8039e368caba..6a59d60e57f9 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -1,5 +1,6 @@ export * from "./CCSpecificCapabilities"; export * from "./MockController"; +export { getDefaultSupportedFunctionTypes } from "./MockControllerCapabilities"; export * from "./MockNode"; export { ccCaps } from "./MockNodeCapabilities"; export type { PartialCCCapabilities } from "./MockNodeCapabilities"; diff --git a/packages/zwave-js/src/lib/controller/Controller.ts b/packages/zwave-js/src/lib/controller/Controller.ts index ad18a70e012e..571dfa70f4c8 100644 --- a/packages/zwave-js/src/lib/controller/Controller.ts +++ b/packages/zwave-js/src/lib/controller/Controller.ts @@ -215,7 +215,7 @@ import { } from "../serialapi/network-mgmt/AssignReturnRouteMessages"; import { AssignSUCReturnRouteRequest, - type AssignSUCReturnRouteRequestTransmitReport, + AssignSUCReturnRouteRequestTransmitReport, } from "../serialapi/network-mgmt/AssignSUCReturnRouteMessages"; import { DeleteReturnRouteRequest, @@ -223,7 +223,7 @@ import { } from "../serialapi/network-mgmt/DeleteReturnRouteMessages"; import { DeleteSUCReturnRouteRequest, - type DeleteSUCReturnRouteRequestTransmitReport, + DeleteSUCReturnRouteRequestTransmitReport, } from "../serialapi/network-mgmt/DeleteSUCReturnRouteMessages"; import { GetPriorityRouteRequest, @@ -4319,14 +4319,23 @@ ${associatedNodes.join(", ")}`, await this.deleteSUCReturnRoutes(nodeId); try { - const result = await this.driver.sendMessage< - AssignSUCReturnRouteRequestTransmitReport - >( + const result = await this.driver.sendMessage( new AssignSUCReturnRouteRequest(this.driver, { nodeId, }), ); + if ( + !(result instanceof AssignSUCReturnRouteRequestTransmitReport) + ) { + this.driver.controllerLog.logNode( + nodeId, + `Assigning SUC return route failed: Invalid callback received`, + "error", + ); + return false; + } + const success = this.handleRouteAssignmentTransmitReport( result, nodeId, @@ -4492,14 +4501,23 @@ ${associatedNodes.join(", ")}`, }); try { - const result = await this.driver.sendMessage< - DeleteSUCReturnRouteRequestTransmitReport - >( + const result = await this.driver.sendMessage( new DeleteSUCReturnRouteRequest(this.driver, { nodeId, }), ); + if ( + !(result instanceof DeleteSUCReturnRouteRequestTransmitReport) + ) { + this.driver.controllerLog.logNode( + nodeId, + `Deleting SUC return route failed: Invalid callback received`, + "error", + ); + return false; + } + const success = this.handleRouteAssignmentTransmitReport( result, nodeId, diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts index e70b3f3a4394..1eb2f7fc64e5 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/AssignSUCReturnRouteMessages.ts @@ -53,8 +53,25 @@ export interface AssignSUCReturnRouteRequestOptions extends MessageBaseOptions { nodeId: number; } +function testAssignSUCReturnRouteCallback( + sent: AssignSUCReturnRouteRequest, + callback: Message, +): boolean { + // Some controllers have a bug where they incorrectly respond with DeleteSUCReturnRoute + if ( + callback.host + .getDeviceConfig?.(callback.host.ownNodeId) + ?.compat + ?.disableCallbackFunctionTypeCheck + ?.includes(FunctionType.AssignSUCReturnRoute) + ) { + return true; + } + return callback.functionType === FunctionType.AssignSUCReturnRoute; +} + @expectedResponse(FunctionType.AssignSUCReturnRoute) -@expectedCallback(FunctionType.AssignSUCReturnRoute) +@expectedCallback(testAssignSUCReturnRouteCallback) export class AssignSUCReturnRouteRequest extends AssignSUCReturnRouteRequestBase implements INodeQuery { diff --git a/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts b/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts index 86e62ad70e51..40960535f621 100644 --- a/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts +++ b/packages/zwave-js/src/lib/serialapi/network-mgmt/DeleteSUCReturnRouteMessages.ts @@ -2,8 +2,6 @@ import { type MessageOrCCLogEntry, MessagePriority, TransmitStatus, - ZWaveError, - ZWaveErrorCodes, encodeNodeID, } from "@zwave-js/core"; import type { ZWaveHost } from "@zwave-js/host"; @@ -14,6 +12,7 @@ import { type MessageBaseOptions, type MessageDeserializationOptions, type MessageOptions, + MessageOrigin, MessageType, expectedCallback, expectedResponse, @@ -27,12 +26,24 @@ import { getEnumMemberName } from "@zwave-js/shared"; @priority(MessagePriority.Normal) export class DeleteSUCReturnRouteRequestBase extends Message { public constructor(host: ZWaveHost, options: MessageOptions) { - if ( - gotDeserializationOptions(options) - && (new.target as any) !== DeleteSUCReturnRouteRequestTransmitReport - ) { - return new DeleteSUCReturnRouteRequestTransmitReport(host, options); + if (gotDeserializationOptions(options)) { + if ( + options.origin === MessageOrigin.Host + && (new.target as any) !== DeleteSUCReturnRouteRequest + ) { + return new DeleteSUCReturnRouteRequest(host, options); + } else if ( + options.origin !== MessageOrigin.Host + && (new.target as any) + !== DeleteSUCReturnRouteRequestTransmitReport + ) { + return new DeleteSUCReturnRouteRequestTransmitReport( + host, + options, + ); + } } + super(host, options); } } @@ -41,8 +52,25 @@ export interface DeleteSUCReturnRouteRequestOptions extends MessageBaseOptions { nodeId: number; } +function testDeleteSUCReturnRouteCallback( + sent: DeleteSUCReturnRouteRequest, + callback: Message, +): boolean { + // Some controllers have a bug where they incorrectly respond with DeleteSUCReturnRoute + if ( + callback.host + .getDeviceConfig?.(callback.host.ownNodeId) + ?.compat + ?.disableCallbackFunctionTypeCheck + ?.includes(FunctionType.DeleteSUCReturnRoute) + ) { + return true; + } + return callback.functionType === FunctionType.DeleteSUCReturnRoute; +} + @expectedResponse(FunctionType.DeleteSUCReturnRoute) -@expectedCallback(FunctionType.DeleteSUCReturnRoute) +@expectedCallback(testDeleteSUCReturnRouteCallback) export class DeleteSUCReturnRouteRequest extends DeleteSUCReturnRouteRequestBase implements INodeQuery { @@ -54,10 +82,8 @@ export class DeleteSUCReturnRouteRequest extends DeleteSUCReturnRouteRequestBase ) { super(host, options); if (gotDeserializationOptions(options)) { - throw new ZWaveError( - `${this.constructor.name}: deserialization not implemented`, - ZWaveErrorCodes.Deserialization_NotImplemented, - ); + this.nodeId = this.payload[0]; + this.callbackId = this.payload[1]; } else { this.nodeId = options.nodeId; } @@ -73,16 +99,26 @@ export class DeleteSUCReturnRouteRequest extends DeleteSUCReturnRouteRequestBase } } +interface DeleteSUCReturnRouteResponseOptions extends MessageBaseOptions { + wasExecuted: boolean; +} + @messageTypes(MessageType.Response, FunctionType.DeleteSUCReturnRoute) export class DeleteSUCReturnRouteResponse extends Message implements SuccessIndicator { public constructor( host: ZWaveHost, - options: MessageDeserializationOptions, + options: + | MessageDeserializationOptions + | DeleteSUCReturnRouteResponseOptions, ) { super(host, options); - this.wasExecuted = this.payload[0] !== 0; + if (gotDeserializationOptions(options)) { + this.wasExecuted = this.payload[0] !== 0; + } else { + this.wasExecuted = options.wasExecuted; + } } public isOK(): boolean { @@ -91,6 +127,11 @@ export class DeleteSUCReturnRouteResponse extends Message public readonly wasExecuted: boolean; + public serialize(): Buffer { + this.payload = Buffer.from([this.wasExecuted ? 0x01 : 0]); + return super.serialize(); + } + public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), @@ -99,18 +140,32 @@ export class DeleteSUCReturnRouteResponse extends Message } } +interface DeleteSUCReturnRouteRequestTransmitReportOptions + extends MessageBaseOptions +{ + transmitStatus: TransmitStatus; + callbackId: number; +} + export class DeleteSUCReturnRouteRequestTransmitReport extends DeleteSUCReturnRouteRequestBase implements SuccessIndicator { public constructor( host: ZWaveHost, - options: MessageDeserializationOptions, + options: + | MessageDeserializationOptions + | DeleteSUCReturnRouteRequestTransmitReportOptions, ) { super(host, options); - this.callbackId = this.payload[0]; - this.transmitStatus = this.payload[1]; + if (gotDeserializationOptions(options)) { + this.callbackId = this.payload[0]; + this.transmitStatus = this.payload[1]; + } else { + this.callbackId = options.callbackId; + this.transmitStatus = options.transmitStatus; + } } public isOK(): boolean { @@ -122,6 +177,11 @@ export class DeleteSUCReturnRouteRequestTransmitReport public readonly transmitStatus: TransmitStatus; + public serialize(): Buffer { + this.payload = Buffer.from([this.callbackId, this.transmitStatus]); + return super.serialize(); + } + public toLogEntry(): MessageOrCCLogEntry { return { ...super.toLogEntry(), diff --git a/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts b/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts new file mode 100644 index 000000000000..5e5d05951f9e --- /dev/null +++ b/packages/zwave-js/src/lib/test/compat/invalidCallbackFunctionTypes.test.ts @@ -0,0 +1,241 @@ +import { WakeUpTime, ZWaveProtocolCCAssignSUCReturnRoute } from "@zwave-js/cc"; +import { TransmitStatus, ZWaveDataRate } from "@zwave-js/core"; +import { FunctionType } from "@zwave-js/serial"; +import { + type MockControllerBehavior, + createMockZWaveRequestFrame, + getDefaultSupportedFunctionTypes, +} from "@zwave-js/testing"; +import { + MockControllerCommunicationState, + MockControllerStateKeys, +} from "../../controller/MockControllerState"; +import { + AssignSUCReturnRouteRequest, + AssignSUCReturnRouteResponse, +} from "../../serialapi/network-mgmt/AssignSUCReturnRouteMessages"; +import { + DeleteSUCReturnRouteRequest, + DeleteSUCReturnRouteRequestTransmitReport, + DeleteSUCReturnRouteResponse, +} from "../../serialapi/network-mgmt/DeleteSUCReturnRouteMessages"; +import { integrationTest } from "../integrationTestSuite"; + +// Repro for https://github.com/zwave-js/node-zwave-js/issues/6363 + +integrationTest( + "Invalid callback function types don't trigger the unresponsive controller detection", + { + // debug: true, + + controllerCapabilities: { + // Aeotec Z-Stick Gen5+, FW 1.2 + manufacturerId: 0x0086, + productType: 0x0001, + productId: 0x005a, + firmwareVersion: "1.2", + supportedFunctionTypes: [ + ...getDefaultSupportedFunctionTypes(), + FunctionType.AssignSUCReturnRoute, + FunctionType.DeleteSUCReturnRoute, + ], + }, + + customSetup: async (driver, controller, mockNode) => { + // Incorrectly respond to AssignSUCReturnRoute with DeleteSUCReturnRoute + const handleAssignSUCReturnRoute: MockControllerBehavior = { + async onHostMessage(host, controller, msg) { + if (msg instanceof AssignSUCReturnRouteRequest) { + // Check if this command is legal right now + const state = controller.state.get( + MockControllerStateKeys.CommunicationState, + ) as MockControllerCommunicationState | undefined; + if ( + state != undefined + && state !== MockControllerCommunicationState.Idle + ) { + throw new Error( + "Received AssignSUCReturnRouteRequest while not idle", + ); + } + + // Put the controller into sending state + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.Sending, + ); + + const expectCallback = msg.callbackId !== 0; + + // Send the command to the node + const node = controller.nodes.get(msg.getNodeId()!)!; + const command = new ZWaveProtocolCCAssignSUCReturnRoute( + host, + { + nodeId: node.id, + destinationNodeId: controller.host.ownNodeId, + repeaters: [], // don't care + routeIndex: 0, // don't care + destinationSpeed: ZWaveDataRate["100k"], + destinationWakeUp: WakeUpTime.None, + }, + ); + const frame = createMockZWaveRequestFrame(command, { + ackRequested: expectCallback, + }); + const ackPromise = controller.sendToNode(node, frame); + + // Notify the host that the message was sent + const res = new AssignSUCReturnRouteResponse(host, { + wasExecuted: true, + }); + await controller.sendToHost(res.serialize()); + + let ack = false; + if (expectCallback) { + // Put the controller into waiting state + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.WaitingForNode, + ); + + // Wait for the ACK and notify the host + try { + const ackResult = await ackPromise; + ack = !!ackResult?.ack; + } catch { + // No response + } + } + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.Idle, + ); + + if (expectCallback) { + const cb = + new DeleteSUCReturnRouteRequestTransmitReport( + host, + { + callbackId: msg.callbackId, + transmitStatus: ack + ? TransmitStatus.OK + : TransmitStatus.NoAck, + }, + ); + + await controller.sendToHost(cb.serialize()); + } + return true; + } + }, + }; + controller.defineBehavior(handleAssignSUCReturnRoute); + + // Incorrectly respond to DeleteSUCReturnRoute with a message with function type 0 + const handleDeleteSUCReturnRoute: MockControllerBehavior = { + async onHostMessage(host, controller, msg) { + if (msg instanceof DeleteSUCReturnRouteRequest) { + // Check if this command is legal right now + const state = controller.state.get( + MockControllerStateKeys.CommunicationState, + ) as MockControllerCommunicationState | undefined; + if ( + state != undefined + && state !== MockControllerCommunicationState.Idle + ) { + throw new Error( + "Received DeleteSUCReturnRouteRequest while not idle", + ); + } + + // Put the controller into sending state + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.Sending, + ); + + const expectCallback = msg.callbackId !== 0; + + // Send the command to the node + const node = controller.nodes.get(msg.getNodeId()!)!; + const command = new ZWaveProtocolCCAssignSUCReturnRoute( + host, + { + nodeId: node.id, + destinationNodeId: controller.host.ownNodeId, + repeaters: [], // don't care + routeIndex: 0, // don't care + destinationSpeed: ZWaveDataRate["100k"], + destinationWakeUp: WakeUpTime.None, + }, + ); + const frame = createMockZWaveRequestFrame(command, { + ackRequested: expectCallback, + }); + const ackPromise = controller.sendToNode(node, frame); + + // Notify the host that the message was sent + const res = new DeleteSUCReturnRouteResponse(host, { + wasExecuted: true, + }); + await controller.sendToHost(res.serialize()); + + let ack = false; + if (expectCallback) { + // Put the controller into waiting state + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.WaitingForNode, + ); + + // Wait for the ACK and notify the host + try { + const ackResult = await ackPromise; + ack = !!ackResult?.ack; + } catch { + // No response + } + } + controller.state.set( + MockControllerStateKeys.CommunicationState, + MockControllerCommunicationState.Idle, + ); + + if (expectCallback) { + const cb = + new DeleteSUCReturnRouteRequestTransmitReport( + host, + { + callbackId: msg.callbackId, + transmitStatus: ack + ? TransmitStatus.OK + : TransmitStatus.NoAck, + }, + ); + // @ts-expect-error 0 is not a valid function type + cb.functionType = 0; + + await controller.sendToHost(cb.serialize()); + } + return true; + } + }, + }; + controller.defineBehavior(handleDeleteSUCReturnRoute); + }, + testBody: async (t, driver, node, mockController, mockNode) => { + mockController.clearReceivedHostMessages(); + driver.options.timeouts.sendDataCallback = 1000; + let result = await driver.controller.assignSUCReturnRoutes( + node.id, + ); + t.false(result); + + result = await driver.controller.deleteSUCReturnRoutes( + node.id, + ); + t.false(result); + }, + }, +); diff --git a/packages/zwave-js/src/lib/test/integrationTestSuite.ts b/packages/zwave-js/src/lib/test/integrationTestSuite.ts index fe733f0e1cd8..95a8f6afc715 100644 --- a/packages/zwave-js/src/lib/test/integrationTestSuite.ts +++ b/packages/zwave-js/src/lib/test/integrationTestSuite.ts @@ -1,7 +1,8 @@ import type { MockPortBinding } from "@zwave-js/serial/mock"; -import { type DeepPartial, noop } from "@zwave-js/shared"; +import { noop } from "@zwave-js/shared"; import { type MockController, + type MockControllerOptions, type MockNode, type MockNodeOptions, } from "@zwave-js/testing"; @@ -12,7 +13,7 @@ import crypto from "node:crypto"; import os from "node:os"; import path from "node:path"; import type { Driver } from "../driver/Driver"; -import type { ZWaveOptions } from "../driver/ZWaveOptions"; +import type { PartialZWaveOptions } from "../driver/ZWaveOptions"; import type { ZWaveNode } from "../node/Node"; import { prepareDriver, prepareMocks } from "./integrationTestSuiteShared"; @@ -23,6 +24,7 @@ interface IntegrationTestOptions { provisioningDirectory?: string; /** Whether the recorded messages and frames should be cleared before executing the test body. Default: true. */ clearMessageStatsBeforeTest?: boolean; + controllerCapabilities?: MockControllerOptions["capabilities"]; nodeCapabilities?: MockNodeOptions["capabilities"]; customSetup?: ( driver: Driver, @@ -36,7 +38,7 @@ interface IntegrationTestOptions { mockController: MockController, mockNode: MockNode, ) => Promise; - additionalDriverOptions?: DeepPartial; + additionalDriverOptions?: PartialZWaveOptions; } export interface IntegrationTestFn { @@ -55,6 +57,7 @@ function suite( modifier?: "only" | "skip", ) { const { + controllerCapabilities, nodeCapabilities, customSetup, testBody, @@ -98,12 +101,18 @@ function suite( ({ mockController, mockNodes: [mockNode], - } = prepareMocks(mockPort, undefined, [ + } = prepareMocks( + mockPort, { - id: 2, - capabilities: nodeCapabilities, + capabilities: controllerCapabilities, }, - ])); + [ + { + id: 2, + capabilities: nodeCapabilities, + }, + ], + )); if (customSetup) { await customSetup(driver, mockController, mockNode); diff --git a/packages/zwave-js/src/lib/test/integrationTestSuiteMulti.ts b/packages/zwave-js/src/lib/test/integrationTestSuiteMulti.ts index ef8e05b6702a..f71fa4683738 100644 --- a/packages/zwave-js/src/lib/test/integrationTestSuiteMulti.ts +++ b/packages/zwave-js/src/lib/test/integrationTestSuiteMulti.ts @@ -2,6 +2,7 @@ import type { MockPortBinding } from "@zwave-js/serial/mock"; import { noop } from "@zwave-js/shared"; import { type MockController, + type MockControllerOptions, type MockNode, type MockNodeOptions, } from "@zwave-js/testing"; @@ -12,7 +13,7 @@ import crypto from "node:crypto"; import os from "node:os"; import path from "node:path"; import type { Driver } from "../driver/Driver"; -import type { ZWaveOptions } from "../driver/ZWaveOptions"; +import type { PartialZWaveOptions } from "../driver/ZWaveOptions"; import type { ZWaveNode } from "../node/Node"; import { prepareDriver, prepareMocks } from "./integrationTestSuiteShared"; @@ -23,6 +24,7 @@ interface IntegrationTestOptions { provisioningDirectory?: string; /** Whether the recorded messages and frames should be cleared before executing the test body. Default: true. */ clearMessageStatsBeforeTest?: boolean; + controllerCapabilities?: MockControllerOptions["capabilities"]; nodeCapabilities?: Pick[]; customSetup?: ( driver: Driver, @@ -36,7 +38,7 @@ interface IntegrationTestOptions { mockController: MockController, mockNodes: MockNode[], ) => Promise; - additionalDriverOptions?: Partial; + additionalDriverOptions?: PartialZWaveOptions; } export interface IntegrationTestFn { @@ -55,6 +57,7 @@ function suite( modifier?: "only" | "skip", ) { const { + controllerCapabilities, nodeCapabilities, customSetup, testBody, @@ -96,7 +99,9 @@ function suite( )); ({ mockController, mockNodes } = prepareMocks( mockPort, - undefined, + { + capabilities: controllerCapabilities, + }, // TODO: This isn't ideal as it requires us to provide the // node capabilities in addition to the provisioning directory nodeCapabilities, diff --git a/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts b/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts index cd73b010bf28..d7c453d9e692 100644 --- a/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts +++ b/packages/zwave-js/src/lib/test/integrationTestSuiteShared.ts @@ -1,5 +1,4 @@ import { type MockPortBinding } from "@zwave-js/serial/mock"; -import { type DeepPartial } from "@zwave-js/shared"; import { MockController, type MockControllerOptions, @@ -15,12 +14,12 @@ import { type CreateAndStartDriverWithMockPortResult, createAndStartDriverWithMockPort, } from "../driver/DriverMock"; -import { type ZWaveOptions } from "../driver/ZWaveOptions"; +import { type PartialZWaveOptions } from "../driver/ZWaveOptions"; export function prepareDriver( cacheDir: string = path.join(__dirname, "cache"), logToFile: boolean = false, - additionalOptions: DeepPartial = {}, + additionalOptions: PartialZWaveOptions = {}, ): Promise { return createAndStartDriverWithMockPort({ ...additionalOptions, From 9d4316e5f824052b1a885fca34922dacc64e0210 Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Thu, 5 Oct 2023 20:56:28 +0200 Subject: [PATCH 07/19] fix: move workaround for 500 series controller bug to master template, enable for Zooz ZST10 (#6372) --- packages/config/config/devices/0x0000/husbzb-1.json | 8 ++------ packages/config/config/devices/0x0086/zw090.json | 8 ++------ packages/config/config/devices/0x027a/zst10.json | 4 ++++ .../config/devices/templates/master_template.json | 10 ++++++++++ 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/config/config/devices/0x0000/husbzb-1.json b/packages/config/config/devices/0x0000/husbzb-1.json index 5be87d4123e0..86bd1a8521b5 100644 --- a/packages/config/config/devices/0x0000/husbzb-1.json +++ b/packages/config/config/devices/0x0000/husbzb-1.json @@ -14,11 +14,7 @@ "max": "255.255" }, "compat": { - // In some situations, AssignSUCReturnRoute and DeleteSUCReturnRoute get answered with a wrong callback function type, - // triggering Z-Wave JS's unresponsive controller check. - "disableCallbackFunctionTypeCheck": [ - 81, // AssignSUCReturnRoute - 85 // DeleteSUCReturnRoute - ] + // Workaround for a firmware bug in 500 series controllers + "$import": "~/templates/master_template.json#500_series_controller_compat_flags" } } diff --git a/packages/config/config/devices/0x0086/zw090.json b/packages/config/config/devices/0x0086/zw090.json index 4dfde0af7952..1152ac38ab0d 100644 --- a/packages/config/config/devices/0x0086/zw090.json +++ b/packages/config/config/devices/0x0086/zw090.json @@ -60,11 +60,7 @@ "manual": "https://products.z-wavealliance.org/ProductManual/File?folder=&filename=MarketCertificationFiles/1345/Z%20Stick%20Gen5%20manual%201.pdf" }, "compat": { - // In some situations, AssignSUCReturnRoute and DeleteSUCReturnRoute get answered with a wrong callback function type, - // triggering Z-Wave JS's unresponsive controller check. - "disableCallbackFunctionTypeCheck": [ - 81, // AssignSUCReturnRoute - 85 // DeleteSUCReturnRoute - ] + // Workaround for a firmware bug in 500 series controllers + "$import": "~/templates/master_template.json#500_series_controller_compat_flags" } } diff --git a/packages/config/config/devices/0x027a/zst10.json b/packages/config/config/devices/0x027a/zst10.json index 7522db68d1b8..4ffed3a31d4d 100644 --- a/packages/config/config/devices/0x027a/zst10.json +++ b/packages/config/config/devices/0x027a/zst10.json @@ -12,5 +12,9 @@ "firmwareVersion": { "min": "0.0", "max": "255.255" + }, + "compat": { + // Workaround for a firmware bug in 500 series controllers + "$import": "~/templates/master_template.json#500_series_controller_compat_flags" } } diff --git a/packages/config/config/devices/templates/master_template.json b/packages/config/config/devices/templates/master_template.json index 3c08bfebc998..affa0a3a467a 100644 --- a/packages/config/config/devices/templates/master_template.json +++ b/packages/config/config/devices/templates/master_template.json @@ -675,5 +675,15 @@ "text": "Firmware version 7.19.3 has a bug that causes the controller to randomly hang during transmission until it is restarted. It is currently unclear if this bug is fixed in a later firmware version." } ] + }, + "500_series_controller_compat_flags": { + // It seems that all 500 series controllers have a firmware bug: + + // When failing, AssignSUCReturnRoute and DeleteSUCReturnRoute get answered with a wrong callback function type, + // triggering Z-Wave JS's unresponsive controller check. + "disableCallbackFunctionTypeCheck": [ + 81, // AssignSUCReturnRoute + 85 // DeleteSUCReturnRoute + ] } } From 6861ff8ffc1cec5edebb30a3e3887550b8b28cc5 Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Thu, 5 Oct 2023 21:36:11 +0200 Subject: [PATCH 08/19] fix: limit recovery after missing callback to `SendData` commands (#6373) --- packages/zwave-js/src/lib/driver/Driver.ts | 4 +- .../sendDataMissingCallbackAbort.test.ts | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index 75b843aa94fa..0da3f4ee3c6a 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -5565,7 +5565,9 @@ ${handlers.length} left`, if (this.isMissingNodeACK(transaction, error)) { if (this.handleMissingNodeACK(transaction as any, error)) return; } else if ( - isMissingControllerACK(error) || isMissingControllerCallback(error) + isMissingControllerACK(error) + || (isSendData(transaction.message) + && isMissingControllerCallback(error)) ) { if (this.handleUnresponsiveController(transaction, error)) return; } diff --git a/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts b/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts index 7c12f8382a0b..f4e453f33eb7 100644 --- a/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts +++ b/packages/zwave-js/src/lib/test/driver/sendDataMissingCallbackAbort.test.ts @@ -9,6 +9,10 @@ import { import { ZWaveErrorCodes, assertZWaveError } from "@zwave-js/core"; import Sinon from "sinon"; import { SoftResetRequest } from "../../serialapi/misc/SoftResetRequest"; +import { + RequestNodeInfoRequest, + RequestNodeInfoResponse, +} from "../../serialapi/network-mgmt/RequestNodeInfoMessages"; import { SendDataAbort, SendDataRequest, @@ -318,3 +322,44 @@ integrationTest( }, }, ); + +integrationTest( + "Missing callback recovery only kicks in for SendData commands", + { + // debug: true, + + additionalDriverOptions: { + testingHooks: { + skipNodeInterview: true, + }, + }, + + customSetup: async (driver, mockController, mockNode) => { + // This is almost a 1:1 copy of the default behavior, except that the callback never gets sent + const handleBrokenRequestNodeInfo: MockControllerBehavior = { + async onHostMessage(host, controller, msg) { + if (msg instanceof RequestNodeInfoRequest) { + // Notify the host that the message was sent + const res = new RequestNodeInfoResponse(host, { + wasSent: true, + }); + await controller.sendToHost(res.serialize()); + + // And never send a callback + return true; + } + }, + }; + mockController.defineBehavior(handleBrokenRequestNodeInfo); + }, + testBody: async (t, driver, node, mockController, mockNode) => { + // Circumvent the options validation so the test doesn't take forever + driver.options.timeouts.sendDataCallback = 1500; + + await assertZWaveError(t, () => node.requestNodeInfo(), { + errorCode: ZWaveErrorCodes.Controller_Timeout, + context: "callback", + }); + }, + }, +); From 9a77bf9385a38b94a3356345dae23efe636dad85 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Thu, 5 Oct 2023 21:47:53 +0200 Subject: [PATCH 09/19] chore: update changelog --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e9b86897b6b..b6eb9f3d16b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ +## __WORK IN PROGRESS__ +The `v12` release was supposed to increase reliability of Z-Wave JS, primarily by detecting situations where the controller was unable to transmit due to excessive RF noise or being unresponsive and automatically taking the necessary steps to recover. + +Instead, it uncovered bugs and erratical behavior in the 500 series firmwares, which triggered the automatic recovery in situations where it was not necessary. In the worst case, this would cause Z-Wave JS to end up in an infinite loop or restart over and over. + +This patch should fix and/or work around most (if not all) of these issues. Really sorry for the inconvenience! + +### Bugfixes +* Fixed an infinite loop caused by assuming the controller was temporarily unable to transmit when when sending a command results in the transmit status `Fail` (#6361) +* Added a workaround to avoid a restart loop caused by 500 series controllers replying with invalid commands when assigning routes back to the controller (SUC) failed (#6370, #6372) +* Automatically recovering an unresponsive controller by restarting it or Z-Wave JS in case of a missing callback is now only done for `SendData` commands. Previously some commands which were expecting a specific command to be received from a node could also trigger this, even if that command was not technically a command callback. (#6373) +* Fixed an issue where rebuilding routes would throw an error because of calling the wrong method internally (#6362) + ## 12.0.2 (2023-09-29) ### Bugfixes * The workaround from `v12.0.0` for the `7.19.x` SDK bug was not working correctly when the command that caused the controller to get stuck could be retried. This has now been fixed. (#6343) @@ -27,6 +40,7 @@ Home Assistant users who manage `zwave-js-server` themselves, **must** install t * `zwave-js-server` **1.32.0** ### Breaking changes ยท [Migration guide](https://zwave-js.github.io/node-zwave-js/#/getting-started/migrating-to-v12) +* Removed auto-disabling of soft-reset capability. If Z-Wave JS is no longer able to communicate with the controller after updating, please read [this issue](https://github.com/zwave-js/node-zwave-js/issues/6341) (#6256) * Remove support for Node.js 14 and 16 (#6245) * Subpath exports are now exposed using the `exports` field in `package.json` instead of `typesVersions` (#5839) * The `"notification"` event now includes a reference to the endpoint that sent the notification (#6083) @@ -43,7 +57,6 @@ Home Assistant users who manage `zwave-js-server` themselves, **must** install t ### Bugfixes * A bug in the `7.19.x` SDK has surfaced where the controller gets stuck in the middle of a transmission. Previously this would go unnoticed because the failed commands would cause the nodes to be marked dead until the controller finally recovered. Since `v11.12.0` however, Z-Wave JS would consider the controller jammed and retry the last command indefinitely. This situation is now detected and Z-Wave JS attempts to recover by soft-resetting the controller when this happens. (#6296) -* Removed auto-disabling of soft-reset capability (#6256) * Default to RF protection state `Unprotected` if not given for `Protection CC` V2+ (#6257) ### Config file changes From f43abb0fc92524ffee83df4e0cbcc39cc8ec97e4 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Thu, 5 Oct 2023 21:48:09 +0200 Subject: [PATCH 10/19] chore: release v12.0.3 The `v12` release was supposed to increase reliability of Z-Wave JS, primarily by detecting situations where the controller was unable to transmit due to excessive RF noise or being unresponsive and automatically taking the necessary steps to recover. Instead, it uncovered bugs and erratical behavior in the 500 series firmwares, which triggered the automatic recovery in situations where it was not necessary. In the worst case, this would cause Z-Wave JS to end up in an infinite loop or restart over and over. This patch should fix and/or work around most (if not all) of these issues. Really sorry for the inconvenience! ### Bugfixes * Fixed an infinite loop caused by assuming the controller was temporarily unable to transmit when when sending a command results in the transmit status `Fail` (#6361) * Added a workaround to avoid a restart loop caused by 500 series controllers replying with invalid commands when assigning routes back to the controller (SUC) failed (#6370, #6372) * Automatically recovering an unresponsive controller by restarting it or Z-Wave JS in case of a missing callback is now only done for `SendData` commands. Previously some commands which were expecting a specific command to be received from a node could also trigger this, even if that command was not technically a command callback. (#6373) * Fixed an issue where rebuilding routes would throw an error because of calling the wrong method internally (#6362) --- CHANGELOG.md | 2 +- package.json | 2 +- packages/cc/package.json | 2 +- packages/config/package.json | 2 +- packages/core/package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/flash/package.json | 2 +- packages/host/package.json | 2 +- packages/maintenance/package.json | 2 +- packages/nvmedit/package.json | 2 +- packages/serial/package.json | 2 +- packages/shared/package.json | 2 +- packages/testing/package.json | 2 +- packages/zwave-js/package.json | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6eb9f3d16b6..900891d990c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ -## __WORK IN PROGRESS__ +## 12.0.3 (2023-10-05) The `v12` release was supposed to increase reliability of Z-Wave JS, primarily by detecting situations where the controller was unable to transmit due to excessive RF noise or being unresponsive and automatically taking the necessary steps to recover. Instead, it uncovered bugs and erratical behavior in the 500 series firmwares, which triggered the automatic recovery in situations where it was not necessary. In the worst case, this would cause Z-Wave JS to end up in an infinite loop or restart over and over. diff --git a/package.json b/package.json index 108a1d6f79f1..c99d10863a9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/repo", - "version": "12.0.2", + "version": "12.0.3", "private": true, "description": "Z-Wave driver written entirely in JavaScript/TypeScript", "keywords": [], diff --git a/packages/cc/package.json b/packages/cc/package.json index 84c6aced429b..6e4edef2e89b 100644 --- a/packages/cc/package.json +++ b/packages/cc/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/cc", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: Command Classes", "keywords": [], "publishConfig": { diff --git a/packages/config/package.json b/packages/config/package.json index 9e103c830dd4..42e1edd68640 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/config", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: configuration files", "publishConfig": { "access": "public" diff --git a/packages/core/package.json b/packages/core/package.json index 424c5fabcd9f..41c5eb9fdde9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/core", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: core components", "keywords": [], "publishConfig": { diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 6fcac0c4e862..613257301142 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/eslint-plugin", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: custom ESLint rules", "private": true, "keywords": [], diff --git a/packages/flash/package.json b/packages/flash/package.json index 50b11f89757b..2b6688c9009c 100644 --- a/packages/flash/package.json +++ b/packages/flash/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/flash", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: firmware flash utility", "keywords": [], "publishConfig": { diff --git a/packages/host/package.json b/packages/host/package.json index 80cf8037e31f..c2d66b41d827 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/host", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: Host abstractions", "keywords": [], "publishConfig": { diff --git a/packages/maintenance/package.json b/packages/maintenance/package.json index 67115e166224..57345f178c5d 100644 --- a/packages/maintenance/package.json +++ b/packages/maintenance/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/maintenance", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: maintenance scripts", "private": true, "keywords": [], diff --git a/packages/nvmedit/package.json b/packages/nvmedit/package.json index ae6552865455..d6a56f254664 100644 --- a/packages/nvmedit/package.json +++ b/packages/nvmedit/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/nvmedit", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: library to edit NVM backups", "keywords": [], "publishConfig": { diff --git a/packages/serial/package.json b/packages/serial/package.json index 4011c31e2580..be77b4c94362 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/serial", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: Serialport driver", "publishConfig": { "access": "public" diff --git a/packages/shared/package.json b/packages/shared/package.json index 8c7bd4c4f855..3e45b62beb04 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/shared", - "version": "12.0.0", + "version": "12.0.3", "description": "zwave-js: shared utilities", "keywords": [], "publishConfig": { diff --git a/packages/testing/package.json b/packages/testing/package.json index f838a2c4674e..b71489735e79 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/testing", - "version": "12.0.2", + "version": "12.0.3", "description": "zwave-js: testing utilities", "keywords": [], "publishConfig": { diff --git a/packages/zwave-js/package.json b/packages/zwave-js/package.json index 0ea9d293f0e6..ce60687801ca 100644 --- a/packages/zwave-js/package.json +++ b/packages/zwave-js/package.json @@ -1,6 +1,6 @@ { "name": "zwave-js", - "version": "12.0.2", + "version": "12.0.3", "description": "Z-Wave driver written entirely in JavaScript/TypeScript", "keywords": [], "main": "build/index.js", From a7e294daf81d805bb4829890b6b78f0947b05957 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 6 Oct 2023 04:43:22 -0400 Subject: [PATCH 11/19] fix: label of notification event `0x0a` is `Emergency Alarm` (#6368) --- packages/config/config/notifications.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/config/config/notifications.json b/packages/config/config/notifications.json index 459e346dd318..c37ab18e8221 100644 --- a/packages/config/config/notifications.json +++ b/packages/config/config/notifications.json @@ -928,7 +928,7 @@ } }, "0x0a": { - "name": "System", + "name": "Emergency Alarm", "events": { "0x01": { "label": "Contact police" From fce2c260ef0309efdacb5b5574e839cd9c302638 Mon Sep 17 00:00:00 2001 From: Z-Wave JS Bot <76957017+zwave-js-bot@users.noreply.github.com> Date: Fri, 6 Oct 2023 11:05:12 +0200 Subject: [PATCH 12/19] =?UTF-8?q?docs:=20update=20typed=20documentation=20?= =?UTF-8?q?and=20API=20report=20=F0=9F=A4=96=20(#6337)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Al Calzone --- packages/config/api.md | 2 ++ packages/core/api.md | 22 +++++++++++++++++++--- packages/serial/api.md | 2 +- packages/testing/api.md | 5 +++++ packages/zwave-js/api.md | 2 ++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/config/api.md b/packages/config/api.md index ebd6418eb9bc..3fbf2a3d13e4 100644 --- a/packages/config/api.md +++ b/packages/config/api.md @@ -144,6 +144,8 @@ export class ConditionalCompatConfig implements ConditionalItem { // (undocumented) readonly disableBasicMapping?: boolean; // (undocumented) + readonly disableCallbackFunctionTypeCheck?: number[]; + // (undocumented) readonly disableStrictEntryControlDataValidation?: boolean; // (undocumented) readonly disableStrictMeasurementValidation?: boolean; diff --git a/packages/core/api.md b/packages/core/api.md index 3f4fcb0c3038..ad8008f316be 100644 --- a/packages/core/api.md +++ b/packages/core/api.md @@ -1218,17 +1218,25 @@ export function isMessagePriority(val: unknown): val is MessagePriority; // Warning: (ae-missing-release-tag) "isMissingControllerACK" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function isMissingControllerACK(e: unknown): e is ZWaveError; +export function isMissingControllerACK(e: unknown): e is ZWaveError & { + code: ZWaveErrorCodes.Controller_Timeout; + context: "ACK"; +}; // Warning: (ae-missing-release-tag) "isMissingControllerCallback" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function isMissingControllerCallback(e: unknown): e is ZWaveError; +export function isMissingControllerCallback(e: unknown): e is ZWaveError & { + code: ZWaveErrorCodes.Controller_Timeout; + context: "callback"; +}; // Warning: (ae-missing-release-tag) "isRecoverableZWaveError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export function isRecoverableZWaveError(e: unknown): e is ZWaveError; +export function isRecoverableZWaveError(e: unknown): e is ZWaveError & { + code: ZWaveErrorCodes.Controller_InterviewRestarted | ZWaveErrorCodes.Controller_NodeRemoved; +}; // Warning: (ae-missing-release-tag) "isRssiError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -2627,6 +2635,14 @@ export enum TransmitStatus { // @public export function tryParseDSKFromQRCodeString(qr: string): string | undefined; +// Warning: (ae-missing-release-tag) "tryParseParamNumber" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function tryParseParamNumber(str: string): { + parameter: number; + valueBitMask?: number; +} | undefined; + // Warning: (ae-missing-release-tag) "TXReport" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public diff --git a/packages/serial/api.md b/packages/serial/api.md index e969db92054c..4ac14b6a9282 100644 --- a/packages/serial/api.md +++ b/packages/serial/api.md @@ -467,7 +467,7 @@ export class Message { getResponseTimeout(): number | undefined; hasCallbackId(): boolean; // (undocumented) - protected host: ZWaveHost; + readonly host: ZWaveHost; static isComplete(data?: Buffer): boolean; isExpectedCallback(msg: Message): boolean; isExpectedNodeUpdate(msg: Message): boolean; diff --git a/packages/testing/api.md b/packages/testing/api.md index e53f35fde54d..eb2efdf05a43 100644 --- a/packages/testing/api.md +++ b/packages/testing/api.md @@ -101,6 +101,11 @@ export interface EnergyProductionCCCapabilities { }; } +// Warning: (ae-missing-release-tag) "getDefaultSupportedFunctionTypes" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function getDefaultSupportedFunctionTypes(): FunctionType[]; + // Warning: (ae-missing-release-tag) "LazyMockZWaveFrame" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/packages/zwave-js/api.md b/packages/zwave-js/api.md index 97e01f528341..184cba7c2e15 100644 --- a/packages/zwave-js/api.md +++ b/packages/zwave-js/api.md @@ -1658,6 +1658,7 @@ export interface ZWaveOptions extends ZWaveHostOptions { attempts: { controller: number; sendData: number; + sendDataJammed: number; nodeInterview: number; }; disableOptimisticValueUpdate?: boolean; @@ -1710,6 +1711,7 @@ export interface ZWaveOptions extends ZWaveHostOptions { sendDataCallback: number; report: number; nonce: number; + retryJammed: number; sendToSleep: number; refreshValue: number; refreshValueAfterTransition: number; From cf1d9c6fd0e152b4dd21b04822942df158c60edc Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Fri, 6 Oct 2023 11:43:33 +0200 Subject: [PATCH 13/19] feat(config): add Zooz ZAC38 Range Extender (#6136) --- .../config/config/devices/0x027a/zac38.json | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packages/config/config/devices/0x027a/zac38.json diff --git a/packages/config/config/devices/0x027a/zac38.json b/packages/config/config/devices/0x027a/zac38.json new file mode 100644 index 000000000000..ad268305e513 --- /dev/null +++ b/packages/config/config/devices/0x027a/zac38.json @@ -0,0 +1,61 @@ +{ + "manufacturer": "Zooz", + "manufacturerId": "0x027a", + "label": "ZAC38", + "description": "Range Extender", + "devices": [ + { + "productType": "0x0004", + "productId": "0x0510" + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "paramInformation": [ + { + "#": "1", + "$import": "templates/zooz_template.json#low_battery_alarm_threshold", + "defaultValue": 10 + }, + { + "#": "2", + "$import": "~/templates/master_template.json#base_enable_disable", + "label": "Enable Battery Threshold Reports", + "defaultValue": 1 + }, + { + "#": "3", + "$import": "templates/zooz_template.json#battery_report_threshold", + "minValue": 5 + }, + { + "#": "4", + "label": "Battery Check Interval", + "description": "How often the device checks the battery level.", + "valueSize": 2, + "unit": "seconds", + "minValue": 1, + "maxValue": 65535, + "defaultValue": 600, + "unsigned": true + }, + { + "#": "5", + "$import": "~/templates/master_template.json#base_enable_disable", + "label": "Enable Timed Battery Reports", + "defaultValue": 1 + }, + { + "#": "6", + "label": "Battery Report Interval", + "valueSize": 2, + "unit": "seconds", + "minValue": 30, + "maxValue": 65535, + "defaultValue": 3600, + "unsigned": true + } + ] +} From 47b8b9e3caf8f86fc974ce3afe8c616f87c956d6 Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Mon, 9 Oct 2023 09:59:39 +0200 Subject: [PATCH 14/19] fix: increase `response` timeout to 30 seconds and maximum to 60 seconds (#6378) --- packages/zwave-js/src/lib/driver/Driver.ts | 8 +++++--- packages/zwave-js/src/lib/driver/ZWaveOptions.ts | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/zwave-js/src/lib/driver/Driver.ts b/packages/zwave-js/src/lib/driver/Driver.ts index 0da3f4ee3c6a..022af36393c8 100644 --- a/packages/zwave-js/src/lib/driver/Driver.ts +++ b/packages/zwave-js/src/lib/driver/Driver.ts @@ -249,7 +249,9 @@ const defaultOptions: ZWaveOptions = { timeouts: { ack: 1000, byte: 150, - response: 10000, + // Ideally we'd want to have this as low as possible, but some + // 500 series controllers can take upwards of 10 seconds to respond sometimes. + response: 30000, report: 1000, // ReportTime timeout SHOULD be set to CommandTime + 1 second nonce: 5000, sendDataCallback: 65000, // as defined in INS13954 @@ -297,9 +299,9 @@ function checkOptions(options: ZWaveOptions): void { ZWaveErrorCodes.Driver_InvalidOptions, ); } - if (options.timeouts.response < 500 || options.timeouts.response > 20000) { + if (options.timeouts.response < 500 || options.timeouts.response > 60000) { throw new ZWaveError( - `The Response timeout must be between 500 and 20000 milliseconds!`, + `The Response timeout must be between 500 and 60000 milliseconds!`, ZWaveErrorCodes.Driver_InvalidOptions, ); } diff --git a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts index 901a133b20e2..b02293e414b8 100644 --- a/packages/zwave-js/src/lib/driver/ZWaveOptions.ts +++ b/packages/zwave-js/src/lib/driver/ZWaveOptions.ts @@ -18,7 +18,7 @@ export interface ZWaveOptions extends ZWaveHostOptions { * How long to wait for a controller response. Usually this timeout should never elapse, * so this is merely a safeguard against the driver stalling. */ - response: number; // [500...20000], default: 10000 ms + response: number; // [500...60000], default: 30000 ms /** How long to wait for a callback from the host for a SendData[Multicast]Request */ sendDataCallback: number; // >=10000, default: 65000 ms From aa36cb70dfd5b4940ac064ab3656412fc04465d9 Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Mon, 9 Oct 2023 10:08:39 +0200 Subject: [PATCH 15/19] fix: use `getSensorTypeName` to print label of dropped sensor reading (#6379) --- packages/cc/src/cc/MultilevelSensorCC.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cc/src/cc/MultilevelSensorCC.ts b/packages/cc/src/cc/MultilevelSensorCC.ts index b40d1c23e366..7bc3cc8f4c6e 100644 --- a/packages/cc/src/cc/MultilevelSensorCC.ts +++ b/packages/cc/src/cc/MultilevelSensorCC.ts @@ -670,7 +670,7 @@ export class MultilevelSensorCCReport extends MultilevelSensorCC { if (supportedSensorTypes?.length) { validatePayload.withReason( `Unsupported sensor type ${ - sensorType!.label + applHost.configManager.getSensorTypeName(this.type) } or corrupted data`, )(supportedSensorTypes.includes(this.type)); } From 47de2bf3396c20495e02d0c5f2e67290d5411efb Mon Sep 17 00:00:00 2001 From: AlCalzone Date: Mon, 9 Oct 2023 10:26:44 +0200 Subject: [PATCH 16/19] fix(config): treat Basic Set as events for TKB TZ35/55 S/D (#6381) --- .../{tz35s.json => tz35s_tz35d_tz55s_tz55d.json} | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename packages/config/config/devices/0x0118/{tz35s.json => tz35s_tz35d_tz55s_tz55d.json} (89%) diff --git a/packages/config/config/devices/0x0118/tz35s.json b/packages/config/config/devices/0x0118/tz35s_tz35d_tz55s_tz55d.json similarity index 89% rename from packages/config/config/devices/0x0118/tz35s.json rename to packages/config/config/devices/0x0118/tz35s_tz35d_tz55s_tz55d.json index 3b7800b465c9..1c24ee40d34d 100644 --- a/packages/config/config/devices/0x0118/tz35s.json +++ b/packages/config/config/devices/0x0118/tz35s_tz35d_tz55s_tz55d.json @@ -1,8 +1,8 @@ { "manufacturer": "TKB Home", "manufacturerId": "0x0118", - "label": "TZ55S", - "description": "Single Paddle Wall Dimmer", + "label": "TZ35S / TZ35D / TZ55S / TZ55D", + "description": "Single/Dual Paddle Wall Dimmer", "devices": [ { "productType": "0x0808", @@ -97,5 +97,9 @@ } ] } - ] + ], + "compat": { + // The right paddle sends its status via Basic Set commands + "treatBasicSetAsEvent": true + } } From 41cdf9520837f8c0963411f47e09f720e8558066 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 9 Oct 2023 04:27:34 -0400 Subject: [PATCH 17/19] fix: normalize result of `Controller.getAvailableFirmwareUpdates` to always include `channel` field (#6359) --- packages/zwave-js/src/lib/controller/FirmwareUpdateService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/zwave-js/src/lib/controller/FirmwareUpdateService.ts b/packages/zwave-js/src/lib/controller/FirmwareUpdateService.ts index 41afabf05477..f8ea1597955f 100644 --- a/packages/zwave-js/src/lib/controller/FirmwareUpdateService.ts +++ b/packages/zwave-js/src/lib/controller/FirmwareUpdateService.ts @@ -226,6 +226,7 @@ export async function getAvailableFirmwareUpdates( return result.map((update) => ({ device: deviceId, ...update, + channel: update.channel ?? "stable", })); } From 99d8f038349d393b1d3d49545b38d2b19c573801 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Mon, 9 Oct 2023 10:30:13 +0200 Subject: [PATCH 18/19] chore: update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 900891d990c2..96a9d544b640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ +## __WORK IN PROGRESS__ +### Bugfixes +* Normalize result of `Controller.getAvailableFirmwareUpdates` to always include `channel` field (#6359) +* Fixed a crash that could happen while logging dropped sensor readings (#6379) +* Increased the range and default of the `response` timeout to accomodate slower 500 series controllers (#6378) + +### Config file changes +* Treat Basic Set as events for TKB TZ35S/D and TZ55S/D (#6381) +* Add Zooz ZAC38 Range Extender (#6136) +* Corrected the label of the notification event `0x0a` to be `Emergency Alarm` (#6368) + ## 12.0.3 (2023-10-05) The `v12` release was supposed to increase reliability of Z-Wave JS, primarily by detecting situations where the controller was unable to transmit due to excessive RF noise or being unresponsive and automatically taking the necessary steps to recover. From f54a9acf2a9ef2ef72bf548b0fd83896a336c8a3 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Mon, 9 Oct 2023 10:30:28 +0200 Subject: [PATCH 19/19] chore: release v12.0.4 ### Bugfixes * Normalize result of `Controller.getAvailableFirmwareUpdates` to always include `channel` field (#6359) * Fixed a crash that could happen while logging dropped sensor readings (#6379) * Increased the range and default of the `response` timeout to accomodate slower 500 series controllers (#6378) ### Config file changes * Treat Basic Set as events for TKB TZ35S/D and TZ55S/D (#6381) * Add Zooz ZAC38 Range Extender (#6136) * Corrected the label of the notification event `0x0a` to be `Emergency Alarm` (#6368) --- CHANGELOG.md | 2 +- package.json | 2 +- packages/cc/package.json | 2 +- packages/config/package.json | 2 +- packages/core/package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/flash/package.json | 2 +- packages/host/package.json | 2 +- packages/maintenance/package.json | 2 +- packages/nvmedit/package.json | 2 +- packages/serial/package.json | 2 +- packages/testing/package.json | 2 +- packages/zwave-js/package.json | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96a9d544b640..ce53c4296b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ -## __WORK IN PROGRESS__ +## 12.0.4 (2023-10-09) ### Bugfixes * Normalize result of `Controller.getAvailableFirmwareUpdates` to always include `channel` field (#6359) * Fixed a crash that could happen while logging dropped sensor readings (#6379) diff --git a/package.json b/package.json index c99d10863a9d..56d43cf9d78e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/repo", - "version": "12.0.3", + "version": "12.0.4", "private": true, "description": "Z-Wave driver written entirely in JavaScript/TypeScript", "keywords": [], diff --git a/packages/cc/package.json b/packages/cc/package.json index 6e4edef2e89b..a04cd3ec0748 100644 --- a/packages/cc/package.json +++ b/packages/cc/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/cc", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: Command Classes", "keywords": [], "publishConfig": { diff --git a/packages/config/package.json b/packages/config/package.json index 42e1edd68640..9a0a4edcc4a5 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/config", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: configuration files", "publishConfig": { "access": "public" diff --git a/packages/core/package.json b/packages/core/package.json index 41c5eb9fdde9..24a5a18b9397 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/core", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: core components", "keywords": [], "publishConfig": { diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 613257301142..842fdc09a349 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/eslint-plugin", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: custom ESLint rules", "private": true, "keywords": [], diff --git a/packages/flash/package.json b/packages/flash/package.json index 2b6688c9009c..b033866b5304 100644 --- a/packages/flash/package.json +++ b/packages/flash/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/flash", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: firmware flash utility", "keywords": [], "publishConfig": { diff --git a/packages/host/package.json b/packages/host/package.json index c2d66b41d827..9d26e92fa2ee 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/host", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: Host abstractions", "keywords": [], "publishConfig": { diff --git a/packages/maintenance/package.json b/packages/maintenance/package.json index 57345f178c5d..d7994c316c21 100644 --- a/packages/maintenance/package.json +++ b/packages/maintenance/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/maintenance", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: maintenance scripts", "private": true, "keywords": [], diff --git a/packages/nvmedit/package.json b/packages/nvmedit/package.json index d6a56f254664..ed468d7823c4 100644 --- a/packages/nvmedit/package.json +++ b/packages/nvmedit/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/nvmedit", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: library to edit NVM backups", "keywords": [], "publishConfig": { diff --git a/packages/serial/package.json b/packages/serial/package.json index be77b4c94362..3ddf599d1b75 100644 --- a/packages/serial/package.json +++ b/packages/serial/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/serial", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: Serialport driver", "publishConfig": { "access": "public" diff --git a/packages/testing/package.json b/packages/testing/package.json index b71489735e79..a1051fbd5ac8 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,6 +1,6 @@ { "name": "@zwave-js/testing", - "version": "12.0.3", + "version": "12.0.4", "description": "zwave-js: testing utilities", "keywords": [], "publishConfig": { diff --git a/packages/zwave-js/package.json b/packages/zwave-js/package.json index ce60687801ca..f16be8d2a2b2 100644 --- a/packages/zwave-js/package.json +++ b/packages/zwave-js/package.json @@ -1,6 +1,6 @@ { "name": "zwave-js", - "version": "12.0.3", + "version": "12.0.4", "description": "Z-Wave driver written entirely in JavaScript/TypeScript", "keywords": [], "main": "build/index.js",