From 9733947feacc8bb44097701ab388cf6c191d1871 Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Thu, 7 Jan 2016 14:30:04 -0600 Subject: [PATCH] arrival: Add support for ZigBee HA arrival sensor Resolves: https://smartthings.atlassian.net/browse/DVCSMP-1305 https://smartthings.atlassian.net/browse/DVCSMP-1322 --- .../arrival-sensor-ha.groovy | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy diff --git a/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy b/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy new file mode 100644 index 0000000..c67ede5 --- /dev/null +++ b/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy @@ -0,0 +1,152 @@ +/** + * Copyright 2016 SmartThings + * + * 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: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") { + capability "Tone" + capability "Actuator" + capability "Presence Sensor" + capability "Sensor" + capability "Battery" + capability "Configuration" + + fingerprint inClusters: "0000,0001,0003,000F,0020", outClusters: "0003,0019", + manufacturer: "SmartThings", model: "tagv4", deviceJoinName: "Arrival Sensor" + } + + preferences { + section { + image(name: 'educationalcontent', multiple: true, images: [ + "http://cdn.device-gse.smartthings.com/Arrival/Arrival1.png", + "http://cdn.device-gse.smartthings.com/Arrival/Arrival2.png" + ]) + } + section { + input "checkInterval", "enum", title: "Presence timeout (minutes)", + defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false + } + } + + tiles { + standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) { + state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0" + state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff" + } + standardTile("beep", "device.beep", decoration: "flat") { + state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff" + } + valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) { + state "battery", label:'${currentValue}% battery', unit:"" + } + + main "presence" + details(["presence", "beep", "battery"]) + } +} + +def updated() { + startTimer() +} + +def configure() { + def cmds = zigbee.configureReporting(0x0001, 0x0020, 0x20, 20, 20, 0x01) + log.debug "configure -- cmds: ${cmds}" + return cmds +} + +def beep() { + log.debug "Sending Identify command to beep the sensor for 5 seconds" + return zigbee.command(0x0003, 0x00, "0500") +} + +def parse(String description) { + state.lastCheckin = now() + handlePresenceEvent(true) + + if (description?.startsWith('read attr -')) { + handleReportAttributeMessage(description) + } +} + +private handleReportAttributeMessage(String description) { + def descMap = zigbee.parseDescriptionAsMap(description) + + if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) { + handleBatteryEvent(Integer.parseInt(descMap.value, 16)) + } +} + +private handleBatteryEvent(rawValue) { + def linkText = getLinkText(device) + + def eventMap = [ + name: 'battery', + value: '--' + ] + + def volts = rawValue / 10 + if (volts > 3.5) { + eventMap.descriptionText = "${linkText} battery has too much power (${volts} volts)." + } + else if (volts > 0){ + def minVolts = 2.1 + def maxVolts = 3.0 + def pct = (volts - minVolts) / (maxVolts - minVolts) + if (pct < 0) + pct = 0 + eventMap.value = Math.min(100, (int) pct * 100) + eventMap.descriptionText = "${linkText} battery was ${eventMap.value}%" + } + + log.debug "Creating battery event: ${eventMap}" + sendEvent(eventMap) +} + +private handlePresenceEvent(present) { + def wasPresent = device.currentState("presence")?.value == "present" + if (!wasPresent && present) { + log.debug "Sensor is present" + startTimer() + } else if (!present) { + log.debug "Sensor is not present" + stopTimer() + } + def linkText = getLinkText(device) + def eventMap = [ + name: "presence", + value: present ? "present" : "not present", + linkText: linkText, + descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}", + ] + log.debug "Creating presence event: ${eventMap}" + sendEvent(eventMap) +} + +private startTimer() { + log.debug "Scheduling periodic timer" + schedule("0 * * * * ?", checkPresenceCallback) +} + +private stopTimer() { + log.debug "Stopping periodic timer" + unschedule() +} + +def checkPresenceCallback() { + def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000 + def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60 + log.debug "Sensor checked in ${timeSinceLastCheckin} seconds ago" + if (timeSinceLastCheckin >= theCheckInterval) { + handlePresenceEvent(false) + } +}