From f919273448bfc82745fba4e1a6029a933c75765a Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Thu, 18 Jul 2024 14:21:50 +0200 Subject: [PATCH] feat(ui): show association error in association dialog (#3804) Co-authored-by: Dominic Griesel --- api/app.ts | 23 ++- api/lib/Constants.ts | 18 +-- api/lib/Gateway.ts | 1 - api/lib/ZnifferManager.ts | 4 +- api/lib/ZwaveClient.ts | 93 +++++------ package-lock.json | 149 +++++++++--------- package.json | 4 +- src/components/dialogs/DialogAssociation.vue | 94 ++++++++++- .../nodes-table/AssociationGroups.vue | 19 ++- src/lib/utils.js | 7 + 10 files changed, 250 insertions(+), 162 deletions(-) diff --git a/api/app.ts b/api/app.ts index eb56c53c12..a3f077c252 100644 --- a/api/app.ts +++ b/api/app.ts @@ -9,12 +9,7 @@ import jsonStore from './lib/jsonStore' import * as loggers from './lib/logger' import MqttClient from './lib/MqttClient' import SocketManager from './lib/SocketManager' -import ZWaveClient, { - CallAPIResult, - configManager, - loadManager, - SensorTypeScale, -} from './lib/ZwaveClient' +import ZWaveClient, { CallAPIResult, SensorTypeScale } from './lib/ZwaveClient' import multer, { diskStorage } from 'multer' import extract from 'extract-zip' import { serverVersion } from '@zwave-js/server' @@ -49,6 +44,7 @@ import backupManager from './lib/BackupManager' import { readFile, realpath } from 'fs/promises' import { generate } from 'selfsigned' import ZnifferManager, { ZnifferConfig } from './lib/ZnifferManager' +import { getAllNamedScaleGroups, getAllSensors } from '@zwave-js/core' const createCertificate = promisify(generate) @@ -258,7 +254,6 @@ export async function startServer(port: number | string, host?: string) { setupSocket(server) setupInterceptor() await loadSnippets() - await loadManager() startZniffer(settings.zniffer) await startGateway(settings) } @@ -1061,15 +1056,15 @@ app.get( apisLimiter, isAuthenticated, async function (req, res) { - const sensorTypes = configManager.sensorTypes - const sensorScalesGroups = configManager.namedScales + const allSensors = getAllSensors() + const namedScaleGroups = getAllNamedScaleGroups() const scales: SensorTypeScale[] = [] - for (const [key, group] of sensorScalesGroups) { - for (const [, scale] of group) { + for (const group of namedScaleGroups) { + for (const scale of Object.values(group.scales)) { scales.push({ - key: key, + key: group.name, sensor: group.name, unit: scale.unit, label: scale.label, @@ -1078,8 +1073,8 @@ app.get( } } - for (const [, sensor] of sensorTypes) { - for (const [, scale] of sensor.scales) { + for (const sensor of allSensors) { + for (const scale of Object.values(sensor.scales)) { scales.push({ key: sensor.key, sensor: sensor.label, diff --git a/api/lib/Constants.ts b/api/lib/Constants.ts index 13c26ee3a0..a555596096 100644 --- a/api/lib/Constants.ts +++ b/api/lib/Constants.ts @@ -1,4 +1,4 @@ -import { ConfigManager } from '@zwave-js/config' +import { getMeter, getMeterScale } from '@zwave-js/core' interface IGenericMap { [key: number]: string @@ -113,19 +113,13 @@ export function productionType(index: number): Record { }, } } -export function meterType( - ccSpecific: IMeterCCSpecific, - configManager: ConfigManager, -): any { - const meter = configManager.lookupMeter(ccSpecific.meterType) - const scale = configManager.lookupMeterScale( - ccSpecific.meterType, - ccSpecific.scale, - ) +export function meterType(ccSpecific: IMeterCCSpecific): any { + const meter = getMeter(ccSpecific.meterType) + const scale = getMeterScale(ccSpecific.meterType, ccSpecific.scale) const cfg = { - sensor: meter ? meter.name : 'unknown', - objectId: scale ? scale.label : `unknown${ccSpecific.scale}`, + sensor: meter?.name || 'unknown', + objectId: scale?.label || `unknown${ccSpecific.scale}`, props: {}, } diff --git a/api/lib/Gateway.ts b/api/lib/Gateway.ts index 197f81c20d..32afa9b6c0 100644 --- a/api/lib/Gateway.ts +++ b/api/lib/Gateway.ts @@ -1566,7 +1566,6 @@ export default class Gateway { if (valueId.ccSpecific) { sensor = Constants.meterType( valueId.ccSpecific as IMeterCCSpecific, - this._zwave.driver.configManager, ) sensor.objectId += '_' + valueId.property diff --git a/api/lib/ZnifferManager.ts b/api/lib/ZnifferManager.ts index 14b3327553..20a847d306 100644 --- a/api/lib/ZnifferManager.ts +++ b/api/lib/ZnifferManager.ts @@ -215,9 +215,7 @@ export default class ZnifferManager extends TypedEventEmitter { try { - const parsed: Record = commandClass.toLogEntry( - this.zniffer as any, - ) + const parsed: Record = commandClass.toLogEntry() if (isEncapsulatingCommandClass(commandClass)) { parsed.encapsulated = [ diff --git a/api/lib/ZwaveClient.ts b/api/lib/ZwaveClient.ts index 4b463d32a2..5a56952c74 100644 --- a/api/lib/ZwaveClient.ts +++ b/api/lib/ZwaveClient.ts @@ -102,6 +102,7 @@ import { InclusionUserCallbacks, InclusionState, ProvisioningEntryStatus, + AssociationCheckResult, LinkReliabilityCheckResult, } from 'zwave-js' import { getEnumMemberName, parseQRCodeString } from 'zwave-js/Utils' @@ -128,11 +129,6 @@ export const configManager = new ConfigManager({ deviceConfigPriorityDir, }) -export async function loadManager() { - await configManager.loadNamedScales() - await configManager.loadSensorTypes() -} - const logger = LogManager.module('Z-Wave') // eslint-disable-next-line @typescript-eslint/no-var-requires const loglevels = require('triple-beam').configs.npm.levels @@ -162,6 +158,7 @@ export const allowedApis = validateMethods([ 'getNodeNeighbors', 'discoverNodeNeighbors', 'getAssociations', + 'checkAssociation', 'addAssociations', 'removeAssociations', 'removeAllAssociations', @@ -1763,6 +1760,21 @@ class ZwaveClient extends TypedEventEmitter { return toReturn } + /** + * Check if a given association is allowed + */ + checkAssociation( + source: AssociationAddress, + groupId: number, + association: AssociationAddress, + ) { + return this.driver.controller.checkAssociation( + source, + groupId, + association, + ) + } + /** * Add a node to the array of specified [associations](https://zwave-js.github.io/node-zwave-js/#/api/controller?id=association-interface) */ @@ -1778,53 +1790,41 @@ class ZwaveClient extends TypedEventEmitter { (source.endpoint ? ' Endpoint ' + source.endpoint : '') }` - if (zwaveNode) { - try { - for (const a of associations) { - if ( - this._driver.controller.isAssociationAllowed( - source, - groupId, - a, - ) - ) { - this.logNode( - zwaveNode, - 'info', - `Adding Node ${a.nodeId} to Group ${groupId} of ${sourceMsg}`, - ) + if (!zwaveNode) { + throw new Error(`Node ${source.nodeId} not found`) + } - await this._driver.controller.addAssociations( - source, - groupId, - [a], - ) + const result: AssociationCheckResult[] = [] - return true - } else { - this.logNode( - zwaveNode, - 'warn', - `Unable to add Node ${a.nodeId} to Group ${groupId} of ${sourceMsg}, association not allowed`, - ) - } - } - } catch (error) { + for (const a of associations) { + const checkResult = this._driver.controller.checkAssociation( + source, + groupId, + a, + ) + + result.push(checkResult) + + if (checkResult === AssociationCheckResult.OK) { + this.logNode( + zwaveNode, + 'info', + `Adding Node ${a.nodeId} to Group ${groupId} of ${sourceMsg}`, + ) + + await this._driver.controller.addAssociations(source, groupId, [ + a, + ]) + } else { this.logNode( zwaveNode, 'warn', - `Error while adding associations to ${sourceMsg}: ${error.message}`, + `Unable to add Node ${a.nodeId} to Group ${groupId} of ${sourceMsg}: ${getEnumMemberName(AssociationCheckResult, checkResult)}`, ) } - } else { - this.logNode( - zwaveNode, - 'warn', - `Error while adding associations to ${sourceMsg}, node not found`, - ) } - return false + return result } /** @@ -4631,7 +4631,8 @@ class ZwaveClient extends TypedEventEmitter { } } - private _onInclusionStarted(secure: boolean) { + private _onInclusionStarted(strategy: InclusionStrategy) { + const secure = strategy !== InclusionStrategy.Insecure const message = `${secure ? 'Secure' : 'Non-secure'} inclusion started` this._updateControllerStatus(message) this.emit('event', EventSource.CONTROLLER, 'inclusion started', secure) @@ -5612,7 +5613,7 @@ class ZwaveClient extends TypedEventEmitter { this.logNode( endpoint.nodeId, 'error', - `Notification received but node doesn't exists`, + `Notification received but node doesn't exist`, ) return @@ -6026,7 +6027,7 @@ class ZwaveClient extends TypedEventEmitter { node.keepAwake = zwaveNode.keepAwake node.maxDataRate = zwaveNode.maxDataRate node.deviceClass = { - basic: zwaveNode.deviceClass?.basic.key, + basic: zwaveNode.deviceClass?.basic, generic: zwaveNode.deviceClass?.generic.key, specific: zwaveNode.deviceClass?.specific.key, } diff --git a/package-lock.json b/package-lock.json index 3ca4c6f188..48c983fbdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@jamescoyle/vue-icon": "^0.1.2", "@kvaster/zwavejs-prom": "^0.0.2", "@mdi/js": "7.4.47", - "@zwave-js/server": "^1.36.0", + "@zwave-js/server": "github:zwave-js/zwave-js-server#zwave-js-13", "@zwave-js/winston-daily-rotate-file": "^4.5.6-1", "ansi_up": "^6.0.2", "archiver": "^7.0.1", @@ -58,7 +58,7 @@ "vuedraggable": "^2.24.3", "vuetify": "^2.7.2", "winston": "^3.13.0", - "zwave-js": "^12.13.0" + "zwave-js": "^13.0.0-beta.6" }, "bin": { "zwave-js-ui": "server/bin/www.js" @@ -179,9 +179,9 @@ } }, "node_modules/@alcalzone/pak": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@alcalzone/pak/-/pak-0.10.2.tgz", - "integrity": "sha512-v+kM7HlfIVNLDlGBcbZvrG3yVK3rPLH5kIoGRJbCcoHwpUqQbfEMzXAy1ZrfP+zbI5phHw2PhgrXZr3z6nh7Ow==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@alcalzone/pak/-/pak-0.11.0.tgz", + "integrity": "sha512-S6s2Xug8VJ04Xgam7kV+dUydVB2gJmTem+Kr7oxneeXndWddgoQxphQNI9WqgpsifTkonC9wiAbj3qkMFlNeAA==", "dependencies": { "axios": "^1.6.2", "execa": "~5.0.1", @@ -4729,14 +4729,14 @@ } }, "node_modules/@zwave-js/cc": { - "version": "12.13.0", - "resolved": "https://registry.npmjs.org/@zwave-js/cc/-/cc-12.13.0.tgz", - "integrity": "sha512-hftGOQwykllhostGhnQCGyw2iGoSYRF5DijodNNclM9PnMDoWHuk5x5Yh4ytFvqBsQVr9aiOie1jaDD2VFSBgQ==", - "dependencies": { - "@zwave-js/core": "12.13.0", - "@zwave-js/host": "12.13.0", - "@zwave-js/serial": "12.13.0", - "@zwave-js/shared": "12.11.1", + "version": "13.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@zwave-js/cc/-/cc-13.0.0-beta.6.tgz", + "integrity": "sha512-lAXrcnieIdExJgmGjjSah+aehKxRQn7tIibdRuQ3uxe8p44Hq++Y/GZedpKKH5oetYH+jar226zixxm6Qaa7Dg==", + "dependencies": { + "@zwave-js/core": "13.0.0-beta.6", + "@zwave-js/host": "13.0.0-beta.6", + "@zwave-js/serial": "13.0.0-beta.6", + "@zwave-js/shared": "13.0.0-beta.1", "alcalzone-shared": "^4.0.8", "ansi-colors": "^4.1.3", "reflect-metadata": "^0.2.2" @@ -4749,12 +4749,12 @@ } }, "node_modules/@zwave-js/config": { - "version": "12.13.0", - "resolved": "https://registry.npmjs.org/@zwave-js/config/-/config-12.13.0.tgz", - "integrity": "sha512-zdU17kNKmvoDyj7wVS/OJW//mNNtQA7SGnsyfR2zpcwh3UnZzPVAGT7uo79oT37xWVc9e8xgDI24/pOynRCWOg==", + "version": "13.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@zwave-js/config/-/config-13.0.0-beta.6.tgz", + "integrity": "sha512-T4lkIkXV95jgfKECkblzmBmukQYVobvSA1kzhh2sEdz30JK4HJ4P2ZC46a/Fncg1rqg7JHhMgWL2AIxo4pz6/Q==", "dependencies": { - "@zwave-js/core": "12.13.0", - "@zwave-js/shared": "12.11.1", + "@zwave-js/core": "13.0.0-beta.6", + "@zwave-js/shared": "13.0.0-beta.1", "alcalzone-shared": "^4.0.8", "ansi-colors": "^4.1.3", "fs-extra": "^11.2.0", @@ -4771,12 +4771,12 @@ } }, "node_modules/@zwave-js/core": { - "version": "12.13.0", - "resolved": "https://registry.npmjs.org/@zwave-js/core/-/core-12.13.0.tgz", - "integrity": "sha512-CtM5vMyG/PdtZC4hC7fzD0+22JVvla7iOgprPwFKbPzFq7hUQLzpzefT+F9YLgK1lMyJTXZ95W2RBKPgM8EUTQ==", + "version": "13.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@zwave-js/core/-/core-13.0.0-beta.6.tgz", + "integrity": "sha512-W76NEbuw7tgcUVaS4a6eYR2tnU4WCHY7ANJDG79BZ4xcSvjVAvNGquYRcoTu1gMKAjQOfQLrViLnKojYQhIt5w==", "dependencies": { "@alcalzone/jsonl-db": "^3.1.1", - "@zwave-js/shared": "12.11.1", + "@zwave-js/shared": "13.0.0-beta.1", "alcalzone-shared": "^4.0.8", "ansi-colors": "^4.1.3", "dayjs": "^1.11.10", @@ -4804,13 +4804,13 @@ } }, "node_modules/@zwave-js/host": { - "version": "12.13.0", - "resolved": "https://registry.npmjs.org/@zwave-js/host/-/host-12.13.0.tgz", - "integrity": "sha512-Jjk+3LSCOWEhFQcMfSTA00mvN5JPWbtyXJx+ZidsjEoiuUf28w0bYtonTtNqUpFYcCIazeY5W1oV3002ZJ8wRw==", + "version": "13.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@zwave-js/host/-/host-13.0.0-beta.6.tgz", + "integrity": "sha512-tmpKJxbV6SNxlLWfOmC6ey19rvw7JKs3LY6PV1WrompuwoHvU5cvVNVdfA09+n1UwJUqz9I6315BWsNSiuPZ4A==", "dependencies": { - "@zwave-js/config": "12.13.0", - "@zwave-js/core": "12.13.0", - "@zwave-js/shared": "12.11.1", + "@zwave-js/config": "13.0.0-beta.6", + "@zwave-js/core": "13.0.0-beta.6", + "@zwave-js/shared": "13.0.0-beta.1", "alcalzone-shared": "^4.0.8" }, "engines": { @@ -4821,12 +4821,12 @@ } }, "node_modules/@zwave-js/nvmedit": { - "version": "12.13.0", - "resolved": "https://registry.npmjs.org/@zwave-js/nvmedit/-/nvmedit-12.13.0.tgz", - "integrity": "sha512-0N+6sFPJkcKvj6h/cu75sASOCWw5y7E876eQx/k5sEHSkdjXCVPBZ4sL+S7yYVjZQzrurt2P7/SW93jep+vDiQ==", + "version": "13.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@zwave-js/nvmedit/-/nvmedit-13.0.0-beta.6.tgz", + "integrity": "sha512-AKXrhvOm+buKDMXT4Ig98VgH6RcZwjF0F/pQsQO+63GGZuA/ycBL4B2PoxXn6d5k3NOiAIPMzi4dc3fR6Signw==", "dependencies": { - "@zwave-js/core": "12.13.0", - "@zwave-js/shared": "12.11.1", + "@zwave-js/core": "13.0.0-beta.6", + "@zwave-js/shared": "13.0.0-beta.1", "alcalzone-shared": "^4.0.8", "fs-extra": "^11.2.0", "reflect-metadata": "^0.2.2", @@ -4938,14 +4938,14 @@ } }, "node_modules/@zwave-js/serial": { - "version": "12.13.0", - "resolved": "https://registry.npmjs.org/@zwave-js/serial/-/serial-12.13.0.tgz", - "integrity": "sha512-/RQ5Y+Jzr7Swb0Q0IfQYsSQhbmEai/tGb4RVT1sKvxhCWYRwSVZsBC3l+TmRX5XpSRuQCtv+zwra64fNaMDLFQ==", + "version": "13.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@zwave-js/serial/-/serial-13.0.0-beta.6.tgz", + "integrity": "sha512-QkHRxauVl90rKxTSiLtILJdoDvqhMPSaiXqr581zIbpzRS8tDC/RRDIyqYdfZQK8TFgxzR2HfsI72WO0K1GdZQ==", "dependencies": { "@serialport/stream": "^12.0.0", - "@zwave-js/core": "12.13.0", - "@zwave-js/host": "12.13.0", - "@zwave-js/shared": "12.11.1", + "@zwave-js/core": "13.0.0-beta.6", + "@zwave-js/host": "13.0.0-beta.6", + "@zwave-js/shared": "13.0.0-beta.1", "alcalzone-shared": "^4.0.8", "serialport": "^12.0.0", "winston": "^3.13.0" @@ -4959,8 +4959,7 @@ }, "node_modules/@zwave-js/server": { "version": "1.36.0", - "resolved": "https://registry.npmjs.org/@zwave-js/server/-/server-1.36.0.tgz", - "integrity": "sha512-yqBRL4ostEx/h5HN8PlwzrbrQQQ11EGxNSoO/vNLdnNXpl7eUvbngms00K5i3a/ZEQvxdV0DdLWC5js7ZAqrHA==", + "resolved": "git+ssh://git@github.com/zwave-js/zwave-js-server.git#1719062c9d3433704d9c95817bcfded57e24b54b", "dependencies": { "@homebridge/ciao": "^1.1.7", "minimist": "^1.2.8", @@ -4971,13 +4970,13 @@ "zwave-server": "dist/bin/server.js" }, "peerDependencies": { - "zwave-js": "^12.11.0" + "zwave-js": "^13.0.0-beta.1" } }, "node_modules/@zwave-js/shared": { - "version": "12.11.1", - "resolved": "https://registry.npmjs.org/@zwave-js/shared/-/shared-12.11.1.tgz", - "integrity": "sha512-/uNlHLEWgqwao0D2em/BpVFx3M8x1EL//KqxY9JNteYxNV175Bhu4uRcRHXAKPiqcPNyCmmU2/fHXHgRpwtOVw==", + "version": "13.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@zwave-js/shared/-/shared-13.0.0-beta.1.tgz", + "integrity": "sha512-L3GNJVQsIU9CoVLwHAa9NTC7v2obaKF/62Q1v0TQ20DlFLfeN9tf6hK+Wl5n2Nn13Cb67cg/xiIy1qIuFLAg/A==", "dependencies": { "alcalzone-shared": "^4.0.8", "fs-extra": "^11.2.0" @@ -4990,14 +4989,14 @@ } }, "node_modules/@zwave-js/testing": { - "version": "12.13.0", - "resolved": "https://registry.npmjs.org/@zwave-js/testing/-/testing-12.13.0.tgz", - "integrity": "sha512-t6UXpUvfNllFCC6HwWy676bjRFXOe0vTsgJ/0Tt2pOp+cc1Z8i2czU5i1MIkTb5qVxcB40VhYoFoerVotYpkQg==", - "dependencies": { - "@zwave-js/core": "12.13.0", - "@zwave-js/host": "12.13.0", - "@zwave-js/serial": "12.13.0", - "@zwave-js/shared": "12.11.1", + "version": "13.0.0-beta.6", + "resolved": "https://registry.npmjs.org/@zwave-js/testing/-/testing-13.0.0-beta.6.tgz", + "integrity": "sha512-YWpEaQKhRxrc6+At1daYMH8nc29CMpC+3OGMCTcFhedkd7/Vvojj7fODQtDZLIrjEUHuQyc0l5LONTHNpxvXZQ==", + "dependencies": { + "@zwave-js/core": "13.0.0-beta.6", + "@zwave-js/host": "13.0.0-beta.6", + "@zwave-js/serial": "13.0.0-beta.6", + "@zwave-js/shared": "13.0.0-beta.1", "alcalzone-shared": "^4.0.8", "ansi-colors": "^4.1.3" }, @@ -14505,26 +14504,26 @@ } }, "node_modules/p-queue": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.4.1.tgz", - "integrity": "sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.0.1.tgz", + "integrity": "sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==", "dependencies": { "eventemitter3": "^5.0.1", - "p-timeout": "^5.0.2" + "p-timeout": "^6.1.2" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-queue/node_modules/p-timeout": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", - "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz", + "integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==", "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -20508,28 +20507,28 @@ } }, "node_modules/zwave-js": { - "version": "12.13.0", - "resolved": "https://registry.npmjs.org/zwave-js/-/zwave-js-12.13.0.tgz", - "integrity": "sha512-/SW6VN6xDBZPEMyGxRUioJUWXzwKBYE9qxf9Tk0JJ1YIz2n4O2cWPeTpBzwOI48Pr/a+0LsC5uMO4g1VBJIkSg==", + "version": "13.0.0-beta.6", + "resolved": "https://registry.npmjs.org/zwave-js/-/zwave-js-13.0.0-beta.6.tgz", + "integrity": "sha512-ZFkAsbNDu/wEeot8kCr/4BTX1r6pQm1n0TJfxOo6ycnseJkDrhb+aHE7T6aou6oxOLAavL1dP/QZiEdl/m8A9g==", "dependencies": { "@alcalzone/jsonl-db": "^3.1.1", - "@alcalzone/pak": "^0.10.1", + "@alcalzone/pak": "^0.11.0", "@homebridge/ciao": "^1.2.0", - "@zwave-js/cc": "12.13.0", - "@zwave-js/config": "12.13.0", - "@zwave-js/core": "12.13.0", - "@zwave-js/host": "12.13.0", - "@zwave-js/nvmedit": "12.13.0", - "@zwave-js/serial": "12.13.0", - "@zwave-js/shared": "12.11.1", - "@zwave-js/testing": "12.13.0", + "@zwave-js/cc": "13.0.0-beta.6", + "@zwave-js/config": "13.0.0-beta.6", + "@zwave-js/core": "13.0.0-beta.6", + "@zwave-js/host": "13.0.0-beta.6", + "@zwave-js/nvmedit": "13.0.0-beta.6", + "@zwave-js/serial": "13.0.0-beta.6", + "@zwave-js/shared": "13.0.0-beta.1", + "@zwave-js/testing": "13.0.0-beta.6", "alcalzone-shared": "^4.0.8", "ansi-colors": "^4.1.3", "execa": "^5.1.1", "fs-extra": "^11.2.0", "got": "^13.0.0", "mdns-server": "^1.0.11", - "p-queue": "^7.4.1", + "p-queue": "^8.0.1", "proper-lockfile": "^4.1.2", "reflect-metadata": "^0.2.2", "semver": "^7.6.2", diff --git a/package.json b/package.json index b0c4d40f05..6241b77055 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@jamescoyle/vue-icon": "^0.1.2", "@kvaster/zwavejs-prom": "^0.0.2", "@mdi/js": "7.4.47", - "@zwave-js/server": "^1.36.0", + "@zwave-js/server": "github:zwave-js/zwave-js-server#zwave-js-13", "@zwave-js/winston-daily-rotate-file": "^4.5.6-1", "ansi_up": "^6.0.2", "archiver": "^7.0.1", @@ -110,7 +110,7 @@ "vuedraggable": "^2.24.3", "vuetify": "^2.7.2", "winston": "^3.13.0", - "zwave-js": "^12.13.0" + "zwave-js": "^13.0.0-beta.6" }, "devDependencies": { "@actions/github": "^6.0.0", diff --git a/src/components/dialogs/DialogAssociation.vue b/src/components/dialogs/DialogAssociation.vue index 68f8736fef..c333f1736f 100644 --- a/src/components/dialogs/DialogAssociation.vue +++ b/src/components/dialogs/DialogAssociation.vue @@ -94,6 +94,12 @@ :items="targetEndpoints" > + + + + {{ associationError }} + + @@ -108,7 +114,7 @@ ADD @@ -121,8 +127,13 @@ import { Protocols } from '@zwave-js/core/safe' import { mapState } from 'pinia' import useBaseStore from '../../stores/base.js' +import { getAssociationAddress } from '../../lib/utils' +import { AssociationCheckResult } from '@zwave-js/cc/safe' +import { getEnumMemberName } from 'zwave-js/safe' +import InstancesMixin from '../../mixins/InstancesMixin.js' export default { + mixins: [InstancesMixin], props: { value: Boolean, associations: Array, @@ -132,6 +143,15 @@ export default { value() { this.$refs.form && this.$refs.form.resetValidation() this.resetGroup() + this.associationError = '' + }, + group: { + deep: true, + handler() { + if (this.$refs.form?.validate()) { + this.allowedAssociation() + } + }, }, }, computed: { @@ -185,11 +205,83 @@ export default { return { valid: true, group: {}, + associationError: '', defaultGroup: { endpoint: null }, required: (v) => !!v || 'This field is required', } }, methods: { + getAssociationAddress, + async allowedAssociation() { + const association = this.group + const target = !isNaN(association.target) + ? parseInt(association.target) + : association.target?.id + + if (isNaN(target)) { + this.associationError = '' + return + } + + const group = association.group + + if (!group) { + this.associationError = '' + return + } + + const toAdd = { nodeId: target } + + if (group.multiChannel && association.targetEndpoint >= 0) { + toAdd.endpoint = association.targetEndpoint + } + + const args = [ + this.getAssociationAddress({ + nodeId: this.node.id, + endpoint: association.endpoint, + }), + group.value, + toAdd, + ] + + const response = await this.app.apiRequest( + 'checkAssociation', + args, + { + showInfo: false, + }, + ) + + if (response.success) { + const checkResult = response.result + + if (checkResult === AssociationCheckResult.OK) { + this.associationError = '' + } else if ( + checkResult === + AssociationCheckResult.Forbidden_SecurityClassMismatch + ) { + this.associationError = `Association not allowed: Node ${this.node.id} does not have the same security class as Node ${target}!` + } else if ( + checkResult === + AssociationCheckResult.Forbidden_DestinationSecurityClassNotGranted + ) { + this.associationError = `Association not allowed: Node ${this.node.id} was not granted the highest security class of Node ${target}!` + } else if ( + checkResult === + AssociationCheckResult.Forbidden_NoSupportedCCs + ) { + this.associationError = `Association not allowed: Node ${this.node.id} sends no commands in this group that Node ${target} supports!` + } else { + // This should not happen, but just in case + this.associationError = `Association not allowed: ${getEnumMemberName( + AssociationCheckResult, + checkResult, + )}` + } + } + }, resetGroup() { this.group = Object.assign({}, this.defaultGroup) }, diff --git a/src/components/nodes-table/AssociationGroups.vue b/src/components/nodes-table/AssociationGroups.vue index 3089d38ab1..f36b87875d 100644 --- a/src/components/nodes-table/AssociationGroups.vue +++ b/src/components/nodes-table/AssociationGroups.vue @@ -79,6 +79,9 @@ import { mapState, mapActions } from 'pinia' import useBaseStore from '../../stores/base.js' import InstancesMixin from '../../mixins/InstancesMixin.js' +import { getEnumMemberName } from 'zwave-js/safe' +import { AssociationCheckResult } from '@zwave-js/cc/safe' +import { getAssociationAddress } from '../../lib/utils' export default { components: { @@ -110,12 +113,7 @@ export default { }, methods: { ...mapActions(useBaseStore, ['showSnackbar']), - getAssociationAddress(ass) { - return { - nodeId: ass.nodeId, - endpoint: ass.endpoint === null ? undefined : ass.endpoint, - } - }, + getAssociationAddress, getNodeName(nodeId) { const node = this.nodes[this.nodesMap.get(nodeId)] return node ? node._name : 'NodeID_' + nodeId @@ -179,11 +177,16 @@ export default { const response = await this.app.apiRequest('addAssociations', args) if (response.success) { - if (response.result) { + const checkResult = response.result[0] + + if (checkResult === AssociationCheckResult.OK) { this.showSnackbar('Association added', 'success') this.getAssociations() } else { - this.showSnackbar('Error while adding association', 'error') + this.showSnackbar( + `Error while adding association: ${getEnumMemberName(AssociationCheckResult, checkResult)}`, + 'error', + ) } } this.dialogAssociation = false diff --git a/src/lib/utils.js b/src/lib/utils.js index 2bfa0b21bb..a639345207 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -329,3 +329,10 @@ export function getProtocolIcon(protocol) { description: getProtocol({ protocol }), } } + +export function getAssociationAddress(ass) { + return { + nodeId: ass.nodeId, + endpoint: ass.endpoint === null ? undefined : ass.endpoint, + } +}