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..d9cdcc2 --- /dev/null +++ b/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy @@ -0,0 +1,153 @@ +/** + * 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 > 0){ + def minVolts = 2.0 + def maxVolts = 2.8 + + if (volts < minVolts) + volts = minVolts + else if (volts > maxVolts) + volts = maxVolts + def pct = (volts - minVolts) / (maxVolts - minVolts) + + eventMap.value = Math.round(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) + } +} diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index f28323b..b439a63 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -67,7 +67,7 @@ metadata { state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up" } valueTile("thermostatSetpoint", "device.thermostatSetpoint", width: 1, height: 1, decoration: "flat") { - state "thermostatSetpoint", label:'${currentValue}°' + state "thermostatSetpoint", label:'${currentValue}' } valueTile("currentStatus", "device.thermostatStatus", height: 1, width: 2, decoration: "flat") { state "thermostatStatus", label:'${currentValue}', backgroundColor:"#ffffff" diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index 8dd6c94..b950034 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -381,7 +381,7 @@ def getTemperature(value) { log.debug "Refreshing Values for manufacturer: SmartThings " refreshCmds = refreshCmds + [ - /* These values of Motion Threshold Multiplier(01) and Motion Threshold (D200) + /* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602) seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer. Separating these out in a separate if-else because I do not want to touch Centralite part as of now. @@ -392,7 +392,7 @@ def getTemperature(value) { "send 0x${device.deviceNetworkId} 1 1", "delay 400", "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global write 0xFC02 2 0x21 {D200}", "delay 200", + "zcl global write 0xFC02 2 0x21 {7602}", "delay 200", "send 0x${device.deviceNetworkId} 1 1", "delay 400", ] @@ -478,50 +478,34 @@ def enrollResponse() { ] } - private Map parseAxis(String description) { - log.debug "parseAxis" - def xyzResults = [x: 0, y: 0, z: 0] - def parts = description.split("2900") - parts[0] = "12" + parts[0] - parts.each { part -> - part = part.trim() - if (part.startsWith("12")) { - def unsignedX = hexToInt(part.split("12")[1].trim()) - def signedX = unsignedX > 32767 ? unsignedX - 65536 : unsignedX - xyzResults.x = signedX - log.debug "X Part: ${signedX}" - } - // Y and the Z axes are interchanged between SmartThings's implementation and Centralite's implementation - else if (part.startsWith("13")) { - def unsignedY = hexToInt(part.split("13")[1].trim()) - def signedY = unsignedY > 32767 ? unsignedY - 65536 : unsignedY - if (device.getDataValue("manufacturer") == "SmartThings") { - xyzResults.z = -signedY - log.debug "Z Part: ${xyzResults.z}" - if (garageSensor == "Yes") - garageEvent(xyzResults.z) - } - else { - xyzResults.y = signedY - log.debug "Y Part: ${signedY}" - } - } - else if (part.startsWith("14")) { - def unsignedZ = hexToInt(part.split("14")[1].trim()) - def signedZ = unsignedZ > 32767 ? unsignedZ - 65536 : unsignedZ - if (device.getDataValue("manufacturer") == "SmartThings") { - xyzResults.y = signedZ - log.debug "Y Part: ${signedZ}" - } else { - xyzResults.z = signedZ - log.debug "Z Part: ${signedZ}" - if (garageSensor == "Yes") - garageEvent(signedZ) - - } - } - } + def hexToSignedInt = { hexVal -> + def unsignedVal = hexToInt(hexVal) + unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal + } + + def z = hexToSignedInt(description[0..3]) + def y = hexToSignedInt(description[10..13]) + def x = hexToSignedInt(description[20..23]) + def xyzResults = [x: x, y: y, z: z] + + if (device.getDataValue("manufacturer") == "SmartThings") { + // This mapping matches the current behavior of the Device Handler for the Centralite sensors + xyzResults.x = z + xyzResults.y = y + xyzResults.z = -x + } else { + // The axises reported by the Device Handler differ from the axises reported by the sensor + // This may change in the future + xyzResults.x = z + xyzResults.y = x + xyzResults.z = y + } + + log.debug "parseAxis -- ${xyzResults}" + + if (garageSensor == "Yes") + garageEvent(xyzResults.z) getXyzResult(xyzResults, description) } diff --git a/devicetypes/smartthings/testing/simulated-thermostat.src/simulated-thermostat.groovy b/devicetypes/smartthings/testing/simulated-thermostat.src/simulated-thermostat.groovy index 5010ed8..ed0b23c 100644 --- a/devicetypes/smartthings/testing/simulated-thermostat.src/simulated-thermostat.groovy +++ b/devicetypes/smartthings/testing/simulated-thermostat.src/simulated-thermostat.groovy @@ -1,5 +1,5 @@ /** - * Copyright 2014 SmartThings + * Copyright 2015 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: @@ -15,6 +15,7 @@ metadata { // Automatically generated. Make future change here. definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") { capability "Thermostat" + capability "Relative Humidity Measurement" command "tempUp" command "tempDown" @@ -22,11 +23,40 @@ metadata { command "heatDown" command "coolUp" command "coolDown" - command "setTemperature", ["number"] + command "setTemperature", ["number"] } - tiles { - valueTile("temperature", "device.temperature", width: 1, height: 1) { + tiles(scale: 2) { + multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) { + tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { + attributeState("default", label:'${currentValue}', unit:"dF") + } + tileAttribute("device.temperature", key: "VALUE_CONTROL") { + attributeState("default", action: "setTemperature") + } + tileAttribute("device.humidity", key: "SECONDARY_CONTROL") { + attributeState("default", label:'${currentValue}%', unit:"%") + } + tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { + attributeState("idle", backgroundColor:"#44b621") + attributeState("heating", backgroundColor:"#ffa81e") + attributeState("cooling", backgroundColor:"#269bd2") + } + tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { + attributeState("off", label:'${name}') + attributeState("heat", label:'${name}') + attributeState("cool", label:'${name}') + attributeState("auto", label:'${name}') + } + tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { + attributeState("default", label:'${currentValue}', unit:"dF") + } + tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") { + attributeState("default", label:'${currentValue}', unit:"dF") + } + } + + valueTile("temperature", "device.temperature", width: 2, height: 2) { state("temperature", label:'${currentValue}', unit:"dF", backgroundColors:[ [value: 31, color: "#153591"], @@ -39,51 +69,51 @@ metadata { ] ) } - standardTile("tempDown", "device.temperature", inactiveLabel: false, decoration: "flat") { + standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "default", label:'down', action:"tempDown" } - standardTile("tempUp", "device.temperature", inactiveLabel: false, decoration: "flat") { + standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "default", label:'up', action:"tempUp" } - valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") { + valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff" } - standardTile("heatDown", "device.temperature", inactiveLabel: false, decoration: "flat") { + standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "default", label:'down', action:"heatDown" } - standardTile("heatUp", "device.temperature", inactiveLabel: false, decoration: "flat") { + standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "default", label:'up', action:"heatUp" } - valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") { + valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff" } - standardTile("coolDown", "device.temperature", inactiveLabel: false, decoration: "flat") { + standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "default", label:'down', action:"coolDown" } - standardTile("coolUp", "device.temperature", inactiveLabel: false, decoration: "flat") { + standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "default", label:'up', action:"coolUp" } - standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { + standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff" state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e" state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2" state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821" } - standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { + standardTile("fanMode", "device.thermostatFanMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff" state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff" state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff" } - standardTile("operatingState", "device.thermostatOperatingState") { + standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) { state "idle", label:'${name}', backgroundColor:"#ffffff" state "heating", label:'${name}', backgroundColor:"#ffa81e" state "cooling", label:'${name}', backgroundColor:"#269bd2" } - main("temperature","operatingState") + main("thermostatMulti") details([ "temperature","tempDown","tempUp", "mode", "fanMode", "operatingState", @@ -101,6 +131,7 @@ def installed() { sendEvent(name: "thermostatMode", value: "off") sendEvent(name: "thermostatFanMode", value: "fanAuto") sendEvent(name: "thermostatOperatingState", value: "idle") + sendEvent(name: "humidity", value: 53, unit: "%") } def parse(String description) { diff --git a/devicetypes/smartthings/wemo-light-switch.src/wemo-light-switch.groovy b/devicetypes/smartthings/wemo-light-switch.src/wemo-light-switch.groovy index 1bd78f3..94e2715 100644 --- a/devicetypes/smartthings/wemo-light-switch.src/wemo-light-switch.groovy +++ b/devicetypes/smartthings/wemo-light-switch.src/wemo-light-switch.groovy @@ -273,7 +273,7 @@ User-Agent: CyberGarage-HTTP/1.0 def poll() { log.debug "Executing 'poll'" if (device.currentValue("currentIP") != "Offline") - runIn(10, setOffline) + runIn(30, setOffline) new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1 SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState" Content-Length: 277 diff --git a/devicetypes/smartthings/wemo-motion.src/wemo-motion.groovy b/devicetypes/smartthings/wemo-motion.src/wemo-motion.groovy index 9649db4..b7fbee1 100644 --- a/devicetypes/smartthings/wemo-motion.src/wemo-motion.groovy +++ b/devicetypes/smartthings/wemo-motion.src/wemo-motion.groovy @@ -77,9 +77,8 @@ def parse(String description) { def result = [] def bodyString = msg.body if (bodyString) { - unschedule("setOffline") + unschedule("setOffline") def body = new XmlSlurper().parseText(bodyString) - if (body?.property?.TimeSyncRequest?.text()) { log.trace "Got TimeSyncRequest" result << timeSyncResponse() @@ -134,7 +133,7 @@ def refresh() { def getStatus() { log.debug "Executing WeMo Motion 'getStatus'" if (device.currentValue("currentIP") != "Offline") - runIn(10, setOffline) + runIn(30, setOffline) new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1 SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState" Content-Length: 277 diff --git a/devicetypes/smartthings/wemo-switch.src/wemo-switch.groovy b/devicetypes/smartthings/wemo-switch.src/wemo-switch.groovy index cd9e0ec..6ac3939 100644 --- a/devicetypes/smartthings/wemo-switch.src/wemo-switch.groovy +++ b/devicetypes/smartthings/wemo-switch.src/wemo-switch.groovy @@ -28,6 +28,7 @@ command "subscribe" command "resubscribe" command "unsubscribe" + command "setOffline" } // simulator metadata @@ -207,7 +208,7 @@ def subscribe(ip, port) { def existingIp = getDataValue("ip") def existingPort = getDataValue("port") if (ip && ip != existingIp) { - log.debug "Updating ip from $existingIp to $ip" + log.debug "Updating ip from $existingIp to $ip" updateDataValue("ip", ip) def ipvalue = convertHexToIP(getDataValue("ip")) sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}") @@ -275,7 +276,7 @@ def setOffline() { def poll() { log.debug "Executing 'poll'" if (device.currentValue("currentIP") != "Offline") - runIn(10, setOffline) + runIn(30, setOffline) new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1 SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState" Content-Length: 277 @@ -290,4 +291,4 @@ User-Agent: CyberGarage-HTTP/1.0 """, physicalgraph.device.Protocol.LAN) -} +} \ No newline at end of file diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 7d81c41..053c266 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -258,8 +258,8 @@ def installed() { def updated() { log.trace "Updated with settings: ${settings}" - unschedule() unsubscribe() + unschedule() initialize() } @@ -325,6 +325,7 @@ def addBulbs() { } else { d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name]) } + log.debug "created ${d.displayName} with id $dni" } else { log.debug "$dni in not longer paired to the Hue Bridge or ID changed" } @@ -333,8 +334,6 @@ def addBulbs() { newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name]) } - - log.debug "created ${d.displayName} with id $dni" d.refresh() } else { log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'" @@ -775,4 +774,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) { private List getRealHubFirmwareVersions() { return location.hubs*.firmwareVersionString.findAll { it } -} +} \ No newline at end of file