diff --git a/plugins/zwave/src/CommandClasses/ZwaveDeviceBase.ts b/plugins/zwave/src/CommandClasses/ZwaveDeviceBase.ts index 47e55c4e4..950278fab 100644 --- a/plugins/zwave/src/CommandClasses/ZwaveDeviceBase.ts +++ b/plugins/zwave/src/CommandClasses/ZwaveDeviceBase.ts @@ -2,7 +2,7 @@ import sdk, { ScryptedDeviceBase, Device, Refresh, Setting, Settings } from "@sc import { getHash } from "../Types"; import { CommandClassInfo, getCommandClassIndex, getCommandClass } from "."; import { ZwaveControllerProvider, NodeLiveness } from "../main"; -import { Endpoint, ValueID, ZWaveController, ZWaveNode, ZWaveNodeValueUpdatedArgs } from "zwave-js"; +import { Endpoint, ValueID, ZWaveController, ZWaveNode, ZWaveNodeValueUpdatedArgs, NodeStatus, InterviewStage, ZWavePlusRoleType, NodeStatistics } from "zwave-js"; import { CommandClasses, ValueMetadataNumeric } from "@zwave-js/core" const { deviceManager } = sdk; @@ -36,10 +36,22 @@ export class ZwaveDeviceBase extends ScryptedDeviceBase implements Refresh, Sett commandClasses: CommandClassInfo[] = []; zwaveController: ZwaveControllerProvider; transientState: TransientState = {}; + statistics: NodeStatistics; constructor(controller: ZWaveController, instance: Endpoint) { super(getHash(controller, instance)); this.instance = instance; + + const node = this.instance.getNodeUnsafe() + node.on('wake up', node => { + this.console.log(`[${node.id}] woke up`) + }); + node.on('sleep', node => { + this.console.log(`[${node.id}] sleeping`) + }); + node.on('statistics updated', (node: ZWaveNode, statistics: NodeStatistics) => { + this.statistics = statistics; + }); } getValueId(valueId: ValueID): ValueID { @@ -110,13 +122,91 @@ export class ZwaveDeviceBase extends ScryptedDeviceBase implements Refresh, Sett return this.putZWaveSetting(key, value); } async getZWaveSettings(): Promise { + const node = this.instance.getNodeUnsafe(); return [ { - group: 'Z-Wave Node Management', + group: 'Info', + title: 'Device Info', + key: 'zwave:deviceInfo', + readonly: true, + value: node.deviceDatabaseUrl, + }, + { + group: 'Info', + title: 'ID', + key: 'zwave:nodeId', + readonly: true, + value: node.id, + }, + { + group: 'Info', + title: 'Status', + key: 'zwave:nodeStatus', + readonly: true, + value: NodeStatus[node.status], + }, + { + group: 'Info', + title: 'Interview Stage', + key: 'zwave:interviewStage', + readonly: true, + value: InterviewStage[node.interviewStage], + }, + { + group: 'Info', + title: 'Device Class', + key: 'zwave:deviceClass', + readonly: true, + value: node.deviceClass.specific.label, + }, + { + group: 'Info', + title: 'ZWave+ Role Type', + key: 'zwave:roleType', + readonly: true, + value: ZWavePlusRoleType[node.zwavePlusRoleType] || "n/a", + }, + { + group: 'Info', + title: 'Firmware', + key: 'zwave:firmware', + readonly: true, + value: node.firmwareVersion, + }, + { + group: 'Info', title: 'Force Remove Node', key: 'zwave:forceRemove', placeholder: `Confirm Node ID to remove: ${this.instance.nodeId}`, value: '', + }, + { + group: 'Info', + title: 'Refresh Info', + key: 'zwave:refreshInfo', + type: 'button', + description: 'Resets (almost) all information about this node and forces a fresh interview.' + }, + { + group: 'Statistics', + title: 'Commands (RX/TX)', + key: 'zwave:commands', + readonly: true, + value: this.statistics ? `${this.statistics.commandsRX} / ${this.statistics.commandsTX}` : 'n/a', + }, + { + group: 'Statistics', + title: 'Commands Dropped (RX/TX)', + key: 'zwave:commandsDropped', + readonly: true, + value: this.statistics ? `${this.statistics.commandsDroppedRX} / ${this.statistics.commandsDroppedTX}` : 'n/a', + }, + { + group: 'Statistics', + title: 'RTT', + key: 'zwave:rtt', + readonly: true, + value: this.statistics ? `${this.statistics.rtt}ms` : 'n/a', } ]; } @@ -126,5 +216,15 @@ export class ZwaveDeviceBase extends ScryptedDeviceBase implements Refresh, Sett this.zwaveController.controller.removeFailedNode(this.instance.nodeId); deviceManager.onDeviceRemoved(this.nativeId); } + if (key === 'zwave:refreshInfo') { + this.console.log(`[${this.name}] Refreshing Info`) + if (!this.instance.getNodeUnsafe().ready) { + this.console.log(`${this.name} Refresh failed, device not ready`); + return + } + this.instance.getNodeUnsafe().refreshInfo().then((r) => { + this.console.log(`[${this.name}] Refresh Completed`) + }) + } } } diff --git a/plugins/zwave/src/main.ts b/plugins/zwave/src/main.ts index c26a1c360..69d9a9851 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, InclusionUserCallbacks, InclusionGrant, InclusionStrategy, NodeStatus } from "zwave-js"; +import { Driver, Endpoint, ZWaveController, ZWaveNode, InclusionUserCallbacks, InclusionGrant, InclusionStrategy, NodeStatus, InclusionState } from "zwave-js"; import { ValueID, CommandClasses } from "@zwave-js/core" import { randomBytes } from "crypto"; import path from "path"; @@ -169,6 +169,48 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic async getSettings(): Promise { return [ { + group: 'Inclusion', + title: 'Inclusion State', + key: 'inclusionState', + readonly: true, + value: InclusionState[this.controller.inclusionState], + }, + { + group: 'Inclusion', + title: 'Exclude Device', + key: 'exclusion', + type: 'button', + description: 'Enter exclusion mode and remove devices.', + }, + { + group: 'Inclusion', + title: 'Include Device', + key: 'inclusion', + type: 'button', + description: 'Enter inclusion mode and add devices.', + }, + { + group: 'Inclusion', + key: 'confirmPin', + title: 'Confirm PIN', + description: 'Some devices will require confirmation of a PIN while including them. Enter the PIN here when prompted.', + }, + { + group: 'Network', + title: 'Healing State', + key: 'healingState', + readonly: true, + value: this.controller.isHealNetworkActive ? 'Healing' : 'Not Healing', + }, + { + group: 'Network', + title: 'Heal Network', + key: 'heal', + type: 'button', + description: 'Heal the Z-Wave Network. This operation may take a long time and the network may become unreponsive while in progress.', + }, + { + group: 'Adapter', title: 'Serial Port', key: 'serialPort', value: this.storage.getItem('serialPort'), @@ -176,52 +218,33 @@ export class ZwaveControllerProvider extends ScryptedDeviceBase implements Devic placeholder: '/dev/tty.usbmodem14501', }, { + group: 'Adapter', title: 'Network Key', key: 'networkKey', value: this.storage.getItem('networkKey'), description: 'The 16 byte hex encoded Network Security Key', }, { + group: 'Adapter', title: 'S2 Access Control Key', key: 's2AccessControlKey', value: this.storage.getItem('s2AccessControlKey'), description: 'The 16 byte hex encoded S2 Access Control Key', }, { + group: 'Adapter', title: 'S2 Authenticated Key', key: 's2AuthenticatedKey', value: this.storage.getItem('s2AuthenticatedKey'), description: 'The 16 byte hex encoded S2 Authenticated Key', }, { + group: 'Adapter', title: 'S2 Unauthenticated Key', key: 's2UnauthenticatedKey', value: this.storage.getItem('s2UnauthenticatedKey'), description: 'The 16 byte hex encoded S2 Unauthenticated Key', }, - { - title: 'Heal Network', - key: 'heal', - type: 'button', - description: 'Heal the Z-Wave Network. This operation may take a long time and the network may become unreponsive while in progress.', - }, - { - title: 'Exclude Device', - key: 'exclusion', - type: 'button', - description: 'Enter exclusion mode and remove devices.', - }, - { - title: 'Include Device', - key: 'inclusion', - type: 'button', - description: 'Enter inclusion mode and add devices.', - }, - { - key: 'confirmPin', - title: 'Confirm PIN', - description: 'Some devices will require confirmation of a PIN while including them. Enter the PIN here when prompted.', - } ] }