Compare commits

..

32 Commits

Author SHA1 Message Date
Matt Pennig
d69abb64bd Merge pull request #385 from SmartThingsCommunity/rich-simulated-thermostat
Adding multiAttributeTile definition to Simulated Thermostat device type handler
2016-01-11 15:10:59 -06:00
Tom Manley
7429ecc83b Merge pull request #423 from tpmanley/feature/arrival_sensor_ha
arrival: Add support for ZigBee HA arrival sensor
2016-01-11 12:42:44 -06:00
Tom Manley
112a35f5db arrival: Change voltage range for battery remaining calculation 2016-01-11 12:41:50 -06:00
Juan Pablo Risso
c297564665 Merge pull request #426 from juano2310/Wemo
Increased delay to mark device as offline
2016-01-08 16:06:38 -05:00
Juan Risso
26ab32565b Increased delay to mark device as offline 2016-01-08 16:01:57 -05:00
Tom Manley
9733947fea arrival: Add support for ZigBee HA arrival sensor
Resolves:
    https://smartthings.atlassian.net/browse/DVCSMP-1305
    https://smartthings.atlassian.net/browse/DVCSMP-1322
2016-01-07 14:30:04 -06:00
Yaima
6abf8c7f20 Merge pull request #420 from Yaima/master
Removed degree sign from tile
2016-01-06 15:25:19 -08:00
Yaima Valdivia
fe505ddc9f Removed degree sign from tile
https://smartthings.atlassian.net/browse/DVCSMP-1318
2016-01-06 15:24:47 -08:00
Tom Manley
f4034f5ccf Merge pull request #419 from tpmanley/bugfix/multi_adjust_threshold
multi: Adjust threshold multiplier to prevent motion detection while …
2016-01-06 14:18:42 -06:00
Tom Manley
c1c2431299 multi: Adjust threshold multiplier to prevent motion detection while idle
The previous threshold multiplier was found to be too low so motion was
being detected while the sensor was idle. This new value of 630 seems to
produce better results.
2016-01-06 13:27:48 -06:00
Tom Manley
39f0c49ea6 Merge pull request #411 from tpmanley/bugfix/multi_accel_parsing
multi: Fix x,y,z parsing error
2016-01-06 13:26:27 -06:00
Yaima
ed5a409c63 Merge pull request #413 from Yaima/master
Added canChangeIcon: true
2016-01-05 16:00:08 -08:00
Yaima Valdivia
8453292038 Added canChangeIcon: true 2016-01-05 15:59:14 -08:00
Tom Manley
e98a04a1b4 multi: Fix x,y,z parsing error
The previous axis value parsing code was very fragile. For example, this
code block:

    def unsignedY = hexToInt(part.split("13")[1].trim())

would fail when `part` was "13xx13", where "xx" is any value. The split
assumed the value "13" was present only once in the string, and everything
after the "13" was the value. When "13" was part of the value this code
would interpret only "xx" as the value, instead of "xx13".

The new parsing code is not fragile like this. It knows exactly what bytes
of the string are X, Y, and Z and parses the values correctly.
2016-01-04 15:21:55 -06:00
Kristofer Schaller
41e95b9248 Merge pull request #400 from kris-schaller/phonebook_auto_unlock
Updating code to use the phonebook API
2016-01-04 03:41:50 -08:00
Tom Manley
63f20c912d Merge pull request #407 from tpmanley/bugfix/multi_invalid_XY
multi: ignore attribute reports that don't include all three axis
2015-12-31 14:42:03 -06:00
Tom Manley
837d2d0cfd multi: ignore attribute reports that don't include all three axis
Resolves:
    https://smartthings.atlassian.net/browse/DVCSMP-1366
