From 7a44c595812cd59e479ba89c9e87ebc3a5deb955 Mon Sep 17 00:00:00 2001 From: Duncan McKee Date: Fri, 17 Jun 2016 21:53:36 -0400 Subject: [PATCH] Z-Wave Plus Motion/Temp Sensor device type DVCSMP-1826 --- .../zwave-door-window-sensor.groovy | 2 +- .../zwave-plus-motion-temp-sensor.groovy | 322 ++++++++++++++++++ 2 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 devicetypes/smartthings/zwave-plus-motion-temp-sensor.src/zwave-plus-motion-temp-sensor.groovy diff --git a/devicetypes/smartthings/zwave-door-window-sensor.src/zwave-door-window-sensor.groovy b/devicetypes/smartthings/zwave-door-window-sensor.src/zwave-door-window-sensor.groovy index 5b48c64..3110c91 100644 --- a/devicetypes/smartthings/zwave-door-window-sensor.src/zwave-door-window-sensor.groovy +++ b/devicetypes/smartthings/zwave-door-window-sensor.src/zwave-door-window-sensor.groovy @@ -175,7 +175,7 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) { cmds << command(zwave.batteryV1.batteryGet()) } else { - cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format() + cmds << zwave.wakeUpV1.wakeUpNoMoreInformation() } [event, response(cmds)] } diff --git a/devicetypes/smartthings/zwave-plus-motion-temp-sensor.src/zwave-plus-motion-temp-sensor.groovy b/devicetypes/smartthings/zwave-plus-motion-temp-sensor.src/zwave-plus-motion-temp-sensor.groovy new file mode 100644 index 0000000..a7adfc3 --- /dev/null +++ b/devicetypes/smartthings/zwave-plus-motion-temp-sensor.src/zwave-plus-motion-temp-sensor.groovy @@ -0,0 +1,322 @@ +/** +* Copyright 2016 SmartThings +* Copyright 2015 AstraLink +* +* 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. +* +* Z-Wave Plus Motion Sensor with Temperature Measurement, ZP3102*-5 +* +*/ + +metadata { + definition (name: "Z-Wave Plus Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") { + capability "Motion Sensor" + capability "Temperature Measurement" + capability "Configuration" + capability "Battery" + capability "Sensor" + + // for Astralink + attribute "ManufacturerCode", "string" + attribute "ProduceTypeCode", "string" + attribute "ProductCode", "string" + attribute "WakeUp", "string" + attribute "WirelessConfig", "string" + + fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x31, 0x70, 0x84, 0x7A" + fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,31,71" + fingerprint mfr:"0109", prod:"2002", model:"0205" + } + + tiles { + standardTile("motion", "device.motion", width: 3, height: 2) { + state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0" + state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff" + } + + valueTile("temperature", "device.temperature", inactiveLabel: false) { + state "temperature", label:'${currentValue}°', + backgroundColors:[ + [value: 31, color: "#153591"], + [value: 44, color: "#1e9cbb"], + [value: 59, color: "#90d2a7"], + [value: 74, color: "#44b621"], + [value: 84, color: "#f1d801"], + [value: 95, color: "#d04e00"], + [value: 96, color: "#bc2323"] + ] + } + valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") { + state "battery", label:'${currentValue}% battery', unit:"%" + } + + main(["motion", "temperature"]) + details(["motion", "temperature", "battery"]) + } +} + +def updated() { + if (!device.currentState("ManufacturerCode")) { + response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())) + } +} + +def configure() { + log.debug "configure()" + def cmds = [] + + if (state.sec != 1) { + // secure inclusion may not be complete yet + cmds << "delay 3000" + } + + cmds += secureSequence([ + zwave.manufacturerSpecificV2.manufacturerSpecificGet(), + zwave.batteryV1.batteryGet(), + zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1) + ], 500) + + cmds << "delay 8000" + cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation()) + return cmds +} + +private getCommandClassVersions() { + [ + 0x71: 3, // Notification + 0x5E: 2, // ZwaveplusInfo + 0x59: 1, // AssociationGrpInfo + 0x85: 2, // Association + 0x20: 1, // Basic + 0x80: 1, // Battery + 0x70: 1, // Configuration + 0x5A: 1, // DeviceResetLocally + 0x7A: 2, // FirmwareUpdateMd + 0x72: 2, // ManufacturerSpecific + 0x73: 1, // Powerlevel + 0x98: 1, // Security + 0x31: 5, // SensorMultilevel + 0x84: 2 // WakeUp + ] +} + +// Parse incoming device messages to generate events +def parse(String description) { + def result = [] + def cmd + if (description.startsWith("Err 106")) { + state.sec = 0 + result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT", + 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.startsWith("Err")) { + result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true) + } else { + cmd = zwave.parse(description, commandClassVersions) + if (cmd) { + result = zwaveEvent(cmd) + } + } + + if (result instanceof List) { + result = result.flatten() + } + + log.debug "Parsed '$description' to $result" + return result +} + +def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { + def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions) + log.debug "encapsulated: $encapsulatedCommand" + if (encapsulatedCommand) { + state.sec = 1 + return zwaveEvent(encapsulatedCommand) + } else { + log.warn "Unable to extract encapsulated cmd from $cmd" + return [createEvent(descriptionText: cmd.toString())] + } +} + +def sensorValueEvent(value) { + def result = [] + if (value) { + log.debug "sensorValueEvent($value) : active" + result << createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion") + } else { + log.debug "sensorValueEvent($value) : inactive" + result << createEvent(name: "motion", value: "inactive", descriptionText: "$device.displayName motion has stopped") + } + return result +} + +def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { + return sensorValueEvent(cmd.value) +} + +def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) { + return sensorValueEvent(cmd.value) +} + +def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { + return sensorValueEvent(cmd.value) +} + +def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) { + return sensorValueEvent(cmd.sensorValue) +} + +def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) { + return sensorValueEvent(cmd.sensorState) +} + +def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) { + def result = [] + if (cmd.notificationType == 0x07) { + if (cmd.event == 0x01 || cmd.event == 0x02) { + result << sensorValueEvent(1) + } else if (cmd.event == 0x03) { + result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true) + result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())) + } else if (cmd.event == 0x05 || cmd.event == 0x06) { + result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true) + } else if (cmd.event == 0x07) { + result << sensorValueEvent(1) + } else if (cmd.event == 0x08) { + result << sensorValueEvent(1) + } else if (cmd.event == 0x00) { + if (cmd.eventParametersLength && cmd.eventParameter[0] == 3) { + result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false) + } else { + result << sensorValueEvent(0) + } + } else if (cmd.event == 0xFF) { + result << sensorValueEvent(1) + } else { + result << createEvent(descriptionText: "$device.displayName sent event $cmd.event") + } + } else if (cmd.notificationType) { + def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}" + result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false) + } else { + def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive" + result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false) + } + return result +} + +def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) { + def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true) // for Astralink + def cmds = [] + + if (!device.currentState("ManufacturerCode")) { + cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()) + cmds << "delay 2000" + } + if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) { + event.descriptionText += ", requesting battery" + cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)) + cmds << "delay 800" + cmds << secure(zwave.batteryV1.batteryGet()) + cmds << "delay 2000" + } else { + log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago" + } + cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation()) + + return [event, response(cmds)] +} + +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} has a low battery" + map.isStateChange = true + } else { + map.value = cmd.batteryLevel + } + state.lastbat = now() + result << createEvent(map) + return result +} + +def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) { + def result = [] + def map = [:] + switch (cmd.sensorType) { + case 1: + def cmdScale = cmd.scale == 1 ? "F" : "C" + map.name = "temperature" + map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision) + map.unit = getTemperatureScale() + break; + case 3: + map.name = "illuminance" + map.value = cmd.scaledSensorValue.toInteger().toString() + map.unit = "lux" + break; + case 5: + map.name = "humidity" + map.value = cmd.scaledSensorValue.toInteger().toString() + map.unit = cmd.scale == 0 ? "%" : "" + break; + case 0x1E: + map.name = "loudness" + map.unit = cmd.scale == 1 ? "dBA" : "dB" + map.value = cmd.scaledSensorValue.toString() + break; + default: + map.descriptionText = cmd.toString() + } + result << createEvent(map) + return result +} + +def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { + def result = [] + def manufacturerCode = String.format("%04X", cmd.manufacturerId) + def productTypeCode = String.format("%04X", cmd.productTypeId) + def productCode = String.format("%04X", cmd.productId) + def wirelessConfig = "ZWP" + log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}" + + result << createEvent(name: "ManufacturerCode", value: manufacturerCode) + result << createEvent(name: "ProduceTypeCode", value: productTypeCode) + result << createEvent(name: "ProductCode", value: productCode) + result << createEvent(name: "WirelessConfig", value: wirelessConfig) + + if (manufacturerCode == "0109" && productTypeCode == "2002") { + result << response(secureSequence([ + // Change re-trigger duration to 1 minute + zwave.configurationV1.configurationSet(parameterNumber: 1, configurationValue: [1], size: 1), + zwave.batteryV1.batteryGet(), + zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1) + ], 400)) + } + + return result +} + +def zwaveEvent(physicalgraph.zwave.Command cmd) { + return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)] +} + +private secure(physicalgraph.zwave.Command cmd) { + if (state.sec == 0) { // default to secure + cmd.format() + } else { + zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format() + } +} + +private secureSequence(commands, delay=200) { + delayBetween(commands.collect{ secure(it) }, delay) +}