Compare commits

...

18 Commits

Author SHA1 Message Date
Brian Warner
55081dd486 MSA-726: I wrote this app to add some garage-door-specific smarts to my outside lighting. I wasn't able to find anything that was aware of multiple garage door sensors, and that would keep the lights on for a certain amount of time after the last door was closed. The use case is as follows:
A garage door goes up while it's dark.  All lights turn on and stay on as long as the door is open, which is useful if you're working outside at night.  If you were actually pulling a car out to leave, you'd want the lights on as you pull out of the driveway, so there is an option to keep them on for a certain time after the door closes.
2015-12-03 19:51:55 -06:00
Duncan McKee
90dee51255 Merge pull request #297 from mckeed/master
Z-Wave Lock: fix Security Exception DVCSMP-1265 DVCSMP-1059
2015-12-02 12:11:11 -06:00
Yaima
7b7fdd43cd Merge pull request #326 from Yaima/master
Implemented toggle() for locks
2015-12-01 10:52:29 -08:00
Yaima Valdivia
9059718818 Implemented toggle() for locks
https://smartthings.atlassian.net/browse/DVCSMP-672
2015-12-01 10:48:15 -08:00
Yaima
9d7c66c7af Merge pull request #310 from Yaima/master
Device Details > "Which?" Label needs to be removed
2015-11-24 14:02:31 -08:00
Yaima Valdivia
8e81967227 Device Details > "Which?" Label needs to be removed
https://smartthings.atlassian.net/browse/DVCSMP-1250
2015-11-24 14:01:33 -08:00
Yaima
8c6c68f102 Merge pull request #303 from Yaima/master
WeMo uninstall not available
2015-11-23 11:26:59 -08:00
Yaima Valdivia
661f8b3bc0 WeMo uninstall not available
https://smartthings.atlassian.net/browse/DVCSMP-1208
2015-11-23 11:22:57 -08:00
Mike Robinet
a62d825f69 Merge pull request #289 from mrobinet/dvcsmp-1262
DVCSMP-1262 Fix reference to Life360 User child device type
2015-11-20 13:52:29 -06:00
Vinay Rao
1890147221 Merge pull request #292 from workingmonk/fingerprint_changes
fingerprint spacing consistency, additional fingerprint color
2015-11-19 08:44:01 -08:00
Vinay Rao
2fb5f8c78c fingerprint spacing consistency, additional fingerprint color
removing sensor from zigbee white color temp, switch and dimmer
2015-11-19 08:41:44 -08:00
Vinay Rao
b44356248c Merge pull request #299 from juano2310/Harmony_Global
Updated - Device Type name
2015-11-19 06:28:00 -08:00
Juan Pablo Risso
c30af84d70 Updated - Device Type name 2015-11-19 09:23:08 -05:00
Yaima Valdivia
5cf72c644c Rename Sonos SmartApps
https://smartthings.atlassian.net/browse/DVCSMP-607
2015-11-18 14:00:57 -06:00
Duncan McKee
8ae9b06022 Z-Wave Lock: fix double updated() commands DVCSMP-1265 2015-11-17 18:08:44 -05:00
Warodom Khamphanchai
5e93bca030 LiFX - change shardUrl param to apiServerUrl 2015-11-16 16:33:07 -08:00
Duncan McKee
71fc8e7f5f Fix Z-Wave Lock SecurityException DVCSMP-1265 2015-11-16 18:52:08 -05:00
Mike Robinet
30dedde0df DVCSMP-1262 Fix reference to Life360 User child device type 2015-11-16 11:49:23 -06:00
31 changed files with 441 additions and 307 deletions

View File

@@ -15,6 +15,7 @@
* Author: SmartThings
* Date: 2013-12-04
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "CentraLite Dimmer", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
@@ -25,7 +26,6 @@ metadata {
capability "Refresh"
capability "Sensor"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B04,0B05", outClusters: "0019"
}
// simulator metadata

View File