2015-12-31 11:13:41 -06:00
Kris Schaller
629c4cc231 Updating code to use the phonebook API 2015-12-30 14:14:37 -08:00
Kristofer Schaller
f12684565c Merge pull request #399 from kris-schaller/vinli_icon
Changing icon paths to hosted files
2015-12-28 16:18:48 -08:00
Kris Schaller
51e727b91a Changing icon paths to hosted files 2015-12-28 16:06:15 -08:00
Kristofer Schaller
49a858eb5c Merge pull request #398 from kris-schaller/vinli_icon
Adding vinli icons
2015-12-28 15:39:16 -08:00
Kris Schaller
112a4087b0 Adding vinli icons 2015-12-28 15:38:23 -08:00
Kristofer Schaller
132d8fc9d8 Merge pull request #302 from SmartThingsCommunity/MSA-699-1
Merged publication request 'Vinli Home Connect'
2015-12-28 10:29:56 -08:00
Umang Parekh
5b0b239caa Merge pull request #386 from umangparekh/multiSensorThresholdFix
Fine-tuning the Motion Threshold and Motion Threshold multiplier. Garage use case works. XYZ values reported correctly.
2015-12-22 19:58:59 -08:00
umangparekh
3ea70fecad Fine-tuning the Motion Threshold and Motion Threshold multiplier for manufacturer Smarthings.
Garage Door use case works + implemented code review feedback

Implementing code review feedback + inverted the signedY to match Centralite like behavior.

Implementing code review feedback + inverted the signedY to match Centralite like behavior.
2015-12-22 19:33:06 -08:00
Matt Pennig
358cf261e8 Adding multiAttributeTile definition to Simulated Thermostat device type handler 2015-12-22 11:14:53 -06:00
Tyler Lange
f420907043 Merge pull request #381 from kris-schaller/master
Adding paths to locally hosted images for bose sound control
2015-12-21 12:53:45 -08:00
Kris Schaller
bf915b49dc Adding paths to locally hosted images for bose sound control 2015-12-21 12:48:17 -08:00
Tyler Lange
1c2a65e313 Merge pull request #380 from kris-schaller/master
Adding icons to Bose Sound Touch
2015-12-21 12:40:14 -08:00
Kris Schaller
075fdf0974 Adding icons to Bose Sound Touch 2015-12-21 12:38:17 -08:00
Daniel
21041570db Modifying 'Vinli Home Connect' 2015-12-17 09:40:00 -06:00
Daniel
96f2c5ed8b MSA-699: Vinli Home Connect allows users to control their Smartthings Devices with their Vinli connect vehicles. The Vinli device is an OBD dongle that can report when it leaves or enters geofences. A user can, for instance, set their doors to lock and lights to turn off when they leave proximity to their home. 2015-11-23 11:38:25 -06:00
17 changed files with 637 additions and 477 deletions

View File

@@ -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)
}
}

View File

@@ -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"

View File

@@ -204,8 +204,10 @@ private List parseReportAttributeMessage(String description) {
}
result << getAccelerationResult(descMap.value)
}
else if (descMap.cluster == "FC02" && descMap.attrId == "0012") {
result << parseAxis(descMap.value)
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
// The size is checked to ensure the attribute report contains X, Y and Z values
// If all three axis are not included then the attribute report is ignored
result << parseAxis(descMap.value)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
result << getBatteryResult(Integer.parseInt(descMap.value, 16))
@@ -371,21 +373,50 @@ def getTemperature(value) {
def refresh() {
log.debug "Refreshing Values "
def refreshCmds = [
/* sensitivity - default value (8) */
def refreshCmds = []
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
/* 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.
*/
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else {
refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
}
//Common refresh commands
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
return refreshCmds + enrollResponse()
}
@@ -447,35 +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}"
}
else if (part.startsWith("13")) {
def unsignedY = hexToInt(part.split("13")[1].trim())
def signedY = unsignedY > 32767 ? unsignedY - 65536 : unsignedY
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
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)
}
@@ -553,3 +583,4 @@ private byte[] reverseArray(byte[] array) {
return array
}

View File

