From d1d4cd28690603b628914e814adeb116ef89e573 Mon Sep 17 00:00:00 2001 From: Przemyslaw Kacprowicz Date: Wed, 6 Nov 2019 16:03:57 +0100 Subject: [PATCH 1/2] POPP Radiator Thermostat integration --- .../zwave-radiator-thermostat.groovy | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy b/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy index 614a84a3af9..dbba1eac717 100644 --- a/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy +++ b/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy @@ -21,10 +21,12 @@ metadata { capability "Health Check" capability "Thermostat" capability "Temperature Measurement" + capability "Configuration" fingerprint mfr: "0060", prod: "0015", model: "0001", deviceJoinName: "Everspring Thermostatic Radiator Valve", mnmn: "SmartThings", vid: "generic-radiator-thermostat" //this DTH is sending temperature setpoint commands using Celsius scale and assumes that they'll be handled correctly by device //if new device added to this DTH won't be able to do that, make sure to you'll handle conversion in a right way + fingerprint mfr: "0002", prod: "0115", model: "A010", deviceJoinName: "POPP Radiator Thermostat Valve", mnmn: "SmartThings", vid: "generic-radiator-thermostat-2" } tiles(scale: 2) { @@ -84,7 +86,7 @@ metadata { } def initialize() { - sendEvent(name: "checkInterval", value: 4 * 60 * 60 + 24 * 60 , displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) + sendEvent(name: "checkInterval", value: checkInterval , displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) sendEvent(name: "supportedThermostatModes", value: thermostatSupportedModes.encodeAsJson(), displayed: false) sendEvent(name: "heatingSetpointRange", value: [minHeatingSetpointTemperature, maxHeatingSetpointTemperature], displayed: false) response(refresh()) @@ -121,7 +123,24 @@ def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulat } } +def zwaveEvent(physicalgraph.zwave.commands.multicmdv1.MultiCmdEncap cmd) { + cmd.encapsulatedCommands().collect { encapsulatedCommand -> + zwaveEvent(encapsulatedCommand) + }.flatten() +} +def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) { + def cmds = [] + if (!isPoppRadiatorThermostat()) { + cmds += zwave.batteryV1.batteryGet() // POPP sends battery report automatically every wake up by itself, there's no need to duplicate it + } + cmds += [ + zwave.thermostatSetpointV2.thermostatSetpointSet([precision: 1, scale: 0, scaledValue: state.cachedSetpoint, setpointType: 1, size: 2]), + zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 1), + zwave.wakeUpV2.wakeUpNoMoreInformation() + ] + [response(multiEncap(cmds))] +} def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { def value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel @@ -198,8 +217,12 @@ def off() { } def setHeatingSetpoint(setpoint) { + if (isPoppRadiatorThermostat()) { + sendEvent(name: "heatingSetpoint", value: setpoint, unit: temperatureScale) + } setpoint = temperatureScale == 'C' ? setpoint : fahrenheitToCelsius(setpoint) setpoint = Math.max(Math.min(setpoint, maxHeatingSetpointTemperature), minHeatingSetpointTemperature) + state.cachedSetpoint = setpoint [ secure(zwave.thermostatSetpointV2.thermostatSetpointSet([precision: 1, scale: 0, scaledValue: setpoint, setpointType: 1, size: 2])), "delay 2000", @@ -222,6 +245,12 @@ def ping() { refresh() } +def configure() { + if (isPoppRadiatorThermostat()) { + secure(zwave.wakeUpV2.wakeUpIntervalSet(seconds: 600, nodeid: zwaveHubNodeId)) + } +} + private secure(cmd) { if (zwaveInfo.zw.endsWith("s")) { zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() @@ -230,9 +259,23 @@ private secure(cmd) { } } +def multiEncap(cmds) { + if (zwaveInfo.cc.contains("8F")) { + secure(zwave.multiCmdV1.multiCmdEncap().encapsulate(cmds.collect { + cmd -> cmd.format() + })) + } else { + delayBetween(cmds.collect { + cmd -> secure(cmd) + }, 2500) + } +} + private getMaxHeatingSetpointTemperature() { if (isEverspringRadiatorThermostat()) { temperatureScale == 'C' ? 35 : 95 + } else if (isPoppRadiatorThermostat()) { + temperatureScale == 'C' ? 28 : 82 } else { temperatureScale == 'C' ? 30 : 86 } @@ -241,6 +284,8 @@ private getMaxHeatingSetpointTemperature() { private getMinHeatingSetpointTemperature() { if (isEverspringRadiatorThermostat()) { temperatureScale == 'C' ? 15 : 59 + } else if (isPoppRadiatorThermostat()) { + temperatureScale == 'C' ? 4 : 39 } else { temperatureScale == 'C' ? 10 : 50 } @@ -249,11 +294,25 @@ private getMinHeatingSetpointTemperature() { private getThermostatSupportedModes() { if (isEverspringRadiatorThermostat()) { ["off", "heat", "emergency heat"] + } else if (isPoppRadiatorThermostat()) { //that's just for looking fine in Classic + ["heat"] } else { ["off","heat"] } } +def getCheckInterval() { + if (isPoppRadiatorThermostat()) { + 2 * 60 * 10 + 2 * 60 + } else { + 4 * 60 * 60 + 24 * 60 + } +} + private isEverspringRadiatorThermostat() { zwaveInfo.mfr == "0060" && zwaveInfo.prod == "0015" } + +private isPoppRadiatorThermostat() { + zwaveInfo.mfr == "0002" && zwaveInfo.prod == "0115" +} \ No newline at end of file From db5df87480b2befe67dcc670755f47131e48c971 Mon Sep 17 00:00:00 2001 From: Przemyslaw Kacprowicz Date: Thu, 7 Nov 2019 14:43:33 +0100 Subject: [PATCH 2/2] Removed 'offlinePingable' flag and fixed secure() method. --- .../zwave-radiator-thermostat.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy b/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy index dbba1eac717..b87e6d3d9a3 100644 --- a/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy +++ b/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy @@ -86,7 +86,7 @@ metadata { } def initialize() { - sendEvent(name: "checkInterval", value: checkInterval , displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"]) + sendEvent(name: "checkInterval", value: checkInterval , displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "supportedThermostatModes", value: thermostatSupportedModes.encodeAsJson(), displayed: false) sendEvent(name: "heatingSetpointRange", value: [minHeatingSetpointTemperature, maxHeatingSetpointTemperature], displayed: false) response(refresh()) @@ -252,7 +252,7 @@ def configure() { } private secure(cmd) { - if (zwaveInfo.zw.endsWith("s")) { + if (zwaveInfo.zw.contains("s")) { zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() } else { cmd.format()