diff --git a/api/lib/ZwaveClient.ts b/api/lib/ZwaveClient.ts index 31576be00ed..40eaa0d4b2b 100644 --- a/api/lib/ZwaveClient.ts +++ b/api/lib/ZwaveClient.ts @@ -569,6 +569,7 @@ export type NodeEvent = { } export type ZwaveConfig = { + enabled?: boolean allowBootloaderOnly?: boolean port?: string networkKey?: string @@ -2122,219 +2123,218 @@ class ZwaveClient extends TypedEventEmitter { * Method used to start Z-Wave connection using configuration `port` */ async connect() { - if (!this.driverReady) { - // this could happen when the driver fails the connect and a reconnect timeout triggers - if (this.closed || this.checkIfDestroyed()) { - return - } - - if (!this.cfg?.port) { - logger.warn('Z-Wave driver not inited, no port configured') - return - } + if (this.cfg.enabled === false) { + logger.info('Z-Wave driver DISABLED') + return + } - // extend options with hidden `options` - const zwaveOptions: PartialZWaveOptions = { - allowBootloaderOnly: this.cfg.allowBootloaderOnly || false, - storage: { - cacheDir: storeDir, - deviceConfigPriorityDir: - this.cfg.deviceConfigPriorityDir || - deviceConfigPriorityDir, - }, - logConfig: { - // https://zwave-js.github.io/node-zwave-js/#/api/driver?id=logconfig - enabled: this.cfg.logEnabled, - level: this.cfg.logLevel - ? loglevels[this.cfg.logLevel] - : 'info', - logToFile: this.cfg.logToFile, - filename: ZWAVEJS_LOG_FILE, - forceConsole: isDocker() ? !this.cfg.logToFile : false, - maxFiles: this.cfg.maxFiles || 7, - nodeFilter: - this.cfg.nodeFilter && this.cfg.nodeFilter.length > 0 - ? this.cfg.nodeFilter.map((n) => parseInt(n)) - : undefined, - }, - emitValueUpdateAfterSetValue: true, - apiKeys: { - firmwareUpdateService: - '421e29797c3c2926f84efc737352d6190354b3b526a6dce6633674dd33a8a4f964c794f5', - }, - timeouts: { - report: this.cfg.higherReportsTimeout ? 10000 : undefined, - sendToSleep: this.cfg.sendToSleepTimeout, - response: this.cfg.responseTimeout, - }, - features: { - unresponsiveControllerRecovery: this.cfg - .disableControllerRecovery - ? false - : true, - }, - userAgent: { - [utils.pkgJson.name]: utils.pkgJson.version, - }, - } + if (this.driverReady) { + logger.info(`Driver already connected to ${this.cfg.port}`) + return + } - if (this.cfg.rf) { - const { region, txPower } = this.cfg.rf + // this could happen when the driver fails the connect and a reconnect timeout triggers + if (this.closed || this.checkIfDestroyed()) { + return + } - zwaveOptions.rf = {} + if (!this.cfg?.port) { + logger.warn('Z-Wave driver not inited, no port configured') + return + } - if (region) { - zwaveOptions.rf.region = region - } + // extend options with hidden `options` + const zwaveOptions: PartialZWaveOptions = { + allowBootloaderOnly: this.cfg.allowBootloaderOnly || false, + storage: { + cacheDir: storeDir, + deviceConfigPriorityDir: + this.cfg.deviceConfigPriorityDir || deviceConfigPriorityDir, + }, + logConfig: { + // https://zwave-js.github.io/node-zwave-js/#/api/driver?id=logconfig + enabled: this.cfg.logEnabled, + level: this.cfg.logLevel + ? loglevels[this.cfg.logLevel] + : 'info', + logToFile: this.cfg.logToFile, + filename: ZWAVEJS_LOG_FILE, + forceConsole: isDocker() ? !this.cfg.logToFile : false, + maxFiles: this.cfg.maxFiles || 7, + nodeFilter: + this.cfg.nodeFilter && this.cfg.nodeFilter.length > 0 + ? this.cfg.nodeFilter.map((n) => parseInt(n)) + : undefined, + }, + emitValueUpdateAfterSetValue: true, + apiKeys: { + firmwareUpdateService: + '421e29797c3c2926f84efc737352d6190354b3b526a6dce6633674dd33a8a4f964c794f5', + }, + timeouts: { + report: this.cfg.higherReportsTimeout ? 10000 : undefined, + sendToSleep: this.cfg.sendToSleepTimeout, + response: this.cfg.responseTimeout, + }, + features: { + unresponsiveControllerRecovery: this.cfg + .disableControllerRecovery + ? false + : true, + }, + userAgent: { + [utils.pkgJson.name]: utils.pkgJson.version, + }, + } - if ( - txPower && - typeof txPower.measured0dBm === 'number' && - typeof txPower.powerlevel === 'number' - ) { - zwaveOptions.rf.txPower = txPower - } - } + if (this.cfg.rf) { + const { region, txPower } = this.cfg.rf - // ensure deviceConfigPriorityDir exists to prevent warnings #2374 - // lgtm [js/path-injection] - await ensureDir(zwaveOptions.storage.deviceConfigPriorityDir) + zwaveOptions.rf = {} - // when not set let zwavejs handle this based on the environment - if (typeof this.cfg.enableSoftReset === 'boolean') { - zwaveOptions.features.softReset = this.cfg.enableSoftReset + if (region) { + zwaveOptions.rf.region = region } - // when server is not enabled, disable the user callbacks set/remove - // so it can be used through MQTT - if (!this.cfg.serverEnabled) { - zwaveOptions.inclusionUserCallbacks = { - ...this.inclusionUserCallbacks, - } + if ( + txPower && + typeof txPower.measured0dBm === 'number' && + typeof txPower.powerlevel === 'number' + ) { + zwaveOptions.rf.txPower = txPower } + } - if (this.cfg.scales) { - const scales: Record = {} - for (const s of this.cfg.scales) { - scales[s.key] = s.label - } + // ensure deviceConfigPriorityDir exists to prevent warnings #2374 + // lgtm [js/path-injection] + await ensureDir(zwaveOptions.storage.deviceConfigPriorityDir) - zwaveOptions.preferences = { - scales, - } - } + // when not set let zwavejs handle this based on the environment + if (typeof this.cfg.enableSoftReset === 'boolean') { + zwaveOptions.features.softReset = this.cfg.enableSoftReset + } - Object.assign(zwaveOptions, this.cfg.options) + // when server is not enabled, disable the user callbacks set/remove + // so it can be used through MQTT + if (!this.cfg.serverEnabled) { + zwaveOptions.inclusionUserCallbacks = { + ...this.inclusionUserCallbacks, + } + } - let s0Key: string + if (this.cfg.scales) { + const scales: Record = {} + for (const s of this.cfg.scales) { + scales[s.key] = s.label + } - // back compatibility - if (this.cfg.networkKey) { - s0Key = this.cfg.networkKey - delete this.cfg.networkKey + zwaveOptions.preferences = { + scales, } + } - this.cfg.securityKeys = this.cfg.securityKeys || {} + Object.assign(zwaveOptions, this.cfg.options) - // update settings to fix compatibility - if (s0Key && !this.cfg.securityKeys.S0_Legacy) { - this.cfg.securityKeys.S0_Legacy = s0Key - const settings = jsonStore.get(store.settings) - settings.zwave = this.cfg - await jsonStore.put(store.settings, settings) - } + let s0Key: string - utils.parseSecurityKeys(this.cfg, zwaveOptions) + // back compatibility + if (this.cfg.networkKey) { + s0Key = this.cfg.networkKey + delete this.cfg.networkKey + } - try { - // init driver here because if connect fails the driver is destroyed - // this could throw so include in the try/catch - this._driver = new Driver(this.cfg.port, zwaveOptions) - this._driver.on('error', this._onDriverError.bind(this)) - this._driver.once( - 'driver ready', - this._onDriverReady.bind(this), - ) - this._driver.on( - 'all nodes ready', - this._onScanComplete.bind(this), - ) - this._driver.on( - 'bootloader ready', - this._onBootLoaderReady.bind(this), - ) + this.cfg.securityKeys = this.cfg.securityKeys || {} - logger.info(`Connecting to ${this.cfg.port}`) + // update settings to fix compatibility + if (s0Key && !this.cfg.securityKeys.S0_Legacy) { + this.cfg.securityKeys.S0_Legacy = s0Key + const settings = jsonStore.get(store.settings) + settings.zwave = this.cfg + await jsonStore.put(store.settings, settings) + } - // setup user callbacks only if there are connected clients - this.hasUserCallbacks = - (await this.socket.fetchSockets()).length > 0 + utils.parseSecurityKeys(this.cfg, zwaveOptions) - if (this.hasUserCallbacks) { - this.setUserCallbacks() - } + try { + // init driver here because if connect fails the driver is destroyed + // this could throw so include in the try/catch + this._driver = new Driver(this.cfg.port, zwaveOptions) + this._driver.on('error', this._onDriverError.bind(this)) + this._driver.once('driver ready', this._onDriverReady.bind(this)) + this._driver.on('all nodes ready', this._onScanComplete.bind(this)) + this._driver.on( + 'bootloader ready', + this._onBootLoaderReady.bind(this), + ) - await this._driver.start() + logger.info(`Connecting to ${this.cfg.port}`) - if (this.checkIfDestroyed()) { - return - } + // setup user callbacks only if there are connected clients + this.hasUserCallbacks = + (await this.socket.fetchSockets()).length > 0 - if (this.cfg.serverEnabled) { - this.server = new ZwavejsServer(this._driver, { - port: this.cfg.serverPort || 3000, - host: this.cfg.serverHost, - logger: LogManager.module('Z-Wave-Server'), - enableDNSServiceDiscovery: - !this.cfg.serverServiceDiscoveryDisabled, - }) + if (this.hasUserCallbacks) { + this.setUserCallbacks() + } - this.server.on('error', () => { - // this is already logged by the server but we need this to prevent - // unhandled exceptions - }) + await this._driver.start() - this.server.on('hard reset', () => { - logger.info('Hard reset requested by ZwaveJS Server') - this.restart().catch((err) => { - logger.error(err) - }) - }) - } + if (this.checkIfDestroyed()) { + return + } - if (this.cfg.enableStatistics) { - this.enableStatistics() - } + if (this.cfg.serverEnabled) { + this.server = new ZwavejsServer(this._driver, { + port: this.cfg.serverPort || 3000, + host: this.cfg.serverHost, + logger: LogManager.module('Z-Wave-Server'), + enableDNSServiceDiscovery: + !this.cfg.serverServiceDiscoveryDisabled, + }) - this.status = ZwaveClientStatus.CONNECTED - } catch (error) { - // destroy diver instance when it fails - if (this._driver) { - this._driver.destroy().catch((err) => { - logger.error( - `Error while destroying driver ${err.message}`, - error, - ) - }) - } + this.server.on('error', () => { + // this is already logged by the server but we need this to prevent + // unhandled exceptions + }) - if (this.checkIfDestroyed()) { - return - } + this.server.on('hard reset', () => { + logger.info('Hard reset requested by ZwaveJS Server') + this.restart().catch((err) => { + logger.error(err) + }) + }) + } - this._onDriverError(error, true) + if (this.cfg.enableStatistics) { + this.enableStatistics() + } - if (error.code !== ZWaveErrorCodes.Driver_InvalidOptions) { - this.backoffRestart() - } else { + this.status = ZwaveClientStatus.CONNECTED + } catch (error) { + // destroy diver instance when it fails + if (this._driver) { + this._driver.destroy().catch((err) => { logger.error( - `Invalid options for driver: ${error.message}`, + `Error while destroying driver ${err.message}`, error, ) - } + }) + } + + if (this.checkIfDestroyed()) { + return + } + + this._onDriverError(error, true) + + if (error.code !== ZWaveErrorCodes.Driver_InvalidOptions) { + this.backoffRestart() + } else { + logger.error( + `Invalid options for driver: ${error.message}`, + error, + ) } - } else { - logger.info(`Driver already connected to ${this.cfg.port}`) } } diff --git a/src/App.vue b/src/App.vue index 959c890b0f0..8ad197081c2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -440,6 +440,7 @@ export default { 'appInfo', 'controllerNode', 'zniffer', + 'zwave', ]), ...mapState(useBaseStore, { darkMode: (store) => store.ui.darkMode, @@ -447,24 +448,38 @@ export default { }), pages() { const pages = [ - { - icon: 'widgets', - title: 'Control Panel', - path: Routes.controlPanel, - }, - - { - icon: 'qr_code_scanner', - title: 'Smart Start', - path: Routes.smartStart, - }, { icon: 'settings', title: 'Settings', path: Routes.settings }, - { icon: 'movie_filter', title: 'Scenes', path: Routes.scenes }, { icon: 'bug_report', title: 'Debug', path: Routes.debug }, { icon: 'folder', title: 'Store', path: Routes.store }, - { icon: 'share', title: 'Network graph', path: Routes.mesh }, ] + if (this.zwave?.enabled) { + pages.unshift( + { + icon: 'widgets', + title: 'Control Panel', + path: Routes.controlPanel, + }, + { + icon: 'qr_code_scanner', + title: 'Smart Start', + path: Routes.smartStart, + }, + ) + + pages.splice(3, 0, { + icon: 'movie_filter', + title: 'Scenes', + path: Routes.scenes, + }) + + pages.push({ + icon: 'share', + title: 'Network graph', + path: Routes.mesh, + }) + } + if (this.zniffer?.enabled) { pages.splice(1, 0, { icon: 'preview', diff --git a/src/stores/base.js b/src/stores/base.js index 331d961274a..7cf51fa105e 100644 --- a/src/stores/base.js +++ b/src/stores/base.js @@ -24,6 +24,7 @@ const useBaseStore = defineStore('base', { smartStartTable: {}, }), zwave: { + enabled: true, port: '/dev/zwave', allowBootloaderOnly: false, commandsTimeout: 30, diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 38196086ffb..554c5d2213b 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -479,7 +479,19 @@ - Z-Wave + + + Z-Wave + + + + - + @@ -991,7 +1003,18 @@ - Zniffer + + + Zniffer + + + - + - - -