@@ -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) {

View File

@@ -29,7 +29,7 @@ metadata {
tiles {
valueTile("power", "device.power") {
valueTile("power", "device.power", canChangeIcon: true) {
state "power", label: '${currentValue} W'
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
</u:GetBinaryState>
</s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,189 @@
/**
* Vinli Home Beta
*
* Copyright 2015 Daniel
*
*/
definition(
name: "Vinli Home Connect",
namespace: "com.vinli.smartthings",
author: "Daniel",
description: "Allows Vinli users to connect their car to SmartThings",
category: "SmartThings Labs",
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_60.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_120.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_120.png",
oauth: true)
preferences {
section ("Allow external service to control these things...") {
input "switches", "capability.switch", multiple: true, required: true
input "locks", "capability.lock", multiple: true, required: true
}
}
mappings {
path("/devices") {
action: [
GET: "listAllDevices"
]
}
path("/switches") {
action: [
GET: "listSwitches"
]
}
path("/switches/:command") {
action: [
PUT: "updateSwitches"
]
}
path("/switches/:id/:command") {
action: [
PUT: "updateSwitch"
]
}
path("/locks/:command") {
action: [
PUT: "updateLocks"
]
}
path("/locks/:id/:command") {
action: [
PUT: "updateLock"
]
}
path("/devices/:id/:command") {
action: [
PUT: "commandDevice"
]
}
}
// returns a list of all devices
def listAllDevices() {
def resp = []
switches.each {
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
}
locks.each {
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
}
return resp
}
// returns a list like
// [[name: "kitchen lamp", value: "off"], [name: "bathroom", value: "on"]]
def listSwitches() {
def resp = []
switches.each {
resp << [name: it.displayName, value: it.currentValue("switch"), type: "switch", id: it.id]
}
return resp
}
void updateLocks() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
locks.each {
if (!it.hasCommand(command)) {
httpError(501, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
locks."$command"()
}
}
void updateLock() {
def command = params.command
locks.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all lock specified")
}
if (it.id == params.id) {
it."$command"()
}
}
}
void updateSwitch() {
def command = params.command
switches.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all switches specified")
}
if (it.id == params.id) {
it."$command"()
}
}
}
void commandDevice() {
def command = params.command
def devices = []
switches.each {
devices << it
}
locks.each {
devices << it
}
devices.each {
if (it.id == params.id) {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for specified device")
}
it."$command"()
}
}
}
void updateSwitches() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
switches.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
switches."$command"()
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
}

View File

