diff --git a/devicetypes/garth/devolo-humidity-sensor.src/devolo-humidity-sensor.groovy b/devicetypes/garth/devolo-humidity-sensor.src/devolo-humidity-sensor.groovy new file mode 100644 index 0000000..b4f6be1 --- /dev/null +++ b/devicetypes/garth/devolo-humidity-sensor.src/devolo-humidity-sensor.groovy @@ -0,0 +1,188 @@ +/** + * Devolo Humidity Sensor + * + * Copyright 2017 Garth Williams + * + * 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. + * + * + * Based on definition (name: "Aeon Multisensor 6", namespace: "smartthings", author: "SmartThings") + * + */ +metadata { + definition (name: "Devolo Humidity Sensor", namespace: "garth", author: "Garth Williams") { + capability "Temperature Measurement" + capability "Relative Humidity Measurement" + capability "Configuration" + capability "Sensor" + capability "Battery" + capability "Health Check" + + attribute "batteryStatus", "string" + + fingerprint mfr: "0175", prod: "0002", model: "0020" + } + + simulator { + for (int i = 0; i <= 100; i += 20) { + status "temperature ${i}F": new physicalgraph.zwave.Zwave().securityV1.securityMessageEncapsulation().encapsulate( + new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport( + scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1) + ).incomingMessage() + } + + for (int i = 0; i <= 100; i += 20) { + status "humidity ${i}%": new physicalgraph.zwave.Zwave().securityV1.securityMessageEncapsulation().encapsulate( + new physicalgraph.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport(scaledSensorValue: i, sensorType: 5) + ).incomingMessage() + } + + for (int i in [0, 5, 10, 15, 50, 99, 100]) { + status "battery ${i}%": new physicalgraph.zwave.Zwave().securityV1.securityMessageEncapsulation().encapsulate( + new physicalgraph.zwave.Zwave().batteryV1.batteryReport(batteryLevel: i) + ).incomingMessage() + } + status "low battery alert": new physicalgraph.zwave.Zwave().securityV1.securityMessageEncapsulation().encapsulate( + new physicalgraph.zwave.Zwave().batteryV1.batteryReport(batteryLevel: 255) + ).incomingMessage() + + status "wake up" : "command: 8407, payload: " + } + + preferences { + } + + tiles(scale: 2) { + valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) { + state "temperature", label:'${currentValue}°', + backgroundColors:[ + [value: 32, color: "#153591"], + [value: 44, color: "#1e9cbb"], + [value: 59, color: "#90d2a7"], + [value: 74, color: "#44b621"], + [value: 84, color: "#f1d801"], + [value: 92, color: "#d04e00"], + [value: 98, color: "#bc2323"] + ] + } + valueTile("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) { + state "humidity", label:'${currentValue}% humidity', unit:"" + } + + valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "battery", label:'${currentValue}% battery', unit:"" + } + + valueTile("batteryStatus", "device.batteryStatus", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "batteryStatus", label:'${currentValue}', unit:"" + } + + main(["temperature", "humidity"]) + details(["temperature", "humidity", "batteryStatus"]) + } +} + +def parse(String description) { + log.debug "parse() >> description: $description" + def result = null + if (description.startsWith("Err 106")) { + log.debug "parse() >> Err 106" + result = createEvent( name: "secureInclusion", value: "failed", isStateChange: true, + descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.") + } else if (description != "updated") { + log.debug "parse() >> zwave.parse(description)" + def cmd = zwave.parse(description, [0x31: 5, 0x30: 2, 0x84: 1]) + if (cmd) { + result = zwaveEvent(cmd) + } + } + log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}" + return result +} + +def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { + def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 2, 0x84: 1]) + state.sec = 1 + log.debug "encapsulated: ${encapsulatedCommand}" + if (encapsulatedCommand) { + zwaveEvent(encapsulatedCommand) + } else { + log.warn "Unable to extract encapsulated cmd from $cmd" + createEvent(descriptionText: cmd.toString()) + } +} + +def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) { + log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd" + state.sec = 1 +} + +def zwaveEvent(physicalgraph.zwave.commands.securityv1.NetworkKeyVerify cmd) { + state.sec = 1 + log.info "Executing zwaveEvent 98 (SecurityV1): 07 (NetworkKeyVerify) with cmd: $cmd (node is securely included)" + def result = [createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful", isStateChange: true)] + result +} + +def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { + def result = [] + def map = [ name: "battery", unit: "%" ] + if (cmd.batteryLevel == 0xFF) { + map.value = 1 + map.descriptionText = "${device.displayName} battery is low" + map.isStateChange = true + } else { + map.value = cmd.batteryLevel + } + state.lastbatt = now() + result << createEvent(map) + if (device.latestValue("powerSupply") != "USB Cable"){ + result << createEvent(name: "batteryStatus", value: "${map.value} % battery", displayed: false) + } + result +} + +def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){ + def map = [:] + switch (cmd.sensorType) { + case 1: + map.name = "temperature" + def cmdScale = cmd.scale == 1 ? "F" : "C" + map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision) + map.unit = getTemperatureScale() + break + case 5: + map.name = "humidity" + map.value = cmd.scaledSensorValue.toInteger() + map.unit = "%" + break + default: + map.descriptionText = cmd.toString() + } + createEvent(map) +} + +def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) { + log.debug "ConfigurationReport: $cmd" + def result = [] + def value + if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 1) { + value = "Battery" + result << createEvent(name: "powerSupply", value: value, displayed: false) + } else if (cmd.parameterNumber == 101){ + result << response(configure()) + } + result +} + +def zwaveEvent(physicalgraph.zwave.Command cmd) { + log.debug "General zwaveEvent cmd: ${cmd}" + createEvent(descriptionText: cmd.toString(), isStateChange: false) +}