Compare commits

..

1 Commits

Author SHA1 Message Date
Amol
f5c91f82b6 DEVTOOLS-162: This is a capability / simulator device type for Beacon. 2016-08-15 18:41:23 -05:00
55 changed files with 798 additions and 2335 deletions

View File

@@ -0,0 +1,37 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* 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: "Beacon Capability", namespace: "capabilities", author: "SmartThings") {
capability "Beacon"
}
simulator {
status "present": "beacon: present"
status "not present": "beacon: not present"
}
tiles {
standardTile("beacon", "device.beacon", width: 2, height: 2) {
state("not present", label:'not present', icon:"st.presence.tile.not-present", backgroundColor:"#ffffff")
state("present", label:'present', icon:"st.presence.tile.present", backgroundColor:"#53a7c0")
}
main "beacon"
details "beacon"
}
}
def parse(String description) {
def pair = description.split(":")
createEvent(name: pair[0].trim(), value: pair[1].trim())
}

View File

@@ -274,7 +274,6 @@ private Map makeTemperatureResult(value) {
name: 'temperature',
value: "" + value,
descriptionText: "${linkText} is ${value}°${temperatureScale}",
unit: temperatureScale
]
}

View File

@@ -254,8 +254,7 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}

View File

@@ -22,6 +22,7 @@ metadata {
capability "Configuration"
capability "Sensor"
capability "Battery"
capability "Health Check"
attribute "tamper", "enum", ["detected", "clear"]
attribute "batteryStatus", "string"
@@ -327,6 +328,9 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
}
def configure() {
// allow device user configured or default 16 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2* (timeOptionValueMap[reportInterval] ?: (2*8*60)), displayed: false)
// This sensor joins as a secure device if you double-click the button to include it
log.debug "${device.displayName} is configuring its settings"
def request = []

View File

@@ -20,6 +20,7 @@ metadata {
capability "Configuration"
capability "Sensor"
capability "Battery"
capability "Health Check"
command "configureAfterSecure"
@@ -247,6 +248,8 @@ def configureAfterSecure() {
def configure() {
// log.debug "configure()"
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
// allow device 16 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2*8*60, displayed: false)
}
private setConfigured() {

View File

@@ -20,6 +20,7 @@ metadata {
capability "Illuminance Measurement"
capability "Sensor"
capability "Battery"
capability "Health Check"
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
}
@@ -180,6 +181,9 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
}
def configure() {
// allow device 10 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2*5*60, displayed: false)
delayBetween([
// send binary sensor report instead of basic set for motion
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),

View File

@@ -47,9 +47,6 @@ metadata {
command "everywhereJoin"
command "everywhereLeave"
command "forceOff"
command "forceOn"
}
/**
@@ -67,9 +64,9 @@ metadata {
}
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "on", label: '${name}', action: "forceOff", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
state "off", label: '${name}', action: "forceOn", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
}
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
@@ -143,22 +140,8 @@ metadata {
* one place.
*
*/
def off() {
if (device.currentState("switch")?.value == "on") {
onAction("off")
}
}
def forceOff() {
onAction("off")
}
def on() {
if (device.currentState("switch")?.value == "off") {
onAction("on")
}
}
def forceOn() {
onAction("on")
}
def off() { onAction("off") }
def on() { onAction("on") }
def volup() { onAction("volup") }
def voldown() { onAction("voldown") }
def preset1() { onAction("1") }
@@ -257,11 +240,11 @@ def onAction(String user, data=null) {
def actions = null
switch (user) {
case "on":
boseSetPowerState(true)
actions = boseSetPowerState(true)
break
case "off":
boseSetNowPlaying(null, "STANDBY")
boseSetPowerState(false)
actions = boseSetPowerState(false)
break
case "volume":
actions = boseSetVolume(data)

View File

@@ -89,17 +89,14 @@ def parse(String description) {
log.debug "TEMP"
map.name = "temperature"
map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
log.debug "COOLING SETPOINT"
map.name = "coolingSetpoint"
map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
log.debug "HEATING SETPOINT"
map.name = "heatingSetpoint"
map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
log.debug "MODE"
map.name = "thermostatMode"
@@ -172,7 +169,7 @@ def setHeatingSetpoint(degrees) {
def degreesInteger = Math.round(degrees)
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "heatingSetpoint", "value": degreesInteger, "unit": temperatureScale)
sendEvent("name": "heatingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
@@ -183,7 +180,7 @@ def setCoolingSetpoint(degrees) {
if (degrees != null) {
def degreesInteger = Math.round(degrees)
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "coolingSetpoint", "value": degreesInteger, "unit": temperatureScale)
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
}

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,36 +0,0 @@
# Connected Cree LED Bulb
Works with:
* [Connected Cree LED Bulb](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Polling** - represents that poll() can be implemented for the device
* **Refresh** - _refresh()_ command for status updates
* **Switch** - can detect state (possible values: on/off)
* **Switch Level** - represents current light level, usually 0-100 in percent
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C6 Connected Cree LED Bulb with maxReportTime of 10 min.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*10 = 20 min
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Cree Connected LED Bulb Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)

View File

@@ -23,7 +23,6 @@ metadata {
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
}
@@ -67,12 +66,6 @@ def parse(String description) {
def resultMap = zigbee.getEvent(description)
if (resultMap) {
sendEvent(resultMap)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
}
else {
log.debug "DID NOT PARSE MESSAGE for description : $description"
@@ -92,21 +85,6 @@ def setLevel(value) {
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.levelRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
}
@@ -117,6 +95,5 @@ def poll() {
def configure() {
log.debug "Configuring Reporting and Bindings."
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

@@ -152,11 +152,11 @@ def generateEvent(Map results) {
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, unit: temperatureScale, displayed: false]
event << [value: sendValue, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false]
@@ -234,9 +234,9 @@ void setHeatingSetpoint(setpoint) {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
@@ -271,9 +271,9 @@ void setCoolingSetpoint(setpoint) {
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
@@ -287,14 +287,14 @@ void resumeProgram() {
log.debug "resumeProgram() is called"
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.resumeProgram(deviceId)) {
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(deviceId)"
log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)"
}
}
@@ -406,7 +406,7 @@ def generateOperatingStateEvent(operatingState) {
def off() {
log.debug "off"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("off", deviceId))
if (parent.setMode (this,"off", deviceId))
generateModeEvent("off")
else {
log.debug "Error setting new mode."
@@ -420,7 +420,7 @@ def off() {
def heat() {
log.debug "heat"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("heat", deviceId))
if (parent.setMode (this,"heat", deviceId))
generateModeEvent("heat")
else {
log.debug "Error setting new mode."
@@ -438,7 +438,7 @@ def emergencyHeat() {
def auxHeatOnly() {
log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("auxHeatOnly", deviceId))
if (parent.setMode (this,"auxHeatOnly", deviceId))
generateModeEvent("auxHeatOnly")
else {
log.debug "Error setting new mode."
@@ -452,7 +452,7 @@ def auxHeatOnly() {
def cool() {
log.debug "cool"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("cool", deviceId))
if (parent.setMode (this,"cool", deviceId))
generateModeEvent("cool")
else {
log.debug "Error setting new mode."
@@ -466,7 +466,7 @@ def cool() {
def auto() {
log.debug "auto"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode ("auto", deviceId))
if (parent.setMode (this,"auto", deviceId))
generateModeEvent("auto")
else {
log.debug "Error setting new mode."
@@ -489,7 +489,7 @@ def fanOn() {
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode)
} else {
log.debug "Error setting new mode."
@@ -510,7 +510,7 @@ def fanAuto() {
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode)
} else {
log.debug "Error setting new mode."
@@ -556,12 +556,12 @@ def generateSetpointEvent() {
if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
}
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
} else if (mode == "auto") {
@@ -573,7 +573,7 @@ def generateSetpointEvent() {
} else if (mode == "auxHeatOnly") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
}
@@ -608,7 +608,7 @@ void raiseSetpoint() {
targetvalue = maxCoolingSetpoint
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
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
@@ -644,7 +644,7 @@ void lowerSetpoint() {
targetvalue = minCoolingSetpoint
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
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
@@ -690,10 +690,10 @@ void alterSetpoint(temp) {
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
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()"

View File

@@ -682,7 +682,7 @@ def setHeatingSetpoint(degrees) {
def temperatureScale = getTemperatureScale()
def degreesInteger = degrees as Integer
sendEvent("name":"heatingSetpoint", "value":degreesInteger, "unit":temperatureScale)
sendEvent("name":"heatingSetpoint", "value":degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}"
@@ -691,7 +691,7 @@ def setHeatingSetpoint(degrees) {
def setCoolingSetpoint(degrees) {
def degreesInteger = degrees as Integer
sendEvent("name":"coolingSetpoint", "value":degreesInteger, "unit":temperatureScale)
sendEvent("name":"coolingSetpoint", "value":degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}"

View File

@@ -43,7 +43,7 @@ metadata {
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single"
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -51,7 +51,7 @@ metadata {
}
main(["rich-control"])
details(["rich-control", "reset", "refresh"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
}
}
@@ -75,78 +75,118 @@ def parse(description) {
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) {
log.trace parent.setLevel(this, percent)
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
}
}
void setSaturation(percent) {
log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) {
log.trace parent.setSaturation(this, percent)
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
}
}
void setHue(percent) {
log.debug "Executing 'setHue'"
if (verifyPercent(percent)) {
log.trace parent.setHue(this, percent)
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
}
}
void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = []
def validValues = [:]
if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue
}
if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation
}
if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex
} else {
log.warn "$value.hex is not a valid color"
}
}
if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level
}
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off"
} else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on"
}
if (!validValues.isEmpty()) {
log.trace parent.setColor(this, validValues)
if (!events.isEmpty()) {
parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
}
}
void reset() {
log.debug "Executing 'reset'"
def value = [hue:20, saturation:2]
setAdjustedColor(value)
def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
}
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
setColor(adjusted)
} else {
log.warn "Invalid color input $value"
log.warn "Invalid color input"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature"
}
}
@@ -155,6 +195,22 @@ void refresh() {
parent.manualRefresh()
}
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) {
if (percent == null)

View File

@@ -7,13 +7,8 @@
metadata {
// Automatically generated. Make future change here.
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
attribute "serialNumber", "string"
attribute "networkAddress", "string"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline"
attribute "status", "string"
// Id is the number on the back of the hub, Hue uses last six digits of Mac address
// This is also used in the Hue application as ID
attribute "idNumber", "string"
}
simulator {
@@ -22,23 +17,22 @@ metadata {
tiles(scale: 2) {
multiAttributeTile(name:"rich-control"){
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
attributeState "Offline", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#ffffff"
attributeState "Online", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#79b821"
tileAttribute ("", key: "PRIMARY_CONTROL") {
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
}
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
attributeState "default", label:'SN: ${currentValue}'
}
valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'If removed, Hue lights will not work properly'
}
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
state "default", label:'SN: ${currentValue}'
}
valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'ID: ${currentValue}'
}
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'IP: ${currentValue}'
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 4, inactiveLabel: false) {
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
}
main (["rich-control"])
details(["rich-control", "doNotRemove", "idNumber", "networkAddress"])
details(["rich-control", "networkAddress"])
}
}

View File

@@ -43,16 +43,16 @@ metadata {
}
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: 'WHITES'
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single"
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -84,86 +84,118 @@ def parse(description) {
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) {
log.trace parent.setLevel(this, percent)
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
}
}
void setSaturation(percent) {
log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) {
log.trace parent.setSaturation(this, percent)
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
}
}
void setHue(percent) {
log.debug "Executing 'setHue'"
if (verifyPercent(percent)) {
log.trace parent.setHue(this, percent)
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
}
}
void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = []
def validValues = [:]
if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue
}
if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation
}
if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex
} else {
log.warn "$value.hex is not a valid color"
}
}
if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level
}
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off"
} else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on"
}
if (!validValues.isEmpty()) {
log.trace parent.setColor(this, validValues)
if (!events.isEmpty()) {
parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
}
}
void reset() {
log.debug "Executing 'reset'"
setColorTemperature(4000)
def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
}
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
setColor(adjusted)
} else {
log.warn "Invalid color input $value"
log.warn "Invalid color input"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
log.trace parent.setColorTemperature(this, value)
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature $value"
log.warn "Invalid color temperature"
}
}
@@ -172,6 +204,23 @@ void refresh() {
parent.manualRefresh()
}
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) {
if (percent == null)
return false

View File

@@ -68,16 +68,20 @@ def parse(description) {
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) {
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else {
log.warn "$percent is not 0-100"
}

View File

@@ -36,12 +36,12 @@ metadata {
}
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2200..6500)") {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: 'WHITES'
state "colorTemperature", label: '${currentValue} K'
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -73,16 +73,20 @@ def parse(description) {
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) {
log.trace parent.setLevel(this, percent)
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else {
log.warn "$percent is not 0-100"
}
@@ -91,7 +95,9 @@ void setLevel(percent) {
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
log.trace parent.setColorTemperature(this, value)
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature"
}
@@ -101,3 +107,4 @@ void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}

View File

@@ -147,8 +147,8 @@ private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
Map resultMap = [:]
resultMap.name = 'motion'
resultMap.value = zs.isAlarm2Set() ? 'active' : 'inactive'
result.name = 'motion'
result.value = zs.isAlarm2Set() ? 'active' : 'inactive'
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
return resultMap

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,38 +0,0 @@
# SmartPower Outlet
Works with:
* [Samsung SmartPower Outlet](https://shop.smartthings.com/#!/products/smartpower-outlet)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Actuator** - represents that a Device has commands
* **Switch** - can detect state (possible values: on/off)
* **Refresh** - _refresh()_ command for status updates
* **Power Meter** - detects power meter for device in either w or kw.
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
A Category C1 smart power outlet with maxReportTime of 10 min.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*10 = 20 min
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following links
for the different models:
* [SmartPower Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/201084854-SmartPower-Outlet)
* [Samsung SmartThings Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957620)

View File

@@ -102,11 +102,11 @@ def parse(String description) {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
if (state.lastOnOff == null){
state.lastOnOff = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
state.lastOnOff = now()
}
}
else {
@@ -123,17 +123,18 @@ def on() {
zigbee.on()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* PING is used by Device-Watch in attempt to reach the Outlet
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
// send read attribute onOFF if the last time we heard from the outlet is outside of the checkInterval
if (state.lastOnOff < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastOnOff=${new Date(state.lastOnOff)}"
state.lastOnOff = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
} else { // if the last onOff activity is within the checkInterval we artificially create a Device-Watch event
log.info "ping, alive=yes, lastOnOff=${new Date(state.lastOnOff)}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true)
}
}
@@ -142,7 +143,7 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 1200, displayed: false)
zigbee.onOffConfig() + powerConfig() + refresh()
}

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,44 +0,0 @@
# Smartsense Moisture Sensor
Works with:
* [Samsung SmartThings Moisture Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-water-leak-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Water Sensor** - can detect presence of water (dry or wet)
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C2 moisture sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2 3V battery required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different sensors from SmartThings can be found in the following links
for the different models:
* [SmartSense Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202847044-SmartSense-Moisture-Sensor)
* [Samsung SmartThings Water Leak Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957630)
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Water Leak Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -24,7 +24,7 @@ metadata {
capability "Temperature Measurement"
capability "Water Sensor"
capability "Health Check"
capability "Sensor"
capability "Sensor"
command "enrollResponse"
@@ -101,13 +101,6 @@ def parse(String description) {
map = parseIasMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
@@ -259,8 +252,7 @@ private Map getTemperatureResult(value) {
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true,
unit: temperatureScale
translatable: true
]
}
@@ -279,21 +271,6 @@ private Map getMoistureResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
@@ -305,7 +282,7 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -9,8 +9,7 @@ Works with:
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
* [Health]($health)
## Capabilities
@@ -22,24 +21,10 @@ Works with:
## Device Health
A Category C2 motion sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
A Category C2 motion sensor that has 120min check-in interval
## Battery Specification
One CR2477 (for Samsung SmartThings Motion Sensor) / CR123A (SmartSense Motion Sensor) 3V battery is required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
for the different models:
* [SmartSense Motion Sensor (original model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903280-SmartSense-Motion-Sensor-original-model-)
* [SmartSense Motion Sensor (2014 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203077520-SmartSense-Motion-Sensor-2014-model-)
* [Samsung SmartThings Motion Sensor (2015 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957580-Samsung-SmartThings-Motion-Sensor-2015-model-)
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Motion Sensor is stuck showing "Motion Detected" or "No Motion"](https://support.smartthings.com/hc/en-us/articles/200961130-Troubleshooting-Samsung-SmartThings-Motion-Sensor-is-stuck-showing-Motion-Detected-or-No-Motion-)
* [Troubleshooting: Samsung SmartThings Motion Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -24,7 +24,7 @@ metadata {
capability "Temperature Measurement"
capability "Refresh"
capability "Health Check"
capability "Sensor"
capability "Sensor"
command "enrollResponse"
@@ -105,13 +105,6 @@ def parse(String description) {
map = parseIasMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
@@ -274,8 +267,7 @@ private Map getTemperatureResult(value) {
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true,
unit: temperatureScale
translatable: true
]
}
@@ -290,21 +282,6 @@ private Map getMotionResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() {
log.debug "refresh called"
def refreshCmds = [
@@ -316,7 +293,7 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -226,8 +226,7 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,45 +0,0 @@
# Smartsense Multi Sensor
Works with:
* [Samsung SmartThings Multi Sensor](https://shop.smartthings.com/#!/products/smartsense-multi)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Three Axis** - monitors the state of a single axis
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Sensor** - detects sensor events
* **Contact Sensor** - can detect contact (possible values: open,closed)
* **Acceleration Sensor** - allows for acceleration detection.
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C2 multi sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2450 (for Samsung SmartThings Multipurpose Sensor) battery / Two AAAA (for SmartSense Multi Sensor) batteries required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor is stuck on "open" or "closed"](https://support.smartthings.com/hc/en-us/articles/200955940-Troubleshooting-Samsung-SmartThings-Multipurpose-Sensor-is-stuck-on-open-or-closed-)
* [Troubleshooting: Temperature reading for the Samsung SmartThings Multipurpose Sensor is off](https://support.smartthings.com/hc/en-us/articles/200756845-Troubleshooting-Temperature-reading-for-the-Samsung-SmartThings-Multipurpose-Sensor-is-off)
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -127,13 +127,6 @@ def parse(String description) {
map = parseIasMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
@@ -235,9 +228,9 @@ private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
Map resultMap = [:]
if (garageSensor != "Yes"){
if(garageSensor != "Yes") {
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
}
}
return resultMap
}
@@ -333,11 +326,10 @@ private Map getTemperatureResult(value) {
'{{ device.displayName }} was {{ value }}°F'
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true,
unit: temperatureScale
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true
]
}
@@ -372,21 +364,6 @@ private getAccelerationResult(numValue) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() {
log.debug "Refreshing Values "
@@ -414,7 +391,7 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
log.debug "Configuring Reporting"

View File

@@ -223,10 +223,9 @@ def getTemperature(value) {
}
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
name: 'temperature',
value: value,
descriptionText: descriptionText
]
}

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,41 +0,0 @@
# Smartsense Open/Closed Sensor
Works with:
* [Samsung SmartThings Open/Closed Sensor](https://shop.smartthings.com/#!/packs/smartsense-open-closed-sensor/)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Contact Sensor** - can detect contact (possible values: open,closed)
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
A Category C2 open/closed sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2 3V battery required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
* [SmartSense Open/Closed Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202836844-SmartSense-Open-Closed-Sensor)

View File

@@ -92,13 +92,6 @@ def parse(String description) {
map = parseIasMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
@@ -226,8 +219,7 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}
@@ -242,21 +234,6 @@ private Map getContactResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
@@ -268,7 +245,7 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,41 +0,0 @@
# SmartSense Temp/Humidity Sensor
Works with:
* [Samsung SmartSense Temp/Humidity Sensor](https://shop.smartthings.com/#!/products/smartsense-temp-humidity-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Relative Humidity Measurement** - defines device measures relative humidity
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2 battery is required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
* [Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203040294)

View File

@@ -83,13 +83,6 @@ def parse(String description) {
map = parseCustomMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map"
return map ? createEvent(map) : null
}
@@ -233,8 +226,7 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}
@@ -247,20 +239,6 @@ private Map getHumidityResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh()
{
log.debug "refresh temperature, humidity, and battery"
@@ -276,7 +254,7 @@ def refresh()
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 7200, displayed: false)
log.debug "Configuring Reporting and Bindings."
def configCmds = [

View File

@@ -213,8 +213,7 @@ private Map getTemperatureResult(value) {
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
unit: temperatureScale
descriptionText: descriptionText
]
}

View File

@@ -19,7 +19,6 @@ metadata {
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
@@ -54,16 +53,7 @@ def parse(String description) {
def event = zigbee.getEvent(description)
if (event) {
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
sendEvent(event)
}
sendEvent(event)
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
@@ -82,20 +72,6 @@ def on() {
def setLevel(value) {
zigbee.setLevel(value)
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
@@ -103,7 +79,5 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

@@ -27,10 +27,6 @@ metadata {
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Health Check"
attribute "colorName", "string"
command "setGenericName"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
@@ -58,15 +54,15 @@ metadata {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorName", label: '${currentValue}'
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "colorTempSliderControl", "colorName", "refresh"])
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
@@ -82,22 +78,10 @@ private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
def parse(String description) {
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
log.debug event
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
if (event.name=="colorTemperature") {
setGenericName(event.value)
}
sendEvent(event)
}
def finalResult = zigbee.getEvent(description)
if (finalResult) {
log.debug finalResult
sendEvent(finalResult)
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
@@ -126,54 +110,20 @@ def on() {
def off() {
zigbee.off()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() {
zigbee.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}
def setColorTemperature(value) {
setGenericName(value)
zigbee.setColorTemperature(value)
}
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
def setGenericName(value){
if (value != null) {
def genericName = "White"
if (value < 3300) {
genericName = "Soft White"
} else if (value < 4150) {
genericName = "Moonlight"
} else if (value <= 5000) {
genericName = "Cool White"
} else if (value >= 5000) {
genericName = "Daylight"
}
sendEvent(name: "colorName", value: genericName)
}
}
def setLevel(value) {
zigbee.setLevel(value)
}

View File

@@ -22,7 +22,6 @@ metadata {
capability "Actuator"
capability "Color Temperature"
capability "Configuration"
capability "Health Check"
capability "Refresh"
capability "Switch"
capability "Switch Level"
@@ -36,7 +35,6 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
}
// UI tile definitions
@@ -51,6 +49,9 @@ metadata {
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("colorName", key: "SECONDARY_CONTROL") {
attributeState "colorName", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
@@ -60,12 +61,12 @@ metadata {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorName", label: '${currentValue}'
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
main(["switch"])
details(["switch", "colorTempSliderControl", "colorName", "refresh"])
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
@@ -74,19 +75,7 @@ def parse(String description) {
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
if (event.name=="colorTemperature") {
setGenericName(event.value)
}
sendEvent(event)
}
sendEvent(event)
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
@@ -106,29 +95,12 @@ def setLevel(value) {
zigbee.setLevel(value)
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
}

View File

@@ -78,7 +78,7 @@ def humidityHandler(evt) {
log.debug "Notification already sent within the last ${deltaMinutes} minutes"
} else {
log.debug "Humidity Rose Above ${tooHumid}: sending SMS and activating ${mySwitch}"
log.debug "Humidity Rose Above ${tooHumid}: sending SMS to $phone1 and activating ${mySwitch}"
send("${humiditySensor1.label} sensed high humidity level of ${evt.value}")
switch1?.on()
}
@@ -91,7 +91,7 @@ def humidityHandler(evt) {
log.debug "Notification already sent within the last ${deltaMinutes} minutes"
} else {
log.debug "Humidity Fell Below ${notHumidEnough}: sending SMS and activating ${mySwitch}"
log.debug "Humidity Fell Below ${notHumidEnough}: sending SMS to $phone1 and activating ${mySwitch}"
send("${humiditySensor1.label} sensed high humidity level of ${evt.value}")
switch1?.off()
}

View File

@@ -25,11 +25,15 @@ preferences {
def installed() {
subscribe(contact1, "contact", contactHandler)
subscribe(switch1, "switch.on", switchOnHandler)
subscribe(switch1, "switch.off", switchOffHandler)
}
def updated() {
unsubscribe()
subscribe(contact1, "contact", contactHandler)
subscribe(switch1, "switch.on", switchOnHandler)
subscribe(switch1, "switch.off", switchOffHandler)
}
def contactHandler(evt) {
@@ -42,4 +46,4 @@ def contactHandler(evt) {
if (evt.value == "closed") {
if(state.wasOn)switch1.on()
}
}
}

View File

@@ -1,145 +0,0 @@
/**
* JSON
*
* Copyright 2015 Jesse Newland
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "JSON API",
namespace: "jnewland",
author: "Jesse Newland",
description: "A JSON API for SmartThings",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
oauth: true)
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
if (!state.accessToken) {
createAccessToken()
}
}
preferences {
page(name: "copyConfig")
}
def copyConfig() {
if (!state.accessToken) {
createAccessToken()
}
dynamicPage(name: "copyConfig", title: "Config", install:true) {
section("Select devices to include in the /devices API call") {
input "switches", "capability.switch", title: "Switches", multiple: true, required: false
input "hues", "capability.colorControl", title: "Hues", multiple: true, required: false
}
section() {
paragraph "View this SmartApp's configuration to use it in other places."
href url:"https://graph-eu01-euwest1.api.smartthings.com/api/smartapps/installations/${app.id}/config?access_token=${state.accessToken}", style:"embedded", required:false, title:"Config", description:"Tap, select, copy, then click \"Done\""
}
section() {
href url:"https://graph-eu01-euwest1.api.smartthings.com/api/smartapps/installations/${app.id}/devices?access_token=${state.accessToken}", style:"embedded", required:false, title:"Debug", description:"View accessories JSON"
}
}
}
def renderConfig() {
def configJson = new groovy.json.JsonOutput().toJson([
description: "JSON API",
platforms: [
[
platform: "SmartThings",
name: "SmartThings",
app_id: app.id,
access_token: state.accessToken
]
],
])
def configString = new groovy.json.JsonOutput().prettyPrint(configJson)
render contentType: "text/plain", data: configString
}
def deviceCommandMap(device, type) {
device.supportedCommands.collectEntries { command->
def commandUrl = "https://graph-eu01-euwest1.api.smartthings.com/api/smartapps/installations/${app.id}/${type}/${device.id}/command/${command.name}?access_token=${state.accessToken}"
[
(command.name): commandUrl
]
}
}
def authorizedDevices() {
[
switches: switches,
hues: hues
]
}
def renderDevices() {
def deviceData = authorizedDevices().collectEntries { devices->
[
(devices.key): devices.value.collect { device->
[
name: device.displayName,
commands: deviceCommandMap(device, devices.key)
]
}
]
}
def deviceJson = new groovy.json.JsonOutput().toJson(deviceData)
def deviceString = new groovy.json.JsonOutput().prettyPrint(deviceJson)
render contentType: "application/json", data: deviceString
}
def deviceCommand() {
def device = authorizedDevices()[params.type].find { it.id == params.id }
def command = params.command
if (!device) {
httpError(404, "Device not found")
} else {
if (params.value) {
device."$command"(params.value)
} else {
device."$command"()
}
}
}
mappings {
if (!params.access_token || (params.access_token && params.access_token != state.accessToken)) {
path("/devices") { action: [GET: "authError"] }
path("/config") { action: [GET: "authError"] }
path("/:type/:id/command/:command") { action: [PUT: "authError"] }
} else {
path("/devices") { action: [GET: "renderDevices"] }
path("/config") { action: [GET: "renderConfig"] }
path("/:type/:id/command/:command") { action: [PUT: "deviceCommand"] }
}
}
def authError() {
[error: "Permission denied"]
}

View File

@@ -62,7 +62,7 @@ def initialize() {
}
def sendit(evt) {
log.debug "$evt.value: $evt"
log.debug "$evt.value: $evt, $settings"
sendMessage()
}
@@ -80,6 +80,6 @@ def sendMessage() {
sendSms phone3, msg
}
if (!phone1 && !phone2 && !phone3) {
sendPush msg
sendPush msg
}
}

View File

@@ -1,188 +0,0 @@
/**
* Medicine Management - Contact Sensor
*
* Copyright 2016 Jim Mangione
*
* 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.
*
* Logic:
* --- Send notification at the medicine reminder time IF draw wasn't alread opened in past 60 minutes
* --- If draw still isn't open 10 minutes AFTER reminder time, LED will turn RED.
* --- ----- Once draw IS open, LED will return back to it's original color
*
*/
import groovy.time.TimeCategory
definition(
name: "Medicine Management - Contact Sensor",
namespace: "MangioneImagery",
author: "Jim Mangione",
description: "This supports devices with capabilities of ContactSensor and ColorControl (LED). It sends an in-app and ambient light notification if you forget to open the drawer or cabinet where meds are stored. A reminder will be set to a single time per day. If the draw or cabinet isn't opened within 60 minutes of that reminder, an in-app message will be sent. If the draw or cabinet still isn't opened after an additional 10 minutes, then an LED light turns red until the draw or cabinet is opened",
category: "Health & Wellness",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
preferences {
section("My Medicine Draw/Cabinet"){
input "deviceContactSensor", "capability.contactSensor", title: "Opened Sensor"
}
section("Remind me to take my medicine at"){
input "reminderTime", "time", title: "Time"
}
// NOTE: Use REAL device - virtual device causes compilation errors
section("My LED Light"){
input "deviceLight", "capability.colorControl", title: "Smart light"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// will stop LED notification incase it was set by med reminder
subscribe(deviceContactSensor, "contact", contactHandler)
// how many minutes to look in the past from the reminder time, for an open draw
state.minutesToCheckOpenDraw = 60
// is true when LED notification is set after exceeding 10 minutes past reminder time
state.ledNotificationTriggered = false
// Set a timer to run once a day to notify if draw wasn't opened yet
schedule(reminderTime, checkOpenDrawInPast)
}
// Should turn off any LED notification on OPEN state
def contactHandler(evt){
if (evt.value == "open") {
// if LED notification triggered, reset it.
log.debug "Cabinet opened"
if (state.ledNotificationTriggered) {
resetLEDNotification()
}
}
}
// If the draw was NOT opened within 60 minutes of the timer send notification out.
def checkOpenDrawInPast(){
log.debug "Checking past 60 minutes of activity from $reminderTime"
// check activity of sensor for past 60 minutes for any OPENED status
def cabinetOpened = isOpened(state.minutesToCheckOpenDraw)
log.debug "Cabinet found opened: $cabinetOpened"
// if it's opened, then do nothing and assume they took their meds
if (!cabinetOpened) {
sendNotification("Hi, please remember to take your meds in the cabinet")
// if no open activity, send out notification and set new reminder
def reminderTimePlus10 = new Date(now() + (10 * 60000))
// needs to be scheduled if draw wasn't already opened
runOnce(reminderTimePlus10, checkOpenDrawAfterReminder)
}
}
// If the draw was NOT opened after 10 minutes past reminder, use LED notification
def checkOpenDrawAfterReminder(){
log.debug "Checking additional 10 minutes of activity from $reminderTime"
// check activity of sensor for past 10 minutes for any OPENED status
def cabinetOpened = isOpened(10)
log.debug "Cabinet found opened: $cabinetOpened"
// if no open activity, blink lights
if (!cabinetOpened) {
log.debug "Set LED to Notification color"
setLEDNotification()
}
}
// Helper function for sending out an app notification
def sendNotification(msg){
log.debug "Message Sent: $msg"
sendPush(msg)
}
// Check if the sensor has been opened since the minutes entered
// Return true if opened found, else false.
def isOpened(minutes){
// query last X minutes of activity log
def previousDateTime = new Date(now() - (minutes * 60000))
// capture all events recorded
def evts = deviceContactSensor.eventsSince(previousDateTime)
def cabinetOpened = false
if (evts.size() > 0) {
evts.each{
if(it.value == "open") {
cabinetOpened = true
}
}
}
return cabinetOpened
}
// Saves current color and sets the light to RED
def setLEDNotification(){
state.ledNotificationTriggered = true
// turn light back off when reset is called if it was originally off
state.ledState = deviceLight.currentValue("switch")
// set light to RED and store original color until stopped
state.origColor = deviceLight.currentValue("hue")
deviceLight.on()
deviceLight.setHue(100)
log.debug "LED set to RED. Original color stored: $state.origColor"
}
// Sets the color back to the original saved color
def resetLEDNotification(){
state.ledNotificationTriggered = false
// return color to original
log.debug "Reset LED color to: $state.origColor"
if (state.origColor != null) {
deviceLight.setHue(state.origColor)
}
// if the light was turned on just for the notification, turn it back off now
if (state.ledState == "off") {
deviceLight.off()
}
}

View File

@@ -1,189 +0,0 @@
/**
* Medicine Management - Temp-Motion
*
* Copyright 2016 Jim Mangione
*
* 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.
*
* Logic:
* --- If temp > threshold set, send notification
* --- Send in-app notification at the medicine reminder time if no motion is detected in past 60 minutes
* --- If motion still isn't detected 10 minutes AFTER reminder time, LED will turn RED
* --- ----- Once motion is detected, LED will turn back to it's original color
*/
import groovy.time.TimeCategory
definition(
name: "Medicine Management - Temp-Motion",
namespace: "MangioneImagery",
author: "Jim Mangione",
description: "This only supports devices with capabilities TemperatureMeasurement, AccelerationSensor and ColorControl (LED). Supports two use cases. First, will notifies via in-app if the fridge where meds are stored exceeds a temperature threshold set in degrees. Secondly, sends an in-app and ambient light notification if you forget to take your meds by sensing movement of the medicine box in the fridge. A reminder will be set to a single time per day. If the box isn't moved within 60 minutes of that reminder, an in-app message will be sent. If the box still isn't moved after an additional 10 minutes, then an LED light turns red until the box is moved",
category: "Health & Wellness",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
preferences {
section("My Medicine in the Refrigerator"){
input "deviceAccelerationSensor", "capability.accelerationSensor", required: true, multiple: false, title: "Movement"
input "deviceTemperatureMeasurement", "capability.temperatureMeasurement", required: true, multiple: false, title: "Temperature"
}
section("Temperature Threshold"){
input "tempThreshold", "number", title: "Temperature Threshold"
}
section("Remind me to take my medicine at"){
input "reminderTime", "time", title: "Time"
}
// NOTE: Use REAL device - virtual device causes compilation errors
section("My LED Light"){
input "deviceLight", "capability.colorControl", title: "Smart light"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// will notify when temp exceeds max
subscribe(deviceTemperatureMeasurement, "temperature", tempHandler)
// will stop LED notification incase it was set by med reminder
subscribe(deviceAccelerationSensor, "acceleration.active", motionHandler)
// how many minutes to look in the past from the reminder time
state.minutesToCheckPriorToReminder = 60
// Set a timer to run once a day to notify if draw wasn't opened yet
schedule(reminderTime, checkMotionInPast)
}
// If temp > 39 then send an app notification out.
def tempHandler(evt){
if (evt.doubleValue > tempThreshold) {
log.debug "Fridge temp of $evt.value exceeded threshold"
sendNotification("WARNING: Fridge temp is $evt.value with threshold of $tempThreshold")
}
}
// Should turn off any LED notification once motion detected
def motionHandler(evt){
// always call out to stop any possible LED notification
log.debug "Medication moved. Send stop LED notification"
resetLEDNotification()
}
// If no motion detected within 60 minutes of the timer send notification out.
def checkMotionInPast(){
log.debug "Checking past 60 minutes of activity from $reminderTime"
// check activity of sensor for past 60 minutes for any OPENED status
def movement = isMoved(state.minutesToCheckPriorToReminder)
log.debug "Motion found: $movement"
// if there was movement, then do nothing and assume they took their meds
if (!movement) {
sendNotification("Hi, please remember to take your meds in the fridge")
// if no movement, send out notification and set new reminder
def reminderTimePlus10 = new Date(now() + (10 * 60000))
// needs to be scheduled if draw wasn't already opened
runOnce(reminderTimePlus10, checkMotionAfterReminder)
}
}
// If still no movement after 10 minutes past reminder, use LED notification
def checkMotionAfterReminder(){
log.debug "Checking additional 10 minutes of activity from $reminderTime"
// check activity of sensor for past 10 minutes for any OPENED status
def movement = isMoved(10)
log.debug "Motion found: $movement"
// if no open activity, blink lights
if (!movement) {
log.debug "Notify LED API"
setLEDNotification()
}
}
// Helper function for sending out an app notification
def sendNotification(msg){
log.debug "Message Sent: $msg"
sendPush(msg)
}
// Check if the accelerometer has been activated since the minutes entered
// Return true if active, else false.
def isMoved(minutes){
// query last X minutes of activity log
def previousDateTime = new Date(now() - (minutes * 60000))
// capture all events recorded
def evts = deviceAccelerationSensor.eventsSince(previousDateTime)
def motion = false
if (evts.size() > 0) {
evts.each{
if(it.value == "active") {
motion = true
}
}
}
return motion
}
// Saves current color and sets the light to RED
def setLEDNotification(){
// turn light back off when reset is called if it was originally off
state.ledState = deviceLight.currentValue("switch")
// set light to RED and store original color until stopped
state.origColor = deviceLight.currentValue("hue")
deviceLight.on()
deviceLight.setHue(100)
log.debug "LED set to RED. Original color stored: $state.origColor"
}
// Sets the color back to the original saved color
def resetLEDNotification(){
// return color to original
log.debug "Reset LED color to: $state.origColor"
deviceLight.setHue(state.origColor)
// if the light was turned on just for the notification, turn it back off now
if (state.ledState == "off") {
deviceLight.off()
}
}

View File

@@ -77,7 +77,7 @@ def humidityHandler(evt) {
} else {
if (state.lastStatus != "off") {
log.debug "Humidity Rose Above $humidityHigh1: sending SMS and deactivating $mySwitch"
log.debug "Humidity Rose Above $humidityHigh1: sending SMS to $phone1 and deactivating $mySwitch"
send("${humiditySensor1.label} sensed high humidity level of ${evt.value}, turning off ${switch1.label}")
switch1?.off()
state.lastStatus = "off"
@@ -99,7 +99,7 @@ def humidityHandler(evt) {
} else {
if (state.lastStatus != "on") {
log.debug "Humidity Dropped Below $humidityLow1: sending SMS and activating $mySwitch"
log.debug "Humidity Dropped Below $humidityLow1: sending SMS to $phone1 and activating $mySwitch"
send("${humiditySensor1.label} sensed low humidity level of ${evt.value}, turning on ${switch1.label}")
switch1?.on()
state.lastStatus = "on"
@@ -125,4 +125,4 @@ private send(msg) {
}
log.debug msg
}
}

View File

@@ -66,7 +66,7 @@ def authPage() {
// get rid of next button until the user is actually auth'd
if (!oauthTokenProvided) {
return dynamicPage(name: "auth", title: "Login", nextPage: "", uninstall:uninstallAllowed) {
section() {
section(){
paragraph "Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button."
href url:redirectUrl, style:"embedded", required:true, title:"ecobee", description:description
}
@@ -76,7 +76,7 @@ def authPage() {
log.debug "thermostat list: $stats"
log.debug "sensor list: ${sensorsDiscovered()}"
return dynamicPage(name: "auth", title: "Select Your Thermostats", uninstall: true) {
section("") {
section(""){
paragraph "Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings."
input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats])
}
@@ -84,7 +84,7 @@ def authPage() {
def options = sensorsDiscovered() ?: []
def numFound = options.size() ?: 0
if (numFound > 0) {
section("") {
section(""){
paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings."
input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options)
}
@@ -115,12 +115,13 @@ def callback() {
def code = params.code
def oauthState = params.state
if (oauthState == atomicState.oauthInitState) {
if (oauthState == atomicState.oauthInitState){
def tokenParams = [
grant_type: "authorization_code",
code : code,
client_id : smartThingsClientId,
redirect_uri: callbackUrl
grant_type: "authorization_code",
code : code,
client_id : smartThingsClientId,
redirect_uri: callbackUrl
]
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
@@ -128,6 +129,9 @@ def callback() {
httpPost(uri: tokenUrl) { resp ->
atomicState.refreshToken = resp.data.refresh_token
atomicState.authToken = resp.data.access_token
log.debug "swapped token: $resp.data"
log.debug "atomicState.refreshToken: ${atomicState.refreshToken}"
log.debug "atomicState.authToken: ${atomicState.authToken}"
}
if (atomicState.authToken) {
@@ -144,8 +148,8 @@ def callback() {
def success() {
def message = """
<p>Your ecobee Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
<p>Your ecobee Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
connectionStatus(message)
}
@@ -167,63 +171,64 @@ def connectionStatus(message, redirectUrl = null) {
}
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=640">
<title>Ecobee & SmartThings connection</title>
<style type="text/css">
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 90%;
padding: 4%;
text-align: center;
}
img {
vertical-align: middle;
}
p {
font-size: 2.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
padding: 0 40px;
margin-bottom: 0;
}
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
</head>
<body>
<div class="container">
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=640">
<title>Ecobee & SmartThings connection</title>
<style type="text/css">
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 90%;
padding: 4%;
/*background: #eee;*/
text-align: center;
}
img {
vertical-align: middle;
}
p {
font-size: 2.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
padding: 0 40px;
margin-bottom: 0;
}
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
</head>
<body>
<div class="container">
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/ecobee%402x.png" alt="ecobee icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
${message}
</div>
</body>
</html>
"""
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
@@ -232,26 +237,19 @@ def getEcobeeThermostats() {
log.debug "getting device list"
atomicState.remoteSensors = []
def bodyParams = [
selection: [
selectionType: "registered",
selectionMatch: "",
includeRuntime: true,
includeSensors: true
]
]
def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}'
def deviceListParams = [
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
// TODO - the query string below is not consistent with the Ecobee docs:
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: toJson(bodyParams)]
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
query: [format: 'json', body: requestBody]
]
def stats = [:]
try {
httpGet(deviceListParams) { resp ->
if (resp.status == 200) {
resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors
@@ -291,10 +289,9 @@ Map sensorsDiscovered() {
}
def getThermostatDisplayName(stat) {
if(stat?.name) {
return stat.name.toString()
}
return (getThermostatTypeName(stat) + " (${stat.identifier})").toString()
if(stat?.name)
return stat.name.toString()
return (getThermostatTypeName(stat) + " (${stat.identifier})").toString()
}
def getThermostatTypeName(stat) {
@@ -313,6 +310,7 @@ def updated() {
}
def initialize() {
log.debug "initialize"
def devices = thermostats.collect { dni ->
def d = getChildDevice(dni)
@@ -352,6 +350,8 @@ def initialize() {
log.warn "delete: ${delete}, deleting ${delete.size()} thermostats"
delete.each { deleteChildDevice(it.deviceNetworkId) } //inherits from SmartApp (data-management)
atomicState.thermostatData = [:] //reset Map to store thermostat data
//send activity feeds to tell that device is connected
def notificationMessage = "is connected to SmartThings"
sendActivityFeeds(notificationMessage)
@@ -381,41 +381,75 @@ def pollHandler() {
}
def pollChildren(child = null) {
def thermostatIdsString = getChildDeviceIdsString()
log.debug "polling children: $thermostatIdsString"
def requestBody = [
selection: [
selectionType: "thermostats",
selectionMatch: thermostatIdsString,
includeExtendedRuntime: true,
includeSettings: true,
includeRuntime: true,
includeSensors: true
]
]
def thermostatIdsString = getChildDeviceIdsString()
log.debug "polling children: $thermostatIdsString"
def data = ""
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}'
def result = false
def pollParams = [
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
// TODO - the query string below is not consistent with the Ecobee docs:
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: toJson(requestBody)]
]
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
query: [format: 'json', body: jsonRequestBody]
]
try{
httpGet(pollParams) { resp ->
if(resp.status == 200) {
log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(resp.data.thermostatList)
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
atomicState.thermostatData = resp.data
updateSensorData()
atomicState.thermostats = resp.data.thermostatList.inject([:]) { collector, stat ->
def dni = [ app.id, stat.identifier ].join('.')
log.debug "updating dni $dni"
data = [
coolMode: (stat.settings.coolStages > 0),
heatMode: (stat.settings.heatStages > 0),
deviceTemperatureUnit: stat.settings.useCelsius,
minHeatingSetpoint: (stat.settings.heatRangeLow / 10),
maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10),
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity,
thermostatFanMode: stat.runtime.desiredFanMode
]
if (location.temperatureScale == "F")
{
data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"]
data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"]
data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"]
data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"]
data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"]
}
if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") {
data["deviceTemperatureUnit"] = "F"
} else {
data["deviceTemperatureUnit"] = "C"
}
collector[dni] = [data:data]
return collector
}
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
@@ -429,12 +463,13 @@ def pollChildren(child = null) {
}
// Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild() {
def pollChild(){
def devices = getChildDevices()
if (pollChildren()) {
if (pollChildren()){
devices.each { child ->
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")) {
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
def tData = atomicState.thermostats[child.device.deviceNetworkId]
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
@@ -457,38 +492,36 @@ void poll() {
}
def availableModes(child) {
debugEvent ("atomicState.thermostats = ${atomicState.thermostats}")
debugEvent ("Child DNI = ${child.device.deviceNetworkId}")
def tData = atomicState.thermostats[child.device.deviceNetworkId]
debugEvent("Data = ${tData}")
if(!tData) {
if(!tData)
{
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
return null
}
def modes = ["off"]
if (tData.data.heatMode) {
modes.add("heat")
}
if (tData.data.coolMode) {
modes.add("cool")
}
if (tData.data.autoMode) {
modes.add("auto")
}
if (tData.data.auxHeatMode) {
modes.add("auxHeatOnly")
}
if (tData.data.heatMode) modes.add("heat")
if (tData.data.coolMode) modes.add("cool")
if (tData.data.autoMode) modes.add("auto")
if (tData.data.auxHeatMode) modes.add("auxHeatOnly")
modes
return modes
}
def currentMode(child) {
debugEvent ("atomicState.Thermos = ${atomicState.thermostats}")
debugEvent ("Child DNI = ${child.device.deviceNetworkId}")
def tData = atomicState.thermostats[child.device.deviceNetworkId]
@@ -497,11 +530,14 @@ def currentMode(child) {
if(!tData) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
return null
}
def mode = tData.data.thermostatMode
return mode
mode
}
def updateSensorData() {
@@ -522,12 +558,12 @@ def updateSensorData() {
}
}
} else if (it.type == "occupancy") {
if(it.value == "true") {
occupancy = "active"
} else {
if(it.value == "true")
occupancy = "active"
else
occupancy = "inactive"
}
}
}
def dni = "ecobee_sensor-"+ it?.id + "-" + it?.code
@@ -546,7 +582,7 @@ def getChildDeviceIdsString() {
}
def toJson(Map m) {
return groovy.json.JsonOutput.toJson(m)
return new org.json.JSONObject(m).toString()
}
def toQueryString(Map m) {
@@ -559,24 +595,54 @@ private refreshAuthToken() {
if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else {
def refreshParams = [
method: 'POST',
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
method: 'POST',
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
log.debug refreshParams
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost
try {
def jsonMap
httpPost(refreshParams) { resp ->
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(resp.data)
}
}
log.debug resp
jsonMap = resp.data
if(resp.data) {
log.debug resp.data
debugEvent("Response = ${resp.data}")
atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token
debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}")
if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}"
"${atomicState.action}"()
atomicState.action = ""
}
}
atomicState.action = ""
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
def reAttemptPeriod = 300 // in sec
@@ -596,220 +662,118 @@ private refreshAuthToken() {
}
}
/**
* Saves the refresh and auth token from the passed-in JSON object,
* and invokes any previously executing action that did not complete due to
* an expired token.
*
* @param json - an object representing the parsed JSON response from Ecobee
*/
private void saveTokenAndResumeAction(json) {
log.debug "token response json: $json"
if (json) {
debugEvent("Response = $json")
atomicState.refreshToken = json?.refresh_token
atomicState.authToken = json?.access_token
if (atomicState.action) {
log.debug "got refresh token, executing next action: ${atomicState.action}"
"${atomicState.action}"()
}
} else {
log.warn "did not get response body from refresh token response"
}
atomicState.action = ""
def resumeProgram(child, deviceId) {
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}'
def result = sendJson(jsonRequestBody)
return result
}
/**
* Executes the resume program command on the Ecobee thermostat
* @param deviceId - the ID of the device
*
* @retrun true if the command was successful, false otherwise.
*/
boolean resumeProgram(deviceId) {
def payload = [
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "resumeProgram"
]
]
]
return sendCommandToEcobee(payload)
def setHold(child, heating, cooling, deviceId, sendHoldType) {
int h = heating * 10
int c = cooling * 10
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}'
def result = sendJson(child, jsonRequestBody)
return result
}
/**
* Executes the set hold command on the Ecobee thermostat
* @param heating - The heating temperature to set in fahrenheit
* @param cooling - the cooling temperature to set in fahrenheit
* @param deviceId - the ID of the device
* @param sendHoldType - the hold type to execute
*
* @return true if the command was successful, false otherwise
*/
boolean setHold(heating, cooling, deviceId, sendHoldType) {
// Ecobee requires that temp values be in fahrenheit multiplied by 10.
int h = heating * 10
int c = cooling * 10
def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) {
def payload = [
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "setHold",
params: [
coolHoldTemp: c,
heatHoldTemp: h,
holdType: sendHoldType
]
]
]
]
int h = heating * 10
int c = cooling * 10
return sendCommandToEcobee(payload)
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+', "fan": '+fanMode+' } } ]}'
def result = sendJson(child, jsonRequestBody)
return result
}
/**
* Executes the set fan mode command on the Ecobee thermostat
* @param heating - The heating temperature to set in fahrenheit
* @param cooling - the cooling temperature to set in fahrenheit
* @param deviceId - the ID of the device
* @param sendHoldType - the hold type to execute
* @param fanMode - the fan mode to set to
*
* @return true if the command was successful, false otherwise
*/
boolean setFanMode(heating, cooling, deviceId, sendHoldType, fanMode) {
// Ecobee requires that temp values be in fahrenheit multiplied by 10.
int h = heating * 10
int c = cooling * 10
def setMode(child, mode, deviceId) {
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}'
def payload = [
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
functions: [
[
type: "setHold",
params: [
coolHoldTemp: c,
heatHoldTemp: h,
holdType: sendHoldType,
fan: fanMode
]
]
]
]
return sendCommandToEcobee(payload)
def result = sendJson(jsonRequestBody)
return result
}
/**
* Sets the mode of the Ecobee thermostat
* @param mode - the mode to set to
* @param deviceId - the ID of the device
*
* @return true if the command was successful, false otherwise
*/
boolean setMode(mode, deviceId) {
def payload = [
selection: [
selectionType: "thermostats",
selectionMatch: deviceId,
includeRuntime: true
],
thermostat: [
settings: [
hvacMode: mode
]
]
]
return sendCommandToEcobee(payload)
}
def sendJson(child = null, String jsonBody) {
/**
* Makes a request to the Ecobee API to actuate the thermostat.
* Used by command methods to send commands to Ecobee.
*
* @param bodyParams - a map of request parameters to send to Ecobee.
*
* @return true if the command was accepted by Ecobee without error, false otherwise.
*/
private boolean sendCommandToEcobee(Map bodyParams) {
def isSuccess = false
def returnStatus = false
def cmdParams = [
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
body: toJson(bodyParams)
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
body: jsonBody
]
try{
httpPost(cmdParams) { resp ->
if(resp.status == 200) {
log.debug "updated ${resp.data}"
def returnStatus = resp.data.status.code
if (returnStatus == 0) {
log.debug "Successful call to ecobee API."
isSuccess = true
} else {
log.debug "Error return code = ${returnStatus}"
debugEvent("Error return code = ${returnStatus}")
}
}
}
httpPost(cmdParams) { resp ->
if(resp.status == 200) {
log.debug "updated ${resp.data}"
returnStatus = resp.data.status.code
if (resp.data.status.code == 0)
log.debug "Successful call to ecobee API."
else {
log.debug "Error return code = ${resp.data.status.code}"
debugEvent("Error return code = ${resp.data.status.code}")
}
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception Sending Json: " + e.response.data.status
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
if (e.response.data.status.code == 14) {
// TODO - figure out why we're setting the next action to be pollChildren
// after refreshing auth token. Is it to keep UI in sync, or just copy/paste error?
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
} else {
}
else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
return isSuccess
if (returnStatus == 0)
return true
else
return false
}
def getChildName() { return "Ecobee Thermostat" }
def getSensorChildName() { return "Ecobee Sensor" }
def getChildName() { "Ecobee Thermostat" }
def getSensorChildName() { "Ecobee Sensor" }
def getServerUrl() { return "https://graph.api.smartthings.com" }
def getShardUrl() { return getApiServerUrl() }
def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback" }
def getBuildRedirectUrl() { return "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" }
def getApiEndpoint() { return "https://api.ecobee.com" }
def getSmartThingsClientId() { return appSettings.clientId }
def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}&apiServerUrl=${shardUrl}" }
def getApiEndpoint() { "https://api.ecobee.com" }
def getSmartThingsClientId() { appSettings.clientId }
def debugEvent(message, displayEvent = false) {
def results = [
name: "appdebug",
descriptionText: message,
displayed: displayEvent
name: "appdebug",
descriptionText: message,
displayed: displayEvent
]
log.debug "Generating AppDebug Event: ${results}"
sendEvent (results)
}
def debugEventFromParent(child, message) {
if (child != null) { child.sendEvent("name":"debugEventFromParent", "value":message, "description":message, displayed: true, isStateChange: true)}
}
//send both push notification and mobile activity feeds
def sendPushAndFeeds(notificationMessage) {
def sendPushAndFeeds(notificationMessage){
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
if (atomicState.timeSendPush) {
if (now() - atomicState.timeSendPush > 86400000) { // notification is sent to remind user once a day
if (atomicState.timeSendPush){
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
@@ -822,58 +786,6 @@ def sendPushAndFeeds(notificationMessage) {
atomicState.authToken = null
}
/**
* Stores data about the thermostats in atomicState.
* @param thermostats - a list of thermostats as returned from the Ecobee API
*/
private void storeThermostatData(thermostats) {
log.trace "Storing thermostat data: $thermostats"
def data
atomicState.thermostats = thermostats.inject([:]) { collector, stat ->
def dni = [ app.id, stat.identifier ].join('.')
log.debug "updating dni $dni"
data = [
coolMode: (stat.settings.coolStages > 0),
heatMode: (stat.settings.heatStages > 0),
deviceTemperatureUnit: stat.settings.useCelsius,
minHeatingSetpoint: (stat.settings.heatRangeLow / 10),
maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10),
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: (stat.runtime.actualTemperature / 10),
heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity,
thermostatFanMode: stat.runtime.desiredFanMode
]
if (location.temperatureScale == "F") {
data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"]
data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"]
data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"]
data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"]
data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"]
}
if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") {
data["deviceTemperatureUnit"] = "F"
} else {
data["deviceTemperatureUnit"] = "C"
}
collector[dni] = [data:data]
return collector
}
log.debug "updated ${atomicState.thermostats?.size()} thermostats: ${atomicState.thermostats}"
}
def sendActivityFeeds(notificationMessage) {
def devices = getChildDevices()
devices.each { child ->
@@ -881,6 +793,14 @@ def sendActivityFeeds(notificationMessage) {
}
}
def roundC (tempC) {
return String.format("%.1f", (Math.round(tempC * 2))/2)
}
def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
}
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
}

View File

@@ -68,7 +68,7 @@ def scheduleCheck()
sendNotificationToContacts("No one has fed the dog", recipients)
}
else {
log.debug "Feeder was not opened since $midnight, texting one phone number"
log.debug "Feeder was not opened since $midnight, texting $phone1"
sendSms(phone1, "No one has fed the dog")
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -69,10 +69,10 @@ def temperatureHandler(evt) {
def alreadySentSms = recentEvents.count { it.doubleValue <= tooCold } > 1
if (alreadySentSms) {
log.debug "SMS already sent within the last $deltaMinutes minutes"
log.debug "SMS already sent to $phone1 within the last $deltaMinutes minutes"
// TODO: Send "Temperature back to normal" SMS, turn switch off
} else {
log.debug "Temperature dropped below $tooCold: sending SMS and activating $mySwitch"
log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch"
def tempScale = location.temperatureScale ?: "F"
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
switch1?.on()

View File

@@ -74,6 +74,8 @@ def authPage()
def redirectUrl = oauthInitUrl()
log.debug "RedirectURL = ${redirectUrl}"
return dynamicPage(name: "Credentials", title: "Life360", nextPage:"listCirclesPage", uninstall: uninstallOption, install:false) {
section {
href url:redirectUrl, style:"embedded", required:false, title:"Life360", description:description
@@ -255,6 +257,8 @@ def initializeLife360Connection() {
def oauthClientId = appSettings.clientId
def oauthClientSecret = appSettings.clientSecret
log.debug "Installed with settings: ${settings}"
initialize()
def username = settings.username
@@ -265,6 +269,8 @@ def initializeLife360Connection() {
def basicCredentials = "${oauthClientId}:${oauthClientSecret}"
def encodedCredentials = basicCredentials.encodeAsBase64().toString()
log.debug "Encoded Creds: ${encodedCredentials}"
// call life360, get OAUTH token using password flow, save
// curl -X POST -H "Authorization: Basic cFJFcXVnYWJSZXRyZTRFc3RldGhlcnVmcmVQdW1hbUV4dWNyRUh1YzptM2ZydXBSZXRSZXN3ZXJFQ2hBUHJFOTZxYWtFZHI0Vg=="
@@ -278,6 +284,8 @@ def initializeLife360Connection() {
"username=${username}&"+
"password=${password}"
log.debug "Post Body: ${postBody}"
def result = null
try {
@@ -287,6 +295,7 @@ def initializeLife360Connection() {
}
if (result.data.access_token) {
state.life360AccessToken = result.data.access_token
log.debug "Access Token = ${state.life360AccessToken}"
return true;
}
log.debug "Response=${result.data}"
@@ -524,6 +533,8 @@ def createCircleSubscription() {
def postBody = "url=${hookUrl}"
log.debug "Post Body: ${postBody}"
def result = null
try {
@@ -575,6 +586,8 @@ def updated() {
// log.debug "After Find Attempt."
log.debug "Member Id = ${member.id}, Name = ${member.firstName} ${member.lastName}, Email Address = ${member.loginEmail}"
// log.debug "External Id=${app.id}:${member.id}"
// create the device

View File

@@ -48,9 +48,9 @@ preferences {
}
section("Via a push notification and/or an SMS message"){
input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Enter a phone number to get SMS", 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: "Notify me via Push Notification", required: false, options: ["Yes", "No"]
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
}
}
section("Minimum time between messages (optional, defaults to every message)") {
@@ -111,17 +111,19 @@ private sendMessage(evt) {
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients, options)
} else {
if (pushAndPhone != 'No') {
if (!phone || pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
sendNotification(msg, options)
//sendPush(msg)
}
if (phone) {
options.phone = phone
log.debug 'sending SMS'
sendNotification(msg, options)
//sendSms(phone, msg)
}
sendNotification(msg, options)
}
if (frequency) {
state[evt.deviceId] = now()
}