@@ -1,427 +1,164 @@
/**
* Nobody Home
*
* Author: brian@bevey.org, raychi@gmail.com
* Date: 12/02/2015
* Author: brian@bevey.org
* Date: 12/19/14
*
* 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.
* Monitors a set of presence detectors and triggers a mode change when everyone has left.
* When everyone has left, sets mode to a new defined mode.
* When at least one person returns home, set the mode back to a new defined mode.
* When someone is home - or upon entering the home, their mode may change dependent on sunrise / sunset.
*/
/**
* Monitors a set of presence sensors and trigger appropriate mode
* based on configured modes and sunrise/sunset time.
*
* - When everyone is away [Away]
* - When someone is home during the day [Home]
* - When someone is home at the night [Night]
*/
// ********** App related functions **********
// The definition provides metadata about the App to SmartThings.
definition (
name: "Nobody Home",
namespace: "imbrianj",
author: "brian@bevey.org",
description: "Automatically set Away/Home/Night mode based on a set of presence sensors and sunrise/sunset time.",
category: "Mode Magic",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
definition(
name: "Nobody Home",
namespace: "imbrianj",
author: "brian@bevey.org",
description: "When everyone leaves, change mode. If at least one person home, switch mode based on sun position.",
category: "Mode Magic",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png"
)
// The preferences defines information the App needs from the user.
preferences {
section("Presence sensors to monitor") {
input "people", "capability.presenceSensor", multiple: true
}
section("When all of these people leave home") {
input "people", "capability.presenceSensor", multiple: true
}
section("Mode setting") {
input "newAwayMode", "mode", title: "Everyone is away"
input "newSunriseMode", "mode", title: "Someone is home during the day"
input "newSunsetMode", "mode", title: "Someone is home at night"
}
section("Change to this mode to...") {
input "newAwayMode", "mode", title: "Everyone is away"
input "newSunsetMode", "mode", title: "At least one person home and nightfall"
input "newSunriseMode", "mode", title: "At least one person home and sunrise"
}
section("Mode change delay (minutes)") {
input "awayThreshold", "decimal", title: "Away delay [5m]", required: false
input "arrivalThreshold", "decimal", title: "Arrival delay [2m]", required: false
}
section("Away threshold (defaults to 10 min)") {
input "awayThreshold", "decimal", title: "Number of minutes", required: false
}
section("Notifications") {
input "sendPushMessage", "bool", title: "Push notification", required:false
}
section("Notifications") {
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
}
}
// called when the user installs the App
def installed()
{
log.debug("installed() @${location.name}: ${settings}")
initialize(true)
def installed() {
init()
}
// called when the user installs the app, or changes the App
// preference
def updated()
{
log.debug("updated() @${location.name}: ${settings}")
unsubscribe()
initialize(false)
def updated() {
unsubscribe()
init()
}
def initialize(isInstall)
{
// subscribe to all the events we care about
log.debug("Subscribing to events ...")
def init() {
subscribe(people, "presence", presence)
subscribe(location, "sunrise", setSunrise)
subscribe(location, "sunset", setSunset)
// thing to subscribe, attribute/state we care about, and callback fn
subscribe(people, "presence", presenceHandler)
subscribe(location, "sunrise", sunriseHandler)
subscribe(location, "sunset", sunsetHandler)
// set the optional parameter values. these are not available
// directly until the app has initialized (that is,
// installed/updated has returned). so here we access them through
// the settings object, as otherwise will get an exception.
// store information we need in state object so we can get access
// to it later in our event handlers.
// calculate the away threshold in seconds. can't use the simpler
// default falsy value, as value of 0 (no delay) is evaluated to
// false (not specified), but we want 0 to represent no delay. so
// we compare against null explicitly to see if the user has set a
// value or not.
if (settings.awayThreshold == null) {
settings.awayThreshold = 5 // default away 5 minute
}
state.awayDelay = (int) settings.awayThreshold * 60
log.debug("awayThreshold set to " + state.awayDelay + " second(s)")
if (settings.arrivalThreshold == null) {
settings.arrivalThreshold = 2 // default arrival 2 minute
}
state.arrivalDelay = (int) settings.arrivalThreshold * 60
log.debug("arrivalThreshold set to " + state.arrivalDelay + " second(s)")
// get push notification setting
state.isPush = settings.sendPushMessage ? true : false
log.debug("sendPushMessage set to " + state.isPush)
// on install (not update), figure out what mode we should be in
// IF someone's home. This value is needed so that when a presence
// sensor is triggered, we know what mode to set the system to, as
// the sunrise/sunset event handler may not be triggered yet after
// a fresh install.
if (isInstall) {
// TODO: for now, we simply assume daytime. a better approach
// would be to figure out whether current time is day or
// night, and set it appropriately. However there
// doesn't seem to be a way to query this directly
// without a zip code. This will become the correct
// value at the next sunrise/sunset event.
log.debug("No sun info yet, assuming daytime")
state.modeIfHome = newSunriseMode
// set keep a separate sun mode state so we can show a more
// helpful message when sun events are triggered.
state.currentSunMode = "sunUnknown"
state.eventDevice = "" // last event device
// device that triggered timer. This is not necessarily the
// eventDevice. For example, if A arrives, kick off timer,
// then b arrives before timer elapsed, we want the
// notification message to reference A, not B.
state.timerDevice = null
// anything in flight? We use this to avoid scheduling
// duplicate timers (so we don't extend the timer).
state.pendingOp = "init"
// now set the correct mode for the location. This way, we
// don't need to wait for the next sun/presence event.
// we schedule this action to run after the app has fully
// initialized. This way, the app install is faster and the
// user customized app name is used in the notification.
runIn(7, "setInitialMode")
}
// On update, we don't change state.modeIfHome. This is so that we
// preserve the current sun rise/set state we obtained in earlier
// sunset/sunrise handler. This way the app remains in the correct
// sun state when the user reconfigures it.
state.sunMode = location.mode
}
def setInitialMode()
{
changeSunMode(state.modeIfHome)
state.pendingOp = null
def setSunrise(evt) {
changeSunMode(newSunriseMode)
}
// ********** sunrise/sunset handling **********
// event handler when the sunrise time is reached
def sunriseHandler(evt)
{
// we store the mode we should be in, IF someone's home
state.modeIfHome = newSunriseMode
state.currentSunMode = "sunRise"
// change mode if someone's home, otherwise set to away
changeSunMode(newSunriseMode)
def setSunset(evt) {
changeSunMode(newSunsetMode)
}
// event handler when the sunset time is reached
def sunsetHandler(evt)
{
// we store the mode we should be in, IF someone's home
state.modeIfHome = newSunsetMode
state.currentSunMode = "sunSet"
def changeSunMode(newMode) {
state.sunMode = newMode
// change mode if someone's home, otherwise set to away
changeSunMode(newSunsetMode)
if(everyoneIsAway() && (location.mode == newAwayMode)) {
log.debug("Mode is away, not evaluating")
}
else if(location.mode != newMode) {
def message = "${app.label} changed your mode to '${newMode}'"
send(message)
setLocationMode(newMode)
}
else {
log.debug("Mode is the same, not evaluating")
}
}
def changeSunMode(newMode)
{
// if everyone is away, we need to check and ensure the system is
// in away mode.
if (isEveryoneAway()) {
// this shouldn not happen normally as the mode should already
// be changed during presenceHandler, but in case this is not
// done, such as when app is initially installed while away,
// and system is not in away mode, then we toggle it to away
// at the sun rise/set event.
changeMode(newAwayMode, " because no one is present")
} else {
// someone is home, we update the mode depending on
// sunrise/sunset.
if (state.currentSunMode == "sunRise") {
changeMode(newMode, " because it's sunrise")
} else if (state.currentSunMode == "sunSet") {
changeMode(newMode, " because it's sunset")
} else {
changeMode(newMode)
}
def presence(evt) {
if(evt.value == "not present") {
log.debug("Checking if everyone is away")
if(everyoneIsAway()) {
log.info("Starting ${newAwayMode} sequence")
def delay = (awayThreshold != null && awayThreshold != "") ? awayThreshold * 60 : 10 * 60
runIn(delay, "setAway")
}
}
else {
if(location.mode != state.sunMode) {
log.debug("Checking if anyone is home")
if(anyoneIsHome()) {
log.info("Starting ${state.sunMode} sequence")
changeSunMode(state.sunMode)
}
}
else {
log.debug("Mode is the same, not evaluating")
}
}
}
// ********** presence handling **********
// event handler when presence sensor changes state
def presenceHandler(evt)
{
// get the device name that resulted in the change
state.eventDevice= evt.device?.displayName
// is setInitialMode() still pending?
if (state.pendingOp == "init") {
log.debug("Pending ${state.pendingOp} op still in progress, ignoring presence event")
return
def setAway() {
if(everyoneIsAway()) {
if(location.mode != newAwayMode) {
def message = "${app.label} changed your mode to '${newAwayMode}' because everyone left home"
log.info(message)
send(message)
setLocationMode(newAwayMode)
}
if (evt.value == "not present") {
handleDeparture()
} else {
handleArrival()
else {
log.debug("Mode is the same, not evaluating")
}
}
else {
log.info("Somebody returned home before we set to '${newAwayMode}'")
}
}
def handleDeparture()
{
log.info("${state.eventDevice} left ${location.name}")
private everyoneIsAway() {
def result = true
// do nothing if someone's still home
if (!isEveryoneAway()) {
log.info("Someone is still present, no actions needed")
return
}
if(people.findAll { it?.currentPresence == "present" }) {
result = false
}
// Now we set away mode. We perform the following actions even if
// home is already in away mode because an arrival timer may be
// pending, and scheduling delaySetMode() has the nice effect of
// canceling any previous pending timer, which is what we want to
// do. So we do this even if delay is 0.
log.info("Scheduling ${newAwayMode} mode in " + state.awayDelay + "s")
state.pendingOp = "away"
state.timerDevice = state.eventDevice
// we always use runIn(). This has the benefit of automatically
// replacing any pending arrival/away timer. if any arrival timer
// is active, it will be clobbered with this away timer. If any
// away timer is active, it will be extended with this new timeout
// (though normally it should not happen)
runIn(state.awayDelay, "delaySetMode")
log.debug("everyoneIsAway: ${result}")
return result
}
def handleArrival()
{
// someone returned home, set home/night mode after delay
log.info("${state.eventDevice} arrived at ${location.name}")
private anyoneIsHome() {
def result = false
def numHome = isAnyoneHome()
if (!numHome) {
// no one home, do nothing for now (should NOT happen)
log.warn("${deviceName} arrived, but isAnyoneHome() returned false!")
return
}
if(people.findAll { it?.currentPresence == "present" }) {
result = true
}
if (numHome > 1) {
// not the first one home, do nothing, as any action that
// should happen would've happened when the first sensor
// arrived. this is the opposite of isEveryoneAway() where we
// don't do anything if someone's still home.
log.debug("Someone is already present, no actions needed")
return
}
log.debug("anyoneIsHome: ${result}")
// check if any pending arrival timer is already active. we want
// the timer to trigger when the 1st person arrives, but not
// extended when the 2nd person arrives later. this should not
// happen because of the >1 check above, but just in case.
if (state.pendingOp == "arrive") {
log.debug("Pending ${state.pendingOp} op already in progress, do nothing")
return
}
// now we set home/night mode
log.info("Scheduling ${state.modeIfHome} mode in " + state.arrivalDelay + "s")
state.pendingOp = "arrive"
state.timerDevice = state.eventDevice
// if any away timer is active, it will be clobbered with
// this arrival timer
runIn(state.arrivalDelay, "delaySetMode")
return result
}
private send(msg) {
if(sendPushMessage != "No") {
log.debug("Sending push message")
sendPush(msg)
}
// ********** helper functions **********
// change the system to the new mode, unless its already in that mode.
def changeMode(newMode, reason="")
{
if (location.mode != newMode) {
// notification message
def message = "${location.name} changed mode from '${location.mode}' to '${newMode}'" + reason
setLocationMode(newMode)
send(message) // send message after changing mode
} else {
log.debug("${location.name} is already in ${newMode} mode, no actions needed")
}
log.debug(msg)
}
// create a useful departure/arrival reason string
def reasonStr(isAway, delaySec, delayMin)
{
def reason
// if we are invoked by timer, use the stored timer trigger
// device, otherwise use the last event device
if (state.timerDevice) {
reason = " because ${state.timerDevice} "
} else {
reason = " because ${state.eventDevice} "
}
if (isAway) {
reason += "left"
} else {
reason += "arrived"
}
if (delaySec) {
if (delaySec > 60) {
if (delayMin == null) {
delayMin = (int) delaySec / 60
}
reason += " ${delayMin} minutes ago"
} else {
reason += " ${delaySec}s ago"
}
}
return reason
}
// http://docs.smartthings.com/en/latest/smartapp-developers-guide/scheduling.html#schedule-from-now
//
// By default, if a method is scheduled to run in the future, and then
// another call to runIn with the same method is made, the last one
// overwrites the previously scheduled method.
//
// We use the above property to schedule our arrval/departure delay
// using the same function so we don't have to worry about
// arrival/departure timer firing independently and complicating code.
def delaySetMode()
{
def newMode = null
def reason = ""
// timer has elapsed, check presence status to figure out what we
// need to do
if (isEveryoneAway()) {
reason = reasonStr(true, state.awayDelay, awayThreshold)
newMode = newAwayMode
if (state.pendingOp) {
log.debug("${state.pendingOp} timer elapsed: everyone is away")
}
} else {
reason = reasonStr(false, state.arrivalDelay, arrivalThreshold)
newMode = state.modeIfHome
if (state.pendingOp) {
log.debug("${state.pendingOp} timer elapsed: someone is home")
}
}
// now change the mode
changeMode(newMode, reason);
state.pendingOp = null
state.timerDevice = null
}
private isEveryoneAway()
{
def result = true
if (people.findAll { it?.currentPresence == "present" }) {
result = false
}
// log.debug("isEveryoneAway: ${result}")
return result
}
// return the number of people that are home
private isAnyoneHome()
{
def result = 0
// iterate over our people variable that we defined
// in the preferences method
for (person in people) {
if (person.currentPresence == "present") {
result++
}
}
return result
}
private send(msg)
{
if (state.isPush) {
log.debug("Sending push notification")
sendPush(msg)
} else {
log.debug("Sending notification")
sendNotificationEvent(msg)
}
log.info(msg)
}

View File

@@ -13,7 +13,7 @@ preferences{
input "lock1", "capability.lock", required: true
}
section("Select the door contact sensor:") {
input "contact", "capability.contactSensor", required: true
input "contact", "capability.contactSensor", required: true
}
section("Automatically lock the door when closed...") {
input "minutesLater", "number", title: "Delay (in minutes):", required: true
@@ -22,9 +22,10 @@ preferences{
input "secondsLater", "number", title: "Delay (in seconds):", required: true
}
section( "Notifications" ) {
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes", "No"]], required: false
input "phoneNumber", "phone", title: "Enter phone number to send text notification.", required: false
}
input("recipients", "contact", title: "Send notifications to", required: false) {
input "phoneNumber", "phone", title: "Warn with text message (optional)", description: "Phone Number", required: false
}
}
}
def installed(){
@@ -42,55 +43,73 @@ def initialize(){
subscribe(lock1, "lock", doorHandler, [filterEvents: false])
subscribe(lock1, "unlock", doorHandler, [filterEvents: false])
subscribe(contact, "contact.open", doorHandler)
subscribe(contact, "contact.closed", doorHandler)
subscribe(contact, "contact.closed", doorHandler)
}
def lockDoor(){
log.debug "Locking the door."
lock1.lock()
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!" )
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients)
}
}
if (phoneNumber) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!")
}
}
def unlockDoor(){
log.debug "Unlocking the door."
lock1.unlock()
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!" )
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients)
}
}
if ( phoneNumber ) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!")
}
}
def doorHandler(evt){
if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then...
def delay = (secondsLater) // runIn uses seconds
runIn( delay, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged.
//def delay = (secondsLater) // runIn uses seconds
runIn( secondsLater, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged.
}
else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then...
unschedule( unlockDoor ) // ...we don't need to unlock it later.
}
}
else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then...
unschedule( lockDoor ) // ...we don't need to lock it later.
}
else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then...
def delay = (minutesLater * 60) // runIn uses seconds
runIn( delay, lockDoor ) // ...schedule (in minutes) to lock.
//def delay = (minutesLater * 60) // runIn uses seconds
runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
}
else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door...
unschedule( lockDoor ) // ...we don't need to lock it later.
}
else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door...
def delay = (minutesLater * 60) // runIn uses seconds
runIn( delay, lockDoor ) // ...schedule (in minutes) to lock.
}
//def delay = (minutesLater * 60) // runIn uses seconds
runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
}
else { //Opening or Closing door when locked (in case you have a handle lock)
log.debug "Unlocking the door."
lock1.unlock()
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!" )
}
log.debug "Unlocking the door."
lock1.unlock()
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients)
}
}
if ( phoneNumber ) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!")
}
}
}

View File

@@ -22,9 +22,9 @@ definition(
author: "SmartThings & Joe Geiger",
description: "Control your Bose® SoundTouch® when certain actions take place in your home.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png"
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png"
)
preferences {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB