Compare commits

..

1 Commits

Author SHA1 Message Date
Jason
9882b5deb2 MSA-1458: home 2016-08-31 11:57:40 -05:00
45 changed files with 386 additions and 491 deletions

View File

@@ -15,7 +15,6 @@
*/
metadata {
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement"
capability "Temperature Measurement"

View File

@@ -15,7 +15,6 @@
*/
metadata {
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement"
capability "Temperature Measurement"

View File

@@ -15,7 +15,6 @@
*/
metadata {
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
capability "Relative Humidity Measurement"
capability "Temperature Measurement"
}

View File

@@ -15,8 +15,6 @@
*/
metadata {
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
capability "Sensor"
attribute "rain", "number"
attribute "rainSumHour", "number"
attribute "rainSumDay", "number"

View File

@@ -67,6 +67,6 @@ def refresh() {
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
parent.poll()
parent.pollChild()
}

View File

@@ -133,7 +133,7 @@ def refresh() {
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
parent.poll()
parent.pollChild()
}
def generateEvent(Map results) {

View File

@@ -16,7 +16,6 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "setAdjustedColor"
command "reset"
@@ -56,10 +55,6 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -171,7 +166,3 @@ def verifyPercent(percent) {
return false
}
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -62,7 +62,7 @@ def parse(description) {
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
results << createEvent(name: "${map.name}", value: "${map.value}")
} else {
log.trace "Parsing description"
log.trace "Parsing description"
def msg = parseLanMessage(description)
if (msg.body) {
def contentType = msg.headers["Content-Type"]
@@ -72,13 +72,13 @@ def parse(description) {
log.info "Bridge response: $msg.body"
} else {
// Sending Bulbs List to parent"
if (parent.isInBulbDiscovery())
log.info parent.bulbListHandler(device.hub.id, msg.body)
if (parent.state.inBulbDiscovery)
log.info parent.bulbListHandler(device.hub.id, msg.body)
}
}
else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body)
parent.hubVerification(device.hub.id, msg.body)
}
}
}

View File

@@ -17,7 +17,6 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "setAdjustedColor"
command "reset"
@@ -65,10 +64,6 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -187,7 +182,3 @@ def verifyPercent(percent) {
return false
}
}
def ping() {
log.trace "${parent.ping(this)}"
}

View File

@@ -14,7 +14,6 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "refresh"
}
@@ -49,10 +48,6 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -92,7 +87,3 @@ void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -15,7 +15,6 @@ metadata {
capability "Color Temperature"
capability "Switch"
capability "Refresh"
capability "Health Check"
command "refresh"
}
@@ -54,10 +53,6 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -106,7 +101,3 @@ void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -233,8 +233,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}

View File

@@ -248,8 +248,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}

View File

@@ -170,7 +170,7 @@ private Map parseCustomMessage(String description) {
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive')
}
def getTemperature(value) {
@@ -206,8 +206,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}

View File

@@ -313,8 +313,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}

View File

@@ -206,8 +206,6 @@ def getTemperature(value) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}

View File

@@ -207,8 +207,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}

View File

@@ -214,8 +214,6 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}

View File

@@ -106,11 +106,11 @@ def parse(String description) {
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else {

View File

@@ -65,16 +65,7 @@ void updateSwitch() {
private void updateAll(devices) {
def command = request.JSON?.command
if (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.")
}
devices."$command"()
}
}
@@ -86,16 +77,7 @@ private void update(devices) {
if (!device) {
httpError(404, "Device not found")
} else {
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.")
}
device."$command"()
}
}
}

View File

@@ -58,7 +58,7 @@ def authPage() {
if (canInstallLabs()) {
def redirectUrl = getBuildRedirectUrl()
// log.debug "Redirect url = ${redirectUrl}"
log.debug "Redirect url = ${redirectUrl}"
if (state.authToken) {
description = "Tap 'Next' to proceed"
@@ -113,13 +113,13 @@ def oauthInitUrl() {
scope: "read_station"
]
// log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
}
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 oauthState = params.state
@@ -135,7 +135,7 @@ def callback() {
scope: "read_station"
]
// log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
def tokenUrl = getVendorTokenPath()
def params = [
@@ -144,7 +144,7 @@ def callback() {
body: tokenParams
]
// log.debug "PARAMS: ${params}"
log.debug "PARAMS: ${params}"
httpPost(params) { resp ->
@@ -156,7 +156,7 @@ def callback() {
state.refreshToken = data.refresh_token
state.authToken = data.access_token
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 ->
def data = slurper.parseText(key);
// log.debug "Data: $data"
log.debug "Data: $data"
state.refreshToken = data.refresh_token
state.accessToken = data.access_token

View File

@@ -0,0 +1,145 @@
/**
* 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

@@ -17,7 +17,7 @@ definition(
name: "Monitor on Sense",
namespace: "resteele",
author: "Rachel Steele",
description: "Turn on switch when vibration is sensed",
description: "Turn on Monitor when vibration is sensed",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
@@ -25,10 +25,10 @@ definition(
preferences {
section("When vibration is sensed...") {
section("When the keyboard is used...") {
input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?"
}
section("Turn on switch...") {
section("Turn on/off a light...") {
input "switch1", "capability.switch"
}
}
@@ -47,3 +47,5 @@ def updated() {
def accelerationActiveHandler(evt) {
switch1.on()
}

View File

@@ -114,16 +114,13 @@ def beaconHandler(evt) {
if (allOk) {
def data = new groovy.json.JsonSlurper().parseText(evt.data)
// removed logging of device names. can be added back for debugging
//log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
def beaconName = getBeaconName(evt)
// removed logging of device names. can be added back for debugging
//log.debug "<beacon-control> beaconName: $beaconName"
log.debug "<beacon-control> beaconName: $beaconName"
def phoneName = getPhoneName(data)
// removed logging of device names. can be added back for debugging
//log.debug "<beacon-control> phoneName: $phoneName"
log.debug "<beacon-control> phoneName: $phoneName"
if (phoneName != null) {
def action = data.presence == "1" ? "arrived" : "left"
def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"

View File

@@ -49,15 +49,13 @@ preferences {
def installed() {
log.debug "Installed with settings: ${settings}"
// 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}}"
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
subscribe(people, "presence", presence)
}
def updated() {
log.debug "Updated with settings: ${settings}"
// 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}}"
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
unsubscribe()
subscribe(people, "presence", presence)
}

View File

@@ -20,8 +20,6 @@
* JLH - 02-15-2014 - Fuller use of ecobee API
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
*/
include 'asynchttp_v1'
definition(
name: "Ecobee (Connect)",
namespace: "smartthings",
@@ -246,7 +244,9 @@ def getEcobeeThermostats() {
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
query: [json: toJson(bodyParams)]
// 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)]
]
def stats = [:]
@@ -265,8 +265,9 @@ def getEcobeeThermostats() {
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: false, nextAction: "getEcobeeThermostats"])
refreshAuthToken()
}
}
atomicState.thermostats = stats
@@ -357,22 +358,16 @@ def initialize() {
atomicState.timeSendPush = null
atomicState.reAttempt = 0
initialPoll() //first time polling data data from thermostat
pollHandler() //first time polling data data from thermostat
//automatically update devices status every 5 mins
runEvery5Minutes("poll")
}
/**
* Polls the child devices (synchronously).
* 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
def pollHandler() {
log.debug "pollHandler()"
pollChildren(null) // Hit the ecobee API for update on all thermostats
atomicState.thermostats.each {stat ->
def dni = stat.key
@@ -385,101 +380,10 @@ def initialPoll() {
}
}
/**
* 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() {
def pollChildren(child = null) {
def thermostatIdsString = getChildDeviceIdsString()
log.debug "polling children: $thermostatIdsString"
def params = getPollParams()
params.query << ["Content-Type": "application/json"]
def result = false
log.debug "making synchronous poll request"
try{
httpGet(params) { resp ->
if(resp.status == 200) {
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(resp.data.thermostatList)
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
if (e.response.data.status.code == 14) {
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: false, nextAction: "pollChildrenSync"])
}
}
return result
}
/**
* Response handler for asynchronous request to get thermostat data.
* Given a successful response, updates the sensor data, stores the thermostat
* data, and generates child device events.
*
* If the access token has expired, will issue a request to refresh the token
* (and pending successful token refresh, the poll request will be made again).
*/
def asyncPollResponseHandler(response, data) {
log.trace "async poll response handler"
if (!response.hasError()) {
if (response.status == 200) {
def json
try {
json = response.getJson()
} catch (e) {
log.error ("error parsing JSON", e)
}
if (json) {
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()}"
}
}
}
private getPollParams() {
def thermostatIdsString = getChildDeviceIdsString()
def requestBody = [
selection: [
selectionType: "thermostats",
@@ -490,32 +394,66 @@ private getPollParams() {
includeSensors: true
]
]
return [
def result = false
def pollParams = [
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Authorization": "Bearer ${atomicState.authToken}"],
query: [json: toJson(requestBody)]
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{
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}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
}
return result
}
/**
* 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
}
}
}
// Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild() {
def devices = getChildDevices()
if (pollChildren()) {
devices.each { child ->
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}"
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
}
}
}
} else {
log.info "ERROR: pollChildren()"
return null
}
}
void poll() {
pollChild()
}
def availableModes(child) {
@@ -615,104 +553,47 @@ def toQueryString(Map m) {
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}
/**
* 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)
}
}
}
private refreshAuthToken() {
log.debug "refreshing auth token"
private void refreshAuthTokenSync(params, nextAction = null) {
try {
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, nextAction)
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],
]
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}"
reauthTokenErrorHandler(e.statusCode)
}
}
private void refreshAuthTokenAsync(refreshParams, nextAction = null) {
log.debug "making asynchronous refresh request"
asynchttp_v1.post('refreshTokenResponseHandler', refreshParams, [nextAction: nextAction])
}
/**
* 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
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
def reAttemptPeriod = 300 // in sec
if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc.
runIn(reAttemptPeriod, "refreshAuthToken")
} else if (e.statusCode == 401) { // unauthorized
atomicState.reAttempt = atomicState.reAttempt + 1
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
if (atomicState.reAttempt <= 3) {
runIn(reAttemptPeriod, "refreshAuthToken")
} else {
sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0
}
}
}
}
}
/**
@@ -722,20 +603,20 @@ private void reauthTokenErrorHandler(responseCode) {
*
* @param json - an object representing the parsed JSON response from Ecobee
*/
private void saveTokenAndResumeAction(json, String nextAction) {
def debugMessage = "token response, scope: ${json?.scope}, expires_in: ${json?.expires_in}, token_type: ${json?.token_type}"
log.debug "debugMessage"
private void saveTokenAndResumeAction(json) {
log.debug "token response json: $json"
if (json) {
debugEvent(debugMessage)
debugEvent("Response = $json")
atomicState.refreshToken = json?.refresh_token
atomicState.authToken = json?.access_token
if (nextAction) {
log.debug "got refresh token, will execute next action (passed in!): $nextAction"
"$nextAction"()
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 = ""
}
/**
@@ -875,6 +756,7 @@ private boolean sendCommandToEcobee(Map bodyParams) {
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."
@@ -889,10 +771,11 @@ private boolean sendCommandToEcobee(Map bodyParams) {
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 poll
// 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([async: true, nextAction: "poll"])
refreshAuthToken()
} else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc."

View File

@@ -64,12 +64,10 @@ def meterHandler(evt) {
def lastValue = atomicState.lastValue as double
atomicState.lastValue = meterValue
def dUnit ? evt.unit : "Watts"
def aboveThresholdValue = aboveThreshold as int
if (meterValue > aboveThresholdValue) {
if (lastValue < aboveThresholdValue) { // only send notifications when crossing the threshold
def msg = "${meter} reported ${evt.value} ${dUnit} which is above your threshold of ${aboveThreshold}."
def msg = "${meter} reported ${evt.value} ${evt.unit} which is above your threshold of ${aboveThreshold}."
sendMessage(msg)
} else {
// log.debug "not sending notification for ${evt.description} because the threshold (${aboveThreshold}) has already been crossed"
@@ -80,7 +78,7 @@ def meterHandler(evt) {
def belowThresholdValue = belowThreshold as int
if (meterValue < belowThresholdValue) {
if (lastValue > belowThresholdValue) { // only send notifications when crossing the threshold
def msg = "${meter} reported ${evt.value} ${dUnit} which is below your threshold of ${belowThreshold}."
def msg = "${meter} reported ${evt.value} ${evt.unit} which is below your threshold of ${belowThreshold}."
sendMessage(msg)
} else {
// log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed"

View File

@@ -54,10 +54,10 @@ def waterWetHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1
if (alreadySentSms) {
log.debug "SMS already sent within the last $deltaSeconds seconds"
log.debug "SMS already sent to $phone within the last $deltaSeconds seconds"
} else {
def msg = "${alarm.displayName} is wet!"
log.debug "$alarm is wet, texting phone number"
log.debug "$alarm is wet, texting $phone"
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients)

View File

@@ -90,7 +90,7 @@ def takeAction(){
}
def sendTextMessage() {
log.debug "$multisensor was open too long, texting phone"
log.debug "$multisensor was open too long, texting $phone"
updateSmsHistory()
def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1)

View File

@@ -761,7 +761,7 @@ String displayableTime(timeRemaining) {
return "${minutes}:00"
}
def fraction = "0.${parts[1]}" as double
def seconds = "${60 * fraction as int}".padLeft(2, "0")
def seconds = "${60 * fraction as int}".padRight(2, "0")
return "${minutes}:${seconds}"
}
@@ -1101,4 +1101,4 @@ def hasStartLevel() {
def hasEndLevel() {
return (endLevel != null && endLevel != "")
}
}

View File

@@ -47,13 +47,13 @@ preferences {
def installed() {
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)
}
def updated() {
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()
subscribe(people, "presence", presence)
}
@@ -71,10 +71,11 @@ def presence(evt)
def person = getPerson(evt)
def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"}
if (recentNotPresent) {
log.debug "skipping notification of arrival of Person because last departure was only ${now() - recentNotPresent.date.time} msec ago"
log.debug "skipping notification of arrival of ${person.displayName} because last departure was only ${now() - recentNotPresent.date.time} msec ago"
}
else {
def message = "${person.displayName} arrived at home, changing mode to '${newMode}'"
log.info message
send(message)
setLocationMode(newMode)
}
@@ -105,4 +106,6 @@ private send(msg) {
sendSms(phone, msg)
}
}
log.debug msg
}

View File

@@ -57,11 +57,12 @@ def scheduleCheck()
def message = message1 ?: "SmartThings - Habit Helper Reminder!"
if (location.contactBookEnabled) {
log.debug "Texting reminder to contacts:${recipients?.size()}"
log.debug "Texting reminder: ($message) to contacts:${recipients?.size()}"
sendNotificationToContacts(message, recipients)
}
else {
log.debug "Texting reminder"
log.debug "Texting reminder: ($message) to $phone1"
sendSms(phone1, message)
}
}

View File

@@ -95,7 +95,8 @@ def bridgeDiscoveryFailed() {
}
}
def bridgeLinking() {
def bridgeLinking()
{
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
state.linkRefreshcount = linkRefreshcount + 1
def refreshInterval = 3
@@ -327,7 +328,7 @@ def bulbListHandler(hub, data = "") {
def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v ->
if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, online: v.state?.reachable]
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub]
}
}
def bridge = null
@@ -447,6 +448,7 @@ def addBridge() {
updateBridgeStatus(childDevice)
childDevice.sendEvent(name: "idNumber", value: idNumber)
if (vbridge.value.ip && vbridge.value.port) {
if (vbridge.value.ip.contains(".")) {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
@@ -647,7 +649,8 @@ def locationHandler(evt) {
}
}
}
} else if (parsedEvent.headers && parsedEvent.body) {
}
else if (parsedEvent.headers && parsedEvent.body) {
log.trace "HUE BRIDGE RESPONSES"
def headerString = parsedEvent.headers.toString()
if (headerString?.contains("xml")) {
@@ -730,7 +733,7 @@ private void updateBridgeStatus(childDevice) {
private void checkBridgeStatus() {
def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 30)
def time = now() - (1000 * 60 * 16)
bridges.each {
def d = getChildDevice(it.value.mac)
if(d) {
@@ -743,8 +746,6 @@ private void checkBridgeStatus() {
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline")
// set all lights to offline since bridge is not reachable
state.bulbs?.each {it.value.online = false}
} else {
d.sendEvent(name: "status", value: "Online")//setOnline(false)
}
@@ -757,10 +758,6 @@ def isValidSource(macAddress) {
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
}
def isInBulbDiscovery() {
return state.inBulbDiscovery
}
/////////////////////////////////////
//CHILD DEVICE METHODS
/////////////////////////////////////
@@ -784,7 +781,8 @@ def parse(childDevice, description) {
if (body instanceof java.util.Map) {
// get (poll) reponse
return handlePoll(body)
} else {
}
else {
//put response
return handleCommandResponse(body)
}
@@ -881,40 +879,36 @@ private handleCommandResponse(body) {
// scan entire response before sending events to make sure they are always in the same order
def updates = [:]
body.each { payload ->
log.debug $payload
body.each { payload ->
log.debug $payload
if (payload?.success) {
def childDeviceNetworkId = app.id + "/"
def eventType
def childDeviceNetworkId = app.id + "/"
def eventType
payload.success.each { k, v ->
def data = k.split("/")
if (data.length == 5) {
childDeviceNetworkId = app.id + "/" + k.split("/")[2]
if (!updates[childDeviceNetworkId])
updates[childDeviceNetworkId] = [:]
eventType = k.split("/")[4]
eventType = k.split("/")[4]
updates[childDeviceNetworkId]."$eventType" = v
}
}
} else if (payload.error) {
log.warn "Error returned from Hue bridge error = ${body?.error}"
}
}
}
}
// send events for each update found above (order of events should be same as handlePoll())
updates.each { childDeviceNetworkId, params ->
def device = getChildDevice(childDeviceNetworkId)
def id = getId(device)
// If device is offline, then don't send events which will update device watch
if (isOnline(id)) {
sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
}
sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
return []
}
}
/**
* Handles a response to a poll (GET) sent to the Hue Bridge.
@@ -934,32 +928,26 @@ private handleCommandResponse(body) {
* @return empty array
*/
private handlePoll(body) {
if (state.updating) {
// If user just executed commands, then ignore poll to not confuse the turning on/off state
return []
}
def bulbs = getChildDevices()
for (bulb in body) {
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
if (device) {
if (bulb.value.state?.reachable) {
if (state.bulbs[bulb.key]?.online == false) {
// light just came back online, notify device watch
def lastActivity = now()
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
}
state.bulbs[bulb.key]?.online = true
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
if (!state.updating) {
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
}
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
} else {
state.bulbs[bulb.key]?.online = false
log.warn "$device is not reachable by Hue bridge"
}
}
}
}
}
return []
}
return []
}
private updateInProgress() {
state.updating = true
@@ -988,34 +976,22 @@ def hubVerification(bodytext) {
def on(childDevice) {
log.debug "Executing 'on'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [on: true])
put("lights/${getId(childDevice)}/state", [on: true])
return "Bulb is turning On"
}
def off(childDevice) {
log.debug "Executing 'off'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
createSwitchEvent(childDevice, "off")
put("lights/$id/state", [on: false])
put("lights/${getId(childDevice)}/state", [on: false])
return "Bulb is turning Off"
}
def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 1 - 254
def level
@@ -1030,64 +1006,48 @@ def setLevel(childDevice, percent) {
// that means that the light will still be on when on is called next time
// Lets emulate that here
if (percent > 0) {
put("lights/$id/state", [bri: level, on: true])
put("lights/${getId(childDevice)}/state", [bri: level, on: true])
} else {
put("lights/$id/state", [on: false])
put("lights/${getId(childDevice)}/state", [on: false])
}
return "Setting level to $percent"
}
def setSaturation(childDevice, percent) {
log.debug "Executing 'setSaturation($percent)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
updateInProgress()
// 0 - 254
def level = Math.min(Math.round(percent * 254 / 100), 254)
// TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [sat: level, on: true])
put("lights/${getId(childDevice)}/state", [sat: level, on: true])
return "Setting saturation to $percent"
}
def setHue(childDevice, percent) {
log.debug "Executing 'setHue($percent)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
// TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [hue: level, on: true])
put("lights/${getId(childDevice)}/state", [hue: level, on: true])
return "Setting hue to $percent"
}
def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [ct: ct, on: true])
put("lights/${getId(childDevice)}/state", [ct: ct, on: true])
return "Setting color temperature to $percent"
}
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
def value = [:]
@@ -1144,23 +1104,15 @@ def setColor(childDevice, huesettings) {
value.on = false
createSwitchEvent(childDevice, value.on ? "on" : "off")
put("lights/$id/state", value)
put("lights/${getId(childDevice)}/state", value)
return "Setting color to $value"
}
def ping(childDevice) {
if (isOnline(getId(childDevice))) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online"
} else {
return "Device is Offline"
}
}
private getId(childDevice) {
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
return childDevice.device?.deviceNetworkId[3..-1]
} else {
}
else {
return childDevice.device?.deviceNetworkId.split("/")[-1]
}
}
@@ -1171,11 +1123,8 @@ private poll() {
log.debug "GET: $host$uri"
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
HOST: ${host}
""", physicalgraph.device.Protocol.LAN, selectedHue))
}
private isOnline(id) {
return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null
""", physicalgraph.device.Protocol.LAN, selectedHue))
}
private put(path, body) {
@@ -1245,7 +1194,7 @@ def convertBulbListToMap() {
if (state.bulbs instanceof java.util.List) {
def map = [:]
state.bulbs.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]]
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]]
}
state.bulbs = map
}

