mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-18 13:20:53 +00:00
Compare commits
43 Commits
PROD_2016.
...
MSA-1476-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
741dd36689 | ||
|
|
a6c7ab49b6 | ||
|
|
629d768575 | ||
|
|
4523498dab | ||
|
|
e89e45e000 | ||
|
|
78ec280e83 | ||
|
|
1f144d36e4 | ||
|
|
30274f0cd7 | ||
|
|
8869cd3af0 | ||
|
|
3184615e87 | ||
|
|
81318bafac | ||
|
|
b78bce55b2 | ||
|
|
01593c3973 | ||
|
|
763d7411e2 | ||
|
|
c703543f36 | ||
|
|
5db6ecda3e | ||
|
|
43b836f413 | ||
|
|
006b5e7bea | ||
|
|
62c8c19805 | ||
|
|
48e9a4bd6a | ||
|
|
07a4c0decc | ||
|
|
6aa09bb052 | ||
|
|
2f889de11a | ||
|
|
5584020e96 | ||
|
|
4ef2e694c2 | ||
|
|
826993cc45 | ||
|
|
ae3306928b | ||
|
|
8777ec5f6d | ||
|
|
91eb59a10d | ||
|
|
324ac13afb | ||
|
|
878eb66b8b | ||
|
|
8dfc270c2d | ||
|
|
614573a15c | ||
|
|
9c7b0875ba | ||
|
|
7568cbf781 | ||
|
|
159d3acf4f | ||
|
|
19b8a7eeb9 | ||
|
|
1b37d649a5 | ||
|
|
dedb0f8465 | ||
|
|
06acc13575 | ||
|
|
c051d719cc | ||
|
|
5e6b4f74e0 | ||
|
|
22185c5440 |
30
build.gradle
30
build.gradle
@@ -19,7 +19,7 @@ buildscript {
|
|||||||
username smartThingsArtifactoryUserName
|
username smartThingsArtifactoryUserName
|
||||||
password smartThingsArtifactoryPassword
|
password smartThingsArtifactoryPassword
|
||||||
}
|
}
|
||||||
url "http://artifactory.smartthings.com/libs-release-local"
|
url "https://artifactory.smartthings.com/libs-release-local"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,9 +27,37 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
credentials {
|
||||||
|
username smartThingsArtifactoryUserName
|
||||||
|
password smartThingsArtifactoryPassword
|
||||||
|
}
|
||||||
|
url "https://artifactory.smartthings.com/libs-release-local"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
devicetypes {
|
||||||
|
groovy {
|
||||||
|
srcDirs = ['devicetypes']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
smartapps {
|
||||||
|
groovy {
|
||||||
|
srcDirs = ['smartapps']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
|
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
|
||||||
|
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
|
||||||
|
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
|
smartappsCompile 'smartthings:appengine-common:0.1.8'
|
||||||
|
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
|
||||||
|
smartappsCompile 'org.grails:grails-web:2.3.11'
|
||||||
|
smartappsCompile 'org.json:json:20140107'
|
||||||
}
|
}
|
||||||
|
|
||||||
slackSendMessage {
|
slackSendMessage {
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ machine:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
override:
|
override:
|
||||||
- echo "Nothing to do."
|
- ./gradlew dependencies -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||||
|
post:
|
||||||
|
- ./gradlew compileSmartappsGroovy compileDevicetypesGroovy -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
override:
|
override:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
|
||||||
|
capability "Sensor"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
|
||||||
|
capability "Sensor"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
|
||||||
|
capability "Sensor"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
|
||||||
|
capability "Sensor"
|
||||||
|
|
||||||
attribute "rain", "number"
|
attribute "rain", "number"
|
||||||
attribute "rainSumHour", "number"
|
attribute "rainSumHour", "number"
|
||||||
attribute "rainSumDay", "number"
|
attribute "rainSumDay", "number"
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||||
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
||||||
attributeState("inactive", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
||||||
attributeState("active", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
||||||
}
|
}
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||||
|
|||||||
@@ -67,6 +67,6 @@ def refresh() {
|
|||||||
|
|
||||||
void poll() {
|
void poll() {
|
||||||
log.debug "Executing 'poll' using parent SmartApp"
|
log.debug "Executing 'poll' using parent SmartApp"
|
||||||
parent.pollChild()
|
parent.poll()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ def refresh() {
|
|||||||
|
|
||||||
void poll() {
|
void poll() {
|
||||||
log.debug "Executing 'poll' using parent SmartApp"
|
log.debug "Executing 'poll' using parent SmartApp"
|
||||||
parent.pollChild()
|
parent.poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateEvent(Map results) {
|
def generateEvent(Map results) {
|
||||||
|
|||||||
@@ -0,0 +1,630 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Author: SmartThings
|
||||||
|
* Date: 2013-12-02
|
||||||
|
*/
|
||||||
|
metadata {
|
||||||
|
definition (name: "ZigBee CentraLite Thermostat", namespace: "smartthings", author: "SmartThings") {
|
||||||
|
capability "Actuator"
|
||||||
|
capability "Temperature Measurement"
|
||||||
|
capability "Thermostat"
|
||||||
|
capability "Configuration"
|
||||||
|
capability "Refresh"
|
||||||
|
capability "Sensor"
|
||||||
|
capability "Polling"
|
||||||
|
capability "Battery"
|
||||||
|
|
||||||
|
|
||||||
|
// Custom commands
|
||||||
|
command "raiseHeatLevel"
|
||||||
|
command "lowHeatLevel"
|
||||||
|
command "raiseCoolLevel"
|
||||||
|
command "lowCoolLevel"
|
||||||
|
command "setTemperature"
|
||||||
|
command "setThermostatHoldMode"
|
||||||
|
command "getPowerSource"
|
||||||
|
|
||||||
|
attribute "temperatureScale", "string"
|
||||||
|
attribute "thermostatHoldMode", "string"
|
||||||
|
attribute "powerSource", "string"
|
||||||
|
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0020,0201,0202,0204,0B05", outClusters: "000A, 0019"
|
||||||
|
}
|
||||||
|
|
||||||
|
// simulator metadata
|
||||||
|
simulator { }
|
||||||
|
|
||||||
|
tiles(scale: 2) {
|
||||||
|
|
||||||
|
multiAttributeTile(name:"summary", type: "thermostat", width: 6, height: 4) {
|
||||||
|
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||||
|
attributeState("temperature", label:'${currentValue}°', unit:"dF",
|
||||||
|
backgroundColors:[
|
||||||
|
// Celsius Color Range
|
||||||
|
[value: 0, color: "#153591"],
|
||||||
|
[value: 7, color: "#1e9cbb"],
|
||||||
|
[value: 15, color: "#90d2a7"],
|
||||||
|
[value: 23, color: "#44b621"],
|
||||||
|
[value: 29, color: "#f1d801"],
|
||||||
|
[value: 33, color: "#d04e00"],
|
||||||
|
[value: 36, color: "#bc2323"],
|
||||||
|
// Fahrenheit Color Range
|
||||||
|
[value: 40, color: "#153591"],
|
||||||
|
[value: 44, color: "#1e9cbb"],
|
||||||
|
[value: 59, color: "#90d2a7"],
|
||||||
|
[value: 65, color: "#99ff33"],
|
||||||
|
[value: 74, color: "#44b621"],
|
||||||
|
[value: 84, color: "#f1d801"],
|
||||||
|
[value: 92, color: "#d04e00"],
|
||||||
|
[value: 96, color: "#bc2323"]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||||
|
attributeState("default", action: "setTemperature")
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAttribute("device.powerSource", key: "SECONDARY_CONTROL") {
|
||||||
|
attributeState("default", label:'PowerSrc: ${currentValue}')
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
||||||
|
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||||
|
}
|
||||||
|
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
||||||
|
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||||
|
state("temperature", label:'${currentValue}°', unit:"",
|
||||||
|
backgroundColors:[
|
||||||
|
// Celsius Color Range
|
||||||
|
[value: 0, color: "#153591"],
|
||||||
|
[value: 7, color: "#1e9cbb"],
|
||||||
|
[value: 15, color: "#90d2a7"],
|
||||||
|
[value: 23, color: "#44b621"],
|
||||||
|
[value: 29, color: "#f1d801"],
|
||||||
|
[value: 33, color: "#d04e00"],
|
||||||
|
[value: 36, color: "#bc2323"],
|
||||||
|
// Fahrenheit Color Range
|
||||||
|
[value: 40, color: "#153591"],
|
||||||
|
[value: 44, color: "#1e9cbb"],
|
||||||
|
[value: 59, color: "#90d2a7"],
|
||||||
|
[value: 65, color: "#99ff33"],
|
||||||
|
[value: 74, color: "#44b621"],
|
||||||
|
[value: 84, color: "#f1d801"],
|
||||||
|
[value: 92, color: "#d04e00"],
|
||||||
|
[value: 96, color: "#bc2323"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("mode", "device.thermostatMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
|
||||||
|
state "off", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.heating-cooling-off"
|
||||||
|
state "cool", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.cool", backgroundColor: '#99ddff'
|
||||||
|
state "heat", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.heat", backgroundColor: '#fd631c'
|
||||||
|
state "emergencyHeat", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.emergency-heat", backgroundColor: '#E11102'
|
||||||
|
//state "auto", label:'', action:"thermostat.setThermostatMode", icon: "st.thermostat.auto", backgroundColor: '#ffff00'
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("fanMode", "device.thermostatFanMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
|
||||||
|
state "fanAuto", label:'', action:"thermostat.setThermostatFanMode", icon: "st.thermostat.fan-auto", backgroundColor: '#ffff00'
|
||||||
|
state "fanOn", label:'', action:"thermostat.setThermostatFanMode", icon: "st.thermostat.fan-on", backgroundColor: '#ffff00'
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("holdMode", "device.thermostatHoldMode", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
|
||||||
|
state "holdOff", label:'Hold Off', action:"setThermostatHoldMode", icon: "st.Lighting.light13", backgroundColor:"#ffffff", nextState:"holdOff"
|
||||||
|
state "holdOn", label:'Hold On', action:"setThermostatHoldMode", icon: "st.Lighting.light11", backgroundColor:"#ffb3ff", nextState:"holdOn"
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("raiseHeatLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "raiseHeatLevel", label:' ', action:"raiseHeatLevel", icon:"st.thermostat.thermostat-up"
|
||||||
|
}
|
||||||
|
valueTile("heatingSetpoint", "device.heatingSetpoint", height: 1, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "heat", label:'${currentValue}° Heat', unit:"", backgroundColor:"#fd631c"
|
||||||
|
}
|
||||||
|
standardTile("lowHeatLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "lowHeatLevel", label:' ', action:"lowHeatLevel", icon:"st.thermostat.thermostat-down"
|
||||||
|
}
|
||||||
|
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 4, inactiveLabel: false) {
|
||||||
|
state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00"
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("raiseCoolLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "raiseCoolLevel", label:' ', action:"raiseCoolLevel", icon:"st.thermostat.thermostat-up"
|
||||||
|
}
|
||||||
|
valueTile("coolingSetpoint", "device.coolingSetpoint", , height: 1, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "cool", label:'${currentValue}° Cool', unit:"", backgroundColor:"#66ccff"
|
||||||
|
}
|
||||||
|
standardTile("lowCoolLevel", "device.heatingSetpoint", canChangeIcon: false, , height: 0.5, width: 0.5, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "lowCoolLevel", label:' ', action:"lowCoolLevel", icon:"st.thermostat.thermostat-down"
|
||||||
|
}
|
||||||
|
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 4, inactiveLabel: false) {
|
||||||
|
state "setCoolingSetpoint", action:"thermostat.setCoolingSetpoint", backgroundColor: "#003CEC"
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("refresh", "device.temperature", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
|
}
|
||||||
|
standardTile("configure", "device.configure", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||||
|
}
|
||||||
|
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||||
|
state "battery", label:'${currentValue}% Battery', unit:""
|
||||||
|
}
|
||||||
|
main(["temperature", "summary"])
|
||||||
|
details(["summary", "holdMode", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "battery", "refresh"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def setTemperature(setpoint) {
|
||||||
|
log.debug "setTemperature() called with setpoint ${setpoint}. "
|
||||||
|
log.debug "Current temperature: ${device.currentValue("temperature")}. Heat Setpoint: ${device.currentValue("heatingSetpoint")}. Cool Setpoint: ${device.currentValue("coolingSetpoint")}"
|
||||||
|
|
||||||
|
def mode = device.currentValue("thermostatMode")
|
||||||
|
def midpoint
|
||||||
|
def targetvalue
|
||||||
|
|
||||||
|
if (mode == "off") {
|
||||||
|
log.warn "setTemperature(): this mode: $mode does not allow raiseSetpoint"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
def currentTemp = device.currentValue("temperature")
|
||||||
|
def deltaTemp = setpoint - currentTemp
|
||||||
|
|
||||||
|
log.debug "deltaTemp = ${deltaTemp}"
|
||||||
|
|
||||||
|
if (mode == "heat") {
|
||||||
|
// Change the heat
|
||||||
|
log.debug "setTemperature(): change the heat temp"
|
||||||
|
if (deltaTemp < 0) {
|
||||||
|
lowHeatLevel()
|
||||||
|
} else if (deltaTemp > 0) {
|
||||||
|
raiseHeatLevel()
|
||||||
|
}
|
||||||
|
} else if (mode == "cool") {
|
||||||
|
// Change the cool
|
||||||
|
log.debug "setTemperature(): change the cool temp"
|
||||||
|
if (deltaTemp < 0) {
|
||||||
|
lowCoolLevel()
|
||||||
|
} else if (deltaTemp > 0) {
|
||||||
|
raiseCoolLevel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def raiseHeatLevel(){
|
||||||
|
|
||||||
|
def mode = device.currentValue("thermostatMode")
|
||||||
|
|
||||||
|
if (mode == "off") {
|
||||||
|
log.warn "raiseHeatLevel(): this mode: $mode does not allow raiseHeatLevel"
|
||||||
|
} else {
|
||||||
|
def locationScale = getTemperatureScale()
|
||||||
|
def maxTemp
|
||||||
|
def minTemp
|
||||||
|
if (locationScale == "C") {
|
||||||
|
maxTemp = 44 // Max Temp in C
|
||||||
|
minTemp = 7 // Min Temp in C
|
||||||
|
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
|
||||||
|
} else {
|
||||||
|
maxTemp = 86 // Max temp in F
|
||||||
|
minTemp = 30 // Max temp in F
|
||||||
|
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
|
||||||
|
}
|
||||||
|
|
||||||
|
def currentLevel = device.currentValue("heatingSetpoint")
|
||||||
|
int nextLevel = currentLevel.toInteger() + 1
|
||||||
|
|
||||||
|
if( nextLevel > maxTemp){
|
||||||
|
nextLevel = maxTemp
|
||||||
|
}
|
||||||
|
log.debug "Setting heat set point up to: ${nextLevel}"
|
||||||
|
setHeatingSetpoint(nextLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def lowHeatLevel(){
|
||||||
|
if (mode == "off") {
|
||||||
|
log.warn "lowHeatLevel(): this mode: $mode does not allow lowHeatLevel"
|
||||||
|
} else {
|
||||||
|
def locationScale = getTemperatureScale()
|
||||||
|
def maxTemp
|
||||||
|
def minTemp
|
||||||
|
if (locationScale == "C") {
|
||||||
|
maxTemp = 44 // Max Temp in C
|
||||||
|
minTemp = 7 // Min Temp in C
|
||||||
|
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
|
||||||
|
} else {
|
||||||
|
maxTemp = 86 // Max temp in F
|
||||||
|
minTemp = 30 // Max temp in F
|
||||||
|
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
|
||||||
|
}
|
||||||
|
|
||||||
|
def currentLevel = device.currentValue("heatingSetpoint")
|
||||||
|
int nextLevel = currentLevel.toInteger() - 1
|
||||||
|
|
||||||
|
if( nextLevel < minTemp){
|
||||||
|
nextLevel = minTemp
|
||||||
|
}
|
||||||
|
log.debug "Setting heat set point down to: ${nextLevel}"
|
||||||
|
setHeatingSetpoint(nextLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def raiseCoolLevel(){
|
||||||
|
if (mode == "off") {
|
||||||
|
log.warn "raiseCoolLevel(): this mode: $mode does not allow raiseCoolLevel"
|
||||||
|
} else {
|
||||||
|
def locationScale = getTemperatureScale()
|
||||||
|
def maxTemp
|
||||||
|
def minTemp
|
||||||
|
if (locationScale == "C") {
|
||||||
|
maxTemp = 44 // Max Temp in C
|
||||||
|
minTemp = 7 // Min Temp in C
|
||||||
|
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
|
||||||
|
} else {
|
||||||
|
maxTemp = 86 // Max temp in F
|
||||||
|
minTemp = 30 // Max temp in F
|
||||||
|
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
|
||||||
|
}
|
||||||
|
|
||||||
|
def currentLevel = device.currentValue("coolingSetpoint")
|
||||||
|
int nextLevel = currentLevel.toInteger() + 1
|
||||||
|
|
||||||
|
if( nextLevel > maxTemp){
|
||||||
|
nextLevel = maxTemp
|
||||||
|
}
|
||||||
|
log.debug "Setting cool set point up to: ${nextLevel}"
|
||||||
|
setCoolingSetpoint(nextLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def lowCoolLevel(){
|
||||||
|
if (mode == "off") {
|
||||||
|
log.warn "lowCoolLevel(): this mode: $mode does not allow lowCoolLevel"
|
||||||
|
} else {
|
||||||
|
def locationScale = getTemperatureScale()
|
||||||
|
def maxTemp
|
||||||
|
def minTemp
|
||||||
|
if (locationScale == "C") {
|
||||||
|
maxTemp = 44 // Max Temp in C
|
||||||
|
minTemp = 7 // Min Temp in C
|
||||||
|
log.trace "Location is in Celsius, MaxTemp: $maxTemp, MinTemp: $minTemp"
|
||||||
|
} else {
|
||||||
|
maxTemp = 86 // Max temp in F
|
||||||
|
minTemp = 30 // Max temp in F
|
||||||
|
log.trace "Location is in Farenheit, MaxTemp: $maxTemp, MinTemp: $minTemp"
|
||||||
|
}
|
||||||
|
|
||||||
|
def currentLevel = device.currentValue("coolingSetpoint")
|
||||||
|
int nextLevel = currentLevel.toInteger() - 1
|
||||||
|
|
||||||
|
if( nextLevel < minTemp){
|
||||||
|
nextLevel = minTemp
|
||||||
|
}
|
||||||
|
log.debug "Setting cool set point down to: ${nextLevel}"
|
||||||
|
setCoolingSetpoint(nextLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// parse events into attributes
|
||||||
|
def parse(String description) {
|
||||||
|
log.debug "Parse description $description"
|
||||||
|
def map = [:]
|
||||||
|
if (description?.startsWith("read attr -")) {
|
||||||
|
def descMap = parseDescriptionAsMap(description)
|
||||||
|
log.debug "Desc Map: $descMap"
|
||||||
|
if (descMap.cluster == "0201" && descMap.attrId == "0000") {
|
||||||
|
log.debug "TEMP"
|
||||||
|
map.name = "temperature"
|
||||||
|
map.value = getTemperature(descMap.value)
|
||||||
|
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
|
||||||
|
log.debug "COOLING SETPOINT"
|
||||||
|
map.name = "coolingSetpoint"
|
||||||
|
map.value = getTemperature(descMap.value)
|
||||||
|
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
|
||||||
|
log.debug "HEATING SETPOINT"
|
||||||
|
map.name = "heatingSetpoint"
|
||||||
|
map.value = getTemperature(descMap.value)
|
||||||
|
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
|
||||||
|
log.debug "MODE"
|
||||||
|
map.name = "thermostatMode"
|
||||||
|
map.value = getModeMap()[descMap.value]
|
||||||
|
} else if (descMap.cluster == "0202" && descMap.attrId == "0000") {
|
||||||
|
log.debug "FAN MODE"
|
||||||
|
map.name = "thermostatFanMode"
|
||||||
|
map.value = getFanModeMap()[descMap.value]
|
||||||
|
} else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||||
|
log.debug "BATTERY"
|
||||||
|
map.name = "battery"
|
||||||
|
map.value = getBatteryLevel(descMap.value)
|
||||||
|
} else if (descMap.cluster == "0201" && descMap.attrId == "001e") {
|
||||||
|
log.debug "RUN MODE"
|
||||||
|
map.name = "thermostatRunMode"
|
||||||
|
//map.value = getRunModeMap()[descMap.value]
|
||||||
|
map.value = getThermostatRunMode(descMap.value)
|
||||||
|
} else if (descMap.cluster == "0201" && descMap.attrId == "0023") {
|
||||||
|
log.debug "HOLD MODE"
|
||||||
|
map.name = "thermostatHoldMode"
|
||||||
|
map.value = getHoldModeMap()[descMap.value]
|
||||||
|
} else if (descMap.cluster == "0000" && descMap.attrId == "0007") {
|
||||||
|
log.debug "Power Source"
|
||||||
|
map.name = "powerSource"
|
||||||
|
map.value = getPowerSource()[descMap.value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def result = null
|
||||||
|
if (map) {
|
||||||
|
result = createEvent(map)
|
||||||
|
}
|
||||||
|
log.debug "Parse returned $map"
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
def parseDescriptionAsMap(description) {
|
||||||
|
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||||
|
def nameAndValue = param.split(":")
|
||||||
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getModeMap() { [
|
||||||
|
"00":"off",
|
||||||
|
//"01":"auto",
|
||||||
|
"03":"cool",
|
||||||
|
"04":"heat",
|
||||||
|
"05":"emergencyHeat"
|
||||||
|
]}
|
||||||
|
|
||||||
|
def getHoldModeMap() { [
|
||||||
|
"00":"holdOff",
|
||||||
|
"01":"holdOn",
|
||||||
|
]}
|
||||||
|
|
||||||
|
def getPowerSource() { [
|
||||||
|
"01":"24VAC",
|
||||||
|
"03":"Battery",
|
||||||
|
]}
|
||||||
|
|
||||||
|
|
||||||
|
def getFanModeMap() { [
|
||||||
|
"04":"fanOn",
|
||||||
|
"05":"fanAuto"
|
||||||
|
]}
|
||||||
|
|
||||||
|
|
||||||
|
def getThermostatRunMode(value) {
|
||||||
|
if (value != null) {
|
||||||
|
def RunModeValue = Integer.parseInt(value, 16)
|
||||||
|
log.debug "Thermostat RunMode: ${RunModeValue} "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def getTemperature(value) {
|
||||||
|
if (value != null) {
|
||||||
|
def celsius = Integer.parseInt(value, 16) / 100
|
||||||
|
if (getTemperatureScale() == "C") {
|
||||||
|
return celsius
|
||||||
|
} else {
|
||||||
|
return Math.round(celsiusToFahrenheit(celsius))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def setHeatingSetpoint(degrees) {
|
||||||
|
if (degrees != null) {
|
||||||
|
def temperatureScale = getTemperatureScale()
|
||||||
|
|
||||||
|
def degreesInteger = Math.round(degrees)
|
||||||
|
log.debug "setHeatingSetpoint({$degreesInteger} ${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) + "}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def setCoolingSetpoint(degrees) {
|
||||||
|
if (degrees != null) {
|
||||||
|
def degreesInteger = Math.round(degrees)
|
||||||
|
log.debug "setCoolingSetpoint({$degreesInteger} ${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) + "}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def modes() {
|
||||||
|
["off", "cool", "heat", "emergencyHeat"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def setThermostatMode() {
|
||||||
|
log.debug "switching thermostatMode"
|
||||||
|
def currentMode = device.currentState("thermostatMode")?.value
|
||||||
|
def modeOrder = modes()
|
||||||
|
log.debug "modeOrder: ${modeOrder}"
|
||||||
|
def index = modeOrder.indexOf(currentMode)
|
||||||
|
log.debug "index: ${index}"
|
||||||
|
def next = index >= 0 && index < modeOrder.size() - 1 ? modeOrder[index + 1] : modeOrder[0]
|
||||||
|
log.debug "switching mode from $currentMode to $next"
|
||||||
|
"$next"()
|
||||||
|
}
|
||||||
|
|
||||||
|
def setThermostatFanMode() {
|
||||||
|
log.debug "Switching fan mode"
|
||||||
|
def currentFanMode = device.currentState("thermostatFanMode")?.value
|
||||||
|
log.debug "switching fan from current mode: $currentFanMode"
|
||||||
|
def returnCommand
|
||||||
|
|
||||||
|
switch (currentFanMode) {
|
||||||
|
case "fanAuto":
|
||||||
|
returnCommand = fanOn()
|
||||||
|
break
|
||||||
|
case "fanOn":
|
||||||
|
returnCommand = fanAuto()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if(!currentFanMode) { returnCommand = fanAuto() }
|
||||||
|
returnCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def setThermostatHoldMode() {
|
||||||
|
log.debug "Switching Hold mode"
|
||||||
|
def currentHoldMode = device.currentState("thermostatHoldMode")?.value
|
||||||
|
log.debug "switching thermostat from current mode: $currentHoldMode"
|
||||||
|
def returnCommand
|
||||||
|
|
||||||
|
switch (currentHoldMode) {
|
||||||
|
case "holdOff":
|
||||||
|
returnCommand = holdOn()
|
||||||
|
break
|
||||||
|
case "holdOn":
|
||||||
|
returnCommand = holdOff()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if(!currentHoldMode) { returnCommand = holdOff() }
|
||||||
|
returnCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
def setThermostatMode(String value) {
|
||||||
|
log.debug "setThermostatMode({$value})"
|
||||||
|
"$value"()
|
||||||
|
}
|
||||||
|
|
||||||
|
def setThermostatFanMode(String value) {
|
||||||
|
log.debug "setThermostatFanMode({$value})"
|
||||||
|
"$value"()
|
||||||
|
}
|
||||||
|
|
||||||
|
def setThermostatHoldMode(String value) {
|
||||||
|
log.debug "setThermostatHoldMode({$value})"
|
||||||
|
"$value"()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def off() {
|
||||||
|
log.debug "off"
|
||||||
|
sendEvent("name":"thermostatMode", "value":"off")
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {00}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def cool() {
|
||||||
|
log.debug "cool"
|
||||||
|
sendEvent("name":"thermostatMode", "value":"cool")
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {03}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def heat() {
|
||||||
|
log.debug "heat"
|
||||||
|
sendEvent("name":"thermostatMode", "value":"heat")
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {04}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def emergencyHeat() {
|
||||||
|
log.debug "emergencyHeat"
|
||||||
|
sendEvent("name":"thermostatMode", "value":"emergencyHeat")
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x1C 0x30 {05}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def on() {
|
||||||
|
fanOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
def fanOn() {
|
||||||
|
log.debug "fanOn"
|
||||||
|
sendEvent("name":"thermostatFanMode", "value":"fanOn")
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 0x202 0 0x30 {04}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def auto() {
|
||||||
|
fanAuto()
|
||||||
|
}
|
||||||
|
|
||||||
|
def fanAuto() {
|
||||||
|
log.debug "fanAuto"
|
||||||
|
sendEvent("name":"thermostatFanMode", "value":"fanAuto")
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 0x202 0 0x30 {05}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def holdOn() {
|
||||||
|
log.debug "Set Hold On for thermostat"
|
||||||
|
sendEvent("name":"thermostatHoldMode", "value":"holdOn")
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x23 0x30 {01}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def holdOff() {
|
||||||
|
log.debug "Set Hold Off for thermostat"
|
||||||
|
sendEvent("name":"thermostatHoldMode", "value":"holdOff")
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x23 0x30 {00}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commment out below if no C-wire since it will kill the batteries.
|
||||||
|
def poll() {
|
||||||
|
// log.debug "Executing 'poll'"
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def configure() {
|
||||||
|
|
||||||
|
log.debug "binding to Thermostat and Fan Control cluster"
|
||||||
|
[
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 0x000 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 0x201 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 0x202 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
|
||||||
|
"zcl global send-me-a-report 1 0x20 0x20 3600 86400 {01}", "delay 100", //battery report request
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def refresh()
|
||||||
|
{
|
||||||
|
log.debug "refresh called"
|
||||||
|
[
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x000 0x07", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x201 0", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x11", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x12", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x1C", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x1E", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x201 0x23", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x001 0x20", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x202 0"
|
||||||
|
] + configure()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private getBatteryLevel(rawValue) {
|
||||||
|
def intValue = Integer.parseInt(rawValue,16)
|
||||||
|
def min = 2.1
|
||||||
|
def max = 3.0
|
||||||
|
def vBatt = intValue / 10
|
||||||
|
return ((vBatt - min) / (max - min) * 100) as int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private hex(value) {
|
||||||
|
new BigInteger(Math.round(value).toString()).toString(16)
|
||||||
|
}
|
||||||
@@ -65,7 +65,16 @@ void updateSwitch() {
|
|||||||
private void updateAll(devices) {
|
private void updateAll(devices) {
|
||||||
def command = request.JSON?.command
|
def command = request.JSON?.command
|
||||||
if (command) {
|
if (command) {
|
||||||
devices."$command"()
|
switch(command) {
|
||||||
|
case "on":
|
||||||
|
devices.on()
|
||||||
|
break
|
||||||
|
case "off":
|
||||||
|
devices.off()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +86,16 @@ private void update(devices) {
|
|||||||
if (!device) {
|
if (!device) {
|
||||||
httpError(404, "Device not found")
|
httpError(404, "Device not found")
|
||||||
} else {
|
} else {
|
||||||
device."$command"()
|
switch(command) {
|
||||||
|
case "on":
|
||||||
|
device.on()
|
||||||
|
break
|
||||||
|
case "off":
|
||||||
|
device.off()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ def authPage() {
|
|||||||
if (canInstallLabs()) {
|
if (canInstallLabs()) {
|
||||||
|
|
||||||
def redirectUrl = getBuildRedirectUrl()
|
def redirectUrl = getBuildRedirectUrl()
|
||||||
log.debug "Redirect url = ${redirectUrl}"
|
// log.debug "Redirect url = ${redirectUrl}"
|
||||||
|
|
||||||
if (state.authToken) {
|
if (state.authToken) {
|
||||||
description = "Tap 'Next' to proceed"
|
description = "Tap 'Next' to proceed"
|
||||||
@@ -113,13 +113,13 @@ def oauthInitUrl() {
|
|||||||
scope: "read_station"
|
scope: "read_station"
|
||||||
]
|
]
|
||||||
|
|
||||||
log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
|
// log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
|
||||||
|
|
||||||
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
|
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
def callback() {
|
def callback() {
|
||||||
log.debug "callback()>> params: $params, params.code ${params.code}"
|
// log.debug "callback()>> params: $params, params.code ${params.code}"
|
||||||
|
|
||||||
def code = params.code
|
def code = params.code
|
||||||
def oauthState = params.state
|
def oauthState = params.state
|
||||||
@@ -135,7 +135,7 @@ def callback() {
|
|||||||
scope: "read_station"
|
scope: "read_station"
|
||||||
]
|
]
|
||||||
|
|
||||||
log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
|
// log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
|
||||||
|
|
||||||
def tokenUrl = getVendorTokenPath()
|
def tokenUrl = getVendorTokenPath()
|
||||||
def params = [
|
def params = [
|
||||||
@@ -144,7 +144,7 @@ def callback() {
|
|||||||
body: tokenParams
|
body: tokenParams
|
||||||
]
|
]
|
||||||
|
|
||||||
log.debug "PARAMS: ${params}"
|
// log.debug "PARAMS: ${params}"
|
||||||
|
|
||||||
httpPost(params) { resp ->
|
httpPost(params) { resp ->
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ def callback() {
|
|||||||
state.refreshToken = data.refresh_token
|
state.refreshToken = data.refresh_token
|
||||||
state.authToken = data.access_token
|
state.authToken = data.access_token
|
||||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||||
log.debug "swapped token: $resp.data"
|
// log.debug "swapped token: $resp.data"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ def refreshToken() {
|
|||||||
|
|
||||||
response.data.each {key, value ->
|
response.data.each {key, value ->
|
||||||
def data = slurper.parseText(key);
|
def data = slurper.parseText(key);
|
||||||
log.debug "Data: $data"
|
// log.debug "Data: $data"
|
||||||
|
|
||||||
state.refreshToken = data.refresh_token
|
state.refreshToken = data.refresh_token
|
||||||
state.accessToken = data.access_token
|
state.accessToken = data.access_token
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
section("Temperature monitor?") {
|
section("Temperature monitor?") {
|
||||||
input "temp", "capability.temperatureMeasurement", title: "Temp Sensor", required: false
|
input "temp", "capability.temperatureMeasurement", title: "Temperature Sensor", required: false
|
||||||
input "maxTemp", "number", title: "Max Temp?", required: false
|
input "maxTemp", "number", title: "Max Temperature (°${location.temperatureScale})", required: false
|
||||||
input "minTemp", "number", title: "Min Temp?", required: false
|
input "minTemp", "number", title: "Min Temperature (°${location.temperatureScale})", required: false
|
||||||
}
|
}
|
||||||
|
|
||||||
section("When which people are away?") {
|
section("When which people are away?") {
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ def installed() {
|
|||||||
def updated() {
|
def updated() {
|
||||||
unschedule()
|
unschedule()
|
||||||
turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule
|
turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule
|
||||||
unsubscribe
|
unsubscribe()
|
||||||
log.debug "Updated with settings: ${settings}"
|
log.debug "Updated with settings: ${settings}"
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,13 +114,16 @@ def beaconHandler(evt) {
|
|||||||
|
|
||||||
if (allOk) {
|
if (allOk) {
|
||||||
def data = new groovy.json.JsonSlurper().parseText(evt.data)
|
def data = new groovy.json.JsonSlurper().parseText(evt.data)
|
||||||
log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
|
// removed logging of device names. can be added back for debugging
|
||||||
|
//log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
|
||||||
|
|
||||||
def beaconName = getBeaconName(evt)
|
def beaconName = getBeaconName(evt)
|
||||||
log.debug "<beacon-control> beaconName: $beaconName"
|
// removed logging of device names. can be added back for debugging
|
||||||
|
//log.debug "<beacon-control> beaconName: $beaconName"
|
||||||
|
|
||||||
def phoneName = getPhoneName(data)
|
def phoneName = getPhoneName(data)
|
||||||
log.debug "<beacon-control> phoneName: $phoneName"
|
// removed logging of device names. can be added back for debugging
|
||||||
|
//log.debug "<beacon-control> phoneName: $phoneName"
|
||||||
if (phoneName != null) {
|
if (phoneName != null) {
|
||||||
def action = data.presence == "1" ? "arrived" : "left"
|
def action = data.presence == "1" ? "arrived" : "left"
|
||||||
def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"
|
def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"
|
||||||
|
|||||||
@@ -49,13 +49,15 @@ preferences {
|
|||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
log.debug "Installed with settings: ${settings}"
|
log.debug "Installed with settings: ${settings}"
|
||||||
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
// commented out log statement because presence sensor label could contain user's name
|
||||||
|
//log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
log.debug "Updated with settings: ${settings}"
|
log.debug "Updated with settings: ${settings}"
|
||||||
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
// commented out log statement because presence sensor label could contain user's name
|
||||||
|
//log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
* JLH - 02-15-2014 - Fuller use of ecobee API
|
* JLH - 02-15-2014 - Fuller use of ecobee API
|
||||||
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
|
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
|
||||||
*/
|
*/
|
||||||
|
include 'asynchttp_v1'
|
||||||
|
|
||||||
definition(
|
definition(
|
||||||
name: "Ecobee (Connect)",
|
name: "Ecobee (Connect)",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
@@ -244,9 +246,7 @@ def getEcobeeThermostats() {
|
|||||||
uri: apiEndpoint,
|
uri: apiEndpoint,
|
||||||
path: "/1/thermostat",
|
path: "/1/thermostat",
|
||||||
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||||
// TODO - the query string below is not consistent with the Ecobee docs:
|
query: [json: toJson(bodyParams)]
|
||||||
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
|
|
||||||
query: [format: 'json', body: toJson(bodyParams)]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def stats = [:]
|
def stats = [:]
|
||||||
@@ -265,9 +265,8 @@ def getEcobeeThermostats() {
|
|||||||
} catch (groovyx.net.http.HttpResponseException e) {
|
} catch (groovyx.net.http.HttpResponseException e) {
|
||||||
log.trace "Exception polling children: " + e.response.data.status
|
log.trace "Exception polling children: " + e.response.data.status
|
||||||
if (e.response.data.status.code == 14) {
|
if (e.response.data.status.code == 14) {
|
||||||
atomicState.action = "getEcobeeThermostats"
|
|
||||||
log.debug "Refreshing your auth_token!"
|
log.debug "Refreshing your auth_token!"
|
||||||
refreshAuthToken()
|
refreshAuthToken([async: false, nextAction: "getEcobeeThermostats"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
atomicState.thermostats = stats
|
atomicState.thermostats = stats
|
||||||
@@ -358,16 +357,22 @@ def initialize() {
|
|||||||
atomicState.timeSendPush = null
|
atomicState.timeSendPush = null
|
||||||
atomicState.reAttempt = 0
|
atomicState.reAttempt = 0
|
||||||
|
|
||||||
pollHandler() //first time polling data data from thermostat
|
initialPoll() //first time polling data data from thermostat
|
||||||
|
|
||||||
//automatically update devices status every 5 mins
|
//automatically update devices status every 5 mins
|
||||||
runEvery5Minutes("poll")
|
runEvery5Minutes("poll")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def pollHandler() {
|
/**
|
||||||
log.debug "pollHandler()"
|
* Polls the child devices (synchronously).
|
||||||
pollChildren(null) // Hit the ecobee API for update on all thermostats
|
* This is used during app install/update, and is synchronous
|
||||||
|
* to maintain current behavior that will cause install/update to fail
|
||||||
|
* if polling fails.
|
||||||
|
*/
|
||||||
|
def initialPoll() {
|
||||||
|
log.debug "initialPoll()"
|
||||||
|
pollChildrenSync() // Hit the ecobee API for update on all thermostats
|
||||||
|
|
||||||
atomicState.thermostats.each {stat ->
|
atomicState.thermostats.each {stat ->
|
||||||
def dni = stat.key
|
def dni = stat.key
|
||||||
@@ -380,36 +385,38 @@ def pollHandler() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def pollChildren(child = null) {
|
/**
|
||||||
def thermostatIdsString = getChildDeviceIdsString()
|
* Polls Ecobee (asynchronously) for updated device state data.
|
||||||
|
* Called from within this Connect SmartApp as well as the child
|
||||||
|
* devices.
|
||||||
|
*/
|
||||||
|
def poll() {
|
||||||
|
log.debug "polling asynchronously"
|
||||||
|
asynchttp_v1.get('asyncPollResponseHandler', getPollParams())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a (synchronous) request to the Ecobee API to get the data for the thermostats.
|
||||||
|
* This request is made synchronously here because it is called as part of the
|
||||||
|
* install/updated lifecycle, and changing it to asynchronous during the install/update
|
||||||
|
* lifecycle may change the behavior if there is an error in polling.
|
||||||
|
*
|
||||||
|
* If further analysis shows that polling can be done asynchronously during
|
||||||
|
* install/update without any adverse consequences, this should then be made
|
||||||
|
* asynchronous just as the scheduled polling is.
|
||||||
|
*/
|
||||||
|
def pollChildrenSync() {
|
||||||
log.debug "polling children: $thermostatIdsString"
|
log.debug "polling children: $thermostatIdsString"
|
||||||
|
|
||||||
def requestBody = [
|
def params = getPollParams()
|
||||||
selection: [
|
params.query << ["Content-Type": "application/json"]
|
||||||
selectionType: "thermostats",
|
|
||||||
selectionMatch: thermostatIdsString,
|
|
||||||
includeExtendedRuntime: true,
|
|
||||||
includeSettings: true,
|
|
||||||
includeRuntime: true,
|
|
||||||
includeSensors: true
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
def result = false
|
def result = false
|
||||||
|
log.debug "making synchronous poll request"
|
||||||
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)]
|
|
||||||
]
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
httpGet(pollParams) { resp ->
|
httpGet(params) { resp ->
|
||||||
if(resp.status == 200) {
|
if(resp.status == 200) {
|
||||||
log.debug "poll results returned resp.data ${resp.data}"
|
|
||||||
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
|
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
|
||||||
updateSensorData()
|
updateSensorData()
|
||||||
storeThermostatData(resp.data.thermostatList)
|
storeThermostatData(resp.data.thermostatList)
|
||||||
@@ -420,40 +427,95 @@ def pollChildren(child = null) {
|
|||||||
} catch (groovyx.net.http.HttpResponseException e) {
|
} catch (groovyx.net.http.HttpResponseException e) {
|
||||||
log.trace "Exception polling children: " + e.response.data.status
|
log.trace "Exception polling children: " + e.response.data.status
|
||||||
if (e.response.data.status.code == 14) {
|
if (e.response.data.status.code == 14) {
|
||||||
atomicState.action = "pollChildren"
|
|
||||||
log.debug "Refreshing your auth_token!"
|
log.debug "Refreshing your auth_token!"
|
||||||
refreshAuthToken()
|
refreshAuthToken([async: false, nextAction: "pollChildrenSync"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll Child is invoked from the Child Device itself as part of the Poll Capability
|
/**
|
||||||
def pollChild() {
|
* Response handler for asynchronous request to get thermostat data.
|
||||||
def devices = getChildDevices()
|
* Given a successful response, updates the sensor data, stores the thermostat
|
||||||
|
* data, and generates child device events.
|
||||||
if (pollChildren()) {
|
*
|
||||||
devices.each { child ->
|
* If the access token has expired, will issue a request to refresh the token
|
||||||
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")) {
|
* (and pending successful token refresh, the poll request will be made again).
|
||||||
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
|
*/
|
||||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
def asyncPollResponseHandler(response, data) {
|
||||||
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
|
log.trace "async poll response handler"
|
||||||
child.generateEvent(tData.data) //parse received message from parent
|
if (!response.hasError()) {
|
||||||
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
|
if (response.status == 200) {
|
||||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
|
def json
|
||||||
return null
|
try {
|
||||||
}
|
json = response.getJson()
|
||||||
}
|
} catch (e) {
|
||||||
}
|
log.error ("error parsing JSON", e)
|
||||||
} else {
|
}
|
||||||
log.info "ERROR: pollChildren()"
|
if (json) {
|
||||||
return null
|
atomicState.remoteSensors = json.thermostatList.remoteSensors
|
||||||
}
|
updateSensorData()
|
||||||
|
storeThermostatData(json.thermostatList)
|
||||||
|
generateChildThermostatEvent()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn "Response returned non-200 response. Status: ${response.status}, data: ${response.getData()}"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.trace "Exception polling children: ${response.getErrorMessage()}"
|
||||||
|
def errorJson
|
||||||
|
try {
|
||||||
|
errorJson = response.getErrorJson()
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Unable to parse error json response", e)
|
||||||
|
}
|
||||||
|
if (errorJson?.status?.code == 14) {
|
||||||
|
log.debug "Refreshing your auth_token!"
|
||||||
|
refreshAuthToken([async: true, nextAction: "poll"])
|
||||||
|
} else {
|
||||||
|
log.warn "Error polling children that is not due to an expired token. Response: ${response.getErrorData()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void poll() {
|
private getPollParams() {
|
||||||
pollChild()
|
def thermostatIdsString = getChildDeviceIdsString()
|
||||||
|
def requestBody = [
|
||||||
|
selection: [
|
||||||
|
selectionType: "thermostats",
|
||||||
|
selectionMatch: thermostatIdsString,
|
||||||
|
includeExtendedRuntime: true,
|
||||||
|
includeSettings: true,
|
||||||
|
includeRuntime: true,
|
||||||
|
includeSensors: true
|
||||||
|
]
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
uri: apiEndpoint,
|
||||||
|
path: "/1/thermostat",
|
||||||
|
headers: ["Authorization": "Bearer ${atomicState.authToken}"],
|
||||||
|
query: [json: toJson(requestBody)]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls each child thermostat device to generate an event with the thermostat
|
||||||
|
* data.
|
||||||
|
*/
|
||||||
|
def generateChildThermostatEvent() {
|
||||||
|
log.trace("generateChildThermostatEvent")
|
||||||
|
getChildDevices().each { child ->
|
||||||
|
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
|
||||||
|
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
|
||||||
|
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||||
|
log.debug "calling child.generateEvent($tData.data)"
|
||||||
|
child.generateEvent(tData.data) //parse received message from parent
|
||||||
|
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
|
||||||
|
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def availableModes(child) {
|
def availableModes(child) {
|
||||||
@@ -553,47 +615,104 @@ def toQueryString(Map m) {
|
|||||||
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
|
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
|
||||||
}
|
}
|
||||||
|
|
||||||
private refreshAuthToken() {
|
/**
|
||||||
log.debug "refreshing auth token"
|
* Uses the refresh token to get a new access token, then executes the nextAction.
|
||||||
|
* @param options - a map of options. valid options are async: true/false, which
|
||||||
|
* specifies if the refresh token request will be done asynchronously or not (default is false)
|
||||||
|
* nextAction: "nameOfMethod" specifies what method to execute after
|
||||||
|
* the token is refreshed (not required).
|
||||||
|
* (note: using a map as the parameter because we need to call it from a schedueled
|
||||||
|
* execution and we can only pass a data map to scheduled executions)
|
||||||
|
*/
|
||||||
|
private void refreshAuthToken(options) {
|
||||||
|
if(!atomicState.refreshToken) {
|
||||||
|
log.warn "Cannot not refresh OAuth token since there is no refreshToken stored"
|
||||||
|
} else {
|
||||||
|
def refreshParams = [
|
||||||
|
uri : apiEndpoint,
|
||||||
|
path : "/token",
|
||||||
|
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
|
||||||
|
]
|
||||||
|
if (options.async) {
|
||||||
|
refreshAuthTokenAsync(refreshParams, options.nextAction)
|
||||||
|
} else {
|
||||||
|
refreshAuthTokenSync(refreshParams, options.nextAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!atomicState.refreshToken) {
|
private void refreshAuthTokenSync(params, nextAction = null) {
|
||||||
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
|
try {
|
||||||
} else {
|
httpPost(refreshParams) { resp ->
|
||||||
def refreshParams = [
|
if(resp.status == 200) {
|
||||||
method: 'POST',
|
log.debug "Token refreshed...calling saved RestAction now!"
|
||||||
uri : apiEndpoint,
|
debugEvent("Token refreshed ... calling saved RestAction now!")
|
||||||
path : "/token",
|
saveTokenAndResumeAction(resp.data, nextAction)
|
||||||
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
|
|
||||||
]
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (groovyx.net.http.HttpResponseException e) {
|
}
|
||||||
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
|
} catch (groovyx.net.http.HttpResponseException e) {
|
||||||
def reAttemptPeriod = 300 // in sec
|
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
|
||||||
if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc.
|
reauthTokenErrorHandler(e.statusCode)
|
||||||
runIn(reAttemptPeriod, "refreshAuthToken")
|
}
|
||||||
} else if (e.statusCode == 401) { // unauthorized
|
}
|
||||||
atomicState.reAttempt = atomicState.reAttempt + 1
|
|
||||||
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
|
private void refreshAuthTokenAsync(refreshParams, nextAction = null) {
|
||||||
if (atomicState.reAttempt <= 3) {
|
log.debug "making asynchronous refresh request"
|
||||||
runIn(reAttemptPeriod, "refreshAuthToken")
|
asynchttp_v1.post('refreshTokenResponseHandler', refreshParams, [nextAction: nextAction])
|
||||||
} else {
|
}
|
||||||
sendPushAndFeeds(notificationMessage)
|
|
||||||
atomicState.reAttempt = 0
|
/**
|
||||||
}
|
* The response handler for the request to refresh the authorization handler.
|
||||||
}
|
* Stores the new authorization token and refresh token, and executes any action
|
||||||
}
|
* (method) that failed due to the authorization token expiring.
|
||||||
}
|
*/
|
||||||
|
private void refreshTokenResponseHandler(response, data) {
|
||||||
|
if (!response.hasError()) {
|
||||||
|
if (response.status == 200) {
|
||||||
|
def json
|
||||||
|
try {
|
||||||
|
json = response.getJson()
|
||||||
|
} catch (e) {
|
||||||
|
log.error "error parsing json from response data: $response.data"
|
||||||
|
}
|
||||||
|
if (json) {
|
||||||
|
log.debug "asnyc refreshTokenHandler: Token refreshed...calling saved RestAction now!"
|
||||||
|
debugEvent("async Token refreshed ... calling saved RestAction now!")
|
||||||
|
saveTokenAndResumeAction(json, data.nextAction)
|
||||||
|
} else {
|
||||||
|
log.warn "successfully parsed json but result is empty or null"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug "Non 200 response returned. Response code: ${response.code}, data: ${response.getData()}"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug "async refreshTokenHandler: RESPONSE ERROR: ${response.getErrorJson()}"
|
||||||
|
reauthTokenErrorHandler(response.getErrorJson().code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries refreshing the authorization token. Will attempt to get the refresh
|
||||||
|
* token later, in case there were errors retrieving it.
|
||||||
|
* Will retry a fixed number of times before sending a push notification to the
|
||||||
|
* user instructing them to reauthenticate
|
||||||
|
*/
|
||||||
|
private void reauthTokenErrorHandler(responseCode) {
|
||||||
|
def retryInterval = 300 // in seconds
|
||||||
|
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."
|
||||||
|
// might get non-401 error from exceeding 20 second app limit, connectivity issues, etc.
|
||||||
|
if (responseCode != 401) {
|
||||||
|
runIn(retryInterval, "refreshAuthToken", [async: true])
|
||||||
|
} else if (responseCode == 401) { // unauthorized
|
||||||
|
atomicState.reAttempt = atomicState.reAttempt + 1
|
||||||
|
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
|
||||||
|
if (atomicState.reAttempt <= 3) {
|
||||||
|
runIn(retryInterval, "refreshAuthToken", [async: true])
|
||||||
|
} else {
|
||||||
|
sendPushAndFeeds(notificationMessage)
|
||||||
|
atomicState.reAttempt = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -603,20 +722,20 @@ private refreshAuthToken() {
|
|||||||
*
|
*
|
||||||
* @param json - an object representing the parsed JSON response from Ecobee
|
* @param json - an object representing the parsed JSON response from Ecobee
|
||||||
*/
|
*/
|
||||||
private void saveTokenAndResumeAction(json) {
|
private void saveTokenAndResumeAction(json, String nextAction) {
|
||||||
log.debug "token response json: $json"
|
def debugMessage = "token response, scope: ${json?.scope}, expires_in: ${json?.expires_in}, token_type: ${json?.token_type}"
|
||||||
|
log.debug "debugMessage"
|
||||||
if (json) {
|
if (json) {
|
||||||
debugEvent("Response = $json")
|
debugEvent(debugMessage)
|
||||||
atomicState.refreshToken = json?.refresh_token
|
atomicState.refreshToken = json?.refresh_token
|
||||||
atomicState.authToken = json?.access_token
|
atomicState.authToken = json?.access_token
|
||||||
if (atomicState.action) {
|
if (nextAction) {
|
||||||
log.debug "got refresh token, executing next action: ${atomicState.action}"
|
log.debug "got refresh token, will execute next action (passed in!): $nextAction"
|
||||||
"${atomicState.action}"()
|
"$nextAction"()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.warn "did not get response body from refresh token response"
|
log.warn "did not get response body from refresh token response"
|
||||||
}
|
}
|
||||||
atomicState.action = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -756,7 +875,6 @@ private boolean sendCommandToEcobee(Map bodyParams) {
|
|||||||
try{
|
try{
|
||||||
httpPost(cmdParams) { resp ->
|
httpPost(cmdParams) { resp ->
|
||||||
if(resp.status == 200) {
|
if(resp.status == 200) {
|
||||||
log.debug "updated ${resp.data}"
|
|
||||||
def returnStatus = resp.data.status.code
|
def returnStatus = resp.data.status.code
|
||||||
if (returnStatus == 0) {
|
if (returnStatus == 0) {
|
||||||
log.debug "Successful call to ecobee API."
|
log.debug "Successful call to ecobee API."
|
||||||
@@ -771,11 +889,10 @@ private boolean sendCommandToEcobee(Map bodyParams) {
|
|||||||
log.trace "Exception Sending Json: " + e.response.data.status
|
log.trace "Exception Sending Json: " + e.response.data.status
|
||||||
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
|
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
|
||||||
if (e.response.data.status.code == 14) {
|
if (e.response.data.status.code == 14) {
|
||||||
// TODO - figure out why we're setting the next action to be pollChildren
|
// TODO - figure out why we're setting the next action to be poll
|
||||||
// after refreshing auth token. Is it to keep UI in sync, or just copy/paste error?
|
// 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!"
|
log.debug "Refreshing your auth_token!"
|
||||||
refreshAuthToken()
|
refreshAuthToken([async: true, nextAction: "poll"])
|
||||||
} else {
|
} else {
|
||||||
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
|
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
|
||||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ def waterWetHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1
|
def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone within the last $deltaSeconds seconds"
|
log.debug "SMS already sent within the last $deltaSeconds seconds"
|
||||||
} else {
|
} else {
|
||||||
def msg = "${alarm.displayName} is wet!"
|
def msg = "${alarm.displayName} is wet!"
|
||||||
log.debug "$alarm is wet, texting $phone"
|
log.debug "$alarm is wet, texting phone number"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts(msg, recipients)
|
sendNotificationToContacts(msg, recipients)
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def takeAction(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
def sendTextMessage() {
|
def sendTextMessage() {
|
||||||
log.debug "$multisensor was open too long, texting $phone"
|
log.debug "$multisensor was open too long, texting phone"
|
||||||
|
|
||||||
updateSmsHistory()
|
updateSmsHistory()
|
||||||
def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1)
|
def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1)
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ preferences {
|
|||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
log.debug "Installed with settings: ${settings}"
|
log.debug "Installed with settings: ${settings}"
|
||||||
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
// log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
log.debug "Updated with settings: ${settings}"
|
log.debug "Updated with settings: ${settings}"
|
||||||
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
// log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
@@ -71,11 +71,10 @@ def presence(evt)
|
|||||||
def person = getPerson(evt)
|
def person = getPerson(evt)
|
||||||
def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"}
|
def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"}
|
||||||
if (recentNotPresent) {
|
if (recentNotPresent) {
|
||||||
log.debug "skipping notification of arrival of ${person.displayName} because last departure was only ${now() - recentNotPresent.date.time} msec ago"
|
log.debug "skipping notification of arrival of Person because last departure was only ${now() - recentNotPresent.date.time} msec ago"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
def message = "${person.displayName} arrived at home, changing mode to '${newMode}'"
|
def message = "${person.displayName} arrived at home, changing mode to '${newMode}'"
|
||||||
log.info message
|
|
||||||
send(message)
|
send(message)
|
||||||
setLocationMode(newMode)
|
setLocationMode(newMode)
|
||||||
}
|
}
|
||||||
@@ -106,6 +105,4 @@ private send(msg) {
|
|||||||
sendSms(phone, msg)
|
sendSms(phone, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug msg
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,12 +57,11 @@ def scheduleCheck()
|
|||||||
def message = message1 ?: "SmartThings - Habit Helper Reminder!"
|
def message = message1 ?: "SmartThings - Habit Helper Reminder!"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
log.debug "Texting reminder: ($message) to contacts:${recipients?.size()}"
|
log.debug "Texting reminder to contacts:${recipients?.size()}"
|
||||||
sendNotificationToContacts(message, recipients)
|
sendNotificationToContacts(message, recipients)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
log.debug "Texting reminder"
|
||||||
log.debug "Texting reminder: ($message) to $phone1"
|
|
||||||
sendSms(phone1, message)
|
sendSms(phone1, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ def accelerationActiveHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent within the last $deltaSeconds seconds"
|
||||||
} else {
|
} else {
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
log.debug "$accelerationSensor has moved, texting contacts: ${recipients?.size()}"
|
log.debug "accelerationSensor has moved, texting contacts: ${recipients?.size()}"
|
||||||
sendNotificationToContacts("${accelerationSensor.label ?: accelerationSensor.name} moved", recipients)
|
sendNotificationToContacts("${accelerationSensor.label ?: accelerationSensor.name} moved", recipients)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "$accelerationSensor has moved, texting $phone1"
|
log.debug "accelerationSensor has moved, sending text message"
|
||||||
sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved")
|
sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ def temperatureHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 1
|
def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone1 within the last $deltaMinutes minutes"
|
log.debug "SMS already sent within the last $deltaMinutes minutes"
|
||||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||||
} else {
|
} else {
|
||||||
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
|
log.debug "Temperature rose above $tooHot: sending SMS and activating $mySwitch"
|
||||||
def tempScale = location.temperatureScale ?: "F"
|
def tempScale = location.temperatureScale ?: "F"
|
||||||
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
||||||
switch1?.on()
|
switch1?.on()
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ def authPage() {
|
|||||||
}
|
}
|
||||||
def description = "Tap to enter LIFX credentials"
|
def description = "Tap to enter LIFX credentials"
|
||||||
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
|
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
|
||||||
// def redirectUrl = "${apiServerUrl}"
|
// def redirectUrl = "${apiServerUrl}"
|
||||||
log.debug "app id: ${app.id}"
|
// log.debug "app id: ${app.id}"
|
||||||
log.debug "redirect url: ${redirectUrl}"
|
// log.debug "redirect url: ${redirectUrl}"s
|
||||||
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
|
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
|
||||||
section {
|
section {
|
||||||
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
|
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
|
||||||
@@ -372,7 +372,7 @@ def updateDevices() {
|
|||||||
def childDevice = getChildDevice(device.id)
|
def childDevice = getChildDevice(device.id)
|
||||||
selectors.add("${device.id}")
|
selectors.add("${device.id}")
|
||||||
if (!childDevice) {
|
if (!childDevice) {
|
||||||
log.info("Adding device ${device.id}: ${device.product}")
|
// log.info("Adding device ${device.id}: ${device.product}")
|
||||||
def data = [
|
def data = [
|
||||||
label: device.label,
|
label: device.label,
|
||||||
level: Math.round((device.brightness ?: 1) * 100),
|
level: Math.round((device.brightness ?: 1) * 100),
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ def updated() {
|
|||||||
|
|
||||||
def presenceHandler(evt) {
|
def presenceHandler(evt) {
|
||||||
if (evt.value == "present") {
|
if (evt.value == "present") {
|
||||||
log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
|
// log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
|
||||||
sendPush("${presence.label ?: presence.name} has arrived at the ${location}")
|
sendPush("${presence.label ?: presence.name} has arrived at the ${location}")
|
||||||
} else if (evt.value == "not present") {
|
} else if (evt.value == "not present") {
|
||||||
log.debug "${presence.label ?: presence.name} has left the ${location}"
|
// log.debug "${presence.label ?: presence.name} has left the ${location}"
|
||||||
sendPush("${presence.label ?: presence.name} has left the ${location}")
|
sendPush("${presence.label ?: presence.name} has left the ${location}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def updated() {
|
|||||||
|
|
||||||
def presenceHandler(evt) {
|
def presenceHandler(evt) {
|
||||||
if (evt.value == "present") {
|
if (evt.value == "present") {
|
||||||
log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
|
// log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("${presence.label ?: presence.name} has arrived at the ${location}", recipients)
|
sendNotificationToContacts("${presence.label ?: presence.name} has arrived at the ${location}", recipients)
|
||||||
@@ -56,7 +56,7 @@ def presenceHandler(evt) {
|
|||||||
sendSms(phone1, "${presence.label ?: presence.name} has arrived at the ${location}")
|
sendSms(phone1, "${presence.label ?: presence.name} has arrived at the ${location}")
|
||||||
}
|
}
|
||||||
} else if (evt.value == "not present") {
|
} else if (evt.value == "not present") {
|
||||||
log.debug "${presence.label ?: presence.name} has left the ${location}"
|
// log.debug "${presence.label ?: presence.name} has left the ${location}"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients)
|
sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients)
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def subscribe() {
|
def subscribe() {
|
||||||
log.debug "present: ${cars.collect{it.displayName + ': ' + it.currentPresence}}"
|
// log.debug "present: ${cars.collect{it.displayName + ': ' + it.currentPresence}}"
|
||||||
subscribe(doorSensor, "contact", garageDoorContact)
|
subscribe(doorSensor, "contact", garageDoorContact)
|
||||||
|
|
||||||
subscribe(cars, "presence", carPresence)
|
subscribe(cars, "presence", carPresence)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def updated() {
|
|||||||
private subscribeToEvents()
|
private subscribeToEvents()
|
||||||
{
|
{
|
||||||
subscribe intrusionMotions, "motion", intruderMotion
|
subscribe intrusionMotions, "motion", intruderMotion
|
||||||
subscribe residentMotions, "motion", residentMotion
|
// subscribe residentMotions, "motion", residentMotion
|
||||||
subscribe intrusionContacts, "contact", contact
|
subscribe intrusionContacts, "contact", contact
|
||||||
subscribe alarms, "alarm", alarm
|
subscribe alarms, "alarm", alarm
|
||||||
subscribe(app, appTouch)
|
subscribe(app, appTouch)
|
||||||
@@ -156,6 +156,7 @@ def residentMotion(evt)
|
|||||||
// startReArmSequence()
|
// startReArmSequence()
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
unsubscribe(residentMotions)
|
||||||
}
|
}
|
||||||
|
|
||||||
def contact(evt)
|
def contact(evt)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ def updated()
|
|||||||
|
|
||||||
def contactOpenHandler(evt) {
|
def contactOpenHandler(evt) {
|
||||||
log.trace "$evt.value: $evt, $settings"
|
log.trace "$evt.value: $evt, $settings"
|
||||||
log.debug "$contact1 was opened, texting $phone1"
|
log.debug "$contact1 was opened, sending text"
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
|
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ def motionActiveHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent within the last $deltaSeconds seconds"
|
||||||
} else {
|
} else {
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
log.debug "$motion1 has moved while you were out, sending notifications to: ${recipients?.size()}"
|
log.debug "$motion1 has moved while you were out, sending notifications to: ${recipients?.size()}"
|
||||||
sendNotificationToContacts("${motion1.label} ${motion1.name} moved while you were out", recipients)
|
sendNotificationToContacts("${motion1.label} ${motion1.name} moved while you were out", recipients)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "$motion1 has moved while you were out, texting $phone1"
|
log.debug "$motion1 has moved while you were out, sending text"
|
||||||
sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out")
|
sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ def accelerationActiveHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent to phone within the last $deltaSeconds seconds"
|
||||||
} else {
|
} else {
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("Gun case has moved!", recipients)
|
sendNotificationToContacts("Gun case has moved!", recipients)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "$accelerationSensor has moved, texting $phone1"
|
log.debug "$accelerationSensor has moved, texting phone"
|
||||||
sendSms(phone1, "Gun case has moved!")
|
sendSms(phone1, "Gun case has moved!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def authPage() {
|
|||||||
|
|
||||||
def oauthInitUrl() {
|
def oauthInitUrl() {
|
||||||
def token = getToken()
|
def token = getToken()
|
||||||
log.debug "initiateOauth got token: $token"
|
//log.debug "initiateOauth got token: $token"
|
||||||
|
|
||||||
// store these for validate after the user takes the oauth journey
|
// store these for validate after the user takes the oauth journey
|
||||||
state.oauth_request_token = token.oauth_token
|
state.oauth_request_token = token.oauth_token
|
||||||
@@ -76,7 +76,7 @@ def getToken() {
|
|||||||
]
|
]
|
||||||
def requestTokenBaseUrl = "https://oauth.withings.com/account/request_token"
|
def requestTokenBaseUrl = "https://oauth.withings.com/account/request_token"
|
||||||
def url = buildSignedUrl(requestTokenBaseUrl, params)
|
def url = buildSignedUrl(requestTokenBaseUrl, params)
|
||||||
log.debug "getToken - url: $url"
|
//log.debug "getToken - url: $url"
|
||||||
|
|
||||||
return getJsonFromUrl(url)
|
return getJsonFromUrl(url)
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ def exchangeToken() {
|
|||||||
|
|
||||||
def requestTokenBaseUrl = "https://oauth.withings.com/account/access_token"
|
def requestTokenBaseUrl = "https://oauth.withings.com/account/access_token"
|
||||||
def url = buildSignedUrl(requestTokenBaseUrl, params, tokenSecret)
|
def url = buildSignedUrl(requestTokenBaseUrl, params, tokenSecret)
|
||||||
log.debug "signed url: $url with secret $tokenSecret"
|
//log.debug "signed url: $url with secret $tokenSecret"
|
||||||
|
|
||||||
def token = getJsonFromUrl(url)
|
def token = getJsonFromUrl(url)
|
||||||
|
|
||||||
@@ -198,8 +198,8 @@ def exchangeToken() {
|
|||||||
|
|
||||||
def load() {
|
def load() {
|
||||||
def json = get(getMeasurement(new Date() - 30))
|
def json = get(getMeasurement(new Date() - 30))
|
||||||
|
// removed logging of actual json payload. Can be put back for debugging
|
||||||
log.debug "swapped, then received: $json"
|
log.debug "swapped, then received json"
|
||||||
parse(data:json)
|
parse(data:json)
|
||||||
|
|
||||||
def html = """
|
def html = """
|
||||||
|
|||||||
Reference in New Issue
Block a user