Compare commits

..

1 Commits

Author SHA1 Message Date
Anders Heie
625940423b MSA-689: Fixed a presence problem. 2015-11-16 17:32:48 -06:00
37 changed files with 1625 additions and 1508 deletions

View File

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

View File

@@ -1,71 +0,0 @@
/**
* Ecobee Sensor
*
* Copyright 2015 Juan Risso
*
* 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: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Sensor"
capability "Temperature Measurement"
capability "Motion Sensor"
capability "Refresh"
capability "Polling"
}
simulator {
// TODO: define status and reply messages here
}
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
standardTile("motion", "device.motion") {
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main (["temperature","motion"])
details(["temperature","motion","refresh"])
}
}
def refresh() {
log.debug "refresh..."
poll()
}
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
parent.pollChildren(this)
}
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
}

View File

@@ -22,31 +22,31 @@ metadata {
capability "Polling" capability "Polling"
capability "Sensor" capability "Sensor"
capability "Refresh" capability "Refresh"
command "generateEvent" command "generateEvent"
command "raiseSetpoint" command "raiseSetpoint"
command "lowerSetpoint" command "lowerSetpoint"
command "resumeProgram" command "resumeProgram"
command "switchMode" command "switchMode"
attribute "thermostatSetpoint","number" attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string" attribute "thermostatStatus","string"
} }
simulator { } simulator { }
tiles { tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) { valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F", state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[ backgroundColors:[
[value: 31, color: "#153591"], [value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"], [value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"], [value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"], [value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"], [value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"], [value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"] [value: 96, color: "#bc2323"]
] ]
) )
} }
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
@@ -54,27 +54,27 @@ metadata {
state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat" state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat"
state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool" state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool"
state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto" state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto"
state "auxHeatOnly", action:"switchMode", icon: "st.thermostat.emergency-heat" state "auxHeatOnly", action:"switchMode", icon: "st.thermostat.emergency-heat"
state "updating", label:"Working", icon: "st.secondary.secondary" state "updating", label:"Working", icon: "st.secondary.secondary"
} }
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on" state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on"
state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off" state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off"
state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate" state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate"
state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto" state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto"
} }
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") { standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up" state "setpoint", action:"raiseSetpoint", backgroundColor:"#d04e00", icon:"st.thermostat.thermostat-up"
} }
valueTile("thermostatSetpoint", "device.thermostatSetpoint", width: 1, height: 1, decoration: "flat") { 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") { valueTile("currentStatus", "device.thermostatStatus", height: 1, width: 2, decoration: "flat") {
state "thermostatStatus", label:'${currentValue}', backgroundColor:"#ffffff" state "thermostatStatus", label:'${currentValue}', backgroundColor:"#ffffff"
} }
standardTile("downButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") { standardTile("downButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
state "setpoint", action:"lowerSetpoint", icon:"st.thermostat.thermostat-down" state "setpoint", action:"lowerSetpoint", backgroundColor:"#d04e00", icon:"st.thermostat.thermostat-down"
} }
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) { controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00" state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00"
} }
@@ -91,196 +91,218 @@ metadata {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") { standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") {
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send" state "resume", label:'Resume Program', action:"device.resumeProgram", icon:"st.sonos.play-icon"
state "updating", label:"Working", icon: "st.secondary.secondary"
} }
main "temperature" main "temperature"
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"]) details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"])
}
preferences {
input "holdType", "enum", title: "Hold Type", description: "When changing temperature, use Temporary or Permanent hold (default)", required: false, options:["Temporary", "Permanent"]
} }
} }
/*
preferences {
input "highTemperature", "number", title: "Auto Mode High Temperature:", defaultValue: 80
input "lowTemperature", "number", title: "Auto Mode Low Temperature:", defaultValue: 70
input name: "holdType", type: "enum", title: "Hold Type", description: "When changing temperature, use Temporary or Permanent hold", required: true, options:["Temporary", "Permanent"]
}
*/
// parse events into attributes // parse events into attributes
def parse(String description) { def parse(String description) {
log.debug "Parsing '${description}'" log.debug "Parsing '${description}'"
// TODO: handle '' attribute // TODO: handle '' attribute
} }
def refresh() { def refresh()
log.debug "refresh called" {
poll() log.debug "refresh called"
log.debug "refresh ended" poll()
log.debug "refresh ended"
} }
def go()
{
log.debug "before:go tile tapped"
poll()
log.debug "after"
}
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
def results = parent.pollChild(this) def results = parent.pollChild(this)
generateEvent(results) //parse received message from parent parseEventData(results)
generateStatusEvent()
} }
def generateEvent(Map results) { def parseEventData(Map results)
{
log.debug "parsing data $results" log.debug "parsing data $results"
if(results) { if(results)
results.each { name, value -> {
results.each { name, value ->
def linkText = getLinkText(device) def linkText = getLinkText(device)
def isChange = false def isChange = false
def isDisplayed = true def isDisplayed = true
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name] if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //API return temperature value in F
isChange = isTemperatureStateChange(device, name, value.toString()) isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange isDisplayed = isChange
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ sendEvent(
isChange = isStateChange(device, name, value.toString()) name: name,
event << [value: value.toString(), isStateChange: isChange, displayed: false] value: value,
} else { unit: "F",
isChange = isStateChange(device, name, value.toString()) linkText: linkText,
isDisplayed = isChange descriptionText: getThermostatDescriptionText(name, value, linkText),
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed] handlerName: name,
} isStateChange: isChange,
sendEvent(event) displayed: isDisplayed)
}
else {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
sendEvent(
name: name,
value: value.toString(),
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
}
generateSetpointEvent ()
generateStatusEvent ()
}
}
void generateEvent(Map results)
{
log.debug "parsing data $results"
if(results)
{
results.each { name, value ->
def linkText = getLinkText(device)
def isChange = false
def isDisplayed = true
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
sendEvent(
name: name,
value: value,
unit: "F",
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
else {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
sendEvent(
name: name,
value: value.toString(),
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
} }
generateSetpointEvent () generateSetpointEvent ()
generateStatusEvent () generateStatusEvent()
} }
} }
//return descriptionText to be shown on mobile activity feed private getThermostatDescriptionText(name, value, linkText)
private getThermostatDescriptionText(name, value, linkText) { {
if(name == "temperature") { if(name == "temperature")
return "$linkText temperature is $value°F" {
return "$linkText was $value°F"
} else if(name == "heatingSetpoint") {
return "heating setpoint is $value°F"
} else if(name == "coolingSetpoint"){
return "cooling setpoint is $value°F"
} else if (name == "thermostatMode") {
return "thermostat mode is ${value}"
} else if (name == "thermostatFanMode") {
return "thermostat fan mode is ${value}"
} else {
return "${name} = ${value}"
} }
else if(name == "heatingSetpoint")
{
return "latest heating setpoint was $value°F"
}
else if(name == "coolingSetpoint")
{
return "latest cooling setpoint was $value°F"
}
else if (name == "thermostatMode")
{
return "thermostat mode is ${value}"
}
else
{
return "${name} = ${value}"
}
} }
void setHeatingSetpoint(setpoint) {
setHeatingSetpoint(setpoint.toDouble()) void setHeatingSetpoint(degreesF) {
setHeatingSetpoint(degreesF.toDouble())
} }
void setHeatingSetpoint(Double setpoint) { void setHeatingSetpoint(Double degreesF) {
// def mode = device.currentValue("thermostatMode") log.debug "setHeatingSetpoint({$degreesF})"
def heatingSetpoint = setpoint sendEvent("name":"heatingSetpoint", "value":degreesF)
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble() Double coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last() log.debug "coolingSetpoint: $coolingSetpoint"
parent.setHold(this, degreesF, coolingSetpoint)
//enforce limits of heatingSetpoint
if (heatingSetpoint > 79) {
heatingSetpoint = 79
} else if (heatingSetpoint < 45) {
heatingSetpoint = 45
}
//enforce limits of heatingSetpoint vs coolingSetpoint
if (heatingSetpoint >= coolingSetpoint) {
coolingSetpoint = heatingSetpoint
}
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
} else {
log.error "Error setHeatingSetpoint(setpoint)" //This error is handled by the connect app
}
} }
void setCoolingSetpoint(setpoint) { void setCoolingSetpoint(degreesF) {
setCoolingSetpoint(setpoint.toDouble()) setCoolingSetpoint(degreesF.toDouble())
} }
void setCoolingSetpoint(Double setpoint) { void setCoolingSetpoint(Double degreesF) {
// def mode = device.currentValue("thermostatMode") log.debug "setCoolingSetpoint({$degreesF})"
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble() sendEvent("name":"coolingSetpoint", "value":degreesF)
def coolingSetpoint = setpoint Double heatingSetpoint = device.currentValue("heatingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last() parent.setHold(this, heatingSetpoint, degreesF)
if (coolingSetpoint > 92) {
coolingSetpoint = 92
} else if (coolingSetpoint < 65) {
coolingSetpoint = 65
}
//enforce limits of heatingSetpoint vs coolingSetpoint
if (heatingSetpoint >= coolingSetpoint) {
heatingSetpoint = coolingSetpoint
}
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
} else {
log.error "Error setCoolingSetpoint(setpoint)" //This error is handled by the connect app
}
} }
void resumeProgram() { def configure() {
log.debug "resumeProgram() is called" }
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.resumeProgram(this, deviceId)) {
sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false)
runIn(5, "poll")
log.debug "resumeProgram() is done"
sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
} else {
sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false)
log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)"
}
def resumeProgram() {
parent.resumeProgram(this)
} }
def modes() { def modes() {
if (state.modes) { if (state.modes) {
log.debug "Modes = ${state.modes}" log.debug "Modes = ${state.modes}"
return state.modes return state.modes
} }
else { else {
state.modes = parent.availableModes(this) state.modes = parent.availableModes(this)
log.debug "Modes = ${state.modes}" log.debug "Modes = ${state.modes}"
return state.modes return state.modes
} }
} }
def fanModes() { def fanModes() {
["off", "on", "auto", "circulate"] ["off", "on", "auto", "circulate"]
} }
def switchMode() { def switchMode() {
log.debug "in switchMode" log.debug "in switchMode"
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
@@ -292,7 +314,7 @@ def switchMode() {
} }
def switchToMode(nextMode) { def switchToMode(nextMode) {
log.debug "In switchToMode = ${nextMode}" log.debug "In switchToMode = ${nextMode}"
if (nextMode in modes()) { if (nextMode in modes()) {
state.lastTriedMode = nextMode state.lastTriedMode = nextMode
"$nextMode"() "$nextMode"()
@@ -354,326 +376,300 @@ def getDataByName(String name) {
def setThermostatMode(String value) { def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})" log.debug "setThermostatMode({$value})"
} }
def setThermostatFanMode(String value) { def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})" log.debug "setThermostatFanMode({$value})"
} }
def generateModeEvent(mode) { def generateModeEvent(mode) {
sendEvent(name: "thermostatMode", value: mode, descriptionText: "$device.displayName is in ${mode} mode", displayed: true)
sendEvent(name: "thermostatMode", value: mode, descriptionText: "$device.displayName is in ${mode} mode", displayed: true, isStateChange: true)
} }
def generateFanModeEvent(fanMode) { def generateFanModeEvent(fanMode) {
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true)
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true, isStateChange: true)
} }
def generateOperatingStateEvent(operatingState) { def generateOperatingStateEvent(operatingState) {
sendEvent(name: "thermostatOperatingState", value: operatingState, descriptionText: "$device.displayName is ${operatingState}", displayed: true)
sendEvent(name: "thermostatOperatingState", value: operatingState, descriptionText: "$device.displayName is ${operatingState}", displayed: true, isStateChange: true)
} }
def off() { def off() {
log.debug "off" log.debug "off"
def deviceId = device.deviceNetworkId.split(/\./).last() generateModeEvent("off")
if (parent.setMode (this,"off", deviceId)) if (parent.setMode (this,"off"))
generateModeEvent("off") generateModeEvent("off")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back generateModeEvent(currentMode) // reset the tile back
} }
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
} }
def heat() { def heat() {
log.debug "heat" log.debug "heat"
def deviceId = device.deviceNetworkId.split(/\./).last() generateModeEvent("heat")
if (parent.setMode (this,"heat", deviceId)) if (parent.setMode (this,"heat"))
generateModeEvent("heat") generateModeEvent("heat")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back generateModeEvent(currentMode) // reset the tile back
} }
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
} }
def auxHeatOnly() { def auxHeatOnly() {
log.debug "auxHeatOnly" log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last() generateModeEvent("auxHeatOnly")
if (parent.setMode (this,"auxHeatOnly", deviceId)) if (parent.setMode (this,"auxHeatOnly"))
generateModeEvent("auxHeatOnly") generateModeEvent("auxHeatOnly")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back generateModeEvent(currentMode) // reset the tile back
} }
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
} }
def cool() { def cool() {
log.debug "cool" log.debug "cool"
def deviceId = device.deviceNetworkId.split(/\./).last() generateModeEvent("cool")
if (parent.setMode (this,"cool", deviceId)) if (parent.setMode (this,"cool"))
generateModeEvent("cool") generateModeEvent("cool")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back generateModeEvent(currentMode) // reset the tile back
} }
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
} }
def auto() { def auto() {
log.debug "auto" log.debug "auto"
def deviceId = device.deviceNetworkId.split(/\./).last() generateModeEvent("auto")
if (parent.setMode (this,"auto", deviceId)) if (parent.setMode (this,"auto"))
generateModeEvent("auto") generateModeEvent("auto")
else { else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back generateModeEvent(currentMode) // reset the tile back
} }
generateSetpointEvent() generateSetpointEvent()
generateStatusEvent() generateStatusEvent()
} }
def fanOn() { def fanOn() {
log.debug "fanOn" log.debug "fanOn"
// parent.setFanMode (this,"on") parent.setFanMode (this,"on")
} }
def fanAuto() { def fanAuto() {
log.debug "fanAuto" log.debug "fanAuto"
// parent.setFanMode (this,"auto") parent.setFanMode (this,"auto")
} }
def fanCirculate() { def fanCirculate() {
log.debug "fanCirculate" log.debug "fanCirculate"
// parent.setFanMode (this,"circulate") parent.setFanMode (this,"circulate")
} }
def fanOff() { def fanOff() {
log.debug "fanOff" log.debug "fanOff"
// parent.setFanMode (this,"off") parent.setFanMode (this,"off")
} }
def generateSetpointEvent() { def generateSetpointEvent() {
log.debug "Generate SetPoint Event" log.debug "Generate SetPoint Event"
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
log.debug "Current Mode = ${mode}" log.debug "Current Mode = ${mode}"
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
log.debug "Heating Setpoint = ${heatingSetpoint}" log.debug "Heating Setpoint = ${heatingSetpoint}"
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
log.debug "Cooling Setpoint = ${coolingSetpoint}" log.debug "Cooling Setpoint = ${coolingSetpoint}"
if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()+"°")
}
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()+"°")
if (mode == "heat") { } else if (mode == "auto") {
sendEvent("name":"thermostatSetpoint", "value":"Auto")
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()) } else if (mode == "off") {
sendEvent("name":"thermostatSetpoint", "value":"Off")
} } else if (mode == "emergencyHeat") {
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()+"°")
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()) }
} else if (mode == "auto") {
sendEvent("name":"thermostatSetpoint", "value":"Auto")
} else if (mode == "off") {
sendEvent("name":"thermostatSetpoint", "value":"Off")
} else if (mode == "emergencyHeat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
}
} }
void raiseSetpoint() { void raiseSetpoint() {
log.debug "Raise SetPoint"
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def targetvalue def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
log.debug "Current Mode = ${mode}"
if (mode == "heat") {
heatingSetpoint++
if (heatingSetpoint > 99)
heatingSetpoint = 99
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()+"°")
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
parent.setHold (this, heatingSetpoint, coolingSetpoint)
log.debug "New Heating Setpoint = ${heatingSetpoint}"
}
else if (mode == "cool") {
coolingSetpoint++
if (coolingSetpoint > 99)
coolingSetpoint = 99
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()+"°")
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
parent.setHold (this, heatingSetpoint, coolingSetpoint)
log.debug "New Cooling Setpoint = ${coolingSetpoint}"
}
generateStatusEvent()
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow raiseSetpoint"
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value as Integer
} else {
targetvalue = 0
}
targetvalue = targetvalue + 1
if (mode == "heat" && targetvalue > 79) {
targetvalue = 79
} else if (mode == "cool" && targetvalue > 92) {
targetvalue = 92
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
log.info "In mode $mode raiseSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
}
} }
//called by tile when user hit raise temperature button on UI
void lowerSetpoint() { void lowerSetpoint() {
log.debug "Lower SetPoint"
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def targetvalue def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
log.debug "Current Mode = ${mode}, Current Heating Setpoint = ${heatingSetpoint}, Current Cooling Setpoint = ${coolingSetpoint}"
if (mode == "heat" || mode == "emergencyHeat") {
heatingSetpoint--
if (heatingSetpoint < 32)
heatingSetpoint = 32
if (mode == "off" || mode == "auto") { sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()+"°")
log.warn "this mode: $mode does not allow lowerSetpoint" sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() parent.setHold (this, heatingSetpoint, coolingSetpoint)
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger() log.debug "New Heating Setpoint = ${heatingSetpoint}"
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) { }
targetvalue = device.latestState('thermostatSetpoint').value as Integer else if (mode == "cool") {
} else {
targetvalue = 0 coolingSetpoint--
}
targetvalue = targetvalue - 1 if (coolingSetpoint < 32)
coolingSetpoint = 32
if (mode == "heat" && targetvalue.toInteger() < 45) {
targetvalue = 45 sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()+"°")
} else if (mode == "cool" && targetvalue.toInteger() < 65) { sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
targetvalue = 65
} parent.setHold (this, heatingSetpoint, coolingSetpoint)
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false) log.debug "New Cooling Setpoint = ${coolingSetpoint}"
log.info "In mode $mode lowerSetpoint() to $targetvalue"
}
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite generateStatusEvent()
}
}
//called by raiseSetpoint() and lowerSetpoint()
void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint
def targetCoolingSetpoint
//step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat"){
if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = coolingSetpoint
}
} else if (mode == "cool") {
//enforce limits before sending request to cloud
if (temp.value < heatingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = heatingSetpoint
targetCoolingSetpoint = temp.value
}
}
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to ${targetHeatingSetpoint} " +
"coolingSetpoint to ${targetCoolingSetpoint} with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
//step2: call parent.setHold to send http request to 3rd party cloud
if (parent.setHold(this, targetHeatingSetpoint, targetCoolingSetpoint, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value.toString(), displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else {
log.error "Error alterSetpoint()"
if (mode == "heat"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
}
}
generateStatusEvent()
} }
def generateStatusEvent() { def generateStatusEvent() {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger() def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger() def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def temperature = device.currentValue("temperature").toInteger() def temperature = device.currentValue("temperature").toInteger()
def statusText def statusText
log.debug "Generate Status Event for Mode = ${mode}" log.debug "Generate Status Event for Mode = ${mode}"
log.debug "Temperature = ${temperature}" log.debug "Temperature = ${temperature}"
log.debug "Heating set point = ${heatingSetpoint}" log.debug "Heating set point = ${heatingSetpoint}"
log.debug "Cooling set point = ${coolingSetpoint}" log.debug "Cooling set point = ${coolingSetpoint}"
log.debug "HVAC Mode = ${mode}" log.debug "HVAC Mode = ${mode}"
if (mode == "heat") { if (mode == "heat") {
if (temperature >= heatingSetpoint) if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle" statusText = "Right Now: Idle"
else else
statusText = "Heating to ${heatingSetpoint}° F" statusText = "Heating to ${heatingSetpoint}° F"
} else if (mode == "cool") { } else if (mode == "cool") {
if (temperature <= coolingSetpoint) if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle" statusText = "Right Now: Idle"
else else
statusText = "Cooling to ${coolingSetpoint}° F" statusText = "Cooling to ${coolingSetpoint}° F"
} else if (mode == "auto") { } else if (mode == "auto") {
statusText = "Right Now: Auto" statusText = "Right Now: Auto"
} else if (mode == "off") { } else if (mode == "off") {
statusText = "Right Now: Off" statusText = "Right Now: Off"
} else if (mode == "emergencyHeat") { } else if (mode == "emergencyHeat") {
statusText = "Emergency Heat" statusText = "Emergency Heat"
} else { } else {
statusText = "?" statusText = "?"
} }
log.debug "Generate Status Event = ${statusText}" log.debug "Generate Status Event = ${statusText}"
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true) sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true, isStateChange: true)
} }
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
}

