From 6acc17c9b82a7b0d12b33a1a848bd4d4b4ceadcb Mon Sep 17 00:00:00 2001 From: Jack Kilby Date: Thu, 11 Feb 2021 07:58:43 +0800 Subject: [PATCH 1/3] Add ThirdReality Zigbee WaterLeak Sensor DeviceHandler into SmartThings. (#55832) * Add ThirdReality Zigbee WaterLeak Sensor DeviceHandler into SmartThings. * Resolve conversion of "standardize tabs/spaces" and "parse apart from startsWith". * Add binding cmd in config procedure. * Use MACRO instead of hex strings and Optimize code structure of parse. * Define 0x8021 Bind cluster as MACRO. * change 0x0006 cluster as MACRO ONOFF_CLUSTER. * Solve a bug of conflict between On/Off and Wet/Dry. * Change "onoff_state" variable name into "buzzing_state". * Modify format. Co-authored-by: jiang shanyang --- .../thirdreality-waterleak-sensor.groovy | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 devicetypes/smartthings/thirdreality-waterleak-sensor.src/thirdreality-waterleak-sensor.groovy diff --git a/devicetypes/smartthings/thirdreality-waterleak-sensor.src/thirdreality-waterleak-sensor.groovy b/devicetypes/smartthings/thirdreality-waterleak-sensor.src/thirdreality-waterleak-sensor.groovy new file mode 100644 index 00000000000..5a931611382 --- /dev/null +++ b/devicetypes/smartthings/thirdreality-waterleak-sensor.src/thirdreality-waterleak-sensor.groovy @@ -0,0 +1,116 @@ +/** + * ThirdReality WaterLeak Sensor + * + * Copyright 2021 THIRDREALITY + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License + * for the specific language governing permissions and limitations under the License. + */ +metadata { + definition (name: "ThirdReality WaterLeak Sensor", namespace: "smartthings", author: "THIRDREALITY", cstHandler: true) { + capability "Battery" + capability "Switch" + capability "Water Sensor" + capability "Refresh" + capability "Configuration" + + fingerprint profileId: "0104", deviceId: "0402", inClusters: "0000,0001,0500", outClusters: "0006,0019", manufacturer:"Third Reality, Inc", model:"3RWS18BZ", deviceJoinName: "ThirdReality Water Leak Sensor" //ThirdReality WaterLeak Sensor + } + + simulator { + // When simulating, define status and reply messages here + } + + tiles { //Seems no use + // define your main and details tiles here + main("water") + details(["water", "battery", "switch"]) + } +} + +def getBIND_CLUSTER() {0x8021} + +// parse events into attributes +def parse(String description) { + log.trace "[parse] Parsing '${description}'" + def resMap = [:] + + if (description?.startsWith("zone status")) { + resMap = createEvent(name: "water", value: zigbee.parseZoneStatus(description).isAlarm1Set() ? "wet" : "dry") + if (getDataValue("buzzing_state") != "on") { + sendEvent(name: "switch", value: zigbee.parseZoneStatus(description).isAlarm1Set() ? "on":"off") + } + } else if (description?.startsWith("on/off")) { + resMap = zigbee.getEvent(description) + updateDataValue("buzzing_state", resMap.value) + sendEvent(resMap) + } else if (description?.startsWith("read attr") || description?.startsWith("catchall")) { + def descMap = zigbee.parseDescriptionAsMap(description) + log.trace "[parse] descMap: ${descMap}" + + if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap?.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS) { //Water: Zone Status + resMap = createEvent(name: "water", value: (descMap.value=="0000") ? "dry" : "wet") + } else if (descMap?.clusterInt == zigbee.POWER_CONFIGURATION_CLUSTER ) { //Battery: Power Config + if (descMap?.attrInt == 0x0021) { + resMap = createEvent(getBatteryPercentageResult(Integer.parseInt(descMap.value, 16))) + } else if (descMap?.attrInt == 0x0020) { + log.debug "[parse] Got Battery Voltage Value: 0x${descMap.value} * 100mV" + } + } else if (descMap?.clusterInt == zigbee.ONOFF_CLUSTER) { + if (descMap?.attrInt == 0x0000) { //Switch: On/Off + resMap = createEvent(name: "switch", value: (descMap.value=="00") ? "off" : "on") + } else if (descMap?.commandInt == 0x0B) { + log.trace "[parse] Cmd On/Off" + } + } else if (descMap?.clusterInt == BIND_CLUSTER) { //Bind Rsp + log.trace "[parse] got Bind Rsp" + } else { + log.warn "[WARN][parse] Unknown descMap: $descMap" + } + } else { + log.warn "[WARN][parse] Unknown description: $description" + } + + log.trace "[parse] return '${resMap}'" + return resMap +} + +// handle commands +def configure() { + log.trace "[configure]" + updateDataValue("buzzing_state", "off") + def enrollCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0021) + zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER,zigbee.ATTRIBUTE_IAS_ZONE_STATUS) + zigbee.readAttribute(0x0006, 0x0000) + return zigbee.addBinding(zigbee.IAS_ZONE_CLUSTER) + enrollCmds +} + +def refresh() { + log.trace "[refresh]" + return zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0021) + zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER,zigbee.ATTRIBUTE_IAS_ZONE_STATUS) + zigbee.readAttribute(0x0006, 0x0000) +} + +def on() { + log.trace "[on]" + zigbee.on() +} + +def off() { + log.trace "[off]" + zigbee.off() +} + +def getBatteryPercentageResult(rawValue) { + def result = [:] + if (0 <= rawValue && rawValue <= 200) { + result.name = 'battery' + result.translatable = true + result.value = Math.round(rawValue) + result.descriptionText = "${device.displayName} battery was ${result.value}%" + } + return result +} From c680784366abf71833586b2655e835820546dbd3 Mon Sep 17 00:00:00 2001 From: vision-raytseng <69944522+vision-raytseng@users.noreply.github.com> Date: Thu, 11 Feb 2021 15:31:54 +0800 Subject: [PATCH 2/3] DevWs for Vision-Elec. Technology Co., Ltd. containing containing Vision 4-in-1 sensor (#52125) * DevWs for Vision-Elec. Technology Co., Ltd. containing containing Vision 4-in-1 sensor * DevWs for Vision-Elec. Technology Co., Ltd. containing containing Vision 4-in-1 sensor * Modifying 'DevWs for Vision-Elec. Technology Co., Ltd. containing containing Vision 4-in-1 sensor' * Modifying 'DevWs for Vision-Elec. Technology Co., Ltd. containing containing Vision 4-in-1 sensor' * Modifying 'DevWs for Vision-Elec. Technology Co., Ltd. containing containing Vision 4-in-1 sensor' * Add Raw description in a comment after fingerprint. * Fixed. * Modified the format. * Add the Raw description comment in the fingerprint. * Fixed the indentations in parameter7options and parameter7enumMap. * Using tab to fix the indentations in parameter7options and parameter7enumMap. * Only include these command version that are parsed and handled. * Replace my conversion functions to scaledConfigurationValue in configuration set/report. * Refactored the configuration parameters and wake up info parameter. --- .../vision-4-in-1-motion-sensor.groovy | 377 ++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 devicetypes/vision-raytseng/vision-4-in-1-motion-sensor.src/vision-4-in-1-motion-sensor.groovy diff --git a/devicetypes/vision-raytseng/vision-4-in-1-motion-sensor.src/vision-4-in-1-motion-sensor.groovy b/devicetypes/vision-raytseng/vision-4-in-1-motion-sensor.src/vision-4-in-1-motion-sensor.groovy new file mode 100644 index 00000000000..e69ae2b90c3 --- /dev/null +++ b/devicetypes/vision-raytseng/vision-4-in-1-motion-sensor.src/vision-4-in-1-motion-sensor.groovy @@ -0,0 +1,377 @@ +/** + * Vision 4-in-1 Motion Sensor + * + * Author: Ray Tseng + */ +metadata { + definition (name: "Vision 4-in-1 Motion Sensor", namespace: "vision-raytseng", author: "Ray Tseng", vid: "generic-motion-8", ocfDeviceType: "x.com.st.d.sensor.motion") { + capability "Battery" + capability "Motion Sensor" + capability "Relative Humidity Measurement" + capability "Temperature Measurement" + capability "Illuminance Measurement" + capability "Tamper Alert" + capability "Health Check" + + fingerprint mfr:"0109", prod:"2021", model:"2112", deviceJoinName: "Vision Multipurpose Sensor" // Raw description: zw:Ss2a type:0701 mfr:0109 prod:2021 model:2112 ver:32.32 zwv:7.13 lib:03 cc:5E,22,98,9F,6C,55 sec:85,59,80,70,5A,7A,87,8E,72,71,73,31,86,84 + } + + preferences { + input title: "", description: "Vision 4-in-1 Motion Sensor", type: "paragraph", element: "paragraph", displayDuringSetup: true, required: true + parameterMap().each { + input name: it.name, + title: it.title, + description: it.description, + type: it.type, + options: (it.type == "enum")? it.options: null, + range: (it.type == "number")? it.options: null, + defaultValue: it.default, + required: true, displayDuringSetup: true + } + + input title: "", description: "Wake up settings", + type: "paragraph", element: "paragraph", displayDuringSetup: true, required: true + input name: wakeUpInfoMap.name, + title: wakeUpInfoMap.title, + description: wakeUpInfoMap.description, + type: wakeUpInfoMap.type, range: wakeUpInfoMap.range, + defaultValue: wakeUpInfoMap.default, + required: true, displayDuringSetup: true + } +} + +def installed() { + def cmds = [] + + parameterMap().each { + if (state."${it.name}" == null) { state."${it.name}" = [value: it.default, refresh: true] } + } + + if (state."${wakeUpInfoMap.name}" == null) { state."${wakeUpInfoMap.name}" = [value: wakeUpInfoMap.default, refresh: true] } + + cmds += configure() + if (cmds) { + cmds += ["delay 5000", zwave.wakeUpV2.wakeUpNoMoreInformation().format()] + } + + sendEvent(name: "motion", value: "inactive") + sendEvent(name: "tamper", value: "clear") + + response(cmds) +} + +def updated() { + parameterMap().each { + if (settings."${it.name}" != null && settings."${it.name}" != state."${it.name}".value) { + state."${it.name}".value = settings."${it.name}" + state."${it.name}".refresh = true + } + } + + if (settings."${wakeUpInfoMap.name}" != null && settings."${wakeUpInfoMap.name}" != state."${wakeUpInfoMap.name}".value) { + state."${wakeUpInfoMap.name}".value = settings."${wakeUpInfoMap.name}" + state."${wakeUpInfoMap.name}".refresh = true + } +} + +def configure() { + def cmds = [] + def value + + if (device?.currentValue("temperature") == null) { + def param = parameterMap().find { it.num == 1 } + if (param != null) { + value = param.enumMap.find { it.key == state."${param.name}".value }?.value + cmds << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01, scale: value?:0x00).format() + } + } + if (device?.currentValue("illuminance") == null) { + cmds << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x03, scale: 0x00).format() + } + if (device?.currentValue("humidity") == null) { + cmds << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x05, scale: 0x00).format() + } + if (canReportBattery() || device?.currentValue("battery") == null) { + cmds << zwave.batteryV1.batteryGet().format() + } + + for (param in parameterMap()) { + if (state."${param.name}".refresh == true) { + value = (param.type == "enum")? param.enumMap.find { it.key == state."${param.name}".value }?.value: state."${param.name}".value + if (value != null) { + cmds << zwave.configurationV2.configurationSet(parameterNumber: param.num, defaultValue: false, scaledConfigurationValue: value).format() + cmds << zwave.configurationV2.configurationGet(parameterNumber: param.num).format() + if (param.num == 1) { + cmds << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01, scale: value?:0x00).format() + } + } + } + } + + if (state."${wakeUpInfoMap.name}".refresh == true) { + cmds << zwave.wakeUpV2.wakeUpIntervalSet(nodeid: zwaveHubNodeId, seconds: hour2Second(state."${wakeUpInfoMap.name}".value)).format() + cmds << zwave.wakeUpV2.wakeUpIntervalGet().format() + } + + sendEvent(name: "checkInterval", value: (hour2Second(state."${wakeUpInfoMap.name}".value) + 2 * 60) * 2, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) + + return cmds ? delayBetween(cmds, 500) : [] +} + +def parameterMap() {[ + [num: 1, + name: "TemperatureUnit", + title: "Temperature Unit [°C/°F]", + description: "", + type: "enum", + options: ["°C", "°F"], + enumMap: ["°C": 0, "°F": 1], + default: "°C", + size: 1 + ], + [num: 2, + name: "TempReportWhenChanged", + title: "Report when temperature difference is over the setting [unit is 0.1°C/°F]", + description: "", + type: "number", + options: "1..50", + enumMap: [], + default: 30, + size: 1], + [num: 3, + name: "HumiReportWhenChanged", + title: "Report when humidity difference is over the setting [%]", + description: "", + type: "number", + options: "1..50", + enumMap: [], + default: 20, + size: 1 + ], + [num: 4, + name: "LightReportWhenChanged", + title: "Report when illuminance difference is over the setting [%](1% is approximately equal to 4.5 lux)", + description: "", + type: "number", + options: "5..50", + enumMap: [], + default: 25, + size: 1 + ], + [num: 5, + name: "MotionRestoreTime", + title: "Motion inactive report time [Minutes] after active", + description: "", + type: "number", + options: "1..127", + enumMap: [], + default: 3, + size: 1 + ], + [num: 6, + name: "MotionSensitivity", + title: "Motion active sensitivity", + description: "", + type: "enum", + options: ["Highest", "Higher", "High", "Medium", "Low", "Lower", "Lowest"], + enumMap: ["Highest": 1, "Higher": 2, "High": 3, "Medium": 4, "Low": 5, "Lower": 6, "Lowest": 7], + default: "Medium", + size: 1 + ], + [num: 7, + name: "LedDispMode", + title: "LED display mode", + description: "", + type: "enum", + options: ["LED off when Temperature report/Motion active", + "LED blink when Temperature report/Motion active", + "LED blink when Motion active/LED off when Temperature report"], + enumMap: ["LED off when Temperature report/Motion active": 1, + "LED blink when Temperature report/Motion active": 2, + "LED blink when Motion active/LED off when Temperature report": 3], + default: "LED off when Temperature report/Motion active", + size: 1 + ], + [num: 8, + name: "RetryTimes", + title: "Motion notification retry times", + description: "", + type: "number", + options: "0..10", + enumMap: [], + default: 3, + size: 1 + ] + ] +} + +def getWakeUpInfoMap() { + [ + name: "wakeUpInterval", + title: "Wake up interval [Hours]", + description: "", + type: "number", + range : "1..4660", + default: 24 + ] +} + +private getCommandClassVersions() { + [ + 0x80: 1, + 0x70: 2, + 0X31: 5, + 0x71: 3, + 0x84: 2 + ] +} + +def parse(String description) { + def result = [] + def cmd = zwave.parse(description, commandClassVersions) + + if (cmd) { + result += zwaveEvent(cmd) + } else { + logDebug "Unable to parse description: ${description}" + } + + return result +} + +def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) { + def cmds = [] + + cmds += configure() + if (cmds) { + cmds << "delay 5000" + } + cmds << zwave.wakeUpV2.wakeUpNoMoreInformation().format() + + return cmds ? response(cmds) : [] +} + +def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpIntervalReport cmd) { + if (cmd.nodeid == zwaveHubNodeId) { + if (state."${wakeUpInfoMap.name}".value == (cmd.seconds / 3600)) { + state."${wakeUpInfoMap.name}".refresh = false + } + } + [] +} + +def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { + def map = [name: "battery", unit: "%"] + + if (cmd.batteryLevel == 0xFF) { + map.value = 1 + map.descriptionText = "${device.displayName} has a low battery" + map.isStateChange = true + } else { + map.value = cmd.batteryLevel + } + + state.lastBatteryReport = new Date().time + createEvent(map) +} + +def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) { + def param = parameterMap().find { it.num == cmd.parameterNumber } + + if (param != null && param.size != null && param.size == cmd.size) { + def value = (param.type == "enum")? param.enumMap.find { it.value == cmd.scaledConfigurationValue }?.key: cmd.scaledConfigurationValue + if (value != null && value == state."${param.name}".value) { + state."${param.name}".refresh = false + } + } + [] +} + +def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) { + def result = [] + + if (cmd.notificationType == 0x07) { + if (cmd.eventParametersLength) { + cmd.eventParameter.each { + if (it == 0x03) { + result = createEvent(name: "tamper", value: "clear") + } else if( it == 0x08) { + result = createEvent(name: "motion", value: "inactive") + } + } + } else if (cmd.event == 0x03) { + result = createEvent(name: "tamper", value: "detected") + } else if (cmd.event == 0x08) { + result = createEvent(name: "motion", value: "active") + } + } + + return result +} + +def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) { + def map = [:] + + switch (cmd.sensorType) { + case 0x01: + map.name = "temperature" + map.value = cmd.scaledSensorValue + map.unit = cmd.scale == 0 ? "C": "F" + break + case 0x03: + map.name = "illuminance" + map.value = getLuxFromPercentage(cmd.scaledSensorValue) + map.unit = "lux" + break + case 0x05: + map.name = "humidity" + map.value = cmd.scaledSensorValue + map.unit = "%" + break + default: + map.descriptionText = cmd.toString() + break + } + + createEvent(map) +} + +def getBatteryReportIntervalSeconds() { + return 8 * 3600 +} + +def canReportBattery() { + def reportEveryMS = (getBatteryReportIntervalSeconds() * 1000) + + return (!state.lastBatteryReport || ((new Date().time) - state?.lastBatteryReport > reportEveryMS)) +} + +def hour2Second(hour) { + return hour * 3600 +} + +private getLuxFromPercentage(percentageValue) { + def multiplier = luxConversionData.find { + percentageValue >= it.min && percentageValue <= it.max + }?.multiplier ?: 5.312 + def luxValue = percentageValue * multiplier + Math.round(luxValue) +} + +private getLuxConversionData() {[ + [min: 0, max: 9.99, multiplier: 3.843], + [min: 10, max: 19.99, multiplier: 5.231], + [min: 20, max: 29.99, multiplier: 4.999], + [min: 30, max: 39.99, multiplier: 4.981], + [min: 40, max: 49.99, multiplier: 5.194], + [min: 50, max: 59.99, multiplier: 6.016], + [min: 60, max: 69.99, multiplier: 4.852], + [min: 70, max: 79.99, multiplier: 4.836], + [min: 80, max: 89.99, multiplier: 4.613], + [min: 90, max: 100, multiplier: 4.5] +]} + +def logDebug(msg) { + log.debug "${msg}" +} + From 0ebdfd7c6dc6a338e323e513e3fe2403988d02ca Mon Sep 17 00:00:00 2001 From: PKacprowiczS <41617389+PKacprowiczS@users.noreply.github.com> Date: Tue, 16 Feb 2021 19:48:00 +0100 Subject: [PATCH 3/3] [ICP-14023] Everspring Radiator Thermostat - additional battery poll on device power up (#58285) * Additional battery poll on device power up * Added proper comment describing notification --- .../zwave-radiator-thermostat.groovy | 6 ++++++ 1 file changed, 6 insertions(+) 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 52ddcab095b..f9375e14b5f 100644 --- a/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy +++ b/devicetypes/smartthings/zwave-radiator-thermostat.src/zwave-radiator-thermostat.groovy @@ -203,6 +203,12 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelR createEvent(name: "temperature", value: convertTemperatureIfNeeded(cmd.scaledSensorValue, deviceTemperatureScale, cmd.precision), unit: temperatureScale) } +def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) { + // Power Management - Power has been applied + if (cmd.notificationType == 0x08 && cmd.event == 0x01) + [response(zwave.batteryV1.batteryGet())] +} + def zwaveEvent(physicalgraph.zwave.Command cmd) { log.warn "Unhandled command: ${cmd}" [:]