View File

@@ -53,14 +53,14 @@ def accelerationActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) {
log.debug "SMS already sent within the last $deltaSeconds seconds"
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
} else {
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)
}
else {
log.debug "accelerationSensor has moved, sending text message"
log.debug "$accelerationSensor has moved, texting $phone1"
sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved")
}
}

View File

@@ -69,10 +69,10 @@ def temperatureHandler(evt) {
def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 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 rose above $tooHot: sending SMS and activating $mySwitch"
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
def tempScale = location.temperatureScale ?: "F"
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
switch1?.on()

View File

@@ -50,9 +50,9 @@ def authPage() {
}
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 = "${apiServerUrl}"
// log.debug "app id: ${app.id}"
// log.debug "redirect url: ${redirectUrl}"s
// def redirectUrl = "${apiServerUrl}"
log.debug "app id: ${app.id}"
log.debug "redirect url: ${redirectUrl}"
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
section {
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)
selectors.add("${device.id}")
if (!childDevice) {
// log.info("Adding device ${device.id}: ${device.product}")
log.info("Adding device ${device.id}: ${device.product}")
def data = [
label: device.label,
level: Math.round((device.brightness ?: 1) * 100),

View File

@@ -111,23 +111,16 @@ private sendMessage(evt) {
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients, options)
} else {
if (pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
sendNotification(msg, options)
}
if (phone) {
options.phone = phone
if (pushAndPhone != 'No') {
log.debug 'Sending push and SMS'
options.method = 'both'
} else {
log.debug 'Sending SMS'
options.method = 'phone'
}
} else if (pushAndPhone != 'No') {
log.debug 'Sending push'
options.method = 'push'
} else {
log.debug 'Sending nothing'
options.method = 'none'
log.debug 'sending SMS'
sendNotification(msg, options)
}
sendNotification(msg, options)
}
if (frequency) {
state[evt.deviceId] = now()

View File

@@ -41,10 +41,10 @@ def updated() {
def presenceHandler(evt) {
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}")
} 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}")
}
}

