diff --git a/server/services/zwavejs-ui/lib/constants.js b/server/services/zwavejs-ui/lib/constants.js index de46954b6a..d2af12a37d 100644 --- a/server/services/zwavejs-ui/lib/constants.js +++ b/server/services/zwavejs-ui/lib/constants.js @@ -3,6 +3,7 @@ const { DEVICE_FEATURE_TYPES, OPENING_SENSOR_STATE, STATE, + BUTTON_STATUS, COVER_STATE, DEVICE_FEATURE_UNITS, } = require('../../../utils/constants'); @@ -47,6 +48,28 @@ const STATES = { }, ], }, + central_scene: { + scene: [ + { + converter: (val) => { + switch (val) { + case 0: + return BUTTON_STATUS.CLICK; + case 1: + return BUTTON_STATUS.RELEASE; + case 2: + return BUTTON_STATUS.HOLD_CLICK; + case 3: + return BUTTON_STATUS.DOUBLE_CLICK; + case 4: + return BUTTON_STATUS.TRIPLE; + default: + return null; + } + }, + }, + ], + }, multilevel_sensor: { air_temperature: [{ converter: (val) => val }], power: [{ converter: (val) => val }], @@ -263,6 +286,17 @@ const EXPOSES = { has_feedback: false, }, }, + central_scene: { + scene: { + category: DEVICE_FEATURE_CATEGORIES.BUTTON, + type: DEVICE_FEATURE_TYPES.BUTTON.CLICK, + min: 0, + max: 4, + keep_history: false, + read_only: true, + has_feedback: true, + }, + }, multilevel_sensor: { air_temperature: { category: DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR, diff --git a/server/services/zwavejs-ui/lib/zwaveJSUI.onNodeValueUpdated.js b/server/services/zwavejs-ui/lib/zwaveJSUI.onNodeValueUpdated.js index 22685213a2..94175b9ad4 100644 --- a/server/services/zwavejs-ui/lib/zwaveJSUI.onNodeValueUpdated.js +++ b/server/services/zwavejs-ui/lib/zwaveJSUI.onNodeValueUpdated.js @@ -14,7 +14,7 @@ function onNodeValueUpdated(message) { // A value has been updated: https://zwave-js.github.io/node-zwave-js/#/api/node?id=quotvalue-addedquot-quotvalue-updatedquot-quotvalue-removedquot const messageNode = message.data[0]; const updatedValue = message.data[1]; - const { commandClassName, propertyName, propertyKeyName, endpoint, newValue } = updatedValue; + const { commandClassName, propertyName, propertyKeyName, endpoint = 0, newValue } = updatedValue; const nodeId = `zwavejs-ui:${messageNode.id}`; const node = this.getDevice(nodeId); @@ -27,7 +27,9 @@ function onNodeValueUpdated(message) { return Promise.resolve(); } - const valueConverters = getProperty(STATES, commandClassName, propertyName, propertyKeyName, zwaveJSNode.deviceClass); + const valueConverters = + getProperty(STATES, commandClassName, propertyName, propertyKeyName, zwaveJSNode.deviceClass) || + getProperty(STATES, commandClassName, propertyName, '', zwaveJSNode.deviceClass); if (!valueConverters) { return Promise.resolve(); diff --git a/server/services/zwavejs-ui/utils/convertToGladysDevice.js b/server/services/zwavejs-ui/utils/convertToGladysDevice.js index 74ff67a837..5ff4ce8329 100644 --- a/server/services/zwavejs-ui/utils/convertToGladysDevice.js +++ b/server/services/zwavejs-ui/utils/convertToGladysDevice.js @@ -46,9 +46,19 @@ const convertToGladysDevice = (serviceId, zwaveJsDevice) => { // Foreach value, we check if there is a matching feature in Gladys Object.keys(zwaveJsDevice.values).forEach((valueKey) => { const value = zwaveJsDevice.values[valueKey]; - const { commandClass, commandClassName, propertyName, propertyKeyName, endpoint, commandClassVersion = 1 } = value; + const { + commandClass, + commandClassName, + propertyName, + propertyKeyName, + endpoint = 0, + commandClassVersion = 1, + } = value; - let exposes = getProperty(EXPOSES, commandClassName, propertyName, propertyKeyName, zwaveJsDevice.deviceClass); + let exposes = + getProperty(EXPOSES, commandClassName, propertyName, propertyKeyName, zwaveJsDevice.deviceClass) || + // We try to get a higher EXPOSEd node (to handle Scene Controllers for example). + getProperty(EXPOSES, commandClassName, propertyName, '', zwaveJsDevice.deviceClass); if (exposes) { if (!Array.isArray(exposes)) { exposes = [ @@ -60,25 +70,25 @@ const convertToGladysDevice = (serviceId, zwaveJsDevice) => { } exposes.forEach((exposeFound) => { + // Let's check we effectively found a valid EXPOSE and not + // just a higher node + if (!exposeFound.feature.category) { + return; + } + const deviceFeatureId = getDeviceFeatureId( + zwaveJsDevice.id, + commandClassName, + endpoint, + propertyName, + propertyKeyName, + exposeFound.name, + ); + features.push({ ...exposeFound.feature, name: `${value.id}${exposeFound.name !== '' ? `:${exposeFound.name}` : ''}`, - external_id: getDeviceFeatureId( - zwaveJsDevice.id, - commandClassName, - endpoint, - propertyName, - propertyKeyName, - exposeFound.name, - ), - selector: getDeviceFeatureId( - zwaveJsDevice.id, - commandClassName, - endpoint, - propertyName, - propertyKeyName, - exposeFound.name, - ), + external_id: deviceFeatureId, + selector: deviceFeatureId, node_id: zwaveJsDevice.id, // These are custom properties only available on the object in memory (not in DB) command_class_version: commandClassVersion, diff --git a/server/test/services/zwavejs-ui/lib/exampleData.json b/server/test/services/zwavejs-ui/lib/exampleData.json index 0d1ce7366c..42f1104ef9 100644 --- a/server/test/services/zwavejs-ui/lib/exampleData.json +++ b/server/test/services/zwavejs-ui/lib/exampleData.json @@ -10489,6 +10489,717 @@ "lastTransmit": 1714901379459, "errorReceive": false, "errorTransmit": true + }, + { + "id": 9, + "name": "SceneController", + "loc": "Loc1", + "values": [ + { + "id": "9-91-0-slowRefresh", + "nodeId": 9, + "toUpdate": false, + "commandClass": 91, + "commandClassName": "Central Scene", + "endpoint": 0, + "property": "slowRefresh", + "propertyName": "slowRefresh", + "type": "boolean", + "readable": true, + "writeable": true, + "description": "When this is true, KeyHeldDown notifications are sent every 55s. When this is false, the notifications are sent every 200ms.", + "label": "Send held down notifications at a slow rate", + "stateless": false, + "commandClassVersion": 2, + "list": false, + "value": true, + "lastUpdate": 1731769606960, + "newValue": true + }, + { + "id": "9-91-0-scene-001", + "nodeId": 9, + "toUpdate": false, + "commandClass": 91, + "commandClassName": "Central Scene", + "endpoint": 0, + "property": "scene", + "propertyName": "scene", + "propertyKey": "001", + "propertyKeyName": "001", + "type": "number", + "readable": true, + "writeable": false, + "label": "Scene 001", + "stateless": true, + "commandClassVersion": 2, + "min": 0, + "max": 255, + "list": true, + "states": [ + { + "text": "KeyPressed", + "value": 0 + }, + { + "text": "KeyReleased", + "value": 1 + }, + { + "text": "KeyHeldDown", + "value": 2 + } + ], + "lastUpdate": 1733081284002 + }, + { + "id": "9-91-0-scene-002", + "nodeId": 9, + "toUpdate": false, + "commandClass": 91, + "commandClassName": "Central Scene", + "endpoint": 0, + "property": "scene", + "propertyName": "scene", + "propertyKey": "002", + "propertyKeyName": "002", + "type": "number", + "readable": true, + "writeable": false, + "label": "Scene 002", + "stateless": true, + "commandClassVersion": 2, + "min": 0, + "max": 255, + "list": true, + "states": [ + { + "text": "KeyPressed", + "value": 0 + }, + { + "text": "KeyReleased", + "value": 1 + }, + { + "text": "KeyHeldDown", + "value": 2 + } + ], + "lastUpdate": 1733081297253 + }, + { + "id": "9-91-0-scene-003", + "nodeId": 9, + "toUpdate": false, + "commandClass": 91, + "commandClassName": "Central Scene", + "endpoint": 0, + "property": "scene", + "propertyName": "scene", + "propertyKey": "003", + "propertyKeyName": "003", + "type": "number", + "readable": true, + "writeable": false, + "label": "Scene 003", + "stateless": false, + "commandClassVersion": 2, + "min": 0, + "max": 255, + "list": true, + "states": [ + { + "text": "KeyPressed", + "value": 0 + }, + { + "text": "KeyReleased", + "value": 1 + }, + { + "text": "KeyHeldDown", + "value": 2 + } + ], + "lastUpdate": 1732307476514 + }, + { + "id": "9-91-0-scene-004", + "nodeId": 9, + "toUpdate": false, + "commandClass": 91, + "commandClassName": "Central Scene", + "endpoint": 0, + "property": "scene", + "propertyName": "scene", + "propertyKey": "004", + "propertyKeyName": "004", + "type": "number", + "readable": true, + "writeable": false, + "label": "Scene 004", + "stateless": false, + "commandClassVersion": 2, + "min": 0, + "max": 255, + "list": true, + "states": [ + { + "text": "KeyPressed", + "value": 0 + }, + { + "text": "KeyReleased", + "value": 1 + }, + { + "text": "KeyHeldDown", + "value": 2 + } + ], + "lastUpdate": 1732307476514 + }, + { + "id": "9-112-0-254", + "nodeId": 9, + "toUpdate": false, + "commandClass": 112, + "commandClassName": "Configuration", + "endpoint": 0, + "property": 254, + "propertyName": "Enable Configuration", + "type": "number", + "readable": true, + "writeable": true, + "description": "Lock/unlock all configuration parameters.", + "label": "Enable Configuration", + "default": 0, + "stateless": false, + "commandClassVersion": 1, + "min": 0, + "max": 1, + "list": true, + "allowManualEntry": false, + "states": [ + { + "text": "Unlock", + "value": 0 + }, + { + "text": "Lock", + "value": 1 + } + ], + "value": 0, + "lastUpdate": 1731764402393, + "newValue": 0 + }, + { + "id": "9-112-0-255", + "nodeId": 9, + "toUpdate": false, + "commandClass": 112, + "commandClassName": "Configuration", + "endpoint": 0, + "property": 255, + "propertyName": "Reset/Remove", + "type": "number", + "readable": true, + "writeable": true, + "description": "Reset the sensor or remove from the Z-Wave network.", + "label": "Reset/Remove", + "default": 1, + "stateless": false, + "commandClassVersion": 1, + "min": 1, + "max": 1431655765, + "list": true, + "allowManualEntry": false, + "states": [ + { + "text": "Idle", + "value": 1 + }, + { + "text": "Reset configuration", + "value": 85 + }, + { + "text": "Reset configuration and remove", + "value": 1431655765 + } + ], + "lastUpdate": 1732307476514 + }, + { + "id": "9-114-0-productId", + "nodeId": 9, + "toUpdate": false, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "endpoint": 0, + "property": "productId", + "propertyName": "productId", + "type": "number", + "readable": true, + "writeable": false, + "label": "Product ID", + "stateless": false, + "commandClassVersion": 2, + "min": 0, + "max": 65535, + "list": false, + "value": 11, + "lastUpdate": 1731764400143, + "newValue": 11 + }, + { + "id": "9-114-0-productType", + "nodeId": 9, + "toUpdate": false, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "endpoint": 0, + "property": "productType", + "propertyName": "productType", + "type": "number", + "readable": true, + "writeable": false, + "label": "Product type", + "stateless": false, + "commandClassVersion": 2, + "min": 0, + "max": 65535, + "list": false, + "value": 512, + "lastUpdate": 1731764400143, + "newValue": 512 + }, + { + "id": "9-114-0-manufacturerId", + "nodeId": 9, + "toUpdate": false, + "commandClass": 114, + "commandClassName": "Manufacturer Specific", + "endpoint": 0, + "property": "manufacturerId", + "propertyName": "manufacturerId", + "type": "number", + "readable": true, + "writeable": false, + "label": "Manufacturer ID", + "stateless": false, + "commandClassVersion": 2, + "min": 0, + "max": 65535, + "list": false, + "value": 520, + "lastUpdate": 1731764400144, + "newValue": 520 + }, + { + "id": "9-128-0-isLow", + "nodeId": 9, + "toUpdate": false, + "commandClass": 128, + "commandClassName": "Battery", + "endpoint": 0, + "property": "isLow", + "propertyName": "isLow", + "type": "boolean", + "readable": true, + "writeable": false, + "label": "Low battery level", + "stateless": false, + "commandClassVersion": 1, + "list": false, + "value": false, + "lastUpdate": 1733081295360, + "newValue": false + }, + { + "id": "9-128-0-level", + "nodeId": 9, + "toUpdate": false, + "commandClass": 128, + "commandClassName": "Battery", + "endpoint": 0, + "property": "level", + "propertyName": "level", + "type": "number", + "readable": true, + "writeable": false, + "label": "Battery level", + "stateless": false, + "commandClassVersion": 1, + "min": 0, + "max": 100, + "unit": "%", + "list": false, + "value": 100, + "lastUpdate": 1733081295361, + "newValue": 100 + }, + { + "id": "9-132-0-controllerNodeId", + "nodeId": 9, + "toUpdate": false, + "commandClass": 132, + "commandClassName": "Wake Up", + "endpoint": 0, + "property": "controllerNodeId", + "propertyName": "controllerNodeId", + "type": "any", + "readable": true, + "writeable": false, + "label": "Node ID of the controller", + "stateless": false, + "commandClassVersion": 2, + "list": false, + "value": 1, + "lastUpdate": 1731769613124, + "newValue": 1 + }, + { + "id": "9-132-0-wakeUpInterval", + "nodeId": 9, + "toUpdate": false, + "commandClass": 132, + "commandClassName": "Wake Up", + "endpoint": 0, + "property": "wakeUpInterval", + "propertyName": "wakeUpInterval", + "type": "number", + "readable": false, + "writeable": true, + "label": "wakeUpInterval (property)", + "default": 0, + "stateless": false, + "commandClassVersion": 2, + "min": 0, + "max": 0, + "step": 0, + "list": false, + "value": 0, + "lastUpdate": 1731769613125, + "newValue": 0 + }, + { + "id": "9-134-0-hardwareVersion", + "nodeId": 9, + "toUpdate": false, + "commandClass": 134, + "commandClassName": "Version", + "endpoint": 0, + "property": "hardwareVersion", + "propertyName": "hardwareVersion", + "type": "number", + "readable": true, + "writeable": false, + "label": "Z-Wave chip hardware version", + "stateless": false, + "commandClassVersion": 2, + "list": false, + "value": 2, + "lastUpdate": 1731764400217, + "newValue": 2 + }, + { + "id": "9-134-0-firmwareVersions", + "nodeId": 9, + "toUpdate": false, + "commandClass": 134, + "commandClassName": "Version", + "endpoint": 0, + "property": "firmwareVersions", + "propertyName": "firmwareVersions", + "type": "string[]", + "readable": true, + "writeable": false, + "label": "Z-Wave chip firmware versions", + "stateless": false, + "commandClassVersion": 2, + "list": false, + "value": ["1.6"], + "lastUpdate": 1731764400218, + "newValue": ["1.6"] + }, + { + "id": "9-134-0-protocolVersion", + "nodeId": 9, + "toUpdate": false, + "commandClass": 134, + "commandClassName": "Version", + "endpoint": 0, + "property": "protocolVersion", + "propertyName": "protocolVersion", + "type": "string", + "readable": true, + "writeable": false, + "label": "Z-Wave protocol version", + "stateless": false, + "commandClassVersion": 2, + "list": false, + "value": "4.34", + "lastUpdate": 1731764400219, + "newValue": "4.34" + }, + { + "id": "9-134-0-libraryType", + "nodeId": 9, + "toUpdate": false, + "commandClass": 134, + "commandClassName": "Version", + "endpoint": 0, + "property": "libraryType", + "propertyName": "libraryType", + "type": "number", + "readable": true, + "writeable": false, + "label": "Library type", + "stateless": false, + "commandClassVersion": 2, + "list": true, + "states": [ + { + "text": "Unknown", + "value": 0 + }, + { + "text": "Static Controller", + "value": 1 + }, + { + "text": "Controller", + "value": 2 + }, + { + "text": "Enhanced Slave", + "value": 3 + }, + { + "text": "Slave", + "value": 4 + }, + { + "text": "Installer", + "value": 5 + }, + { + "text": "Routing Slave", + "value": 6 + }, + { + "text": "Bridge Controller", + "value": 7 + }, + { + "text": "Device under Test", + "value": 8 + }, + { + "text": "N/A", + "value": 9 + }, + { + "text": "AV Remote", + "value": 10 + }, + { + "text": "AV Device", + "value": 11 + } + ], + "value": 3, + "lastUpdate": 1731764400220, + "newValue": 3 + } + ], + "groups": [ + { + "text": "Lifeline", + "endpoint": 0, + "value": 1, + "maxNodes": 5, + "isLifeline": true, + "multiChannel": false + }, + { + "text": "On/Off control,Button 1", + "endpoint": 0, + "value": 2, + "maxNodes": 5, + "isLifeline": false, + "multiChannel": false + }, + { + "text": "Dimming control,Button 1", + "endpoint": 0, + "value": 3, + "maxNodes": 5, + "isLifeline": false, + "multiChannel": false + }, + { + "text": "On/Off control,Button 2", + "endpoint": 0, + "value": 4, + "maxNodes": 5, + "isLifeline": false, + "multiChannel": false + }, + { + "text": "Dimming control,Button 2", + "endpoint": 0, + "value": 5, + "maxNodes": 5, + "isLifeline": false, + "multiChannel": false + }, + { + "text": "On/Off control,Button 3", + "endpoint": 0, + "value": 6, + "maxNodes": 5, + "isLifeline": false, + "multiChannel": false + }, + { + "text": "Dimming control,Button 3", + "endpoint": 0, + "value": 7, + "maxNodes": 5, + "isLifeline": false, + "multiChannel": false + }, + { + "text": "On/Off control,Button 4", + "endpoint": 0, + "value": 8, + "maxNodes": 5, + "isLifeline": false, + "multiChannel": false + }, + { + "text": "Dimming control,Button 4", + "endpoint": 0, + "value": 9, + "maxNodes": 5, + "isLifeline": false, + "multiChannel": false + } + ], + "neighbors": [], + "ready": true, + "available": true, + "hassDevices": {}, + "failed": false, + "inited": true, + "eventsQueue": [], + "status": "Asleep", + "interviewStage": "Complete", + "priorityReturnRoute": {}, + "customReturnRoute": {}, + "prioritySUCReturnRoute": false, + "customSUCReturnRoutes": [], + "hexId": "0x0208 0x0200-0x000b", + "dbLink": "https://devices.zwave-js.io/?jumpTo=0x0208:0x0200:0x000b:1.6", + "manufacturerId": 520, + "productId": 11, + "productType": 512, + "deviceConfig": { + "filename": "/usr/src/app/store/.config-db/devices/0x0208/hkzw-scn04.json", + "isEmbedded": true, + "manufacturer": "HANK Electronics Ltd.", + "manufacturerId": 520, + "label": "SCN04", + "description": "Four-Key Scene Controller", + "devices": [ + { + "productType": 512, + "productId": 11 + }, + { + "productType": 513, + "productId": 11 + } + ], + "firmwareVersion": { + "min": "0.0", + "max": "255.255" + }, + "preferred": false, + "paramInformation": { + "_map": {} + } + }, + "productLabel": "SCN04", + "productDescription": "Four-Key Scene Controller", + "manufacturer": "HANK Electronics Ltd.", + "firmwareVersion": "1.6", + "protocolVersion": 3, + "zwavePlusVersion": 1, + "zwavePlusNodeType": 0, + "zwavePlusRoleType": 4, + "nodeType": 1, + "endpointsCount": 0, + "endpoints": [ + { + "index": 0, + "label": "Root Endpoint", + "deviceClass": { + "basic": 4, + "generic": 24, + "specific": 1 + } + } + ], + "isSecure": false, + "security": "None", + "supportsSecurity": false, + "supportsBeaming": true, + "isControllerNode": false, + "isListening": false, + "isFrequentListening": false, + "isRouting": true, + "keepAwake": false, + "maxDataRate": 100000, + "deviceClass": { + "basic": 4, + "generic": 24, + "specific": 1 + }, + "lastActive": 1733088009919, + "firmwareCapabilities": { + "firmwareUpgradable": true, + "firmwareTargets": [0] + }, + "protocol": 0, + "deviceId": "520-11-512", + "hasDeviceConfigChanged": false, + "batteryLevels": { + "0": 100 + }, + "minBatteryLevel": 100, + "supportsTime": false, + "statistics": { + "commandsTX": 0, + "commandsRX": 57, + "commandsDroppedRX": 0, + "commandsDroppedTX": 0, + "timeoutResponse": 0, + "lwr": { + "repeaters": [5], + "protocolDataRate": 1 + }, + "lastSeen": "2024-12-01T21:20:09.919Z" + }, + "_name": "SceneController (Loc1)", + "applicationRoute": false, + "lastReceive": 1733088009919, + "errorReceive": false, + "errorTransmit": false } ], "args": [], diff --git a/server/test/services/zwavejs-ui/lib/zwaveJSUI.onNewDeviceDiscover.test.js b/server/test/services/zwavejs-ui/lib/zwaveJSUI.onNewDeviceDiscover.test.js index 3eb4577853..64e4f6955a 100644 --- a/server/test/services/zwavejs-ui/lib/zwaveJSUI.onNewDeviceDiscover.test.js +++ b/server/test/services/zwavejs-ui/lib/zwaveJSUI.onNewDeviceDiscover.test.js @@ -99,6 +99,46 @@ describe('zwaveJSUIHandler.onNewDeviceDiscover.js', () => { { external_id: 'zwavejs-ui:5', features: [ + { + category: 'button', + command_class: 91, + command_class_name: 'Central Scene', + command_class_version: 3, + endpoint: 0, + external_id: 'zwavejs-ui:5:0:central_scene:scene:001', + feature_name: '', + has_feedback: true, + keep_history: false, + max: 4, + min: 0, + name: '5-91-0-scene-001', + node_id: 5, + property_key_name: '001', + property_name: 'scene', + read_only: true, + selector: 'zwavejs-ui:5:0:central_scene:scene:001', + type: 'click', + }, + { + category: 'button', + command_class: 91, + command_class_name: 'Central Scene', + command_class_version: 3, + endpoint: 0, + external_id: 'zwavejs-ui:5:0:central_scene:scene:002', + feature_name: '', + has_feedback: true, + keep_history: false, + max: 4, + min: 0, + name: '5-91-0-scene-002', + node_id: 5, + property_key_name: '002', + property_name: 'scene', + read_only: true, + selector: 'zwavejs-ui:5:0:central_scene:scene:002', + type: 'click', + }, { category: 'shutter', command_class: 38, @@ -257,6 +297,46 @@ describe('zwaveJSUIHandler.onNewDeviceDiscover.js', () => { selector: 'zwavejs-ui:6:0:multilevel_switch:restoreprevious', type: 'binary', }, + { + category: 'button', + command_class: 91, + command_class_name: 'Central Scene', + command_class_version: 3, + endpoint: 0, + external_id: 'zwavejs-ui:6:0:central_scene:scene:001', + feature_name: '', + has_feedback: true, + keep_history: false, + max: 4, + min: 0, + name: '6-91-0-scene-001', + node_id: 6, + property_key_name: '001', + property_name: 'scene', + read_only: true, + selector: 'zwavejs-ui:6:0:central_scene:scene:001', + type: 'click', + }, + { + category: 'button', + command_class: 91, + command_class_name: 'Central Scene', + command_class_version: 3, + endpoint: 0, + external_id: 'zwavejs-ui:6:0:central_scene:scene:002', + feature_name: '', + has_feedback: true, + keep_history: false, + max: 4, + min: 0, + name: '6-91-0-scene-002', + node_id: 6, + property_key_name: '002', + property_name: 'scene', + read_only: true, + selector: 'zwavejs-ui:6:0:central_scene:scene:002', + type: 'click', + }, ], name: 'inter-01', params: [ @@ -366,6 +446,101 @@ describe('zwaveJSUIHandler.onNewDeviceDiscover.js', () => { service_id: 'ffa13430-df93-488a-9733-5c540e9558e0', should_poll: false, }, + { + external_id: 'zwavejs-ui:9', + features: [ + { + category: 'button', + command_class: 91, + command_class_name: 'Central Scene', + command_class_version: 2, + endpoint: 0, + external_id: 'zwavejs-ui:9:0:central_scene:scene:001', + feature_name: '', + has_feedback: true, + keep_history: false, + max: 4, + min: 0, + name: '9-91-0-scene-001', + node_id: 9, + property_key_name: '001', + property_name: 'scene', + read_only: true, + selector: 'zwavejs-ui:9:0:central_scene:scene:001', + type: 'click', + }, + { + category: 'button', + command_class: 91, + command_class_name: 'Central Scene', + command_class_version: 2, + endpoint: 0, + external_id: 'zwavejs-ui:9:0:central_scene:scene:002', + feature_name: '', + has_feedback: true, + keep_history: false, + max: 4, + min: 0, + name: '9-91-0-scene-002', + node_id: 9, + property_key_name: '002', + property_name: 'scene', + read_only: true, + selector: 'zwavejs-ui:9:0:central_scene:scene:002', + type: 'click', + }, + { + category: 'button', + command_class: 91, + command_class_name: 'Central Scene', + command_class_version: 2, + endpoint: 0, + external_id: 'zwavejs-ui:9:0:central_scene:scene:003', + feature_name: '', + has_feedback: true, + keep_history: false, + max: 4, + min: 0, + name: '9-91-0-scene-003', + node_id: 9, + property_key_name: '003', + property_name: 'scene', + read_only: true, + selector: 'zwavejs-ui:9:0:central_scene:scene:003', + type: 'click', + }, + { + category: 'button', + command_class: 91, + command_class_name: 'Central Scene', + command_class_version: 2, + endpoint: 0, + external_id: 'zwavejs-ui:9:0:central_scene:scene:004', + feature_name: '', + has_feedback: true, + keep_history: false, + max: 4, + min: 0, + name: '9-91-0-scene-004', + node_id: 9, + property_key_name: '004', + property_name: 'scene', + read_only: true, + selector: 'zwavejs-ui:9:0:central_scene:scene:004', + type: 'click', + }, + ], + name: 'SceneController', + params: [ + { + name: 'location', + value: 'Loc1', + }, + ], + selector: 'zwavejs-ui:9', + service_id: 'ffa13430-df93-488a-9733-5c540e9558e0', + should_poll: false, + }, ]); }); }); diff --git a/server/test/services/zwavejs-ui/lib/zwaveJSUI.onNodeValueUpdated.test.js b/server/test/services/zwavejs-ui/lib/zwaveJSUI.onNodeValueUpdated.test.js index 733be25917..834c18f51e 100644 --- a/server/test/services/zwavejs-ui/lib/zwaveJSUI.onNodeValueUpdated.test.js +++ b/server/test/services/zwavejs-ui/lib/zwaveJSUI.onNodeValueUpdated.test.js @@ -3,7 +3,7 @@ const sinon = require('sinon'); const { assert, fake } = sinon; const ZwaveJSUIHandler = require('../../../../services/zwavejs-ui/lib'); -const { STATE } = require('../../../../utils/constants'); +const { STATE, BUTTON_STATUS } = require('../../../../utils/constants'); const serviceId = 'ffa13430-df93-488a-9733-5c540e9558e0'; @@ -807,4 +807,267 @@ describe('zwaveJSUIHandler.onNodeValueUpdated', () => { state: 45, }); }); + + it('should handle a click scene controller value', async () => { + const zwaveJSUIHandler = new ZwaveJSUIHandler(gladys, {}, serviceId); + zwaveJSUIHandler.devices = [ + { + external_id: 'zwavejs-ui:13', + features: [ + { + external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + }, + ], + }, + ]; + zwaveJSUIHandler.zwaveJSDevices = [ + { + id: 13, + deviceClass: { + basic: 4, + generic: 24, + specific: 1, + }, + }, + ]; + + await zwaveJSUIHandler.onNodeValueUpdated({ + data: [ + { id: 13 }, + { + commandClassName: 'Central Scene', + commandClass: 91, + property: 'scene', + propertyKey: '001', + value: 0, + propertyName: 'scene', + propertyKeyName: '001', + newValue: 0, + stateless: true, + }, + ], + }); + assert.calledWith(gladys.event.emit, 'device.new-state', { + device_feature_external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + state: BUTTON_STATUS.CLICK, + }); + }); + it('should handle a released button scene controller value', async () => { + const zwaveJSUIHandler = new ZwaveJSUIHandler(gladys, {}, serviceId); + zwaveJSUIHandler.devices = [ + { + external_id: 'zwavejs-ui:13', + features: [ + { + external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + }, + ], + }, + ]; + zwaveJSUIHandler.zwaveJSDevices = [ + { + id: 13, + deviceClass: { + basic: 4, + generic: 24, + specific: 1, + }, + }, + ]; + + await zwaveJSUIHandler.onNodeValueUpdated({ + data: [ + { id: 13 }, + { + commandClassName: 'Central Scene', + commandClass: 91, + property: 'scene', + propertyKey: '001', + value: 1, + propertyName: 'scene', + propertyKeyName: '001', + newValue: 1, + stateless: true, + }, + ], + }); + assert.calledWith(gladys.event.emit, 'device.new-state', { + device_feature_external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + state: BUTTON_STATUS.RELEASE, + }); + }); + it('should handle a hold button scene controller value', async () => { + const zwaveJSUIHandler = new ZwaveJSUIHandler(gladys, {}, serviceId); + zwaveJSUIHandler.devices = [ + { + external_id: 'zwavejs-ui:13', + features: [ + { + external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + }, + ], + }, + ]; + zwaveJSUIHandler.zwaveJSDevices = [ + { + id: 13, + deviceClass: { + basic: 4, + generic: 24, + specific: 1, + }, + }, + ]; + + await zwaveJSUIHandler.onNodeValueUpdated({ + data: [ + { id: 13 }, + { + commandClassName: 'Central Scene', + commandClass: 91, + property: 'scene', + propertyKey: '001', + value: 2, + propertyName: 'scene', + propertyKeyName: '001', + newValue: 2, + stateless: true, + }, + ], + }); + assert.calledWith(gladys.event.emit, 'device.new-state', { + device_feature_external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + state: BUTTON_STATUS.HOLD_CLICK, + }); + }); + it('should handle a double click scene controller value', async () => { + const zwaveJSUIHandler = new ZwaveJSUIHandler(gladys, {}, serviceId); + zwaveJSUIHandler.devices = [ + { + external_id: 'zwavejs-ui:13', + features: [ + { + external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + }, + ], + }, + ]; + zwaveJSUIHandler.zwaveJSDevices = [ + { + id: 13, + deviceClass: { + basic: 4, + generic: 24, + specific: 1, + }, + }, + ]; + + await zwaveJSUIHandler.onNodeValueUpdated({ + data: [ + { id: 13 }, + { + commandClassName: 'Central Scene', + commandClass: 91, + property: 'scene', + propertyKey: '001', + value: 3, + propertyName: 'scene', + propertyKeyName: '001', + newValue: 3, + stateless: true, + }, + ], + }); + assert.calledWith(gladys.event.emit, 'device.new-state', { + device_feature_external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + state: BUTTON_STATUS.DOUBLE_CLICK, + }); + }); + it('should handle a triple click scene controller value', async () => { + const zwaveJSUIHandler = new ZwaveJSUIHandler(gladys, {}, serviceId); + zwaveJSUIHandler.devices = [ + { + external_id: 'zwavejs-ui:13', + features: [ + { + external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + }, + ], + }, + ]; + zwaveJSUIHandler.zwaveJSDevices = [ + { + id: 13, + deviceClass: { + basic: 4, + generic: 24, + specific: 1, + }, + }, + ]; + + await zwaveJSUIHandler.onNodeValueUpdated({ + data: [ + { id: 13 }, + { + commandClassName: 'Central Scene', + commandClass: 91, + property: 'scene', + propertyKey: '001', + value: 4, + propertyName: 'scene', + propertyKeyName: '001', + newValue: 4, + stateless: true, + }, + ], + }); + assert.calledWith(gladys.event.emit, 'device.new-state', { + device_feature_external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + state: BUTTON_STATUS.TRIPLE, + }); + }); + + it('should not fail on unknown value from scene controller', async () => { + const zwaveJSUIHandler = new ZwaveJSUIHandler(gladys, {}, serviceId); + zwaveJSUIHandler.devices = [ + { + external_id: 'zwavejs-ui:13', + features: [ + { + external_id: 'zwavejs-ui:13:0:central_scene:scene:001', + }, + ], + }, + ]; + zwaveJSUIHandler.zwaveJSDevices = [ + { + id: 13, + deviceClass: { + basic: 4, + generic: 24, + specific: 1, + }, + }, + ]; + + await zwaveJSUIHandler.onNodeValueUpdated({ + data: [ + { id: 13 }, + { + commandClassName: 'Central Scene', + commandClass: 91, + property: 'scene', + propertyKey: '001', + value: -1, + propertyName: 'scene', + propertyKeyName: '001', + newValue: -1, + stateless: true, + }, + ], + }); + assert.notCalled(gladys.event.emit); + }); });