mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-04-02 06:13:07 +01:00
Compare commits
2 Commits
MSA-1472-1
...
MSA-1451-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73e73dc5a6 | ||
|
|
156adc3b86 |
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
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,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
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,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
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,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
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"
|
||||||
|
|||||||
@@ -274,7 +274,6 @@ private Map makeTemperatureResult(value) {
|
|||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: "" + value,
|
value: "" + value,
|
||||||
descriptionText: "${linkText} is ${value}°${temperatureScale}",
|
descriptionText: "${linkText} is ${value}°${temperatureScale}",
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,8 +254,7 @@ private Map getTemperatureResult(value) {
|
|||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ metadata {
|
|||||||
|
|
||||||
command "everywhereJoin"
|
command "everywhereJoin"
|
||||||
command "everywhereLeave"
|
command "everywhereLeave"
|
||||||
|
|
||||||
command "forceOff"
|
|
||||||
command "forceOn"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,9 +64,9 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
||||||
state "on", label: '${name}', action: "forceOff", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
|
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
|
||||||
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
|
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
|
||||||
state "off", label: '${name}', action: "forceOn", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
|
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
|
||||||
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
|
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
|
||||||
}
|
}
|
||||||
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
|
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
|
||||||
@@ -143,22 +140,8 @@ metadata {
|
|||||||
* one place.
|
* one place.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
def off() {
|
def off() { onAction("off") }
|
||||||
if (device.currentState("switch")?.value == "on") {
|
def on() { onAction("on") }
|
||||||
onAction("off")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def forceOff() {
|
|
||||||
onAction("off")
|
|
||||||
}
|
|
||||||
def on() {
|
|
||||||
if (device.currentState("switch")?.value == "off") {
|
|
||||||
onAction("on")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def forceOn() {
|
|
||||||
onAction("on")
|
|
||||||
}
|
|
||||||
def volup() { onAction("volup") }
|
def volup() { onAction("volup") }
|
||||||
def voldown() { onAction("voldown") }
|
def voldown() { onAction("voldown") }
|
||||||
def preset1() { onAction("1") }
|
def preset1() { onAction("1") }
|
||||||
@@ -257,11 +240,11 @@ def onAction(String user, data=null) {
|
|||||||
def actions = null
|
def actions = null
|
||||||
switch (user) {
|
switch (user) {
|
||||||
case "on":
|
case "on":
|
||||||
boseSetPowerState(true)
|
actions = boseSetPowerState(true)
|
||||||
break
|
break
|
||||||
case "off":
|
case "off":
|
||||||
boseSetNowPlaying(null, "STANDBY")
|
boseSetNowPlaying(null, "STANDBY")
|
||||||
boseSetPowerState(false)
|
actions = boseSetPowerState(false)
|
||||||
break
|
break
|
||||||
case "volume":
|
case "volume":
|
||||||
actions = boseSetVolume(data)
|
actions = boseSetVolume(data)
|
||||||
|
|||||||
@@ -89,17 +89,14 @@ def parse(String description) {
|
|||||||
log.debug "TEMP"
|
log.debug "TEMP"
|
||||||
map.name = "temperature"
|
map.name = "temperature"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(descMap.value)
|
||||||
map.unit = temperatureScale
|
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
|
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
|
||||||
log.debug "COOLING SETPOINT"
|
log.debug "COOLING SETPOINT"
|
||||||
map.name = "coolingSetpoint"
|
map.name = "coolingSetpoint"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(descMap.value)
|
||||||
map.unit = temperatureScale
|
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
|
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
|
||||||
log.debug "HEATING SETPOINT"
|
log.debug "HEATING SETPOINT"
|
||||||
map.name = "heatingSetpoint"
|
map.name = "heatingSetpoint"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(descMap.value)
|
||||||
map.unit = temperatureScale
|
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
|
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
|
||||||
log.debug "MODE"
|
log.debug "MODE"
|
||||||
map.name = "thermostatMode"
|
map.name = "thermostatMode"
|
||||||
@@ -172,7 +169,7 @@ def setHeatingSetpoint(degrees) {
|
|||||||
|
|
||||||
def degreesInteger = Math.round(degrees)
|
def degreesInteger = Math.round(degrees)
|
||||||
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
|
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
|
||||||
sendEvent("name": "heatingSetpoint", "value": degreesInteger, "unit": temperatureScale)
|
sendEvent("name": "heatingSetpoint", "value": degreesInteger)
|
||||||
|
|
||||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
|
||||||
@@ -183,7 +180,7 @@ def setCoolingSetpoint(degrees) {
|
|||||||
if (degrees != null) {
|
if (degrees != null) {
|
||||||
def degreesInteger = Math.round(degrees)
|
def degreesInteger = Math.round(degrees)
|
||||||
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
|
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
|
||||||
sendEvent("name": "coolingSetpoint", "value": degreesInteger, "unit": temperatureScale)
|
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
|
||||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Connected Cree LED Bulb
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Connected Cree LED Bulb](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Actuator** - represents that a Device has commands
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Polling** - represents that poll() can be implemented for the device
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Switch** - can detect state (possible values: on/off)
|
|
||||||
* **Switch Level** - represents current light level, usually 0-100 in percent
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C6 Connected Cree LED Bulb with maxReportTime of 10 min.
|
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 2*10 = 20 min
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
|
||||||
Pairing needs to be tried again by placing the device closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
|
||||||
* [Cree Connected LED Bulb Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
|
|
||||||
@@ -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.poll()
|
parent.pollChild()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.poll()
|
parent.pollChild()
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateEvent(Map results) {
|
def generateEvent(Map results) {
|
||||||
@@ -152,11 +152,11 @@ def generateEvent(Map results) {
|
|||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||||
isChange = isTemperatureStateChange(device, name, value.toString())
|
isChange = isTemperatureStateChange(device, name, value.toString())
|
||||||
isDisplayed = isChange
|
isDisplayed = isChange
|
||||||
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
|
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
|
||||||
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
||||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||||
event << [value: sendValue, unit: temperatureScale, displayed: false]
|
event << [value: sendValue, displayed: false]
|
||||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||||
isChange = isStateChange(device, name, value.toString())
|
isChange = isStateChange(device, name, value.toString())
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
||||||
@@ -234,9 +234,9 @@ void setHeatingSetpoint(setpoint) {
|
|||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||||
|
|
||||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||||
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
|
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
||||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
||||||
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||||
generateSetpointEvent()
|
generateSetpointEvent()
|
||||||
generateStatusEvent()
|
generateStatusEvent()
|
||||||
@@ -271,9 +271,9 @@ void setCoolingSetpoint(setpoint) {
|
|||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||||
|
|
||||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||||
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
|
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
||||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
||||||
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
|
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
|
||||||
generateSetpointEvent()
|
generateSetpointEvent()
|
||||||
generateStatusEvent()
|
generateStatusEvent()
|
||||||
@@ -287,14 +287,14 @@ void resumeProgram() {
|
|||||||
log.debug "resumeProgram() is called"
|
log.debug "resumeProgram() is called"
|
||||||
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
|
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
if (parent.resumeProgram(deviceId)) {
|
if (parent.resumeProgram(this, deviceId)) {
|
||||||
sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false)
|
sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false)
|
||||||
runIn(5, "poll")
|
runIn(5, "poll")
|
||||||
log.debug "resumeProgram() is done"
|
log.debug "resumeProgram() is done"
|
||||||
sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
|
sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
|
||||||
} else {
|
} else {
|
||||||
sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false)
|
sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false)
|
||||||
log.error "Error resumeProgram() check parent.resumeProgram(deviceId)"
|
log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -406,7 +406,7 @@ def generateOperatingStateEvent(operatingState) {
|
|||||||
def off() {
|
def off() {
|
||||||
log.debug "off"
|
log.debug "off"
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
if (parent.setMode ("off", deviceId))
|
if (parent.setMode (this,"off", deviceId))
|
||||||
generateModeEvent("off")
|
generateModeEvent("off")
|
||||||
else {
|
else {
|
||||||
log.debug "Error setting new mode."
|
log.debug "Error setting new mode."
|
||||||
@@ -420,7 +420,7 @@ def off() {
|
|||||||
def heat() {
|
def heat() {
|
||||||
log.debug "heat"
|
log.debug "heat"
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
if (parent.setMode ("heat", deviceId))
|
if (parent.setMode (this,"heat", deviceId))
|
||||||
generateModeEvent("heat")
|
generateModeEvent("heat")
|
||||||
else {
|
else {
|
||||||
log.debug "Error setting new mode."
|
log.debug "Error setting new mode."
|
||||||
@@ -438,7 +438,7 @@ def emergencyHeat() {
|
|||||||
def auxHeatOnly() {
|
def auxHeatOnly() {
|
||||||
log.debug "auxHeatOnly"
|
log.debug "auxHeatOnly"
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
if (parent.setMode ("auxHeatOnly", deviceId))
|
if (parent.setMode (this,"auxHeatOnly", deviceId))
|
||||||
generateModeEvent("auxHeatOnly")
|
generateModeEvent("auxHeatOnly")
|
||||||
else {
|
else {
|
||||||
log.debug "Error setting new mode."
|
log.debug "Error setting new mode."
|
||||||
@@ -452,7 +452,7 @@ def auxHeatOnly() {
|
|||||||
def cool() {
|
def cool() {
|
||||||
log.debug "cool"
|
log.debug "cool"
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
if (parent.setMode ("cool", deviceId))
|
if (parent.setMode (this,"cool", deviceId))
|
||||||
generateModeEvent("cool")
|
generateModeEvent("cool")
|
||||||
else {
|
else {
|
||||||
log.debug "Error setting new mode."
|
log.debug "Error setting new mode."
|
||||||
@@ -466,7 +466,7 @@ def cool() {
|
|||||||
def auto() {
|
def auto() {
|
||||||
log.debug "auto"
|
log.debug "auto"
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
if (parent.setMode ("auto", deviceId))
|
if (parent.setMode (this,"auto", deviceId))
|
||||||
generateModeEvent("auto")
|
generateModeEvent("auto")
|
||||||
else {
|
else {
|
||||||
log.debug "Error setting new mode."
|
log.debug "Error setting new mode."
|
||||||
@@ -489,7 +489,7 @@ def fanOn() {
|
|||||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||||
|
|
||||||
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
||||||
generateFanModeEvent(fanMode)
|
generateFanModeEvent(fanMode)
|
||||||
} else {
|
} else {
|
||||||
log.debug "Error setting new mode."
|
log.debug "Error setting new mode."
|
||||||
@@ -510,7 +510,7 @@ def fanAuto() {
|
|||||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||||
|
|
||||||
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
||||||
generateFanModeEvent(fanMode)
|
generateFanModeEvent(fanMode)
|
||||||
} else {
|
} else {
|
||||||
log.debug "Error setting new mode."
|
log.debug "Error setting new mode."
|
||||||
@@ -556,12 +556,12 @@ def generateSetpointEvent() {
|
|||||||
|
|
||||||
if (mode == "heat") {
|
if (mode == "heat") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (mode == "cool") {
|
else if (mode == "cool") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
|
||||||
|
|
||||||
} else if (mode == "auto") {
|
} else if (mode == "auto") {
|
||||||
|
|
||||||
@@ -573,7 +573,7 @@ def generateSetpointEvent() {
|
|||||||
|
|
||||||
} else if (mode == "auxHeatOnly") {
|
} else if (mode == "auxHeatOnly") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +608,7 @@ void raiseSetpoint() {
|
|||||||
targetvalue = maxCoolingSetpoint
|
targetvalue = maxCoolingSetpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
|
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
||||||
log.info "In mode $mode raiseSetpoint() to $targetvalue"
|
log.info "In mode $mode raiseSetpoint() to $targetvalue"
|
||||||
|
|
||||||
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
|
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
|
||||||
@@ -644,7 +644,7 @@ void lowerSetpoint() {
|
|||||||
targetvalue = minCoolingSetpoint
|
targetvalue = minCoolingSetpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
|
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
||||||
log.info "In mode $mode lowerSetpoint() to $targetvalue"
|
log.info "In mode $mode lowerSetpoint() to $targetvalue"
|
||||||
|
|
||||||
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
|
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
|
||||||
@@ -690,10 +690,10 @@ void alterSetpoint(temp) {
|
|||||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
|
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
|
||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
|
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
|
||||||
|
|
||||||
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
|
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||||
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
|
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
|
||||||
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
|
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
|
||||||
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
|
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
|
||||||
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
||||||
} else {
|
} else {
|
||||||
log.error "Error alterSetpoint()"
|
log.error "Error alterSetpoint()"
|
||||||
|
|||||||
@@ -682,7 +682,7 @@ def setHeatingSetpoint(degrees) {
|
|||||||
def temperatureScale = getTemperatureScale()
|
def temperatureScale = getTemperatureScale()
|
||||||
|
|
||||||
def degreesInteger = degrees as Integer
|
def degreesInteger = degrees as Integer
|
||||||
sendEvent("name":"heatingSetpoint", "value":degreesInteger, "unit":temperatureScale)
|
sendEvent("name":"heatingSetpoint", "value":degreesInteger)
|
||||||
|
|
||||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}"
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}"
|
||||||
@@ -691,7 +691,7 @@ def setHeatingSetpoint(degrees) {
|
|||||||
|
|
||||||
def setCoolingSetpoint(degrees) {
|
def setCoolingSetpoint(degrees) {
|
||||||
def degreesInteger = degrees as Integer
|
def degreesInteger = degrees as Integer
|
||||||
sendEvent("name":"coolingSetpoint", "value":degreesInteger, "unit":temperatureScale)
|
sendEvent("name":"coolingSetpoint", "value":degreesInteger)
|
||||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}"
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}"
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
command "reset"
|
command "reset"
|
||||||
@@ -56,10 +55,6 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(description) {
|
def parse(description) {
|
||||||
log.debug "parse() - $description"
|
log.debug "parse() - $description"
|
||||||
@@ -171,7 +166,3 @@ def verifyPercent(percent) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
||||||
state "default", label:'If removed, Hue lights will not work properly'
|
state "default", label:'Do not remove'
|
||||||
}
|
}
|
||||||
valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
||||||
state "default", label:'ID: ${currentValue}'
|
state "default", label:'ID: ${currentValue}'
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main (["rich-control"])
|
main (["rich-control"])
|
||||||
details(["rich-control", "doNotRemove", "idNumber", "networkAddress"])
|
details(["rich-control", "idNumber", "networkAddress", "doNotRemove"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ def parse(description) {
|
|||||||
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
|
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
|
||||||
results << createEvent(name: "${map.name}", value: "${map.value}")
|
results << createEvent(name: "${map.name}", value: "${map.value}")
|
||||||
} else {
|
} else {
|
||||||
log.trace "Parsing description"
|
log.trace "Parsing description"
|
||||||
def msg = parseLanMessage(description)
|
def msg = parseLanMessage(description)
|
||||||
if (msg.body) {
|
if (msg.body) {
|
||||||
def contentType = msg.headers["Content-Type"]
|
def contentType = msg.headers["Content-Type"]
|
||||||
@@ -72,13 +72,13 @@ def parse(description) {
|
|||||||
log.info "Bridge response: $msg.body"
|
log.info "Bridge response: $msg.body"
|
||||||
} else {
|
} else {
|
||||||
// Sending Bulbs List to parent"
|
// Sending Bulbs List to parent"
|
||||||
if (parent.isInBulbDiscovery())
|
if (parent.state.inBulbDiscovery)
|
||||||
log.info parent.bulbListHandler(device.hub.id, msg.body)
|
log.info parent.bulbListHandler(device.hub.id, msg.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (contentType?.contains("xml")) {
|
else if (contentType?.contains("xml")) {
|
||||||
log.debug "HUE BRIDGE ALREADY PRESENT"
|
log.debug "HUE BRIDGE ALREADY PRESENT"
|
||||||
parent.hubVerification(device.hub.id, msg.body)
|
parent.hubVerification(device.hub.id, msg.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
command "reset"
|
command "reset"
|
||||||
@@ -65,10 +64,6 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(description) {
|
def parse(description) {
|
||||||
log.debug "parse() - $description"
|
log.debug "parse() - $description"
|
||||||
@@ -187,7 +182,3 @@ def verifyPercent(percent) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.trace "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "refresh"
|
command "refresh"
|
||||||
}
|
}
|
||||||
@@ -49,10 +48,6 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(description) {
|
def parse(description) {
|
||||||
log.debug "parse() - $description"
|
log.debug "parse() - $description"
|
||||||
@@ -92,7 +87,3 @@ void refresh() {
|
|||||||
log.debug "Executing 'refresh'"
|
log.debug "Executing 'refresh'"
|
||||||
parent.manualRefresh()
|
parent.manualRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
capability "Color Temperature"
|
capability "Color Temperature"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "refresh"
|
command "refresh"
|
||||||
}
|
}
|
||||||
@@ -54,10 +53,6 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(description) {
|
def parse(description) {
|
||||||
log.debug "parse() - $description"
|
log.debug "parse() - $description"
|
||||||
@@ -106,7 +101,3 @@ void refresh() {
|
|||||||
log.debug "Executing 'refresh'"
|
log.debug "Executing 'refresh'"
|
||||||
parent.manualRefresh()
|
parent.manualRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -147,8 +147,8 @@ private Map parseIasMessage(String description) {
|
|||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
|
|
||||||
resultMap.name = 'motion'
|
result.name = 'motion'
|
||||||
resultMap.value = zs.isAlarm2Set() ? 'active' : 'inactive'
|
result.value = zs.isAlarm2Set() ? 'active' : 'inactive'
|
||||||
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
|
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# SmartPower Outlet
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartPower Outlet](https://shop.smartthings.com/#!/products/smartpower-outlet)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Actuator** - represents that a Device has commands
|
|
||||||
* **Switch** - can detect state (possible values: on/off)
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Power Meter** - detects power meter for device in either w or kw.
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C1 smart power outlet with maxReportTime of 10 min.
|
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 2*10 = 20 min
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
|
||||||
Pairing needs to be tried again by placing the device closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following links
|
|
||||||
for the different models:
|
|
||||||
* [SmartPower Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/201084854-SmartPower-Outlet)
|
|
||||||
* [Samsung SmartThings Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957620)
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Smartsense Moisture Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartThings Moisture Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-water-leak-sensor)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - defines device measures current temperature
|
|
||||||
* **Water Sensor** - can detect presence of water (dry or wet)
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 moisture sensor with maxReportTime of 1 hr.
|
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 2*60 = 120 min
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2 3V battery required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the different sensors from SmartThings can be found in the following links
|
|
||||||
for the different models:
|
|
||||||
* [SmartSense Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202847044-SmartSense-Moisture-Sensor)
|
|
||||||
* [Samsung SmartThings Water Leak Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957630)
|
|
||||||
Other troubleshooting tips are listed as follows:
|
|
||||||
* [Troubleshooting: Samsung SmartThings Water Leak Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
|
||||||
@@ -24,7 +24,7 @@ metadata {
|
|||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Water Sensor"
|
capability "Water Sensor"
|
||||||
capability "Health Check"
|
capability "Health Check"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
@@ -233,8 +233,6 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
}
|
||||||
@@ -261,8 +259,7 @@ private Map getTemperatureResult(value) {
|
|||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText,
|
||||||
translatable: true,
|
translatable: true
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +304,7 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ Works with:
|
|||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
* [Capabilities](#capabilities)
|
||||||
* [Health](#device-health)
|
* [Health]($health)
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
|
|
||||||
@@ -22,24 +21,10 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C2 motion sensor with maxReportTime of 1 hr.
|
A Category C2 motion sensor that has 120min check-in interval
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 2*60 = 120 min
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2477 (for Samsung SmartThings Motion Sensor) / CR123A (SmartSense Motion Sensor) 3V battery is required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
|
|
||||||
for the different models:
|
|
||||||
* [SmartSense Motion Sensor (original model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903280-SmartSense-Motion-Sensor-original-model-)
|
|
||||||
* [SmartSense Motion Sensor (2014 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203077520-SmartSense-Motion-Sensor-2014-model-)
|
|
||||||
* [Samsung SmartThings Motion Sensor (2015 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957580-Samsung-SmartThings-Motion-Sensor-2015-model-)
|
|
||||||
Other troubleshooting tips are listed as follows:
|
|
||||||
* [Troubleshooting: Samsung SmartThings Motion Sensor is stuck showing "Motion Detected" or "No Motion"](https://support.smartthings.com/hc/en-us/articles/200961130-Troubleshooting-Samsung-SmartThings-Motion-Sensor-is-stuck-showing-Motion-Detected-or-No-Motion-)
|
|
||||||
* [Troubleshooting: Samsung SmartThings Motion Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ metadata {
|
|||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Health Check"
|
capability "Health Check"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
@@ -248,8 +248,6 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
}
|
||||||
@@ -276,8 +274,7 @@ private Map getTemperatureResult(value) {
|
|||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText,
|
||||||
translatable: true,
|
translatable: true
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +315,7 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ private Map parseCustomMessage(String description) {
|
|||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(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) {
|
def getTemperature(value) {
|
||||||
@@ -206,8 +206,6 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
@@ -228,8 +226,7 @@ private Map getTemperatureResult(value) {
|
|||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Smartsense Multi Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartThings Multi Sensor](https://shop.smartthings.com/#!/products/smartsense-multi)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Three Axis** - monitors the state of a single axis
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
* **Contact Sensor** - can detect contact (possible values: open,closed)
|
|
||||||
* **Acceleration Sensor** - allows for acceleration detection.
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - defines device measures current temperature
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 multi sensor with maxReportTime of 1 hr.
|
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 2*60 = 120 min
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2450 (for Samsung SmartThings Multipurpose Sensor) battery / Two AAAA (for SmartSense Multi Sensor) batteries required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Other troubleshooting tips are listed as follows:
|
|
||||||
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor is stuck on "open" or "closed"](https://support.smartthings.com/hc/en-us/articles/200955940-Troubleshooting-Samsung-SmartThings-Multipurpose-Sensor-is-stuck-on-open-or-closed-)
|
|
||||||
* [Troubleshooting: Temperature reading for the Samsung SmartThings Multipurpose Sensor is off](https://support.smartthings.com/hc/en-us/articles/200756845-Troubleshooting-Temperature-reading-for-the-Samsung-SmartThings-Multipurpose-Sensor-is-off)
|
|
||||||
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
|
||||||
@@ -313,8 +313,6 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
}
|
||||||
@@ -335,11 +333,10 @@ private Map getTemperatureResult(value) {
|
|||||||
'{{ device.displayName }} was {{ value }}°F'
|
'{{ device.displayName }} was {{ value }}°F'
|
||||||
|
|
||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText,
|
||||||
translatable: true,
|
translatable: true
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +413,7 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||||
|
|
||||||
log.debug "Configuring Reporting"
|
log.debug "Configuring Reporting"
|
||||||
|
|
||||||
|
|||||||
@@ -206,8 +206,6 @@ def getTemperature(value) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
@@ -225,10 +223,9 @@ def getTemperature(value) {
|
|||||||
}
|
}
|
||||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Smartsense Open/Closed Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartThings Open/Closed Sensor](https://shop.smartthings.com/#!/packs/smartsense-open-closed-sensor/)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Contact Sensor** - can detect contact (possible values: open,closed)
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - defines device measures current temperature
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 open/closed sensor with maxReportTime of 1 hr.
|
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 2*60 = 120 min
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2 3V battery required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
|
|
||||||
* [SmartSense Open/Closed Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202836844-SmartSense-Open-Closed-Sensor)
|
|
||||||
@@ -207,8 +207,6 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
@@ -228,8 +226,7 @@ private Map getTemperatureResult(value) {
|
|||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +267,7 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# SmartSense Temp/Humidity Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartSense Temp/Humidity Sensor](https://shop.smartthings.com/#!/products/smartsense-temp-humidity-sensor)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Relative Humidity Measurement** - defines device measures relative humidity
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - defines device measures current temperature
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 1 hr.
|
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 2*60 = 120 min
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2 battery is required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
|
|
||||||
* [Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203040294)
|
|
||||||
@@ -214,8 +214,6 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
@@ -235,8 +233,7 @@ private Map getTemperatureResult(value) {
|
|||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +275,7 @@ def refresh()
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 7200, displayed: false, data: [protocol: "zigbee"])
|
||||||
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
def configCmds = [
|
def configCmds = [
|
||||||
|
|||||||
@@ -213,8 +213,7 @@ private Map getTemperatureResult(value) {
|
|||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,11 +106,11 @@ def parse(String description) {
|
|||||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
|
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
|
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
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 {
|
else {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ metadata {
|
|||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Color Temperature"
|
capability "Color Temperature"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Health Check"
|
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
@@ -36,7 +35,6 @@ metadata {
|
|||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
@@ -74,12 +72,6 @@ def parse(String description) {
|
|||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
def event = zigbee.getEvent(description)
|
def event = zigbee.getEvent(description)
|
||||||
if (event) {
|
if (event) {
|
||||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
|
||||||
if (state.lastActivity == null){
|
|
||||||
state.lastActivity = now()
|
|
||||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
|
||||||
}
|
|
||||||
state.lastActivity = now()
|
|
||||||
if (event.name=="level" && event.value==0) {}
|
if (event.name=="level" && event.value==0) {}
|
||||||
else {
|
else {
|
||||||
if (event.name=="colorTemperature") {
|
if (event.name=="colorTemperature") {
|
||||||
@@ -106,29 +98,12 @@ def setLevel(value) {
|
|||||||
zigbee.setLevel(value)
|
zigbee.setLevel(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
|
|
||||||
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
|
||||||
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
|
|
||||||
state.lastActivity = null
|
|
||||||
return zigbee.onOffRefresh()
|
|
||||||
} else {
|
|
||||||
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
|
|
||||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
|
|
||||||
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
|
|
||||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
927
smartapps/citrix/octoblu.src/octoblu.groovy
Normal file
927
smartapps/citrix/octoblu.src/octoblu.groovy
Normal file
@@ -0,0 +1,927 @@
|
|||||||
|
/**
|
||||||
|
The MIT License (MIT)
|
||||||
|
Copyright (c) 2016 Octoblu
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import groovy.transform.Field
|
||||||
|
|
||||||
|
@Field final USE_DEBUG = true
|
||||||
|
@Field final selectedCapabilities = [ "actuator", "sensor" ]
|
||||||
|
|
||||||
|
private getVendorName() { "Octoblu" }
|
||||||
|
private getVendorIcon() { "http://i.imgur.com/BjTfDYk.png" }
|
||||||
|
private apiUrl() { appSettings.apiUrl ?: "https://meshblu.octoblu.com/" }
|
||||||
|
private getVendorAuthPath() { appSettings.vendorAuthPath ?: "https://oauth.octoblu.com/authorize" }
|
||||||
|
private getVendorTokenPath() { appSettings.vendorTokenPath ?: "https://oauth.octoblu.com/access_token" }
|
||||||
|
|
||||||
|
definition(
|
||||||
|
name: "Octoblu",
|
||||||
|
namespace: "citrix",
|
||||||
|
author: "Octoblu",
|
||||||
|
description: "Connect SmartThings devices to Octoblu",
|
||||||
|
category: "SmartThings Labs",
|
||||||
|
iconUrl: "http://i.imgur.com/BjTfDYk.png",
|
||||||
|
iconX2Url: "http://i.imgur.com/BjTfDYk.png"
|
||||||
|
) {
|
||||||
|
appSetting "apiUrl"
|
||||||
|
appSetting "vendorAuthPath"
|
||||||
|
appSetting "vendorTokenPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
page(name: "welcomePage")
|
||||||
|
page(name: "authPage")
|
||||||
|
page(name: "subscribePage")
|
||||||
|
page(name: "devicesPage")
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings {
|
||||||
|
path("/oauthCode") {
|
||||||
|
action: [ GET: "getOauthCode" ]
|
||||||
|
}
|
||||||
|
path("/message") {
|
||||||
|
action: [ POST: "postMessage" ]
|
||||||
|
}
|
||||||
|
path("/app") {
|
||||||
|
action: [ POST: "postApp" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def getDevInfo() {
|
||||||
|
return state.vendorDevices.collect { k, v -> "${v.uuid} " }.sort().join(" \n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def welcomePage() {
|
||||||
|
cleanUpTokens()
|
||||||
|
|
||||||
|
return dynamicPage(name: "welcomePage", nextPage: "authPage", uninstall: showUninstall) {
|
||||||
|
section {
|
||||||
|
paragraph title: "Welcome to the Octoblu SmartThings App!", "press 'Next' to continue"
|
||||||
|
}
|
||||||
|
if (state.vendorDevices && state.vendorDevices.size()>0) {
|
||||||
|
section {
|
||||||
|
paragraph title: "My SmartThings in Octobu (${state.vendorDevices.size()}):", getDevInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.installed) {
|
||||||
|
section {
|
||||||
|
input name: "showUninstall", type: "bool", title: "Uninstall", submitOnChange: true
|
||||||
|
if (showUninstall) {
|
||||||
|
state.removeDevices = removeDevices
|
||||||
|
input name: "removeDevices", type: "bool", title: "Remove Octoblu devices", submitOnChange: true
|
||||||
|
paragraph title: "Sorry to see you go!", "please email <support@octoblu.com> with any feedback or issues"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def authPage() {
|
||||||
|
|
||||||
|
if (!state.accessToken) {
|
||||||
|
createAccessToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
debug "using app access token ${state.accessToken}"
|
||||||
|
|
||||||
|
if (!state.vendorOAuthToken) {
|
||||||
|
createOAuthDevice()
|
||||||
|
}
|
||||||
|
|
||||||
|
def oauthParams = [
|
||||||
|
response_type: "code",
|
||||||
|
client_id: state.vendorOAuthUuid,
|
||||||
|
redirect_uri: getApiServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/oauthCode"
|
||||||
|
]
|
||||||
|
|
||||||
|
def redirectUrl = getVendorAuthPath() + '?' + toQueryString(oauthParams)
|
||||||
|
debug "tokened redirect_uri = ${oauthParams.redirect_uri}"
|
||||||
|
|
||||||
|
def isRequired = !state.vendorBearerToken
|
||||||
|
return dynamicPage(name: "authPage", title: "Octoblu Authentication", nextPage:(isRequired ? null : "subscribePage"), install: isRequired) {
|
||||||
|
section {
|
||||||
|
debug "url: ${redirectUrl}"
|
||||||
|
if (isRequired) {
|
||||||
|
href url:redirectUrl, style:"embedded", title: "Authorize with Octoblu", required: isRequired, description:"please login with Octoblu to complete setup"
|
||||||
|
} else {
|
||||||
|
paragraph title: "Please press 'Next' to continue", "Octoblu token has been created"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def createOAuthDevice() {
|
||||||
|
def oAuthDevice = [
|
||||||
|
"name": "SmartThings",
|
||||||
|
"owner": "68c39f40-cc13-4560-a68c-e8acd021cff9",
|
||||||
|
"type": "device:oauth",
|
||||||
|
"online": true,
|
||||||
|
"options": [
|
||||||
|
"name": "SmartThings",
|
||||||
|
"imageUrl": "https://i.imgur.com/TsXefbK.png",
|
||||||
|
"callbackUrl": getApiServerUrl() + "/api"
|
||||||
|
],
|
||||||
|
"configureWhitelist": [ "68c39f40-cc13-4560-a68c-e8acd021cff9" ],
|
||||||
|
"discoverWhitelist": [ "*" ],
|
||||||
|
"receiveWhitelist": [],
|
||||||
|
"sendWhitelist": []
|
||||||
|
]
|
||||||
|
|
||||||
|
def postParams = [ uri: apiUrl()+"devices",
|
||||||
|
body: groovy.json.JsonOutput.toJson(oAuthDevice)]
|
||||||
|
|
||||||
|
try {
|
||||||
|
httpPostJson(postParams) { response ->
|
||||||
|
debug "got new token for oAuth device ${response.data}"
|
||||||
|
state.vendorOAuthUuid = response.data.uuid
|
||||||
|
state.vendorOAuthToken = response.data.token
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "unable to create oAuth device: ${e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def subscribePage() {
|
||||||
|
return dynamicPage(name: "subscribePage", title: "Subscribe to SmartThing devices", nextPage: "devicesPage") {
|
||||||
|
section {
|
||||||
|
// input name: "selectedCapabilities", type: "enum", title: "capability filter",
|
||||||
|
// submitOnChange: true, multiple: true, required: false, options: [ "actuator", "sensor" ]
|
||||||
|
for (capability in selectedCapabilities) {
|
||||||
|
input name: "${capability}Capability".toString(), type: "capability.$capability", title: "${capability.capitalize()} Things", multiple: true, required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
section(" ") {
|
||||||
|
input name: "pleaseCreateAppDevice", type: "bool", title: "Create a SmartApp device", defaultValue: true
|
||||||
|
paragraph "A SmartApp device allows access to location and hub information for this installation"
|
||||||
|
}
|
||||||
|
section(" ") {
|
||||||
|
paragraph title: "", "Existing Octoblu devices may be modified!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def devicesPage() {
|
||||||
|
def postParams = [
|
||||||
|
uri: apiUrl() + "devices?owner=${state.vendorUuid}&category=smart-things",
|
||||||
|
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"]
|
||||||
|
]
|
||||||
|
state.vendorDevices = [:]
|
||||||
|
|
||||||
|
def hasDevice = [:]
|
||||||
|
hasDevice[app.id] = true
|
||||||
|
selectedCapabilities.each { capability ->
|
||||||
|
def smartDevices = settings["${capability}Capability"]
|
||||||
|
smartDevices.each { smartDevice ->
|
||||||
|
hasDevice[smartDevice.id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug "getting url ${postParams.uri}"
|
||||||
|
try {
|
||||||
|
httpGet(postParams) { response ->
|
||||||
|
debug "devices json ${response.data.devices}"
|
||||||
|
response.data.devices.each { device ->
|
||||||
|
if (device.smartDeviceId && hasDevice[device.smartDeviceId]) {
|
||||||
|
debug "found device ${device.uuid} with smartDeviceId ${device.smartDeviceId}"
|
||||||
|
state.vendorDevices[device.smartDeviceId] = getDeviceInfo(device)
|
||||||
|
}
|
||||||
|
debug "has device: ${device.uuid} ${device.name} ${device.type}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "devices error ${e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedCapabilities.each { capability ->
|
||||||
|
debug "checking devices for capability ${capability}"
|
||||||
|
createDevices(settings["${capability}Capability"])
|
||||||
|
}
|
||||||
|
if (pleaseCreateAppDevice)
|
||||||
|
createAppDevice()
|
||||||
|
|
||||||
|
return dynamicPage(name: "devicesPage", title: "Octoblu Things", install: true) {
|
||||||
|
section {
|
||||||
|
paragraph title: "Please press 'Done' to finish setup", "and subscribe to SmartThing events"
|
||||||
|
paragraph title: "My Octoblu UUID:", "${state.vendorUuid}"
|
||||||
|
paragraph title: "My SmartThings in Octobu (${state.vendorDevices.size()}):", getDevInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def createDevices(smartDevices) {
|
||||||
|
|
||||||
|
smartDevices.each { smartDevice ->
|
||||||
|
def commands = [
|
||||||
|
[ "name": "app-get-value" ],
|
||||||
|
[ "name": "app-get-state" ],
|
||||||
|
[ "name": "app-get-device" ],
|
||||||
|
[ "name": "app-get-events" ]
|
||||||
|
]
|
||||||
|
|
||||||
|
smartDevice.supportedCommands.each { command ->
|
||||||
|
if (command.arguments.size()>0) {
|
||||||
|
commands.push([ "name": command.name, "args": command.arguments ])
|
||||||
|
} else {
|
||||||
|
commands.push([ "name": command.name ])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug "creating device for ${smartDevice.id}"
|
||||||
|
|
||||||
|
def schemas = [
|
||||||
|
"version": "2.0.0",
|
||||||
|
"message": [:]
|
||||||
|
]
|
||||||
|
|
||||||
|
commands.each { command ->
|
||||||
|
schemas."message"."$command.name" = [
|
||||||
|
"type": "object",
|
||||||
|
"properties": [
|
||||||
|
"smartDeviceId": [
|
||||||
|
"type": "string",
|
||||||
|
"readOnly": true,
|
||||||
|
"default": "$smartDevice.id",
|
||||||
|
"x-schema-form": [
|
||||||
|
"condition": "false"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"command": [
|
||||||
|
"type": "string",
|
||||||
|
"readOnly": true,
|
||||||
|
"default": "$command.name",
|
||||||
|
"enum": ["$command.name"],
|
||||||
|
"x-schema-form": [
|
||||||
|
"condition": "false"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
if (command.args) {
|
||||||
|
schemas."message"."$command.name"."properties"."args" = [
|
||||||
|
"type": "object",
|
||||||
|
"title": "Arguments",
|
||||||
|
"properties": [:]
|
||||||
|
]
|
||||||
|
|
||||||
|
command.args.each { arg ->
|
||||||
|
def argLower = "$arg"
|
||||||
|
argLower = argLower.toLowerCase()
|
||||||
|
if (argLower == "color_map") {
|
||||||
|
schemas."message"."$command.name"."properties"."args"."properties"."$argLower" = [
|
||||||
|
"type": "object",
|
||||||
|
"properties": [
|
||||||
|
"hex": [
|
||||||
|
"type": "string"
|
||||||
|
],
|
||||||
|
"level": [
|
||||||
|
"type": "number"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
schemas."message"."$command.name"."properties"."args"."properties"."$argLower" = [
|
||||||
|
"type": "$argLower"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug "UPDATED message schema: ${schemas}"
|
||||||
|
|
||||||
|
def deviceProperties = [
|
||||||
|
"schemas": schemas,
|
||||||
|
"needsSetup": false,
|
||||||
|
"online": true,
|
||||||
|
"name": "${smartDevice.displayName}",
|
||||||
|
"smartDeviceId": "${smartDevice.id}",
|
||||||
|
"logo": "https://i.imgur.com/TsXefbK.png",
|
||||||
|
"owner": "${state.vendorUuid}",
|
||||||
|
"configureWhitelist": [],
|
||||||
|
"discoverWhitelist": ["${state.vendorUuid}"],
|
||||||
|
"receiveWhitelist": [],
|
||||||
|
"sendWhitelist": [],
|
||||||
|
"type": "device:${smartDevice.name.replaceAll('\\s','-').toLowerCase()}",
|
||||||
|
"category": "smart-things",
|
||||||
|
"meshblu": [
|
||||||
|
"forwarders": [
|
||||||
|
"received": [[
|
||||||
|
"url": getApiServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/message",
|
||||||
|
"method": "POST",
|
||||||
|
"type": "webhook"
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
updatePermissions(deviceProperties, smartDevice.id)
|
||||||
|
def params = [
|
||||||
|
uri: apiUrl() + "devices",
|
||||||
|
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"],
|
||||||
|
body: groovy.json.JsonOutput.toJson(deviceProperties)
|
||||||
|
]
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (!state.vendorDevices[smartDevice.id]) {
|
||||||
|
debug "creating new device for ${smartDevice.id}"
|
||||||
|
httpPostJson(params) { response ->
|
||||||
|
state.vendorDevices[smartDevice.id] = getDeviceInfo(response.data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params.uri = params.uri + "/${state.vendorDevices[smartDevice.id].uuid}"
|
||||||
|
debug "the device ${smartDevice.id} has already been created, updating ${params.uri}"
|
||||||
|
httpPutJson(params) { response ->
|
||||||
|
resetVendorDeviceToken(smartDevice.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
log.error "unable to create new device ${e}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def createAppDevice() {
|
||||||
|
def commands = [
|
||||||
|
[ "name": "app-get-location" ],
|
||||||
|
[ "name": "app-get-devices" ],
|
||||||
|
[ "name": "app-set-mode" ],
|
||||||
|
]
|
||||||
|
|
||||||
|
def schemas = [
|
||||||
|
"version": "2.0.0",
|
||||||
|
"message": [:]
|
||||||
|
]
|
||||||
|
|
||||||
|
commands.each { command ->
|
||||||
|
schemas."message"."$command.name" = [
|
||||||
|
"type": "object",
|
||||||
|
"properties": [
|
||||||
|
"command": [
|
||||||
|
"type": "string",
|
||||||
|
"readOnly": true,
|
||||||
|
"default": "$command.name",
|
||||||
|
"enum": ["$command.name"],
|
||||||
|
"x-schema-form": [
|
||||||
|
"condition": "false"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
schemas."message"."app-set-mode"."properties"."args" = [
|
||||||
|
"type": "object",
|
||||||
|
"title": "Arguments",
|
||||||
|
"properties": [
|
||||||
|
"mode": [
|
||||||
|
"type": "string"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
def deviceProperties = [
|
||||||
|
"schemas": schemas,
|
||||||
|
"needsSetup": false,
|
||||||
|
"online": true,
|
||||||
|
"name": "${location.name} SmartApp",
|
||||||
|
"smartDeviceId": "${app.id}",
|
||||||
|
"logo": "https://i.imgur.com/TsXefbK.png",
|
||||||
|
"owner": "${state.vendorUuid}",
|
||||||
|
"configureWhitelist": [],
|
||||||
|
"discoverWhitelist": ["${state.vendorUuid}"],
|
||||||
|
"receiveWhitelist": [],
|
||||||
|
"sendWhitelist": [],
|
||||||
|
"type": "device:smart-things-app",
|
||||||
|
"category": "smart-things",
|
||||||
|
"meshblu": [
|
||||||
|
"forwarders": [
|
||||||
|
"received": [[
|
||||||
|
"url": getApiServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/app",
|
||||||
|
"method": "POST",
|
||||||
|
"type": "webhook"
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
updatePermissions(deviceProperties, app.id)
|
||||||
|
def params = [
|
||||||
|
uri: apiUrl() + "devices",
|
||||||
|
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"],
|
||||||
|
body: groovy.json.JsonOutput.toJson(deviceProperties)
|
||||||
|
]
|
||||||
|
|
||||||
|
debug "creating app device!"
|
||||||
|
debug params.body
|
||||||
|
try {
|
||||||
|
|
||||||
|
// debug params
|
||||||
|
if (!state.vendorDevices[app.id]) {
|
||||||
|
debug "creating new app device for ${app.id}"
|
||||||
|
httpPostJson(params) { response ->
|
||||||
|
state.vendorDevices[app.id] = getDeviceInfo(response.data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params.uri = params.uri + "/${state.vendorDevices[app.id].uuid}"
|
||||||
|
debug "the app device ${app.id} has already been created, updating ${params.uri}"
|
||||||
|
httpPutJson(params) { response ->
|
||||||
|
resetVendorDeviceToken(app.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
log.error "unable to create new device ${e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def updatePermissions(newDevice, id) {
|
||||||
|
def device = state.vendorDevices[id]
|
||||||
|
if (!device) return
|
||||||
|
newDevice.configureWhitelist = device.configureWhitelist
|
||||||
|
newDevice.discoverWhitelist = device.discoverWhitelist
|
||||||
|
newDevice.receiveWhitelist = device.receiveWhitelist
|
||||||
|
newDevice.sendWhitelist = device.sendWhitelist
|
||||||
|
}
|
||||||
|
|
||||||
|
def getDeviceInfo(device) {
|
||||||
|
return [
|
||||||
|
"uuid": device.uuid,
|
||||||
|
"token": device.token,
|
||||||
|
"configureWhitelist": device.configureWhitelist,
|
||||||
|
"discoverWhitelist": device.discoverWhitelist,
|
||||||
|
"receiveWhitelist": device.receiveWhitelist,
|
||||||
|
"sendWhitelist": device.sendWhitelist,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def resetVendorDeviceToken(smartDeviceId) {
|
||||||
|
def deviceUUID = state.vendorDevices[smartDeviceId].uuid
|
||||||
|
if (!deviceUUID) {
|
||||||
|
debug "no device uuid in resetVendorDeviceToken?"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug "getting new token for ${smartDeviceId}/${deviceUUID}"
|
||||||
|
def postParams = [
|
||||||
|
uri: apiUrl() + "devices/${deviceUUID}/token",
|
||||||
|
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"]]
|
||||||
|
try {
|
||||||
|
httpPost(postParams) { response ->
|
||||||
|
state.vendorDevices[smartDeviceId] = getDeviceInfo(response.data)
|
||||||
|
debug "got new token for ${smartDeviceId}/${deviceUUID}"
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "unable to get new token ${e}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
unsubscribe()
|
||||||
|
debug "Updated with settings: ${settings}"
|
||||||
|
def subscribed = [:]
|
||||||
|
selectedCapabilities.each{ capability ->
|
||||||
|
settings."${capability}Capability".each { thing ->
|
||||||
|
if (subscribed[thing.id]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subscribed[thing.id] = true
|
||||||
|
thing.supportedAttributes.each { attribute ->
|
||||||
|
debug "subscribe to attribute ${attribute.name}"
|
||||||
|
subscribe thing, attribute.name, eventForward
|
||||||
|
}
|
||||||
|
thing.supportedCommands.each { command ->
|
||||||
|
debug "subscribe to command ${command.name}"
|
||||||
|
subscribeToCommand thing, command.name, eventForward
|
||||||
|
}
|
||||||
|
debug "subscribed to thing ${thing.id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanUpTokens()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def cleanUpTokens() {
|
||||||
|
|
||||||
|
if (state.vendorToken) {
|
||||||
|
def params = [
|
||||||
|
uri: apiUrl() + "devices/${state.vendorUuid}/tokens/${state.vendorToken}",
|
||||||
|
headers: ["Authorization": "Bearer ${state.vendorBearerToken}"]
|
||||||
|
]
|
||||||
|
|
||||||
|
debug "deleting url ${params.uri}"
|
||||||
|
try {
|
||||||
|
httpDelete(params) { response ->
|
||||||
|
debug "revoked token for ${state.vendorUuid}...?"
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "token delete error ${e}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.vendorBearerToken = null
|
||||||
|
state.vendorUuid = null
|
||||||
|
state.vendorToken = null
|
||||||
|
|
||||||
|
if (state.vendorOAuthToken) {
|
||||||
|
def params = [
|
||||||
|
uri: apiUrl() + "devices/${state.vendorOAuthUuid}",
|
||||||
|
headers: [
|
||||||
|
"meshblu_auth_uuid": state.vendorOAuthUuid,
|
||||||
|
"meshblu_auth_token": state.vendorOAuthToken
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
debug "deleting url ${params.uri}"
|
||||||
|
try {
|
||||||
|
httpDelete(params) { response ->
|
||||||
|
debug "deleted oauth device for ${state.vendorOAuthUuid}...?"
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "oauth token delete error ${e}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.vendorOAuthUuid = null
|
||||||
|
state.vendorOAuthToken = null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def getOauthCode() {
|
||||||
|
// revokeAccessToken()
|
||||||
|
// state.accessToken = createAccessToken()
|
||||||
|
debug "generated app access token ${state.accessToken}"
|
||||||
|
|
||||||
|
def postParams = [
|
||||||
|
uri: getVendorTokenPath(),
|
||||||
|
body: [
|
||||||
|
client_id: state.vendorOAuthUuid,
|
||||||
|
client_secret: state.vendorOAuthToken,
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
code: params.code
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
def style = "<style type='text/css'>body{font-size:2em;padding:1em}</style>"
|
||||||
|
def startBody = "<html>${style}<body>"
|
||||||
|
def endBody = "</body></html>"
|
||||||
|
def goodResponse = "${startBody}<h1>Received Octoblu Token!</h1><h2>Press 'Done' to finish setup.</h2>${endBody}"
|
||||||
|
def badResponse = "${startBody}<h1>Something went wrong...</h1><h2>PANIC!</h2>${endBody}"
|
||||||
|
debug "authorizeToken with postParams ${postParams}"
|
||||||
|
|
||||||
|
try {
|
||||||
|
httpPost(postParams) { response ->
|
||||||
|
debug "response: ${response.data}"
|
||||||
|
state.vendorBearerToken = response.data.access_token
|
||||||
|
def bearer = new String((new Base64()).decode(state.vendorBearerToken)).split(":")
|
||||||
|
state.vendorUuid = bearer[0]
|
||||||
|
state.vendorToken = bearer[1]
|
||||||
|
|
||||||
|
debug "have octoblu tokens ${state.vendorBearerToken}"
|
||||||
|
render contentType: 'text/html', data: (state.vendorBearerToken ? goodResponse : badResponse)
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
log.error "second leg oauth error ${e}"
|
||||||
|
render contentType: 'text/html', data: badResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getEventData(evt) {
|
||||||
|
return [
|
||||||
|
"date" : evt.date,
|
||||||
|
"id" : evt.id,
|
||||||
|
"data" : evt.data,
|
||||||
|
"description" : evt.description,
|
||||||
|
"descriptionText" : evt.descriptionText,
|
||||||
|
"displayName" : evt.displayName,
|
||||||
|
"deviceId" : evt.deviceId,
|
||||||
|
"hubId" : evt.hubId,
|
||||||
|
"installedSmartAppId" : evt.installedSmartAppId,
|
||||||
|
"isoDate" : evt.isoDate,
|
||||||
|
"isDigital" : evt.isDigital(),
|
||||||
|
"isPhysical" : evt.isPhysical(),
|
||||||
|
"isStateChange" : evt.isStateChange(),
|
||||||
|
"locationId" : evt.locationId,
|
||||||
|
"name" : evt.name,
|
||||||
|
"source" : evt.source,
|
||||||
|
"unit" : evt.unit,
|
||||||
|
"value" : evt.value,
|
||||||
|
"category" : "event",
|
||||||
|
"type" : "device:smart-thing"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def eventForward(evt) {
|
||||||
|
def eventData = [ "devices" : [ "*" ], "payload" : getEventData(evt) ]
|
||||||
|
|
||||||
|
debug "sending event: ${groovy.json.JsonOutput.toJson(eventData)}"
|
||||||
|
|
||||||
|
def vendorDevice = state.vendorDevices[evt.deviceId]
|
||||||
|
if (!vendorDevice) {
|
||||||
|
log.error "aborting, vendor device for ${evt.deviceId} doesn't exist?"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
debug "using device ${vendorDevice}"
|
||||||
|
|
||||||
|
def postParams = [
|
||||||
|
uri: apiUrl() + "messages",
|
||||||
|
headers: [
|
||||||
|
"meshblu_auth_uuid": vendorDevice.uuid,
|
||||||
|
"meshblu_auth_token": vendorDevice.token
|
||||||
|
],
|
||||||
|
body: groovy.json.JsonOutput.toJson(eventData)
|
||||||
|
]
|
||||||
|
|
||||||
|
try {
|
||||||
|
httpPostJson(postParams) { response ->
|
||||||
|
debug "sent off device event"
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "unable to send device event ${e}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def postMessage() {
|
||||||
|
debug("received message data ${request.JSON}")
|
||||||
|
def foundDevice = false
|
||||||
|
selectedCapabilities.each{ capability ->
|
||||||
|
settings."${capability}Capability".each { thing ->
|
||||||
|
if (!foundDevice && thing.id == request.JSON.smartDeviceId) {
|
||||||
|
def vendorDevice = state.vendorDevices[thing.id]
|
||||||
|
foundDevice = true
|
||||||
|
if (vendorDevice.uuid == request.JSON.fromUuid) {
|
||||||
|
log.error "aborting message from self"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request.JSON.command.startsWith("app-")) {
|
||||||
|
def args = []
|
||||||
|
if (request.JSON.args) {
|
||||||
|
request.JSON.args.each { k, v ->
|
||||||
|
args.push(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug "command being sent: ${request.JSON.command}\targs to be sent: ${args}"
|
||||||
|
thing."${request.JSON.command}"(*args)
|
||||||
|
} else {
|
||||||
|
debug "calling internal command ${request.JSON.command}"
|
||||||
|
def commandData = [:]
|
||||||
|
switch (request.JSON.command) {
|
||||||
|
case "app-get-value":
|
||||||
|
debug "got command value"
|
||||||
|
thing.supportedAttributes.each { attribute ->
|
||||||
|
commandData[attribute.name] = thing.latestValue(attribute.name)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "app-get-state":
|
||||||
|
debug "got command state"
|
||||||
|
thing.supportedAttributes.each { attribute ->
|
||||||
|
commandData[attribute.name] = thing.latestState(attribute.name)?.value
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "app-get-device":
|
||||||
|
debug "got command device"
|
||||||
|
commandData = [
|
||||||
|
"id" : thing.id,
|
||||||
|
"displayName" : thing.displayName,
|
||||||
|
"name" : thing.name,
|
||||||
|
"label" : thing.label,
|
||||||
|
"capabilities" : thing.capabilities.collect{ thingCapability -> return thingCapability.name },
|
||||||
|
"supportedAttributes" : thing.supportedAttributes.collect{ attribute -> return attribute.name },
|
||||||
|
"supportedCommands" : thing.supportedCommands.collect{ command -> return ["name" : command.name, "arguments" : command.arguments ] }
|
||||||
|
]
|
||||||
|
break
|
||||||
|
case "app-get-events":
|
||||||
|
debug "got command events"
|
||||||
|
commandData.events = []
|
||||||
|
thing.events().each { event ->
|
||||||
|
commandData.events.push(getEventData(event))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
commandData.error = "unknown command"
|
||||||
|
debug "unknown command ${request.JSON.command}"
|
||||||
|
}
|
||||||
|
commandData.command = request.JSON.command
|
||||||
|
debug "with vendorDevice ${vendorDevice} for ${groovy.json.JsonOutput.toJson(commandData)}"
|
||||||
|
|
||||||
|
def postParams = [
|
||||||
|
uri: apiUrl() + "messages",
|
||||||
|
headers: ["meshblu_auth_uuid": vendorDevice.uuid, "meshblu_auth_token": vendorDevice.token],
|
||||||
|
body: groovy.json.JsonOutput.toJson([ "devices" : [ "*" ], "payload" : commandData ])
|
||||||
|
]
|
||||||
|
|
||||||
|
debug "posting params ${postParams}"
|
||||||
|
|
||||||
|
try {
|
||||||
|
debug "calling httpPostJson!"
|
||||||
|
httpPostJson(postParams) { response ->
|
||||||
|
debug "sent off command result"
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "unable to send command result ${e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
def postApp() {
|
||||||
|
debug("received app data ${request.JSON}")
|
||||||
|
if (state.vendorDevices[app.id].uuid == request.JSON.fromUuid) {
|
||||||
|
log.error "aborting message from self"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
def args = []
|
||||||
|
if (request.JSON.args) {
|
||||||
|
request.JSON.args.each { k, v ->
|
||||||
|
args.push(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def commandData = [:]
|
||||||
|
|
||||||
|
switch (request.JSON.command) {
|
||||||
|
case "app-get-location":
|
||||||
|
debug "got command location"
|
||||||
|
|
||||||
|
def modes = []
|
||||||
|
location.modes.each { mode ->
|
||||||
|
modes.push([
|
||||||
|
"id" : mode.id,
|
||||||
|
"name" : mode.name
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
def hubs = []
|
||||||
|
location.hubs.each { hub ->
|
||||||
|
debug "hub : ${hub}"
|
||||||
|
hubs.push([
|
||||||
|
"firmwareVersionString" : hub.firmwareVersionString,
|
||||||
|
"id" : hub.id,
|
||||||
|
"localIP" : hub.localIP,
|
||||||
|
"localSrvPortTCP" : hub.localSrvPortTCP,
|
||||||
|
"name" : hub.name,
|
||||||
|
"type" : hub.type,
|
||||||
|
"zigbeeEui" : hub.zigbeeEui,
|
||||||
|
"zigbeeId" : hub.zigbeeId
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
commandData = [
|
||||||
|
"contactBookEnabled" : location.contactBookEnabled,
|
||||||
|
"id" : location.id,
|
||||||
|
"latitude" : location.latitude,
|
||||||
|
"longitude" : location.longitude,
|
||||||
|
"temperatureScale" : location.temperatureScale,
|
||||||
|
"timeZone" : location.timeZone.getID(),
|
||||||
|
"zipCode" : location.zipCode,
|
||||||
|
"mode" : location.mode,
|
||||||
|
"modes" : modes,
|
||||||
|
"hubs" : hubs
|
||||||
|
]
|
||||||
|
|
||||||
|
debug "copied location!"
|
||||||
|
debug commandData
|
||||||
|
break
|
||||||
|
|
||||||
|
case "app-get-devices":
|
||||||
|
debug "got command devices"
|
||||||
|
commandData.devices = state.vendorDevices.collect { k, v -> [ "smartDeviceId" : k, "uuid" : v.uuid ] }
|
||||||
|
break
|
||||||
|
|
||||||
|
case "app-set-mode":
|
||||||
|
location.setMode(*args)
|
||||||
|
commandData.mode = args[0]
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
commandData.error = "unknown command"
|
||||||
|
debug "unknown command ${request.JSON.command}"
|
||||||
|
}
|
||||||
|
commandData.command = request.JSON.command
|
||||||
|
debug "sending ${commandData}"
|
||||||
|
|
||||||
|
def vendorDevice = state.vendorDevices[app.id]
|
||||||
|
debug "with vendorDevice ${vendorDevice} for ${groovy.json.JsonOutput.toJson(commandData)}"
|
||||||
|
|
||||||
|
def postParams = [
|
||||||
|
uri: apiUrl() + "messages",
|
||||||
|
headers: [ "meshblu_auth_uuid" : vendorDevice.uuid, "meshblu_auth_token" : vendorDevice.token ],
|
||||||
|
body: groovy.json.JsonOutput.toJson([ "devices" : [ "*" ], "payload" : commandData ])
|
||||||
|
]
|
||||||
|
|
||||||
|
debug "posting params ${postParams}"
|
||||||
|
|
||||||
|
try {
|
||||||
|
debug "calling httpPostJson!"
|
||||||
|
httpPostJson(postParams) { response ->
|
||||||
|
debug "sent off command result"
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "unable to send command result ${e}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
private debug(logStr) {
|
||||||
|
if (USE_DEBUG)
|
||||||
|
log.debug logStr
|
||||||
|
}
|
||||||
|
|
||||||
|
String toQueryString(Map m) {
|
||||||
|
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize()
|
||||||
|
{
|
||||||
|
debug "Initialized with settings: ${settings}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def uninstalled()
|
||||||
|
{
|
||||||
|
debug "In uninstalled ${state.removeDevices}"
|
||||||
|
if (state.removeDevices) {
|
||||||
|
state.vendorDevices.each { k, device ->
|
||||||
|
def params = [
|
||||||
|
uri: apiUrl() + "devices/${device.uuid}",
|
||||||
|
headers: [ "meshblu_auth_uuid" : device.uuid, "meshblu_auth_token" : device.token ],
|
||||||
|
]
|
||||||
|
|
||||||
|
debug "deleting url ${params.uri}"
|
||||||
|
try {
|
||||||
|
httpDelete(params) { response ->
|
||||||
|
debug "delete device ${device.uuid}"
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error "token delete error ${e}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
debug "Installed with settings: ${settings}"
|
||||||
|
state.installed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean canInstallLabs()
|
||||||
|
{
|
||||||
|
return hasAllHubsOver("000.011.00603")
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean hasAllHubsOver(String desiredFirmware)
|
||||||
|
{
|
||||||
|
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
||||||
|
}
|
||||||
|
|
||||||
|
private List getRealHubFirmwareVersions()
|
||||||
|
{
|
||||||
|
return location.hubs*.firmwareVersionString.findAll { it }
|
||||||
|
}
|
||||||
@@ -65,16 +65,7 @@ void updateSwitch() {
|
|||||||
private void updateAll(devices) {
|
private void updateAll(devices) {
|
||||||
def command = request.JSON?.command
|
def command = request.JSON?.command
|
||||||
if (command) {
|
if (command) {
|
||||||
switch(command) {
|
devices."$command"()
|
||||||
case "on":
|
|
||||||
devices.on()
|
|
||||||
break
|
|
||||||
case "off":
|
|
||||||
devices.off()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,16 +77,7 @@ private void update(devices) {
|
|||||||
if (!device) {
|
if (!device) {
|
||||||
httpError(404, "Device not found")
|
httpError(404, "Device not found")
|
||||||
} else {
|
} else {
|
||||||
switch(command) {
|
device."$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
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ def initialize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def sendit(evt) {
|
def sendit(evt) {
|
||||||
log.debug "$evt.value: $evt"
|
log.debug "$evt.value: $evt, $settings"
|
||||||
sendMessage()
|
sendMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +80,6 @@ def sendMessage() {
|
|||||||
sendSms phone3, msg
|
sendSms phone3, msg
|
||||||
}
|
}
|
||||||
if (!phone1 && !phone2 && !phone3) {
|
if (!phone1 && !phone2 && !phone3) {
|
||||||
sendPush msg
|
sendPush msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ definition(
|
|||||||
name: "Monitor on Sense",
|
name: "Monitor on Sense",
|
||||||
namespace: "resteele",
|
namespace: "resteele",
|
||||||
author: "Rachel Steele",
|
author: "Rachel Steele",
|
||||||
description: "Turn on switch when vibration is sensed",
|
description: "Turn on Monitor when vibration is sensed",
|
||||||
category: "My Apps",
|
category: "My Apps",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||||
@@ -25,10 +25,10 @@ definition(
|
|||||||
|
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
section("When vibration is sensed...") {
|
section("When the keyboard is used...") {
|
||||||
input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?"
|
input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?"
|
||||||
}
|
}
|
||||||
section("Turn on switch...") {
|
section("Turn on/off a light...") {
|
||||||
input "switch1", "capability.switch"
|
input "switch1", "capability.switch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,3 +47,5 @@ def updated() {
|
|||||||
def accelerationActiveHandler(evt) {
|
def accelerationActiveHandler(evt) {
|
||||||
switch1.on()
|
switch1.on()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -114,16 +114,13 @@ 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)
|
||||||
// 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)
|
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)
|
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) {
|
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,15 +49,13 @@ preferences {
|
|||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
log.debug "Installed with settings: ${settings}"
|
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)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
log.debug "Updated with settings: ${settings}"
|
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()
|
unsubscribe()
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -64,12 +64,10 @@ def meterHandler(evt) {
|
|||||||
def lastValue = atomicState.lastValue as double
|
def lastValue = atomicState.lastValue as double
|
||||||
atomicState.lastValue = meterValue
|
atomicState.lastValue = meterValue
|
||||||
|
|
||||||
def dUnit ? evt.unit : "Watts"
|
|
||||||
|
|
||||||
def aboveThresholdValue = aboveThreshold as int
|
def aboveThresholdValue = aboveThreshold as int
|
||||||
if (meterValue > aboveThresholdValue) {
|
if (meterValue > aboveThresholdValue) {
|
||||||
if (lastValue < aboveThresholdValue) { // only send notifications when crossing the threshold
|
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)
|
sendMessage(msg)
|
||||||
} else {
|
} else {
|
||||||
// log.debug "not sending notification for ${evt.description} because the threshold (${aboveThreshold}) has already been crossed"
|
// 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
|
def belowThresholdValue = belowThreshold as int
|
||||||
if (meterValue < belowThresholdValue) {
|
if (meterValue < belowThresholdValue) {
|
||||||
if (lastValue > belowThresholdValue) { // only send notifications when crossing the threshold
|
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)
|
sendMessage(msg)
|
||||||
} else {
|
} else {
|
||||||
// log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed"
|
// log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed"
|
||||||
|
|||||||
@@ -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 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent to $phone 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 number"
|
log.debug "$alarm is wet, texting $phone"
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -761,7 +761,7 @@ String displayableTime(timeRemaining) {
|
|||||||
return "${minutes}:00"
|
return "${minutes}:00"
|
||||||
}
|
}
|
||||||
def fraction = "0.${parts[1]}" as double
|
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}"
|
return "${minutes}:${seconds}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1101,4 +1101,4 @@ def hasStartLevel() {
|
|||||||
|
|
||||||
def hasEndLevel() {
|
def hasEndLevel() {
|
||||||
return (endLevel != null && endLevel != "")
|
return (endLevel != null && endLevel != "")
|
||||||
}
|
}
|
||||||
@@ -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,10 +71,11 @@ 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 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 {
|
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)
|
||||||
}
|
}
|
||||||
@@ -105,4 +106,6 @@ private send(msg) {
|
|||||||
sendSms(phone, msg)
|
sendSms(phone, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug msg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,11 +57,12 @@ 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 to contacts:${recipients?.size()}"
|
log.debug "Texting reminder: ($message) 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ def bridgeDiscoveryFailed() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def bridgeLinking() {
|
def bridgeLinking()
|
||||||
|
{
|
||||||
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
|
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
|
||||||
state.linkRefreshcount = linkRefreshcount + 1
|
state.linkRefreshcount = linkRefreshcount + 1
|
||||||
def refreshInterval = 3
|
def refreshInterval = 3
|
||||||
@@ -132,23 +133,14 @@ def bulbDiscovery() {
|
|||||||
state.inBulbDiscovery = true
|
state.inBulbDiscovery = true
|
||||||
def bridge = null
|
def bridge = null
|
||||||
if (selectedHue) {
|
if (selectedHue) {
|
||||||
bridge = getChildDevice(selectedHue)
|
bridge = getChildDevice(selectedHue)
|
||||||
subscribe(bridge, "bulbList", bulbListData)
|
subscribe(bridge, "bulbList", bulbListData)
|
||||||
}
|
}
|
||||||
state.bridgeRefreshCount = 0
|
state.bridgeRefreshCount = 0
|
||||||
def allLightsFound = bulbsDiscovered() ?: [:]
|
def bulboptions = bulbsDiscovered() ?: [:]
|
||||||
|
def numFound = bulboptions.size() ?: 0
|
||||||
// List lights currently not added to the user (editable)
|
if (numFound == 0)
|
||||||
def newLights = allLightsFound.findAll {getChildDevice(it.key) == null} ?: [:]
|
app.updateSetting("selectedBulbs", "")
|
||||||
newLights = newLights.sort {it.value.toLowerCase()}
|
|
||||||
|
|
||||||
// List lights already added to the user (not editable)
|
|
||||||
def existingLights = allLightsFound.findAll {getChildDevice(it.key) != null} ?: [:]
|
|
||||||
existingLights = existingLights.sort {it.value.toLowerCase()}
|
|
||||||
|
|
||||||
def numFound = newLights.size() ?: 0
|
|
||||||
if (numFound == 0)
|
|
||||||
app.updateSetting("selectedBulbs", "")
|
|
||||||
|
|
||||||
if((bulbRefreshCount % 5) == 0) {
|
if((bulbRefreshCount % 5) == 0) {
|
||||||
discoverHueBulbs()
|
discoverHueBulbs()
|
||||||
@@ -156,25 +148,14 @@ def bulbDiscovery() {
|
|||||||
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
|
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
|
||||||
def title = selectedBridge?.value?.name ?: "Find bridges"
|
def title = selectedBridge?.value?.name ?: "Find bridges"
|
||||||
|
|
||||||
// List of all lights previously added shown to user
|
|
||||||
def existingLightsDescription = ""
|
|
||||||
if (existingLights) {
|
|
||||||
existingLights.each {
|
|
||||||
if (existingLightsDescription.isEmpty()) {
|
|
||||||
existingLightsDescription += it.value
|
|
||||||
} else {
|
|
||||||
existingLightsDescription += ", ${it.value}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||||
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, options:newLights
|
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights (${numFound} found)", multiple:true, options:bulboptions
|
||||||
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
|
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,11 +290,8 @@ def manualRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def uninstalled(){
|
def uninstalled(){
|
||||||
// Remove bridgedevice connection to allow uninstall of smartapp even though bridge is listed
|
|
||||||
// as user of smartapp
|
|
||||||
app.updateSetting("bridgeDevice", null)
|
|
||||||
state.bridges = [:]
|
state.bridges = [:]
|
||||||
state.username = null
|
state.username = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles events to add new bulbs
|
// Handles events to add new bulbs
|
||||||
@@ -327,7 +305,7 @@ def bulbListHandler(hub, data = "") {
|
|||||||
def object = new groovy.json.JsonSlurper().parseText(data)
|
def object = new groovy.json.JsonSlurper().parseText(data)
|
||||||
object.each { k,v ->
|
object.each { k,v ->
|
||||||
if (v instanceof Map)
|
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
|
def bridge = null
|
||||||
@@ -437,31 +415,23 @@ def addBridge() {
|
|||||||
// Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge
|
// Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge
|
||||||
def idNumber = getBridgeIdNumber(selectedHue)
|
def idNumber = getBridgeIdNumber(selectedHue)
|
||||||
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"])
|
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"])
|
||||||
if (d) {
|
d?.completedSetup = true
|
||||||
// Associate smartapp to bridge so user will be warned if trying to delete bridge
|
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
||||||
app.updateSetting("bridgeDevice", [type: "device.hueBridge", value: d.id])
|
def childDevice = getChildDevice(d.deviceNetworkId)
|
||||||
|
updateBridgeStatus(childDevice)
|
||||||
d.completedSetup = true
|
childDevice.sendEvent(name: "idNumber", value: idNumber)
|
||||||
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
if (vbridge.value.ip && vbridge.value.port) {
|
||||||
def childDevice = getChildDevice(d.deviceNetworkId)
|
if (vbridge.value.ip.contains(".")) {
|
||||||
updateBridgeStatus(childDevice)
|
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
|
||||||
childDevice.sendEvent(name: "idNumber", value: idNumber)
|
childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port)
|
||||||
|
} else {
|
||||||
if (vbridge.value.ip && vbridge.value.port) {
|
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
||||||
if (vbridge.value.ip.contains(".")) {
|
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
||||||
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
|
}
|
||||||
childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port)
|
|
||||||
} else {
|
|
||||||
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
|
||||||
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
|
||||||
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.error "Failed to create Hue Bridge device"
|
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
||||||
}
|
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug "found ${d.displayName} with id $selectedHue already exists"
|
log.debug "found ${d.displayName} with id $selectedHue already exists"
|
||||||
@@ -647,7 +617,8 @@ def locationHandler(evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (parsedEvent.headers && parsedEvent.body) {
|
}
|
||||||
|
else if (parsedEvent.headers && parsedEvent.body) {
|
||||||
log.trace "HUE BRIDGE RESPONSES"
|
log.trace "HUE BRIDGE RESPONSES"
|
||||||
def headerString = parsedEvent.headers.toString()
|
def headerString = parsedEvent.headers.toString()
|
||||||
if (headerString?.contains("xml")) {
|
if (headerString?.contains("xml")) {
|
||||||
@@ -730,7 +701,7 @@ private void updateBridgeStatus(childDevice) {
|
|||||||
private void checkBridgeStatus() {
|
private void checkBridgeStatus() {
|
||||||
def bridges = getHueBridges()
|
def bridges = getHueBridges()
|
||||||
// Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer)
|
// 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 {
|
bridges.each {
|
||||||
def d = getChildDevice(it.value.mac)
|
def d = getChildDevice(it.value.mac)
|
||||||
if(d) {
|
if(d) {
|
||||||
@@ -743,8 +714,6 @@ private void checkBridgeStatus() {
|
|||||||
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
|
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
|
||||||
log.warn "Bridge $it.key is Offline"
|
log.warn "Bridge $it.key is Offline"
|
||||||
d.sendEvent(name: "status", value: "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 {
|
} else {
|
||||||
d.sendEvent(name: "status", value: "Online")//setOnline(false)
|
d.sendEvent(name: "status", value: "Online")//setOnline(false)
|
||||||
}
|
}
|
||||||
@@ -757,10 +726,6 @@ def isValidSource(macAddress) {
|
|||||||
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
|
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
def isInBulbDiscovery() {
|
|
||||||
return state.inBulbDiscovery
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
//CHILD DEVICE METHODS
|
//CHILD DEVICE METHODS
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
@@ -784,7 +749,8 @@ def parse(childDevice, description) {
|
|||||||
if (body instanceof java.util.Map) {
|
if (body instanceof java.util.Map) {
|
||||||
// get (poll) reponse
|
// get (poll) reponse
|
||||||
return handlePoll(body)
|
return handlePoll(body)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
//put response
|
//put response
|
||||||
return handleCommandResponse(body)
|
return handleCommandResponse(body)
|
||||||
}
|
}
|
||||||
@@ -881,40 +847,36 @@ private handleCommandResponse(body) {
|
|||||||
// scan entire response before sending events to make sure they are always in the same order
|
// scan entire response before sending events to make sure they are always in the same order
|
||||||
def updates = [:]
|
def updates = [:]
|
||||||
|
|
||||||
body.each { payload ->
|
body.each { payload ->
|
||||||
log.debug $payload
|
log.debug $payload
|
||||||
|
|
||||||
if (payload?.success) {
|
if (payload?.success) {
|
||||||
def childDeviceNetworkId = app.id + "/"
|
def childDeviceNetworkId = app.id + "/"
|
||||||
def eventType
|
def eventType
|
||||||
payload.success.each { k, v ->
|
payload.success.each { k, v ->
|
||||||
def data = k.split("/")
|
def data = k.split("/")
|
||||||
if (data.length == 5) {
|
if (data.length == 5) {
|
||||||
childDeviceNetworkId = app.id + "/" + k.split("/")[2]
|
childDeviceNetworkId = app.id + "/" + k.split("/")[2]
|
||||||
if (!updates[childDeviceNetworkId])
|
if (!updates[childDeviceNetworkId])
|
||||||
updates[childDeviceNetworkId] = [:]
|
updates[childDeviceNetworkId] = [:]
|
||||||
eventType = k.split("/")[4]
|
eventType = k.split("/")[4]
|
||||||
updates[childDeviceNetworkId]."$eventType" = v
|
updates[childDeviceNetworkId]."$eventType" = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (payload.error) {
|
} else if (payload.error) {
|
||||||
log.warn "Error returned from Hue bridge error = ${body?.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())
|
// send events for each update found above (order of events should be same as handlePoll())
|
||||||
updates.each { childDeviceNetworkId, params ->
|
updates.each { childDeviceNetworkId, params ->
|
||||||
def device = getChildDevice(childDeviceNetworkId)
|
def device = getChildDevice(childDeviceNetworkId)
|
||||||
def id = getId(device)
|
sendBasicEvents(device, "on", params.on)
|
||||||
// If device is offline, then don't send events which will update device watch
|
sendBasicEvents(device, "bri", params.bri)
|
||||||
if (isOnline(id)) {
|
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 []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a response to a poll (GET) sent to the Hue Bridge.
|
* Handles a response to a poll (GET) sent to the Hue Bridge.
|
||||||
@@ -934,32 +896,26 @@ private handleCommandResponse(body) {
|
|||||||
* @return empty array
|
* @return empty array
|
||||||
*/
|
*/
|
||||||
private handlePoll(body) {
|
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()
|
def bulbs = getChildDevices()
|
||||||
for (bulb in body) {
|
for (bulb in body) {
|
||||||
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
||||||
if (device) {
|
if (device) {
|
||||||
if (bulb.value.state?.reachable) {
|
if (bulb.value.state?.reachable) {
|
||||||
if (state.bulbs[bulb.key]?.online == false) {
|
sendBasicEvents(device, "on", bulb.value?.state?.on)
|
||||||
// light just came back online, notify device watch
|
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
|
||||||
def lastActivity = now()
|
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
|
||||||
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)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
state.bulbs[bulb.key]?.online = false
|
|
||||||
log.warn "$device is not reachable by Hue bridge"
|
log.warn "$device is not reachable by Hue bridge"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateInProgress() {
|
private updateInProgress() {
|
||||||
state.updating = true
|
state.updating = true
|
||||||
@@ -988,34 +944,22 @@ def hubVerification(bodytext) {
|
|||||||
|
|
||||||
def on(childDevice) {
|
def on(childDevice) {
|
||||||
log.debug "Executing 'on'"
|
log.debug "Executing 'on'"
|
||||||
def id = getId(childDevice)
|
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
createSwitchEvent(childDevice, "on")
|
createSwitchEvent(childDevice, "on")
|
||||||
put("lights/$id/state", [on: true])
|
put("lights/${getId(childDevice)}/state", [on: true])
|
||||||
return "Bulb is turning On"
|
return "Bulb is turning On"
|
||||||
}
|
}
|
||||||
|
|
||||||
def off(childDevice) {
|
def off(childDevice) {
|
||||||
log.debug "Executing 'off'"
|
log.debug "Executing 'off'"
|
||||||
def id = getId(childDevice)
|
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
createSwitchEvent(childDevice, "off")
|
createSwitchEvent(childDevice, "off")
|
||||||
put("lights/$id/state", [on: false])
|
put("lights/${getId(childDevice)}/state", [on: false])
|
||||||
return "Bulb is turning Off"
|
return "Bulb is turning Off"
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(childDevice, percent) {
|
def setLevel(childDevice, percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
def id = getId(childDevice)
|
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 1 - 254
|
// 1 - 254
|
||||||
def level
|
def level
|
||||||
@@ -1030,71 +974,55 @@ def setLevel(childDevice, percent) {
|
|||||||
// that means that the light will still be on when on is called next time
|
// that means that the light will still be on when on is called next time
|
||||||
// Lets emulate that here
|
// Lets emulate that here
|
||||||
if (percent > 0) {
|
if (percent > 0) {
|
||||||
put("lights/$id/state", [bri: level, on: true])
|
put("lights/${getId(childDevice)}/state", [bri: level, on: true])
|
||||||
} else {
|
} else {
|
||||||
put("lights/$id/state", [on: false])
|
put("lights/${getId(childDevice)}/state", [on: false])
|
||||||
}
|
}
|
||||||
return "Setting level to $percent"
|
return "Setting level to $percent"
|
||||||
}
|
}
|
||||||
|
|
||||||
def setSaturation(childDevice, percent) {
|
def setSaturation(childDevice, percent) {
|
||||||
log.debug "Executing 'setSaturation($percent)'"
|
log.debug "Executing 'setSaturation($percent)'"
|
||||||
def id = getId(childDevice)
|
updateInProgress()
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInProgress()
|
|
||||||
// 0 - 254
|
// 0 - 254
|
||||||
def level = Math.min(Math.round(percent * 254 / 100), 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?
|
// TODO should this be done by app only or should we default to on?
|
||||||
createSwitchEvent(childDevice, "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"
|
return "Setting saturation to $percent"
|
||||||
}
|
}
|
||||||
|
|
||||||
def setHue(childDevice, percent) {
|
def setHue(childDevice, percent) {
|
||||||
log.debug "Executing 'setHue($percent)'"
|
log.debug "Executing 'setHue($percent)'"
|
||||||
def id = getId(childDevice)
|
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 0 - 65535
|
// 0 - 65535
|
||||||
def level = Math.min(Math.round(percent * 65535 / 100), 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?
|
// TODO should this be done by app only or should we default to on?
|
||||||
createSwitchEvent(childDevice, "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"
|
return "Setting hue to $percent"
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColorTemperature(childDevice, huesettings) {
|
def setColorTemperature(childDevice, huesettings) {
|
||||||
log.debug "Executing 'setColorTemperature($huesettings)'"
|
log.debug "Executing 'setColorTemperature($huesettings)'"
|
||||||
def id = getId(childDevice)
|
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 153 (6500K) to 500 (2000K)
|
// 153 (6500K) to 500 (2000K)
|
||||||
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
|
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
|
||||||
createSwitchEvent(childDevice, "on")
|
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"
|
return "Setting color temperature to $percent"
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColor(childDevice, huesettings) {
|
def setColor(childDevice, huesettings) {
|
||||||
log.debug "Executing 'setColor($huesettings)'"
|
log.debug "Executing 'setColor($huesettings)'"
|
||||||
def id = getId(childDevice)
|
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
updateInProgress()
|
|
||||||
|
|
||||||
|
updateInProgress()
|
||||||
|
|
||||||
def value = [:]
|
def value = [:]
|
||||||
def hue = null
|
def hue = null
|
||||||
def sat = null
|
def sat = null
|
||||||
def xy = null
|
def xy = null
|
||||||
|
|
||||||
// For now ignore model to get a consistent color if same color is set across multiple devices
|
// For now ignore model to get a consistent color if same color is set across multiple devices
|
||||||
// def model = state.bulbs[getId(childDevice)]?.modelid
|
// def model = state.bulbs[getId(childDevice)]?.modelid
|
||||||
if (huesettings.hex != null) {
|
if (huesettings.hex != null) {
|
||||||
@@ -1110,12 +1038,11 @@ def setColor(childDevice, huesettings) {
|
|||||||
else
|
else
|
||||||
value.hue = Math.min(Math.round(childDevice.device?.currentValue("hue") * 65535 / 100), 65535)
|
value.hue = Math.min(Math.round(childDevice.device?.currentValue("hue") * 65535 / 100), 65535)
|
||||||
|
|
||||||
if (huesettings.saturation != null)
|
if (huesettings.saturation != null)
|
||||||
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
|
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
|
||||||
else
|
else
|
||||||
value.sat = Math.min(Math.round(childDevice.device?.currentValue("saturation") * 254 / 100), 254)
|
value.sat = Math.min(Math.round(childDevice.device?.currentValue("saturation") * 254 / 100), 254)
|
||||||
|
|
||||||
/* Disabled for now due to bad behavior via Lightning Wizard
|
|
||||||
if (!value.xy) {
|
if (!value.xy) {
|
||||||
// Below will translate values to hex->XY to take into account the color support of the different hue types
|
// Below will translate values to hex->XY to take into account the color support of the different hue types
|
||||||
def hex = colorUtil.hslToHex((int) huesettings.hue, (int) huesettings.saturation)
|
def hex = colorUtil.hslToHex((int) huesettings.hue, (int) huesettings.saturation)
|
||||||
@@ -1123,7 +1050,6 @@ def setColor(childDevice, huesettings) {
|
|||||||
// Once groups, or scenes are introduced it might be a good idea to use unique models again
|
// Once groups, or scenes are introduced it might be a good idea to use unique models again
|
||||||
value.xy = calculateXY(hex)
|
value.xy = calculateXY(hex)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// Default behavior is to turn light on
|
// Default behavior is to turn light on
|
||||||
value.on = true
|
value.on = true
|
||||||
@@ -1133,7 +1059,7 @@ def setColor(childDevice, huesettings) {
|
|||||||
value.on = false
|
value.on = false
|
||||||
else if (huesettings.level == 1)
|
else if (huesettings.level == 1)
|
||||||
value.bri = 1
|
value.bri = 1
|
||||||
else
|
else
|
||||||
value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254)
|
value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254)
|
||||||
}
|
}
|
||||||
value.alert = huesettings.alert ? huesettings.alert : "none"
|
value.alert = huesettings.alert ? huesettings.alert : "none"
|
||||||
@@ -1144,23 +1070,15 @@ def setColor(childDevice, huesettings) {
|
|||||||
value.on = false
|
value.on = false
|
||||||
|
|
||||||
createSwitchEvent(childDevice, value.on ? "on" : "off")
|
createSwitchEvent(childDevice, value.on ? "on" : "off")
|
||||||
put("lights/$id/state", value)
|
put("lights/${getId(childDevice)}/state", value)
|
||||||
return "Setting color to $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) {
|
private getId(childDevice) {
|
||||||
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
|
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
|
||||||
return childDevice.device?.deviceNetworkId[3..-1]
|
return childDevice.device?.deviceNetworkId[3..-1]
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return childDevice.device?.deviceNetworkId.split("/")[-1]
|
return childDevice.device?.deviceNetworkId.split("/")[-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1171,11 +1089,8 @@ private poll() {
|
|||||||
log.debug "GET: $host$uri"
|
log.debug "GET: $host$uri"
|
||||||
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
|
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
|
||||||
HOST: ${host}
|
HOST: ${host}
|
||||||
""", physicalgraph.device.Protocol.LAN, selectedHue))
|
|
||||||
}
|
|
||||||
|
|
||||||
private isOnline(id) {
|
""", physicalgraph.device.Protocol.LAN, selectedHue))
|
||||||
return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private put(path, body) {
|
private put(path, body) {
|
||||||
@@ -1245,7 +1160,7 @@ def convertBulbListToMap() {
|
|||||||
if (state.bulbs instanceof java.util.List) {
|
if (state.bulbs instanceof java.util.List) {
|
||||||
def map = [:]
|
def map = [:]
|
||||||
state.bulbs.unique {it.id}.each { bulb ->
|
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
|
state.bulbs = map
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent to $phone1 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, sending text message"
|
log.debug "$accelerationSensor has moved, texting $phone1"
|
||||||
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 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
|
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||||
} else {
|
} 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"
|
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()
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ def authPage()
|
|||||||
|
|
||||||
def redirectUrl = oauthInitUrl()
|
def redirectUrl = oauthInitUrl()
|
||||||
|
|
||||||
|
log.debug "RedirectURL = ${redirectUrl}"
|
||||||
|
|
||||||
return dynamicPage(name: "Credentials", title: "Life360", nextPage:"listCirclesPage", uninstall: uninstallOption, install:false) {
|
return dynamicPage(name: "Credentials", title: "Life360", nextPage:"listCirclesPage", uninstall: uninstallOption, install:false) {
|
||||||
section {
|
section {
|
||||||
href url:redirectUrl, style:"embedded", required:false, title:"Life360", description:description
|
href url:redirectUrl, style:"embedded", required:false, title:"Life360", description:description
|
||||||
@@ -255,6 +257,8 @@ def initializeLife360Connection() {
|
|||||||
def oauthClientId = appSettings.clientId
|
def oauthClientId = appSettings.clientId
|
||||||
def oauthClientSecret = appSettings.clientSecret
|
def oauthClientSecret = appSettings.clientSecret
|
||||||
|
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
|
||||||
initialize()
|
initialize()
|
||||||
|
|
||||||
def username = settings.username
|
def username = settings.username
|
||||||
@@ -265,6 +269,8 @@ def initializeLife360Connection() {
|
|||||||
def basicCredentials = "${oauthClientId}:${oauthClientSecret}"
|
def basicCredentials = "${oauthClientId}:${oauthClientSecret}"
|
||||||
def encodedCredentials = basicCredentials.encodeAsBase64().toString()
|
def encodedCredentials = basicCredentials.encodeAsBase64().toString()
|
||||||
|
|
||||||
|
log.debug "Encoded Creds: ${encodedCredentials}"
|
||||||
|
|
||||||
|
|
||||||
// call life360, get OAUTH token using password flow, save
|
// call life360, get OAUTH token using password flow, save
|
||||||
// curl -X POST -H "Authorization: Basic cFJFcXVnYWJSZXRyZTRFc3RldGhlcnVmcmVQdW1hbUV4dWNyRUh1YzptM2ZydXBSZXRSZXN3ZXJFQ2hBUHJFOTZxYWtFZHI0Vg=="
|
// curl -X POST -H "Authorization: Basic cFJFcXVnYWJSZXRyZTRFc3RldGhlcnVmcmVQdW1hbUV4dWNyRUh1YzptM2ZydXBSZXRSZXN3ZXJFQ2hBUHJFOTZxYWtFZHI0Vg=="
|
||||||
@@ -278,6 +284,8 @@ def initializeLife360Connection() {
|
|||||||
"username=${username}&"+
|
"username=${username}&"+
|
||||||
"password=${password}"
|
"password=${password}"
|
||||||
|
|
||||||
|
log.debug "Post Body: ${postBody}"
|
||||||
|
|
||||||
def result = null
|
def result = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -287,6 +295,7 @@ def initializeLife360Connection() {
|
|||||||
}
|
}
|
||||||
if (result.data.access_token) {
|
if (result.data.access_token) {
|
||||||
state.life360AccessToken = result.data.access_token
|
state.life360AccessToken = result.data.access_token
|
||||||
|
log.debug "Access Token = ${state.life360AccessToken}"
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
log.debug "Response=${result.data}"
|
log.debug "Response=${result.data}"
|
||||||
@@ -524,6 +533,8 @@ def createCircleSubscription() {
|
|||||||
|
|
||||||
def postBody = "url=${hookUrl}"
|
def postBody = "url=${hookUrl}"
|
||||||
|
|
||||||
|
log.debug "Post Body: ${postBody}"
|
||||||
|
|
||||||
def result = null
|
def result = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -575,6 +586,8 @@ def updated() {
|
|||||||
|
|
||||||
// log.debug "After Find Attempt."
|
// log.debug "After Find Attempt."
|
||||||
|
|
||||||
|
log.debug "Member Id = ${member.id}, Name = ${member.firstName} ${member.lastName}, Email Address = ${member.loginEmail}"
|
||||||
|
|
||||||
// log.debug "External Id=${app.id}:${member.id}"
|
// log.debug "External Id=${app.id}:${member.id}"
|
||||||
|
|
||||||
// create the device
|
// create the device
|
||||||
|
|||||||
@@ -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}"s
|
log.debug "redirect url: ${redirectUrl}"
|
||||||
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
|
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install: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),
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ preferences {
|
|||||||
}
|
}
|
||||||
section("Via a push notification and/or an SMS message"){
|
section("Via a push notification and/or an SMS message"){
|
||||||
input("recipients", "contact", title: "Send notifications to") {
|
input("recipients", "contact", title: "Send notifications to") {
|
||||||
input "phone", "phone", title: "Enter a phone number to get SMS", required: false
|
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
|
||||||
paragraph "If outside the US please make sure to enter the proper country code"
|
paragraph "If outside the US please make sure to enter the proper country code"
|
||||||
input "pushAndPhone", "enum", title: "Notify me via Push Notification", required: false, options: ["Yes", "No"]
|
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section("Minimum time between messages (optional, defaults to every message)") {
|
section("Minimum time between messages (optional, defaults to every message)") {
|
||||||
@@ -111,24 +111,19 @@ private sendMessage(evt) {
|
|||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts(msg, recipients, options)
|
sendNotificationToContacts(msg, recipients, options)
|
||||||
} else {
|
} else {
|
||||||
|
if (!phone || pushAndPhone != 'No') {
|
||||||
|
log.debug 'sending push'
|
||||||
|
options.method = 'push'
|
||||||
|
//sendPush(msg)
|
||||||
|
}
|
||||||
if (phone) {
|
if (phone) {
|
||||||
options.phone = phone
|
options.phone = phone
|
||||||
if (pushAndPhone != 'No') {
|
log.debug 'sending SMS'
|
||||||
log.debug 'Sending push and SMS'
|
//sendSms(phone, msg)
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
sendNotification(msg, options)
|
sendNotification(msg, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frequency) {
|
if (frequency) {
|
||||||
state[evt.deviceId] = now()
|
state[evt.deviceId] = now()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +156,6 @@ def residentMotion(evt)
|
|||||||
// startReArmSequence()
|
// startReArmSequence()
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
unsubscribe(residentMotions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def contact(evt)
|
def contact(evt)
|
||||||
@@ -215,7 +214,7 @@ def checkForReArm()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection"
|
log.warn "checkForReArm: lastIntruderMotion was null, unable to check for re-arming intrusion detection"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private startAlarmSequence()
|
private startAlarmSequence()
|
||||||
|
|||||||
@@ -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, sending text"
|
log.debug "$contact1 was opened, texting $phone1"
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
|
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ def updated() {
|
|||||||
|
|
||||||
def motionActiveHandler(evt) {
|
def motionActiveHandler(evt) {
|
||||||
log.trace "$evt.value: $evt, $settings"
|
log.trace "$evt.value: $evt, $settings"
|
||||||
|
|
||||||
if (presence1.latestValue("presence") == "not present") {
|
if (presence1.latestValue("presence") == "not present") {
|
||||||
// Don't send a continuous stream of text messages
|
// Don't send a continuous stream of text messages
|
||||||
def deltaSeconds = 10
|
def deltaSeconds = 10
|
||||||
@@ -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 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent to $phone1 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, sending text"
|
log.debug "$motion1 has moved while you were out, texting $phone1"
|
||||||
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 phone within the last $deltaSeconds seconds"
|
log.debug "SMS already sent to $phone1 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 phone"
|
log.debug "$accelerationSensor has moved, texting $phone1"
|
||||||
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