View File

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

View File

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

View File

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

View File

@@ -79,8 +79,8 @@ metadata {
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
} }

View File

@@ -88,8 +88,8 @@ metadata {
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
} }

View File

@@ -43,8 +43,8 @@ metadata {
]) ])
} }
section { section {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
} }

View File

@@ -45,8 +45,8 @@ metadata {
]) ])
} }
section { section {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
} }

View File

@@ -37,8 +37,8 @@ metadata {
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
tiles(scale: 2) { tiles(scale: 2) {

View File

@@ -61,8 +61,8 @@
]) ])
} }
section { section {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
section { 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) input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
@@ -115,30 +115,31 @@
} }
} }
def parse(String description) { def parse(String description) {
Map map = [:]
if (description?.startsWith('catchall:')) { Map map = [:]
map = parseCatchAllMessage(description) if (description?.startsWith('catchall:')) {
} map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ')) { else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description) map = parseCustomMessage(description)
} }
else if (description?.startsWith('zone status')) { else if (description?.startsWith('zone status')) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) { if (description?.startsWith('enroll request')) {
List cmds = enrollResponse() List cmds = enrollResponse()
log.debug "enroll response: ${cmds}" log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) } result = cmds?.collect { new physicalgraph.device.HubAction(it) }
} }
else if (description?.startsWith('read attr -')) { return result
result = parseReportAttributeMessage(description).each { createEvent(it) } }
}
return result
}
private Map parseCatchAllMessage(String description) { private Map parseCatchAllMessage(String description) {
Map resultMap = [:] Map resultMap = [:]
@@ -177,40 +178,28 @@ private boolean shouldProcessMessage(cluster) {
return !ignoredMessage return !ignoredMessage
} }
private List parseReportAttributeMessage(String description) { private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param -> Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":") def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
} }
List result = [] Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") { if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value) def value = getTemperature(descMap.value)
result << getTemperatureResult(value) resultMap = getTemperatureResult(value)
} }
else if (descMap.cluster == "FC02" && descMap.attrId == "0010") { else if (descMap.cluster == "FC02" && descMap.attrId == "0010") {
if (descMap.value.size() == 32) { resultMap = getAccelerationResult(descMap.value)
// value will look like 00ae29001403e2290013001629001201
// breaking this apart and swapping byte order where appropriate, this breaks down to:
// X (0x0012) = 0x0016
// Y (0x0013) = 0x03E2
// Z (0x0014) = 0x00AE
// note that there is a known bug in that the x,y,z attributes are interpreted in the wrong order
// this will be fixed in a future update
def threeAxisAttributes = descMap.value[0..-9]
result << parseAxis(threeAxisAttributes)
descMap.value = descMap.value[-2..-1]
}
result << getAccelerationResult(descMap.value)
} }
else if (descMap.cluster == "FC02" && descMap.attrId == "0012") { else if (descMap.cluster == "FC02" && descMap.attrId == "0012") {
result << parseAxis(descMap.value) resultMap = parseAxis(descMap.value)
} }
else if (descMap.cluster == "0001" && descMap.attrId == "0020") { else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
result << getBatteryResult(Integer.parseInt(descMap.value, 16)) resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
} }
return result return resultMap
} }
private Map parseCustomMessage(String description) { private Map parseCustomMessage(String description) {

View File

@@ -43,8 +43,8 @@ metadata {
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
tiles(scale: 2) { tiles(scale: 2) {

View File

@@ -32,8 +32,8 @@
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
tiles(scale: 2) { tiles(scale: 2) {

View File

@@ -34,8 +34,8 @@ metadata {
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
tiles(scale: 2) { tiles(scale: 2) {

View File

@@ -33,8 +33,8 @@ metadata {
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
tiles(scale: 2) { tiles(scale: 2) {

View File

@@ -45,8 +45,8 @@ metadata {
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
tiles { tiles {

View File

@@ -33,8 +33,8 @@ metadata {
} }
preferences { preferences {
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 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 input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
tiles { tiles {

View File

@@ -33,14 +33,14 @@ metadata {
state "power", label: '${currentValue} W' state "power", label: '${currentValue} W'
} }
htmlTile(name: "powerContent", attribute: "powerContent", type: "HTML", whitelist: "www.wattvision.com" , url: '${currentValue}', width: 3, height: 2) tile(name: "powerChart", attribute: "powerContent", type: "HTML", url: '${currentValue}', width: 3, height: 2) { }
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat") {
state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh" state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh"
} }
main "power" main "power"
details(["powerContent", "power", "refresh"]) details(["powerChart", "power", "refresh"])
} }
} }
@@ -74,10 +74,10 @@ public addWattvisionData(json) {
log.trace "Adding data from Wattvision" log.trace "Adding data from Wattvision"
def data = parseJson(json.data.toString()) def data = json.data
def units = json.units ?: "watts" def units = json.units ?: "watts"
if (data.size() > 0) { if (data) {
def latestData = data[-1] def latestData = data[-1]
data.each { data.each {
sendPowerEvent(it.t, it.v, units, (latestData == it)) sendPowerEvent(it.t, it.v, units, (latestData == it))
@@ -103,7 +103,3 @@ private sendPowerEvent(time, value, units, isLatest = false) {
sendEvent(eventData) sendEvent(eventData)
} }
def parseJson(String s) {
new groovy.json.JsonSlurper().parseText(s)
}

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, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702" 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, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-CIA19NAE26", deviceJoinName: "Sengled Element touch"
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,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, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45857", deviceJoinName: "GE Zigbee In-Wall 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"
} }
tiles(scale: 2) { tiles(scale: 2) {

View File

@@ -17,15 +17,15 @@ metadata {
capability "Actuator" capability "Actuator"
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Refresh"
capability "Sensor"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008" 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, 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,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,0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
fingerprint profileId: "0104", inClusters: "0001, 0006, 0008, 000E", outClusters: "0019", manufacturer: "Nanoleaf", model: "IvyBulbs", deviceJoinName: "Nanoleaf Smart Bulbs"
} }
tiles(scale: 2) { tiles(scale: 2) {
@@ -87,4 +87,4 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

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, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
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,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, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall 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"
} }
tiles(scale: 2) { tiles(scale: 2) {

View File

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

View File

@@ -23,18 +23,18 @@ metadata {
capability "Color Temperature" capability "Color Temperature"
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Refresh"
capability "Sensor"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
attribute "colorName", "string" attribute "colorName", "string"
command "setGenericName" command "setGenericName"
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", 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 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: "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: "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, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
} }
// UI tile definitions // UI tile definitions

View File

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

View File

@@ -115,10 +115,6 @@ def strobe() {
] ]
} }
def both() {
on()
}
def refresh() { def refresh() {
log.debug "sending battery refresh command" log.debug "sending battery refresh command"
zwave.batteryV1.batteryGet().format() zwave.batteryV1.batteryGet().format()

View File

@@ -10,7 +10,7 @@
* The timeout perid begins when the contact is closed, or motion stops, so leaving a door open won't start the timer until it's closed. * The timeout perid begins when the contact is closed, or motion stops, so leaving a door open won't start the timer until it's closed.
* *
* Author: andersheie@gmail.com * Author: andersheie@gmail.com
* Date: 2014-08-31 * Date: 2015-10-30
*/ */
definition( definition(
@@ -23,12 +23,18 @@ definition(
iconX2Url: "http://upload.wikimedia.org/wikipedia/commons/6/6a/Light_bulb_icon_tips.svg") iconX2Url: "http://upload.wikimedia.org/wikipedia/commons/6/6a/Light_bulb_icon_tips.svg")
preferences { preferences {
section("Turn on when there's movement..."){ section("Turn on when there is movement..."){
input "motions", "capability.motionSensor", multiple: true, title: "Select motion detectors", required: false input "motions", "capability.motionSensor", multiple: true, title: "Select motion detectors", required: false
} }
section("Or, turn on when one of these contacts opened"){ section("Or, turn on when one of these contacts opened"){
input "contacts", "capability.contactSensor", multiple: true, title: "Select Contacts", required: false input "contacts", "capability.contactSensor", multiple: true, title: "Select Contacts", required: false
} }
section("Or, turn on when any of these people come home") {
input "peoplearrive", "capability.presenceSensor", multiple: true, required: false
}
section("Or, turn on when any of these people leave") {
input "peopleleave", "capability.presenceSensor", multiple: true, required: false
}
section("And off after no more triggers after..."){ section("And off after no more triggers after..."){
input "minutes1", "number", title: "Minutes?", defaultValue: "5" input "minutes1", "number", title: "Minutes?", defaultValue: "5"
} }
@@ -40,109 +46,232 @@ preferences {
def installed() def installed()
{ {
subscribe(switches, "switch", switchChange) log.debug "installed()"
subscribe(motions, "motion", motionHandler) initialize()
subscribe(contacts, "contact", contactHandler)
schedule("0 * * * * ?", "scheduleCheck")
state.myState = "ready"
} }
def updated() def updated()
{ {
unsubscribe() log.debug "updated()"
initialize()
}
def initialize()
{
log.debug "initialize()"
// Reset to new set of Switches, just in case.
unsubscribe()
state.switches = [:]
switches.each {
// log.debug "ID: $it.id"
// Set ready state for each switch
state.switches["$it.id"] = "ready"
}
logStates()
subscribe(motions, "motion", motionHandler) subscribe(motions, "motion", motionHandler)
subscribe(switches, "switch", switchChange) subscribe(switches, "switch", switchChange)
subscribe(contacts, "contact", contactHandler) subscribe(contacts, "contact", contactHandler)
subscribe(peoplearrive, "presence.present", presencePresentHandler)
subscribe(peopleleave, "presence.not present", presenceNotPresentHandler)
state.lastAction = null
schedule("0 * * * * ?", "scheduleCheck")
}
def logStates() {
if(state.switches == null) {
state.switches = [:]
}
state.switches.each {
log.debug "State: $it.key = $it.value"
}
}
def turnSwitchOn(id) {
for(S in switches) {
if(S.id == id) {
S.on()
}
}
}
def turnSwitchOff(id) {
for(S in switches) {
if(S.id == id) {
S.off()
}
}
}
def initState(deviceID) {
// Have to add this so existing apps that are neither updated nor initiated can slowly get 'reset' with states.
// Smartthings doesn't call any specifc method when new version is published.
if(state.switches == null) {
state.switches = [:]
}
if(state.switches[deviceID] == null) {
state.switches[deviceID] = "ready"
}
state.myState = "ready"
log.debug "state: " + state.myState
} }
def switchChange(evt) { def switchChange(evt) {
log.debug "SwitchChange: $evt.name: $evt.value" def deviceID = evt.device.id
log.debug "SwitchChange: $evt.name: $evt.value $evt.device.id current state = " + state.switches[deviceID]
initState(deviceID)
if(evt.value == "on") { if(evt.value == "on") {
// Slight change of Race condition between motion or contact turning the switch on, if(state.switches[deviceID] == "activating") {
// versus user turning the switch on. Since we can't pass event parameters :-(, we rely // OK, probably an event from Activating something, and not the switch itself. Go to Active mode.
// on the state and hope the best. log.debug "$deviceID = " + state.switches[deviceID] + " -> active"
if(state.myState == "activating") { state.switches[deviceID] = "active"
// OK, probably an event from Activating something, and not the switch itself. Go to Active mode. } else if(state.switches[deviceID] != "active") {
state.myState = "active" log.debug "$deviceID = " + state.switches[deviceID] + " -> already on"
} else if(state.myState != "active") { state.switches[deviceID] = "already on"
state.myState = "already on" }
}
} else { } else {
// If active and switch is turned of manually, then stop the schedule and go to ready state // If active and switch is turned of manually, then stop the schedule and go to ready state
if(state.myState == "active" || state.myState == "activating") { state.switches[deviceID] = "ready"
unschedule()
}
state.myState = "ready"
} }
log.debug "state: " + state.myState logStates()
} }
def contactHandler(evt) { def contactHandler(evt) {
log.debug "contactHandler: $evt.name: $evt.value" log.debug "contactHandler: $evt.name: $evt.value"
if (evt.value == "open") { state.switches.each { thisswitch ->
if(state.myState == "ready") { initState(thisswitch.key)
log.debug "Turning on lights by contact opening" //log.debug "Looking for $thisswitch.key"
switches.on() if (evt.value == "open") {
state.inactiveAt = null if(state.switches[thisswitch.key] == "ready") {
state.myState = "activating" log.debug "Turning on lights by contact opening: $thisswitch"
} log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> activating"
} else if (evt.value == "closed") { state.switches[thisswitch.key] = "activating"
if (!state.inactiveAt && state.myState == "active" || state.myState == "activating") { turnSwitchOn(thisswitch.key)
// When contact closes, we reset the timer if not already set setActiveAndSchedule()
setActiveAndSchedule()
}
} else if (evt.value == "closed") {
if (!state.lastAction && (state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating")) {
// When contact closes, we reset the timer if not already set
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> active"
state.switches[thisswitch.key] = "active"
setActiveAndSchedule()
}
} }
} }
log.debug "state: " + state.myState logStates()
} }
def motionHandler(evt) { def scheduleCheck() {
log.debug "motionHandler: $evt.name: $evt.value" log.debug "schedule check, ts = ${state.lastAction}"
boolean resetInactive = false
if (evt.value == "active") { state.switches.each { thisswitch ->
if(state.myState == "ready" || state.myState == "active" || state.myState == "activating" ) { initState(thisswitch.key)
log.debug "turning on lights" if(state.switches[thisswitch.key] != "already on") {
switches.on() if(state.lastAction != null) {
state.inactiveAt = null def elapsed = now() - state.lastAction
state.myState = "activating" log.debug "${elapsed / 1000} sec since events stopped"
} def threshold = 1000 * 60 * minutes1
} else if (evt.value == "inactive") { if (elapsed >= threshold) {
if (!state.inactiveAt && state.myState == "active" || state.myState == "activating") { if (state.switches[thisswitch.key] == "active"
// When Motion ends, we reset the timer if not already set || state.switches[thisswitch.key] == "activating") {
setActiveAndSchedule() state.switches[thisswitch.key] = "ready"
log.debug "Turning off lights by switch closing: $thisswitch"
turnSwitchOff(thisswitch.key)
}
resetInactive = true
}
}
} }
} }
log.debug "state: " + state.myState if(resetInactive) {
state.lastAction = null
unschedule()
}
logStates()
}
/**********************************************************************/
def motionHandler(evt) {
log.debug "motionHandler: $evt.name: $evt.value (current state: " + state.myState + ")"
state.switches.each { thisswitch ->
initState(thisswitch.key)
if (evt.value == "active") {
if(state.switches[thisswitch.key] == "ready"
|| state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating" ) {
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> activating"
state.switches[thisswitch.key] = "activating"
turnSwitchOn(thisswitch.key)
setActiveAndSchedule()
}
} else if (evt.value == "inactive") {
if (state.switches[thisswitch.key] == "active" || state.switches[thisswitch.key] == "activating") {
// When Motion ends, we reset the timer if not already set
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> active"
state.switches[thisswitch.key] = "active"
setActiveAndSchedule()
}
}
}
logStates()
}
def presencePresentHandler(evt) {
log.debug "presence: $evt.linkText is now $evt.value"
state.switches.each { thisswitch ->
initState(thisswitch.key)
if (evt.value == "present") {
if(state.switches[thisswitch.key] == "ready"
|| state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating" ) {
log.debug "Presence turning on switch $thisswitch"
state.switches[thisswitch.key] = "active"
turnSwitchOn(thisswitch.key)
// We don't wait until the person leave, but instead start timer immediately.
setActiveAndSchedule()
}
}
}
logStates()
}
def presenceNotPresentHandler(evt) {
log.debug "presence: $evt.linkText is now $evt.value"
state.switches.each { thisswitch ->
initState(thisswitch.key)
if (evt.value == "not present") {
if(state.switches[thisswitch.key] == "ready"
|| state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating" ) {
log.debug "No Presence turning on lights $thisswitch"
state.switches[thisswitch.key] = "active"
turnSwitchOn(thisswitch.key)
// We don't wait until the person arrive back, but instead start timer immediately.
setActiveAndSchedule()
}
}
}
logStates()
} }
def setActiveAndSchedule() { def setActiveAndSchedule() {
unschedule() unschedule()
state.myState = "active" state.lastAction = now()
state.inactiveAt = now()
schedule("0 * * * * ?", "scheduleCheck") schedule("0 * * * * ?", "scheduleCheck")
log.debug "Scheduled new timer"
} }
def scheduleCheck() {
log.debug "schedule check, ts = ${state.inactiveAt}"
if(state.myState != "already on") {
if(state.inactiveAt != null) {
def elapsed = now() - state.inactiveAt
log.debug "${elapsed / 1000} sec since motion stopped"
def threshold = 1000 * 60 * minutes1
if (elapsed >= threshold) {
if (state.myState == "active") {
log.debug "turning off lights"
switches.off()
}
state.inactiveAt = null
state.myState = "ready"
}
}
}
log.debug "state: " + state.myState
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -592,7 +592,7 @@ def updated() {
// log.debug "External Id=${app.id}:${member.id}" // log.debug "External Id=${app.id}:${member.id}"
// create the device // 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) // childDevice.setMemberId(member.id)
if (childDevice) if (childDevice)

View File

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

View File

@@ -49,7 +49,6 @@ preferences {
section("Via a push notification and/or an SMS message"){ section("Via a push notification and/or an SMS message"){
input("recipients", "contact", title: "Send notifications to") { input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
paragraph "If outside the US please make sure to enter the proper country code"
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"] input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
} }
} }

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 * 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. * for the specific language governing permissions and limitations under the License.
* *
* Speaker Control * Sonos Control
* *
* Author: SmartThings * Author: SmartThings
* *
* Date: 2013-12-10 * Date: 2013-12-10
*/ */
definition( definition(
name: "Speaker Control", name: "Sonos Control",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Play or pause your Speaker when certain actions take place in your home.", description: "Play or pause your Sonos when certain actions take place in your home.",
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png" iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
) )
preferences { preferences {
page(name: "mainPage", title: "Control your Speaker when something happens", install: true, uninstall: true) page(name: "mainPage", title: "Control your Sonos when something happens", install: true, uninstall: true)
page(name: "timeIntervalInput", title: "Only during a certain time") { page(name: "timeIntervalInput", title: "Only during a certain time") {
section { section {
input "starting", "time", title: "Starting", required: false input "starting", "time", title: "Starting", required: false
@@ -81,7 +81,7 @@ def mainPage() {
] ]
} }
section { section {
input "sonos", "capability.musicPlayer", title: "Speaker music player", required: true input "sonos", "capability.musicPlayer", title: "Sonos music player", required: true
} }
section("More options", hideable: true, hidden: true) { section("More options", hideable: true, hidden: true) {
input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false 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 * 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. * for the specific language governing permissions and limitations under the License.
* *
* Speaker Mood Music * Sonos Mood Music
* *
* Author: SmartThings * Author: SmartThings
* Date: 2014-02-12 * Date: 2014-02-12
@@ -65,7 +65,7 @@ private saveSelectedSong() {
} }
definition( definition(
name: "Speaker Mood Music", name: "Sonos Mood Music",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Plays a selected song or station.", description: "Plays a selected song or station.",
@@ -75,7 +75,7 @@ definition(
) )
preferences { preferences {
page(name: "mainPage", title: "Play a selected song or station on your Speaker when something happens", nextPage: "chooseTrack", uninstall: true) page(name: "mainPage", title: "Play a selected song or station on your Sonos when something happens", nextPage: "chooseTrack", uninstall: true)
page(name: "chooseTrack", title: "Select a song", install: true) page(name: "chooseTrack", title: "Select a song", install: true)
page(name: "timeIntervalInput", title: "Only during a certain time") { page(name: "timeIntervalInput", title: "Only during a certain time") {
section { section {
@@ -125,7 +125,7 @@ def mainPage() {
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
} }
section { section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
} }
section("More options", hideable: true, hidden: true) { section("More options", hideable: true, hidden: true) {
input "volume", "number", title: "Set the volume", description: "0-100%", required: false 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 * 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. * for the specific language governing permissions and limitations under the License.
* *
* Speaker Custom Message * Sonos Custom Message
* *
* Author: SmartThings * Author: SmartThings
* Date: 2014-1-29 * Date: 2014-1-29
*/ */
definition( definition(
name: "Speaker Notify with Sound", name: "Sonos Notify with Sound",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Play a sound or custom message through your Speaker when the mode changes or other events occur.", description: "Play a sound or custom message through your Sonos when the mode changes or other events occur.",
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png" iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
) )
preferences { preferences {
page(name: "mainPage", title: "Play a message on your Speaker when something happens", install: true, uninstall: true) page(name: "mainPage", title: "Play a message on your Sonos when something happens", install: true, uninstall: true)
page(name: "chooseTrack", title: "Select a song or station") page(name: "chooseTrack", title: "Select a song or station")
page(name: "timeIntervalInput", title: "Only during a certain time") { page(name: "timeIntervalInput", title: "Only during a certain time") {
section { section {
@@ -92,7 +92,7 @@ def mainPage() {
input "message","text",title:"Play this message", required:false, multiple: false input "message","text",title:"Play this message", required:false, multiple: false
} }
section { section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
} }
section("More options", hideable: true, hidden: true) { section("More options", hideable: true, hidden: true) {
input "resumePlaying", "bool", title: "Resume currently playing music after notification", required: false, defaultValue: 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 * 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. * for the specific language governing permissions and limitations under the License.
* *
* Speaker Weather Forecast * Sonos Weather Forecast
* *
* Author: SmartThings * Author: SmartThings
* Date: 2014-1-29 * Date: 2014-1-29
*/ */
definition( definition(
name: "Speaker Weather Forecast", name: "Sonos Weather Forecast",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Play a weather report through your Speaker when the mode changes or other events occur", description: "Play a weather report through your Sonos when the mode changes or other events occur",
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png" iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
) )
preferences { preferences {
page(name: "mainPage", title: "Play the weather report on your speaker", install: true, uninstall: true) page(name: "mainPage", title: "Play the weather report on your sonos", install: true, uninstall: true)
page(name: "chooseTrack", title: "Select a song or station") page(name: "chooseTrack", title: "Select a song or station")
page(name: "timeIntervalInput", title: "Only during a certain time") { page(name: "timeIntervalInput", title: "Only during a certain time") {
section { section {
@@ -85,7 +85,7 @@ def mainPage() {
) )
} }
section { section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
} }
section("More options", hideable: true, hidden: true) { section("More options", hideable: true, hidden: true) {
input "resumePlaying", "bool", title: "Resume currently playing music after weather report finishes", required: false, defaultValue: 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 motionsDiscovered = motionsDiscovered()
def lightSwitchesDiscovered = lightSwitchesDiscovered() def lightSwitchesDiscovered = lightSwitchesDiscovered()
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) { return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: selectedSwitches != null || selectedMotions != null || selectedLightSwitches != null) {
section("Select a device...") { section("Select a device...") {
input "selectedSwitches", "enum", required:false, title:"Select Wemo Switches \n(${switchesDiscovered.size() ?: 0} found)", multiple:true, options:switchesDiscovered 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 input "selectedMotions", "enum", required:false, title:"Select Wemo Motions \n(${motionsDiscovered.size() ?: 0} found)", multiple:true, options:motionsDiscovered