View File

@@ -47,7 +47,7 @@ def updated() {
def presenceHandler(evt) {
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) {
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}")
}
} 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) {
sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients)

View File

@@ -67,7 +67,7 @@ def updated() {
}
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(cars, "presence", carPresence)

View File

@@ -71,7 +71,7 @@ def updated() {
private subscribeToEvents()
{
subscribe intrusionMotions, "motion", intruderMotion
// subscribe residentMotions, "motion", residentMotion
subscribe residentMotions, "motion", residentMotion
subscribe intrusionContacts, "contact", contact
subscribe alarms, "alarm", alarm
subscribe(app, appTouch)
@@ -156,7 +156,6 @@ def residentMotion(evt)
// startReArmSequence()
// }
//}
unsubscribe(residentMotions)
}
def contact(evt)
@@ -215,7 +214,7 @@ def checkForReArm()
}
else {
log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection"
}
}
}
private startAlarmSequence()

View File

@@ -48,7 +48,7 @@ def updated()
def contactOpenHandler(evt) {
log.trace "$evt.value: $evt, $settings"
log.debug "$contact1 was opened, sending text"
log.debug "$contact1 was opened, texting $phone1"
if (location.contactBookEnabled) {
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
}

View File

@@ -50,7 +50,7 @@ def updated() {
def motionActiveHandler(evt) {
log.trace "$evt.value: $evt, $settings"
if (presence1.latestValue("presence") == "not present") {
// Don't send a continuous stream of text messages
def deltaSeconds = 10
@@ -60,14 +60,14 @@ def motionActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) {
log.debug "SMS already sent within the last $deltaSeconds seconds"
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
} else {
if (location.contactBookEnabled) {
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)
}
else {
log.debug "$motion1 has moved while you were out, sending text"
log.debug "$motion1 has moved while you were out, texting $phone1"
sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out")
}
}

View File

@@ -53,13 +53,13 @@ def accelerationActiveHandler(evt) {
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
if (alreadySentSms) {
log.debug "SMS already sent to phone within the last $deltaSeconds seconds"
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
} else {
if (location.contactBookEnabled) {
sendNotificationToContacts("Gun case has moved!", recipients)
}
else {
log.debug "$accelerationSensor has moved, texting phone"
log.debug "$accelerationSensor has moved, texting $phone1"
sendSms(phone1, "Gun case has moved!")
}
}

View File

@@ -60,7 +60,7 @@ def authPage() {
def oauthInitUrl() {
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
state.oauth_request_token = token.oauth_token
@@ -76,7 +76,7 @@ def getToken() {
]
def requestTokenBaseUrl = "https://oauth.withings.com/account/request_token"
def url = buildSignedUrl(requestTokenBaseUrl, params)
//log.debug "getToken - url: $url"
log.debug "getToken - url: $url"
return getJsonFromUrl(url)
}
@@ -182,7 +182,7 @@ def exchangeToken() {
def requestTokenBaseUrl = "https://oauth.withings.com/account/access_token"
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)
@@ -198,8 +198,8 @@ def exchangeToken() {
def load() {
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)
def html = """