@@ -14,7 +14,7 @@
*
*/
metadata {
definition (name: "Logitech Harmony Activity", namespace: "smartthings", author: "Juan Risso") {
definition (name: "Harmony Activity", namespace: "smartthings", author: "Juan Risso") {
capability "Switch"
capability "Actuator"
capability "Refresh"

View File

@@ -17,44 +17,50 @@ metadata {
}
simulator {
// TODO: define status and reply messages here
}
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "unreachable", label: "?", action:"refresh.refresh", icon:"st.switches.light.off", backgroundColor:"#666666"
state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'Turning on', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'Turning off', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setColor"
}
tileAttribute ("device.model", key: "SECONDARY_CONTROL") {
attributeState "model", label: '${currentValue}'
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:''
}
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setColor"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, icon: "st.illuminance.illuminance.light", decoration: "flat") {
state "level", label: '${currentValue}%'
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..9000)") {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 2, width: 4, inactiveLabel: false, range:"(2700..9000)") {
state "colorTemp", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", height: 2, width: 2) {
state "colorTemp", label: '${currentValue}K'
}
main(["switch"])
details(["switch", "refresh", "level", "levelSliderControl", "rgbSelector", "colorTempSliderControl", "colorTemp"])
main "switch"
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
@@ -70,7 +76,7 @@ def parse(String description) {
def setHue(percentage) {
log.debug "setHue ${percentage}"
parent.logErrors(logObject: log) {
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "hue:${percentage * 3.6}"])
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "hue:${percentage * 3.6}", power: "on"])
if (resp.status < 300) {
sendEvent(name: "hue", value: percentage)
sendEvent(name: "switch", value: "on")
@@ -83,7 +89,7 @@ def setHue(percentage) {
def setSaturation(percentage) {
log.debug "setSaturation ${percentage}"
parent.logErrors(logObject: log) {
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "saturation:${percentage / 100}"])
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "saturation:${percentage / 100}", power: "on"])
if (resp.status < 300) {
sendEvent(name: "saturation", value: percentage)
sendEvent(name: "switch", value: "on")
@@ -114,7 +120,7 @@ def setColor(Map color) {
}
}
parent.logErrors(logObject:log) {
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: attrs.join(" ")])
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
if (resp.status < 300) {
sendEvent(name: "color", value: color.hex)
sendEvent(name: "switch", value: "on")
@@ -135,9 +141,10 @@ def setLevel(percentage) {
return off() // if the brightness is set to 0, just turn it off
}
parent.logErrors(logObject:log) {
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", ["color": "brightness:${percentage / 100}"])
def resp = parent.apiPUT("/lights/${selector()}/state", ["brightness": percentage / 100, "power": "on"])
if (resp.status < 300) {
sendEvent(name: "level", value: percentage)
sendEvent(name: "switch.setLevel", value: percentage)
sendEvent(name: "switch", value: "on")
} else {
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
@@ -148,7 +155,7 @@ def setLevel(percentage) {
def setColorTemperature(kelvin) {
log.debug "Executing 'setColorTemperature' to ${kelvin}"
parent.logErrors() {
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "kelvin:${kelvin}"])
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "kelvin:${kelvin}", power: "on"])
if (resp.status < 300) {
sendEvent(name: "colorTemperature", value: kelvin)
sendEvent(name: "color", value: "#ffffff")
@@ -163,7 +170,7 @@ def setColorTemperature(kelvin) {
def on() {
log.debug "Device setOn"
parent.logErrors() {
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "on"]) != null) {
if (parent.apiPUT("/lights/${selector()}/state", [power: "on"]) != null) {
sendEvent(name: "switch", value: "on")
}
}
@@ -172,7 +179,7 @@ def on() {
def off() {
log.debug "Device setOff"
parent.logErrors() {
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "off"]) != null) {
if (parent.apiPUT("/lights/${selector()}/state", [power: "off"]) != null) {
sendEvent(name: "switch", value: "off")
}
}
@@ -180,19 +187,26 @@ def off() {
def poll() {
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
def resp = parent.apiGET("/lights/${device.deviceNetworkId}")
if (resp.status != 200) {
def resp = parent.apiGET("/lights/${selector()}")
if (resp.status == 404) {
sendEvent(name: "switch", value: "unreachable")
return []
} else if (resp.status != 200) {
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
return []
}
def data = resp.data
def data = resp.data[0]
log.debug("Data: ${data}")
sendEvent(name: "level", value: sprintf("%.1f", (data.brightness ?: 1) * 100))
sendEvent(name: "label", value: data.label)
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int))
sendEvent(name: "hue", value: data.color.hue / 3.6)
sendEvent(name: "hue", value: data.color.hue / 3.6)
sendEvent(name: "saturation", value: data.color.saturation * 100)
sendEvent(name: "colorTemperature", value: data.color.kelvin)
sendEvent(name: "model", value: "${data.product.company} ${data.product.name}")
return []
}
@@ -201,3 +215,11 @@ def refresh() {
log.debug "Executing 'refresh'"
poll()
}
def selector() {
if (device.deviceNetworkId.contains(":")) {
return device.deviceNetworkId
} else {
return "id:${device.deviceNetworkId}"
}
}

View File

@@ -16,41 +16,44 @@ metadata {
}
simulator {
// TODO: define status and reply messages here
}
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'Turning on', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'Turning off', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "unreachable", label: "?", action:"refresh.refresh", icon:"st.switches.light.off", backgroundColor:"#666666"
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:''
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, icon: "st.illuminance.illuminance.light", decoration: "flat") {
state "level", label: '${currentValue}%'
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 2, width: 4, inactiveLabel: false, range:"(2700..9000)") {
state "colorTemp", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", height: 2, width: 2) {
state "colorTemp", label: '${currentValue}K'
}
main(["switch"])
details(["switch", "refresh", "level", "levelSliderControl", "colorTempSliderControl", "colorTemp"])
main "switch"
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
// parse events into attributes
@@ -72,9 +75,10 @@ def setLevel(percentage) {
return off() // if the brightness is set to 0, just turn it off
}
parent.logErrors(logObject:log) {
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", ["color": "brightness:${percentage / 100}"])
def resp = parent.apiPUT("/lights/${selector()}/state", [brightness: percentage / 100, power: "on"])
if (resp.status < 300) {
sendEvent(name: "level", value: percentage)
sendEvent(name: "switch.setLevel", value: percentage)
sendEvent(name: "switch", value: "on")
} else {
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
@@ -85,7 +89,7 @@ def setLevel(percentage) {
def setColorTemperature(kelvin) {
log.debug "Executing 'setColorTemperature' to ${kelvin}"
parent.logErrors() {
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "kelvin:${kelvin}"])
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "kelvin:${kelvin}", power: "on"])
if (resp.status < 300) {
sendEvent(name: "colorTemperature", value: kelvin)
sendEvent(name: "color", value: "#ffffff")
@@ -100,7 +104,7 @@ def setColorTemperature(kelvin) {
def on() {
log.debug "Device setOn"
parent.logErrors() {
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "on"]) != null) {
if (parent.apiPUT("/lights/${selector()}/state", [power: "on"]) != null) {
sendEvent(name: "switch", value: "on")
}
}
@@ -109,7 +113,7 @@ def on() {
def off() {
log.debug "Device setOff"
parent.logErrors() {
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "off"]) != null) {
if (parent.apiPUT("/lights/${selector()}/state", [power: "off"]) != null) {
sendEvent(name: "switch", value: "off")
}
}
@@ -117,16 +121,22 @@ def off() {
def poll() {
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
def resp = parent.apiGET("/lights/${device.deviceNetworkId}")
if (resp.status != 200) {
def resp = parent.apiGET("/lights/${selector()}")
if (resp.status == 404) {
sendEvent(name: "switch", value: "unreachable")
return []
} else if (resp.status != 200) {
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
return []
}
def data = resp.data
def data = resp.data[0]
sendEvent(name: "level", value: sprintf("%f", (data.brightness ?: 1) * 100))
sendEvent(name: "label", value: data.label)
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
sendEvent(name: "colorTemperature", value: data.color.kelvin)
sendEvent(name: "model", value: data.product.name)
return []
}
@@ -135,3 +145,11 @@ def refresh() {
log.debug "Executing 'refresh'"
poll()
}
def selector() {
if (device.deviceNetworkId.contains(":")) {
return device.deviceNetworkId
} else {
return "id:${device.deviceNetworkId}"
}
}

View File

@@ -79,8 +79,8 @@ metadata {
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}

View File

@@ -88,8 +88,8 @@ metadata {
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}

View File

@@ -43,8 +43,8 @@ metadata {
])
}
section {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}

View File

@@ -45,8 +45,8 @@ metadata {
])
}
section {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}

View File

@@ -37,8 +37,8 @@ metadata {
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -61,8 +61,8 @@
])
}
section {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
section {
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)

View File

@@ -43,8 +43,8 @@ metadata {
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -32,8 +32,8 @@
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -34,8 +34,8 @@ metadata {
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -33,8 +33,8 @@ metadata {
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -45,8 +45,8 @@ metadata {
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles {

View File

@@ -33,8 +33,8 @@ metadata {
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles {

View File

@@ -25,8 +25,8 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-CIA19NAE26", deviceJoinName: "Sengled Element touch"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45852", deviceJoinName: "GE Zigbee Plug-In Dimmer"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45857", deviceJoinName: "GE Zigbee In-Wall Dimmer"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45852", deviceJoinName: "GE Zigbee Plug-In Dimmer"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45857", deviceJoinName: "GE Zigbee In-Wall Dimmer"
}
tiles(scale: 2) {

View File

@@ -17,15 +17,14 @@ metadata {
capability "Actuator"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
capability "Switch Level"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
}
tiles(scale: 2) {

View File

@@ -23,8 +23,8 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "0003, 000A,0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "0003, 000A, 0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
}
tiles(scale: 2) {

View File

@@ -17,7 +17,6 @@ metadata {
capability "Actuator"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"

View File

@@ -23,18 +23,18 @@ metadata {
capability "Color Temperature"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
capability "Switch Level"
attribute "colorName", "string"
command "setGenericName"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
}
// UI tile definitions

View File

@@ -66,9 +66,20 @@ metadata {
import physicalgraph.zwave.commands.doorlockv1.*
import physicalgraph.zwave.commands.usercodev1.*
def updated() {
try {
if (!state.init) {
state.init = true
response(secureSequence([zwave.doorLockV1.doorLockOperationGet(), zwave.batteryV1.batteryGet()]))
}
} catch (e) {
log.warn "updated() threw $e"
}
}
def parse(String description) {
def result = null
if (description.startsWith("Err")) {
if (description.startsWith("Err 106")) {
if (state.sec) {
result = createEvent(descriptionText:description, displayed:false)
} else {
@@ -80,6 +91,8 @@ def parse(String description) {
displayed: true,
)
}
} else if (description == "updated") {
return null
} else {
def cmd = zwave.parse(description, [ 0x98: 1, 0x72: 2, 0x85: 2, 0x86: 1 ])
if (cmd) {
@@ -286,7 +299,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
}
break
case 167:
if (!state.lastbatt || (new Date().time) - state.lastbatt > 12*60*60*1000) {
if (!state.lastbatt || now() - state.lastbatt > 12*60*60*1000) {
map = [ descriptionText: "$device.displayName: battery low", isStateChange: true ]
result << response(secure(zwave.batteryV1.batteryGet()))
} else {
@@ -431,7 +444,7 @@ def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
} else {
map.value = cmd.batteryLevel
}
state.lastbatt = new Date().time
state.lastbatt = now()
createEvent(map)
}
@@ -499,15 +512,14 @@ def refresh() {
cmds << "delay 4200"
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format() // old Schlage locks use group 2 and don't secure the Association CC
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
state.associationQuery = new Date().time
} else if (new Date().time - state.associationQuery.toLong() > 9000) {
log.debug "setting association"
state.associationQuery = now()
} else if (secondsPast(state.associationQuery, 9)) {
cmds << "delay 6000"
cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format()
cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format()
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
state.associationQuery = new Date().time
state.associationQuery = now()
}
log.debug "refresh sending ${cmds.inspect()}"
cmds
@@ -515,55 +527,22 @@ def refresh() {
def poll() {
def cmds = []
if (state.assoc != zwaveHubNodeId && secondsPast(state.associationQuery, 19 * 60)) {
log.debug "setting association"
cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format()
cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format()
cmds << "delay 6000"
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
cmds << "delay 6000"
state.associationQuery = new Date().time
} else {
// Only check lock state if it changed recently or we haven't had an update in an hour
def latest = device.currentState("lock")?.date?.time
if (!latest || !secondsPast(latest, 6 * 60) || secondsPast(state.lastPoll, 55 * 60)) {
cmds << secure(zwave.doorLockV1.doorLockOperationGet())
state.lastPoll = (new Date()).time
} else if (!state.MSR) {
cmds << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
} else if (!state.fw) {
cmds << zwave.versionV1.versionGet().format()
} else if (!state.codes) {
state.pollCode = 1
cmds << secure(zwave.userCodeV1.usersNumberGet())
} else if (state.pollCode && state.pollCode <= state.codes) {
cmds << requestCode(state.pollCode)
} else if (!state.lastbatt || (new Date().time) - state.lastbatt > 53*60*60*1000) {
cmds << secure(zwave.batteryV1.batteryGet())
} else if (!state.enc) {
encryptCodes()
state.enc = 1
}
// Only check lock state if it changed recently or we haven't had an update in an hour
def latest = device.currentState("lock")?.date?.time
if (!latest || !secondsPast(latest, 6 * 60) || secondsPast(state.lastPoll, 55 * 60)) {
cmds << secure(zwave.doorLockV1.doorLockOperationGet())
state.lastPoll = now()
} else if (!state.lastbatt || now() - state.lastbatt > 53*60*60*1000) {
cmds << secure(zwave.batteryV1.batteryGet())
state.lastbatt = now() //inside-214
}
log.debug "poll is sending ${cmds.inspect()}"
device.activity()
cmds ?: null
}
private def encryptCodes() {
def keys = new ArrayList(state.keySet().findAll { it.startsWith("code") })
keys.each { key ->
def match = (key =~ /^code(\d+)$/)
if (match) try {
def keynum = match[0][1].toInteger()
if (keynum > 30 && !state[key]) {
state.remove(key)
} else if (state[key] && !state[key].startsWith("~")) {
log.debug "encrypting $key: ${state[key].inspect()}"
state[key] = encrypt(state[key])
}
} catch (java.lang.NumberFormatException e) { }
if (cmds) {
log.debug "poll is sending ${cmds.inspect()}"
cmds
} else {
// workaround to keep polling from stopping due to lack of activity
sendEvent(descriptionText: "skipping poll", isStateChange: true, displayed: false)
null
}
}
@@ -672,7 +651,7 @@ private Boolean secondsPast(timestamp, seconds) {
return true
}
}
return (new Date().time - timestamp) > (seconds * 1000)
return (now() - timestamp) > (seconds * 1000)
}
private allCodesDeleted() {

View File

@@ -0,0 +1,105 @@
/**
* Light Up The Night
*
* Copyright 2015 Brian Warner
*
* 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.
*
*/
definition(
name: "Light Up The Night",
namespace: "brianwarner",
author: "Brian Warner",
description: "Turn on certain lights when a door opens at night, and turn them off a certain time after the last door closes. Great for garage doors and driveway lights.",
category: "Convenience",
iconUrl: "http://cdn.device-icons.smartthings.com/Lighting/light9-icn.png",
iconX2Url: "http://cdn.device-icons.smartthings.com/Lighting/light9-icn@2x.png",
iconX3Url: "http://cdn.device-icons.smartthings.com/Lighting/light9-icn@3x.png")
preferences {
section() {
input "contactsensors", "capability.contactSensor", multiple: true, title: "When these doors open:"
}
section() {
input "lights", "capability.switch", required: true, multiple: true, title: "Turn on these lights:"
}
section() {
input "timer", "number", required: true, title: "Keep them on until all doors are closed for this many minutes:", range: "0..240"
}
section() {
input "sunriseoffset", "number", required: true, title: "Start turning on the lights this many minutes before sunset:", range: "0..360"
}
section() {
input "sunsetoffset", "number", required: true, title: "Stop turning on the lights this many minutes after sunrise:", range: "0..360"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
subscribe(garagedoors, "door.open", doorOpenHandler)
subscribe(garagedoors, "door.closed", doorClosedHandler)
subscribe(contactsensors, "contact.open", doorOpenHandler)
subscribe(contactsensors, "contact.closed", doorClosedHandler)
}
def doorOpenHandler(evt) {
// log.debug "Door opened."
def now = new Date()
def sunTime = getSunriseAndSunset(sunriseOffset: "00:$sunriseoffset", sunsetOffset: "-00:$sunsetoffset")
if (now > sunTime.sunset || now < sunTime.sunrise) {
// log.debug "Turning lights on."
lights.on()
}
}
def doorClosedHandler(evt) {
// log.debug "A door closed."
runIn(60 * timer, checkClosed)
}
def checkClosed() {
// log.debug "Checking to ensure all doors are still closed."
def contactSensorState = contactsensors.currentState("contact")
def anyContactSensorsOpen = contactSensorState.value.findAll {it == "open"}
if (!anyContactSensorsOpen) {
def elapsed = now() - contactSensorState.date.time.max()
def timeout = 1000 * 60 * timer
if (elapsed >= timeout) {
// log.debug "Doors have stayed closed. Turning off the lights."
lights.off()
} else {
// log.debug "Doors were opened. Wait a little longer."
}
} else {
// log.debug "It appears a door is still open."
}
}

View File

@@ -246,6 +246,9 @@ def toggle(devices) {
else if (devices*.currentValue('lock').contains('locked')) {
devices.unlock()
}
else if (devices*.currentValue('lock').contains('unlocked')) {
devices.lock()
}
else if (devices*.currentValue('alarm').contains('off')) {
devices.siren()
}

View File

@@ -592,7 +592,7 @@ def updated() {
// log.debug "External Id=${app.id}:${member.id}"
// create the device
def childDevice = addChildDevice("smartthings", "life360-user", "${app.id}.${member.id}",null,[name:member.firstName, completedSetup: true])
def childDevice = addChildDevice("smartthings", "Life360 User", "${app.id}.${member.id}",null,[name:member.firstName, completedSetup: true])
// childDevice.setMemberId(member.id)
if (childDevice)

View File

@@ -5,23 +5,23 @@
*
*/
definition(
name: "LIFX (Connect)",
namespace: "smartthings",
author: "LIFX",
description: "Allows you to use LIFX smart light bulbs with SmartThings.",
category: "Convenience",
iconUrl: "https://cloud.lifx.com/images/lifx.png",
iconX2Url: "https://cloud.lifx.com/images/lifx.png",
iconX3Url: "https://cloud.lifx.com/images/lifx.png",
oauth: true,
singleInstance: true) {
appSetting "clientId"
appSetting "clientSecret"
}
name: "LIFX (Connect)",
namespace: "smartthings",
author: "LIFX",
description: "Allows you to use LIFX smart light bulbs with SmartThings.",
category: "Convenience",
iconUrl: "https://cloud.lifx.com/images/lifx.png",
iconX2Url: "https://cloud.lifx.com/images/lifx.png",
iconX3Url: "https://cloud.lifx.com/images/lifx.png",
oauth: true,
singleInstance: true) {
appSetting "clientId"
appSetting "clientSecret"
}
preferences {
page(name: "Credentials", title: "LIFX", content: "authPage", install: false)
page(name: "Credentials", title: "LIFX", content: "authPage", install: true)
}
mappings {
@@ -33,29 +33,29 @@ mappings {
path("/test") { action: [ GET: "oauthSuccess" ] }
}
def getServerUrl() { return "https://graph.api.smartthings.com" }
def apiURL(path = '/') { return "https://api.lifx.com/v1beta1${path}" }
def buildRedirectUrl(page) {
return "${serverUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/${page}"
}
def getServerUrl() { return "https://graph.api.smartthings.com" }
def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback"}
def apiURL(path = '/') { return "https://api.lifx.com/v1${path}" }
def getSecretKey() { return appSettings.secretKey }
def getClientId() { return appSettings.clientId }
def authPage() {
log.debug "authPage"
log.debug "authPage test1"
if (!state.lifxAccessToken) {
log.debug "no LIFX access token"
// This is the SmartThings access token
if (!state.accessToken) {
log.debug "no access token, create access token"
createAccessToken() // predefined method
state.accessToken = createAccessToken() // predefined method
}
def description = "Tap to enter LIFX credentials"
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}" // this triggers oauthInit() below
log.debug "app id: ${app.id}"
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
// def redirectUrl = "${apiServerUrl}"
log.debug "app id: ${app.id}"
log.debug "redirect url: ${redirectUrl}"
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:false) {
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
section {
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
// href(url:buildRedirectUrl("test"), title: "Message test")
}
}
} else {
@@ -63,17 +63,15 @@ def authPage() {
def options = locationOptions() ?: []
def count = options.size()
def refreshInterval = 3
return dynamicPage(name:"Credentials", title:"Select devices...", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) {
section("Select your location") {
input "selectedLocationId", "enum", required:true, title:"Select location (${count} found)", multiple:false, options:options
input "selectedLocationId", "enum", required:true, title:"Select location (${count} found)", multiple:false, options:options, submitOnChange: true
}
}
}
}
// OAuth
def oauthInit() {
@@ -112,7 +110,7 @@ def oauthCallback() {
}
def oauthReceiveToken(redirectUrl = null) {
// Not sure what redirectUrl is for
log.debug "receiveToken - params: ${params}"
def oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code, scope: params.scope ] // how is params.code valid here?
def params = [
@@ -135,25 +133,25 @@ def oauthReceiveToken(redirectUrl = null) {
def oauthSuccess() {
def message = """
<p>Your LIFX Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
<p>Your LIFX Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
oauthConnectionStatus(message)
}
def oauthFailure() {
def message = """
<p>The connection could not be established!</p>
<p>Click 'Done' to return to the menu.</p>
"""
<p>The connection could not be established!</p>
<p>Click 'Done' to return to the menu.</p>
"""
oauthConnectionStatus(message)
}
def oauthReceivedToken() {
def message = """
<p>Your LIFX Account is already connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
<p>Your LIFX Account is already connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
oauthConnectionStatus(message)
}
@@ -161,74 +159,74 @@ def oauthConnectionStatus(message, redirectUrl = null) {
def redirectHtml = ""
if (redirectUrl) {
redirectHtml = """
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
}
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<title>SmartThings Connection</title>
<style type="text/css">
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 280;
padding: 20px;
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 15px;
}
p {
font-size: 1.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
padding: 0 20px;
margin-bottom: 0;
}
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
${redirectHtml}
</head>
<body>
<div class="container">
<img src='https://cloud.lifx.com/images/lifx.png' alt='LIFX icon' width='100'/>
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png' alt='connected device icon' width="40"/>
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png' alt='SmartThings logo' width="100"/>
<p>
${message}
</p>
</div>
</body>
</html>
"""
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<title>SmartThings Connection</title>
<style type="text/css">
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 280;
padding: 20px;
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 15px;
}
p {
font-size: 1.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
padding: 0 20px;
margin-bottom: 0;
}
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
${redirectHtml}
</head>
<body>
<div class="container">
<img src='https://cloud.lifx.com/images/lifx.png' alt='LIFX icon' width='100'/>
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png' alt='connected device icon' width="40"/>
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png' alt='SmartThings logo' width="100"/>
<p>
${message}
</p>
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
@@ -239,7 +237,6 @@ String toQueryString(Map m) {
// App lifecycle hooks
def installed() {
enableCallback() // wtf does this do?
if (!state.accessToken) {
createAccessToken()
} else {
@@ -251,7 +248,6 @@ def installed() {
// called after settings are changed
def updated() {
enableCallback() // not sure what this does
if (!state.accessToken) {
createAccessToken()
} else {
@@ -305,27 +301,36 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) {
state.remove("lifxAccessToken")
options.logObject.warn "Access token is not valid"
}
return options.errerReturn
return options.errorReturn
} catch (java.net.SocketTimeoutException e) {
options.logObject.warn "Connection timed out, not much we can do here"
return options.errerReturn
return options.errorReturn
}
}
def apiGET(path) {
httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response ->
logResponse(response)
return response
try {
httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response ->
logResponse(response)
return response
}
} catch (groovyx.net.http.HttpResponseException e) {
logResponse(e.response)
return e.response
}
}
def apiPUT(path, body = [:]) {
log.debug("Beginning API PUT: ${path}, ${body}")
httpPutJson(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders(), ) {response ->
logResponse(response)
return response
}
}
try {
log.debug("Beginning API PUT: ${path}, ${body}")
httpPutJson(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders(), ) {response ->
logResponse(response)
return response
}
} catch (groovyx.net.http.HttpResponseException e) {
logResponse(e.response)
return e.response
}}
def devicesList(selector = '') {
logErrors([]) {
@@ -340,12 +345,12 @@ def devicesList(selector = '') {
}
Map locationOptions() {
def options = [:]
def devices = devicesList()
devices.each { device ->
options[device.location.id] = device.location.name
}
log.debug("Locations: ${options}")
return options
}
@@ -359,28 +364,32 @@ def updateDevices() {
state.devices = [:]
}
def devices = devicesInLocation()
def deviceIds = devices*.id
def selectors = []
log.debug("All selectors: ${selectors}")
devices.each { device ->
def childDevice = getChildDevice(device.id)
selectors.add("${device.id}")
if (!childDevice) {
log.info("Adding device ${device.id}: ${device.capabilities}")
log.info("Adding device ${device.id}: ${device.product}")
def data = [
label: device.label,
level: sprintf("%f", (device.brightness ?: 1) * 100),
level: Math.round((device.brightness ?: 1) * 100),
switch: device.connected ? device.power : "unreachable",
colorTemperature: device.color.kelvin
]
if (device.capabilities.has_color) {
if (device.product.capabilities.has_color) {
data["color"] = colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)
data["hue"] = device.color.hue / 3.6
data["saturation"] = device.color.saturation * 100
childDevice = addChildDevice("smartthings", "LIFX Color Bulb", device.id, null, data)
childDevice = addChildDevice(app.namespace, "LIFX Color Bulb", device.id, null, data)
} else {
childDevice = addChildDevice("smartthings", "LIFX White Bulb", device.id, null, data)
childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data)
}
}
}
getChildDevices().findAll { !deviceIds.contains(it.deviceNetworkId) }.each {
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
log.info("Deleting ${it.deviceNetworkId}")
deleteChildDevice(it.deviceNetworkId)
}
@@ -392,4 +401,4 @@ def refreshDevices() {
getChildDevices().each { device ->
device.refresh()
}
}
}

View File

@@ -10,24 +10,24 @@
* 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.
*
* Sonos Control
* Speaker Control
*
* Author: SmartThings
*
* Date: 2013-12-10
*/
definition(
name: "Sonos Control",
name: "Speaker Control",
namespace: "smartthings",
author: "SmartThings",
description: "Play or pause your Sonos when certain actions take place in your home.",
description: "Play or pause your Speaker when certain actions take place in your home.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
)
preferences {
page(name: "mainPage", title: "Control your Sonos when something happens", install: true, uninstall: true)
page(name: "mainPage", title: "Control your Speaker when something happens", install: true, uninstall: true)
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
input "starting", "time", title: "Starting", required: false
@@ -81,7 +81,7 @@ def mainPage() {
]
}
section {
input "sonos", "capability.musicPlayer", title: "Sonos music player", required: true
input "sonos", "capability.musicPlayer", title: "Speaker music player", required: true
}
section("More options", hideable: true, hidden: true) {
input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false

View File

@@ -10,7 +10,7 @@
* 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.
*
* Sonos Mood Music
* Speaker Mood Music
*
* Author: SmartThings
* Date: 2014-02-12
@@ -65,7 +65,7 @@ private saveSelectedSong() {
}
definition(
name: "Sonos Mood Music",
name: "Speaker Mood Music",
namespace: "smartthings",
author: "SmartThings",
description: "Plays a selected song or station.",
@@ -75,7 +75,7 @@ definition(
)
preferences {
page(name: "mainPage", title: "Play a selected song or station on your Sonos when something happens", nextPage: "chooseTrack", uninstall: true)
page(name: "mainPage", title: "Play a selected song or station on your Speaker when something happens", nextPage: "chooseTrack", uninstall: true)
page(name: "chooseTrack", title: "Select a song", install: true)
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
@@ -125,7 +125,7 @@ def mainPage() {
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
section {
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
}
section("More options", hideable: true, hidden: true) {
input "volume", "number", title: "Set the volume", description: "0-100%", required: false

View File

@@ -10,23 +10,23 @@
* 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.
*
* Sonos Custom Message
* Speaker Custom Message
*
* Author: SmartThings
* Date: 2014-1-29
*/
definition(
name: "Sonos Notify with Sound",
name: "Speaker Notify with Sound",
namespace: "smartthings",
author: "SmartThings",
description: "Play a sound or custom message through your Sonos when the mode changes or other events occur.",
description: "Play a sound or custom message through your Speaker when the mode changes or other events occur.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
)
preferences {
page(name: "mainPage", title: "Play a message on your Sonos when something happens", install: true, uninstall: true)
page(name: "mainPage", title: "Play a message on your Speaker when something happens", install: true, uninstall: true)
page(name: "chooseTrack", title: "Select a song or station")
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
@@ -92,7 +92,7 @@ def mainPage() {
input "message","text",title:"Play this message", required:false, multiple: false
}
section {
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
}
section("More options", hideable: true, hidden: true) {
input "resumePlaying", "bool", title: "Resume currently playing music after notification", required: false, defaultValue: true

View File

@@ -10,23 +10,23 @@
* 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.
*
* Sonos Weather Forecast
* Speaker Weather Forecast
*
* Author: SmartThings
* Date: 2014-1-29
*/
definition(
name: "Sonos Weather Forecast",
name: "Speaker Weather Forecast",
namespace: "smartthings",
author: "SmartThings",
description: "Play a weather report through your Sonos when the mode changes or other events occur",
description: "Play a weather report through your Speaker when the mode changes or other events occur",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
)
preferences {
page(name: "mainPage", title: "Play the weather report on your sonos", install: true, uninstall: true)
page(name: "mainPage", title: "Play the weather report on your speaker", install: true, uninstall: true)
page(name: "chooseTrack", title: "Select a song or station")
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
@@ -85,7 +85,7 @@ def mainPage() {
)
}
section {
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
}
section("More options", hideable: true, hidden: true) {
input "resumePlaying", "bool", title: "Resume currently playing music after weather report finishes", required: false, defaultValue: true

View File

@@ -78,7 +78,7 @@ def firstPage()
def motionsDiscovered = motionsDiscovered()
def lightSwitchesDiscovered = lightSwitchesDiscovered()
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: selectedSwitches != null || selectedMotions != null || selectedLightSwitches != null) {
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
section("Select a device...") {
input "selectedSwitches", "enum", required:false, title:"Select Wemo Switches \n(${switchesDiscovered.size() ?: 0} found)", multiple:true, options:switchesDiscovered
input "selectedMotions", "enum", required:false, title:"Select Wemo Motions \n(${motionsDiscovered.size() ?: 0} found)", multiple:true, options:motionsDiscovered