From a882bb8e807314a571b8aaa6c8b47eb1cebcf9f8 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 11 Feb 2022 22:52:20 -0800 Subject: [PATCH] zwave: update --- plugins/zwave/README.md | 16 +---- plugins/zwave/package-lock.json | 101 +++++++++++++++++++------------- plugins/zwave/package.json | 4 +- plugins/zwave/src/main.ts | 95 +++++++++++++++++++++++++----- 4 files changed, 144 insertions(+), 72 deletions(-) diff --git a/plugins/zwave/README.md b/plugins/zwave/README.md index eb32293fd..390f4ab2c 100644 --- a/plugins/zwave/README.md +++ b/plugins/zwave/README.md @@ -1,15 +1 @@ -# Z-Wave USB Controller - -## npm commands - * npm run scrypted-webpack - * npm run scrypted-deploy - * npm run scrypted-debug - -## scrypted distribution via npm - 1. Ensure package.json is set up properly for publishing on npm. - 2. npm publish - -## Visual Studio Code configuration - -* If using a remote server, edit [.vscode/settings.json](blob/master/.vscode/settings.json) to specify the IP Address of the Scrypted server. -* Launch Scrypted Debugger from the launch menu. +# Z-Wave Plugin for Scrypted diff --git a/plugins/zwave/package-lock.json b/plugins/zwave/package-lock.json index 1f5e7fb3b..07e78bdac 100644 --- a/plugins/zwave/package-lock.json +++ b/plugins/zwave/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/zwave", - "version": "0.0.39", + "version": "0.0.41", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/zwave", - "version": "0.0.39", + "version": "0.0.41", "license": "Apache", "dependencies": { "@scrypted/sdk": "file:../../sdk", @@ -18,7 +18,7 @@ "@types/node": "^16.7.1" }, "optionalDependencies": { - "zwave-js": "^8.11.3" + "zwave-js": "^8.11.5" } }, "../../sdk": { @@ -67,10 +67,11 @@ "extraneous": true }, "file-stream-rotator": { + "name": "@zwave-js/file-stream-rotator", "version": "0.5.8-0", "license": "MIT", "dependencies": { - "moment": "^2.11.2" + "date-fns-tz": "^1.2.2" } }, "node_modules/@alcalzone/jsonl-db": { @@ -420,9 +421,9 @@ "dev": true }, "node_modules/@zwave-js/config": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/@zwave-js/config/-/config-8.11.3.tgz", - "integrity": "sha512-K5zXd5gPauWzpRqXMB1+9Xkx/wgZtnOnjWcgFV6cMiC7dzGLcQX6sCZXySMb2QFTu6J/DRrWTDWYBHACq09rVA==", + "version": "8.11.4", + "resolved": "https://registry.npmjs.org/@zwave-js/config/-/config-8.11.4.tgz", + "integrity": "sha512-BsMq2ecOXm3xGyRKvcwYX5Xn84Vuwa9nmIC1VR9k2IwfAHaG6wwhqRgjrZGoHpDnHc12YfyCKvGsvvBh38mFlg==", "optional": true, "dependencies": { "@zwave-js/core": "8.11.3", @@ -472,9 +473,9 @@ "link": true }, "node_modules/@zwave-js/nvmedit": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/@zwave-js/nvmedit/-/nvmedit-8.11.3.tgz", - "integrity": "sha512-pa6PUkveZnHyU7DjM4yvdrwYlbWYVl/CZHp1lv90uxGSf1+TPM5ouaOVFEuWlhAiecUQgv6wcdDkx7O32WzCOA==", + "version": "8.11.5", + "resolved": "https://registry.npmjs.org/@zwave-js/nvmedit/-/nvmedit-8.11.5.tgz", + "integrity": "sha512-2dOVDGjT/ZaUcTMX92U/SUZPHbgtl1G02QxLx6GDBGNa1vZIeDTsAvcXdzr08XRl2wxjc8YrRCFxuR3ok3QiqA==", "optional": true, "dependencies": { "@zwave-js/core": "8.11.3", @@ -907,6 +908,27 @@ "node": ">= 8" } }, + "node_modules/date-fns": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==", + "peer": true, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/date-fns-tz": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.2.2.tgz", + "integrity": "sha512-vWtn44eEqnLbkACb7T5G5gPgKR4nY8NkNMOCyoY49NsRGHrcDmY2aysCyzDeA+u+vcDBn/w6nQqEDyouRs4m8w==", + "peerDependencies": { + "date-fns": ">=2.0.0" + } + }, "node_modules/dayjs": { "version": "1.10.7", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", @@ -1378,14 +1400,6 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "optional": true }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2193,18 +2207,18 @@ } }, "node_modules/zwave-js": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/zwave-js/-/zwave-js-8.11.3.tgz", - "integrity": "sha512-LKqj4j0i9MwkwhN1H+yZw4pAyuVnMcCYUpX+rSjtZkAZdsxp3n6QGac61da275t3SNBheiyzdsixrhVjxRVSiw==", + "version": "8.11.5", + "resolved": "https://registry.npmjs.org/zwave-js/-/zwave-js-8.11.5.tgz", + "integrity": "sha512-3uK7nwUec6cyC2q6U0cTCXNHDKGWnZPVwkYr2yCn3/vBHm7qB1GLHnT+k65JM3fsNV8Y6j03rlxLq/E9WatSMg==", "optional": true, "dependencies": { "@alcalzone/jsonl-db": "^2.4.1", "@alcalzone/pak": "^0.7.0", "@sentry/integrations": "^6.17.3", "@sentry/node": "^6.17.3", - "@zwave-js/config": "8.11.3", + "@zwave-js/config": "8.11.4", "@zwave-js/core": "8.11.3", - "@zwave-js/nvmedit": "8.11.3", + "@zwave-js/nvmedit": "8.11.5", "@zwave-js/serial": "8.11.3", "@zwave-js/shared": "8.11.3", "alcalzone-shared": "^4.0.1", @@ -2507,9 +2521,9 @@ "dev": true }, "@zwave-js/config": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/@zwave-js/config/-/config-8.11.3.tgz", - "integrity": "sha512-K5zXd5gPauWzpRqXMB1+9Xkx/wgZtnOnjWcgFV6cMiC7dzGLcQX6sCZXySMb2QFTu6J/DRrWTDWYBHACq09rVA==", + "version": "8.11.4", + "resolved": "https://registry.npmjs.org/@zwave-js/config/-/config-8.11.4.tgz", + "integrity": "sha512-BsMq2ecOXm3xGyRKvcwYX5Xn84Vuwa9nmIC1VR9k2IwfAHaG6wwhqRgjrZGoHpDnHc12YfyCKvGsvvBh38mFlg==", "optional": true, "requires": { "@zwave-js/core": "8.11.3", @@ -2545,13 +2559,13 @@ "@zwave-js/file-stream-rotator": { "version": "file:file-stream-rotator", "requires": { - "moment": "^2.11.2" + "date-fns-tz": "^1.2.2" } }, "@zwave-js/nvmedit": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/@zwave-js/nvmedit/-/nvmedit-8.11.3.tgz", - "integrity": "sha512-pa6PUkveZnHyU7DjM4yvdrwYlbWYVl/CZHp1lv90uxGSf1+TPM5ouaOVFEuWlhAiecUQgv6wcdDkx7O32WzCOA==", + "version": "8.11.5", + "resolved": "https://registry.npmjs.org/@zwave-js/nvmedit/-/nvmedit-8.11.5.tgz", + "integrity": "sha512-2dOVDGjT/ZaUcTMX92U/SUZPHbgtl1G02QxLx6GDBGNa1vZIeDTsAvcXdzr08XRl2wxjc8YrRCFxuR3ok3QiqA==", "optional": true, "requires": { "@zwave-js/core": "8.11.3", @@ -2884,6 +2898,18 @@ "which": "^2.0.1" } }, + "date-fns": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==", + "peer": true + }, + "date-fns-tz": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.2.2.tgz", + "integrity": "sha512-vWtn44eEqnLbkACb7T5G5gPgKR4nY8NkNMOCyoY49NsRGHrcDmY2aysCyzDeA+u+vcDBn/w6nQqEDyouRs4m8w==", + "requires": {} + }, "dayjs": { "version": "1.10.7", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", @@ -3248,11 +3274,6 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "optional": true }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3888,18 +3909,18 @@ "optional": true }, "zwave-js": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/zwave-js/-/zwave-js-8.11.3.tgz", - "integrity": "sha512-LKqj4j0i9MwkwhN1H+yZw4pAyuVnMcCYUpX+rSjtZkAZdsxp3n6QGac61da275t3SNBheiyzdsixrhVjxRVSiw==", + "version": "8.11.5", + "resolved": "https://registry.npmjs.org/zwave-js/-/zwave-js-8.11.5.tgz", + "integrity": "sha512-3uK7nwUec6cyC2q6U0cTCXNHDKGWnZPVwkYr2yCn3/vBHm7qB1GLHnT+k65JM3fsNV8Y6j03rlxLq/E9WatSMg==", "optional": true, "requires": { "@alcalzone/jsonl-db": "^2.4.1", "@alcalzone/pak": "^0.7.0", "@sentry/integrations": "^6.17.3", "@sentry/node": "^6.17.3", - "@zwave-js/config": "8.11.3", + "@zwave-js/config": "8.11.4", "@zwave-js/core": "8.11.3", - "@zwave-js/nvmedit": "8.11.3", + "@zwave-js/nvmedit": "8.11.5", "@zwave-js/serial": "8.11.3", "@zwave-js/shared": "8.11.3", "alcalzone-shared": "^4.0.1", diff --git a/plugins/zwave/package.json b/plugins/zwave/package.json index 45dffd72c..c846c7ff6 100644 --- a/plugins/zwave/package.json +++ b/plugins/zwave/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/zwave", - "version": "0.0.39", + "version": "0.0.41", "description": "Z-Wave USB Controller for Scrypted", "author": "Scrypted", "license": "Apache", @@ -40,6 +40,6 @@ "@types/node": "^16.7.1" }, "optionalDependencies": { - "zwave-js": "^8.11.3" + "zwave-js": "^8.11.5" } } diff --git a/plugins/zwave/src/main.ts b/plugins/zwave/src/main.ts index 79314c1d1..0f15738de 100644 --- a/plugins/zwave/src/main.ts +++ b/plugins/zwave/src/main.ts @@ -4,7 +4,7 @@ import { CommandClassInfo, getCommandClass, getCommandClassIndex } from "./Comma import { ZwaveDeviceBase } from "./CommandClasses/ZwaveDeviceBase"; import { getHash, getNodeHash, getInstanceHash } from "./Types"; import debounce from "lodash/debounce"; -import { Driver, Endpoint, ZWaveController, ZWaveNode, CommandClass } from "zwave-js"; +import { Driver, Endpoint, ZWaveController, ZWaveNode, CommandClass, InclusionUserCallbacks, InclusionGrant, InclusionStrategy, NodeStatus } from "zwave-js"; import { ValueID, CommandClasses } from "@zwave-js/core" import { randomBytes } from "crypto"; import path from "path"; @@ -55,38 +55,38 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic let s2AccessControlKey = this.storage.getItem('s2AccessControlKey'); let s2AuthenticatedKey = this.storage.getItem('s2AuthenticatedKey'); let s2UnauthenticatedKey = this.storage.getItem('s2UnauthenticatedKey'); - + // 1/17/2022: the network key was stored as base64, but for consistency with HA // and others, it was switched to hex. this is the data migration. if (!isHex(networkKey) && networkKey) { networkKey = Buffer.from(networkKey, 'base64').toString('hex'); this.storage.setItem('networkKey', networkKey); } - + if (!networkKey) { networkKey = randomBytes(16).toString('hex').toUpperCase(); this.storage.setItem('networkKey', networkKey); this.log.a('No Network Key was present, so a random one was generated. You can change the Network Key in Settings.') } - + if (!s2AccessControlKey) { s2AccessControlKey = randomBytes(16).toString('hex').toUpperCase(); this.storage.setItem('s2AccessControlKey', s2AccessControlKey); this.log.a('No S2 Access Control Key was present, so a random one was generated. You can change the S2 Access Control Key in Settings.'); } - + if (!s2AuthenticatedKey) { s2AuthenticatedKey = randomBytes(16).toString('hex').toUpperCase(); this.storage.setItem('s2AuthenticatedKey', s2AuthenticatedKey); - this.log.a('No S2 Authenticated Key was present, so a random one was generated. You can change the S2 Access Control Key in Settings.'); + this.log.a('No S2 Authenticated Key was present, so a random one was generated. You can change the S2 Access Control Key in Settings.'); } - + if (!s2UnauthenticatedKey) { s2UnauthenticatedKey = randomBytes(16).toString('hex').toUpperCase(); this.storage.setItem('s2UnauthenticatedKey', s2UnauthenticatedKey); this.log.a('No S2 Unauthenticated Key was present, so a random one was generated. You can change the S2 Unauthenticated Key in Settings.') } - + const cacheDir = path.join(process.env['SCRYPTED_PLUGIN_VOLUME'], 'cache'); this.console.log(process.cwd()); @@ -103,12 +103,12 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic }); this.driver = driver; console.log(driver.cacheDir); - + driver.on("error", (e) => { console.error('driver error', e); reject(e); }); - + driver.once("driver ready", () => { this.controller = driver.controller; const rebuildNode = async (node: ZWaveNode) => { @@ -116,10 +116,15 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic await this.rebuildInstance(endpoint); } } - + const bindNode = (node: ZWaveNode) => { node.on('value added', node => rebuildNode(node)); - node.on('value removed', node => rebuildNode(node)); + node.on('value removed', node => { + // node is being removed + if (!this.controller.nodes.get(node.id)) + return; + rebuildNode(node); + }); node.on('value updated', (node, valueId) => { const dirtyKey = getInstanceHash(this.controller.homeId, node.id, valueId.endpoint); const device: ZwaveDeviceBase = this.devices[dirtyKey]; @@ -131,7 +136,7 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic }); node.on('interview completed', node => rebuildNode(node)); } - + this.controller.on('node added', node => { this.console.log('node added', node.nodeId); bindNode(node); @@ -140,7 +145,7 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic this.controller.on('node removed', node => { this.console.log('node removed', node?.nodeId); }) - + driver.controller.nodes.forEach(node => { this.console.log('node loaded', node.nodeId); bindNode(node); @@ -191,10 +196,67 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic key: 's2UnauthenticatedKey', value: this.storage.getItem('s2UnauthenticatedKey'), description: 'The 16 byte hex encoded S2 Unauthenticated Key', - } + }, + { + title: 'Include Device', + key: 'inclusion', + type: 'button', + description: 'Enter inclusion mode and add devices.', + }, + { + title: 'Exclude Device', + key: 'exclusion', + type: 'button', + description: 'Enter exclusion mode and remove devices.', + }, ] } + + async inclusion() { + const userCallbacks: InclusionUserCallbacks = { + grantSecurityClasses: async (requested: InclusionGrant): Promise => { + this.console.log('grantSecurityClasses'); + return requested; + }, + validateDSKAndEnterPIN: async (dsk: string) => { + this.console.error('validateDSKAndEnterPIN not implemented in zwave plugin'); + throw new Error("validateDSKAndEnterPIN Function not implemented."); + }, + abort: function (): void { + this.console.log('abort'); + } + } + await this.controller.stopExclusion(); + await this.controller.stopInclusion(); + const including = await this.driver.controller.beginInclusion({ + userCallbacks, + strategy: InclusionStrategy.Default, + }); + this.log.a('Including devices for 5 minutes.'); + this.console.log('including', including); + + setTimeout(() => this.driver.controller.stopInclusion(), 300000); + } + + async exclusion() { + await this.controller.stopExclusion(); + await this.controller.stopInclusion(); + const excluding = await this.driver.controller.beginExclusion(); + this.log.a('Excluding devices for 5 minutes.'); + this.console.log('excluding', excluding); + setTimeout(() => this.driver.controller.stopExclusion(), 300000); + } + async putSetting(key: string, value: string | number | boolean) { + if (key === 'inclusion') { + this.inclusion(); + return; + } + if (key === 'exclusion') { + this.exclusion(); + return; + } + this.storage.setItem(key, value as string); await this.driver?.destroy(); @@ -229,6 +291,9 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic async rebuildInstance(instance: Endpoint) { const nativeId = getHash(this.controller, instance); let scryptedDevice: ZwaveDeviceBase = this.devices[nativeId]; + if (this.controller.nodes.get(instance.nodeId).status === NodeStatus.Dead) { + scryptedDevice.log.a('Node is dead.'); + } if (!scryptedDevice) { scryptedDevice = new ZwaveDeviceBase(this.controller, instance); scryptedDevice.zwaveController = this;