mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-15 21:03:23 +00:00
Compare commits
73 Commits
PROD_2016.
...
PROD_2016.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aae7f23a22 | ||
|
|
aab3b8d7f8 | ||
|
|
a0ccf35eaa | ||
|
|
02f30cf425 | ||
|
|
fea802ffce | ||
|
|
6400d26f4a | ||
|
|
5e3aaa3270 | ||
|
|
f5c3997679 | ||
|
|
30993aa218 | ||
|
|
2f8ed277ff | ||
|
|
8c4f7edc83 | ||
|
|
4f188581df | ||
|
|
0b7bb40474 | ||
|
|
8d920ea072 | ||
|
|
e373b6f92e | ||
|
|
43a1ae6371 | ||
|
|
a441b94a33 | ||
|
|
5341d0d06f | ||
|
|
2a58d7ff62 | ||
|
|
260917d515 | ||
|
|
c1478d3e96 | ||
|
|
8b9bff15dc | ||
|
|
75c1ede16c | ||
|
|
a7acc384a2 | ||
|
|
c6998e5f1d | ||
|
|
f95e906d6e | ||
|
|
a6c7ab49b6 | ||
|
|
4891e3b947 | ||
|
|
ae91f9bff5 | ||
|
|
bb87ad2cf0 | ||
|
|
5dff03fb69 | ||
|
|
629d768575 | ||
|
|
4523498dab | ||
|
|
e89e45e000 | ||
|
|
78ec280e83 | ||
|
|
1f144d36e4 | ||
|
|
30274f0cd7 | ||
|
|
8869cd3af0 | ||
|
|
3184615e87 | ||
|
|
81318bafac | ||
|
|
b78bce55b2 | ||
|
|
01593c3973 | ||
|
|
763d7411e2 | ||
|
|
c703543f36 | ||
|
|
5db6ecda3e | ||
|
|
43b836f413 | ||
|
|
006b5e7bea | ||
|
|
62c8c19805 | ||
|
|
48e9a4bd6a | ||
|
|
07a4c0decc | ||
|
|
6aa09bb052 | ||
|
|
2f889de11a | ||
|
|
5584020e96 | ||
|
|
4ef2e694c2 | ||
|
|
826993cc45 | ||
|
|
ae3306928b | ||
|
|
8777ec5f6d | ||
|
|
91eb59a10d | ||
|
|
324ac13afb | ||
|
|
878eb66b8b | ||
|
|
8dfc270c2d | ||
|
|
614573a15c | ||
|
|
9c7b0875ba | ||
|
|
7568cbf781 | ||
|
|
159d3acf4f | ||
|
|
19b8a7eeb9 | ||
|
|
1b37d649a5 | ||
|
|
dedb0f8465 | ||
|
|
06acc13575 | ||
|
|
c051d719cc | ||
|
|
fe2fbc3b97 | ||
|
|
5e6b4f74e0 | ||
|
|
22185c5440 |
30
build.gradle
30
build.gradle
@@ -19,7 +19,7 @@ buildscript {
|
|||||||
username smartThingsArtifactoryUserName
|
username smartThingsArtifactoryUserName
|
||||||
password smartThingsArtifactoryPassword
|
password smartThingsArtifactoryPassword
|
||||||
}
|
}
|
||||||
url "http://artifactory.smartthings.com/libs-release-local"
|
url "https://artifactory.smartthings.com/libs-release-local"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,9 +27,37 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
credentials {
|
||||||
|
username smartThingsArtifactoryUserName
|
||||||
|
password smartThingsArtifactoryPassword
|
||||||
|
}
|
||||||
|
url "https://artifactory.smartthings.com/libs-release-local"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
devicetypes {
|
||||||
|
groovy {
|
||||||
|
srcDirs = ['devicetypes']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
smartapps {
|
||||||
|
groovy {
|
||||||
|
srcDirs = ['smartapps']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
|
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
|
||||||
|
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
|
||||||
|
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
|
smartappsCompile 'smartthings:appengine-common:0.1.8'
|
||||||
|
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
|
||||||
|
smartappsCompile 'org.grails:grails-web:2.3.11'
|
||||||
|
smartappsCompile 'org.json:json:20140107'
|
||||||
}
|
}
|
||||||
|
|
||||||
slackSendMessage {
|
slackSendMessage {
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ machine:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
override:
|
override:
|
||||||
- echo "Nothing to do."
|
- ./gradlew dependencies -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||||
|
post:
|
||||||
|
- ./gradlew compileSmartappsGroovy compileDevicetypesGroovy -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
override:
|
override:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
|
||||||
|
capability "Sensor"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
|
||||||
|
capability "Sensor"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
|
||||||
|
capability "Sensor"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
|
||||||
|
capability "Sensor"
|
||||||
|
|
||||||
attribute "rain", "number"
|
attribute "rain", "number"
|
||||||
attribute "rainSumHour", "number"
|
attribute "rainSumHour", "number"
|
||||||
attribute "rainSumDay", "number"
|
attribute "rainSumDay", "number"
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||||
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
||||||
attributeState("inactive", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
||||||
attributeState("active", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
||||||
}
|
}
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||||
@@ -278,4 +278,4 @@ private encap(physicalgraph.zwave.Command cmd) {
|
|||||||
} else {
|
} else {
|
||||||
crc16(cmd)
|
crc16(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ metadata {
|
|||||||
|
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
@@ -97,14 +96,17 @@ def refresh() {
|
|||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
def poll() {
|
def healthPoll() {
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
|
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
|
unschedule()
|
||||||
|
schedule("0 0/5 * * * ? *", "healthPoll")
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||||
// minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false)
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false)
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false)
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 15, data: [protocol: "lan"], displayed: false)
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
|
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,9 +180,9 @@ private Map parseIasMessage(String description) {
|
|||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return celsius
|
return Math.round(celsius)
|
||||||
} else {
|
} else {
|
||||||
return celsiusToFahrenheit(celsius) as Integer
|
return Math.round(celsiusToFahrenheit(celsius))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,8 +292,8 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, 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."
|
||||||
|
|||||||
@@ -194,9 +194,9 @@ private Map parseIasMessage(String description) {
|
|||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return celsius
|
return Math.round(celsius)
|
||||||
} else {
|
} else {
|
||||||
return celsiusToFahrenheit(celsius) as Integer
|
return Math.round(celsiusToFahrenheit(celsius))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,8 +303,8 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, 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."
|
||||||
|
|||||||
@@ -261,9 +261,9 @@ def updated() {
|
|||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return celsius
|
return Math.round(celsius)
|
||||||
} else {
|
} else {
|
||||||
return celsiusToFahrenheit(celsius) as Integer
|
return Math.round(celsiusToFahrenheit(celsius))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,8 +401,8 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||||
|
|
||||||
log.debug "Configuring Reporting"
|
log.debug "Configuring Reporting"
|
||||||
|
|
||||||
|
|||||||
@@ -255,8 +255,8 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, 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."
|
||||||
|
|||||||
@@ -264,8 +264,8 @@ def refresh()
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||||
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
def humidityConfigCmds = [
|
def humidityConfigCmds = [
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 3 check-in misses from device. 300 seconds x 3 = 15min
|
// Device-Watch allows 2 check-in misses from device
|
||||||
sendEvent(name: "checkInterval", value: 900, displayed: false, data: [protocol: "zigbee"])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,16 @@ void updateSwitch() {
|
|||||||
private void updateAll(devices) {
|
private void updateAll(devices) {
|
||||||
def command = request.JSON?.command
|
def command = request.JSON?.command
|
||||||
if (command) {
|
if (command) {
|
||||||
devices."$command"()
|
switch(command) {
|
||||||
|
case "on":
|
||||||
|
devices.on()
|
||||||
|
break
|
||||||
|
case "off":
|
||||||
|
devices.off()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +86,16 @@ private void update(devices) {
|
|||||||
if (!device) {
|
if (!device) {
|
||||||
httpError(404, "Device not found")
|
httpError(404, "Device not found")
|
||||||
} else {
|
} else {
|
||||||
device."$command"()
|
switch(command) {
|
||||||
|
case "on":
|
||||||
|
device.on()
|
||||||
|
break
|
||||||
|
case "off":
|
||||||
|
device.off()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ def authPage() {
|
|||||||
if (canInstallLabs()) {
|
if (canInstallLabs()) {
|
||||||
|
|
||||||
def redirectUrl = getBuildRedirectUrl()
|
def redirectUrl = getBuildRedirectUrl()
|
||||||
log.debug "Redirect url = ${redirectUrl}"
|
// log.debug "Redirect url = ${redirectUrl}"
|
||||||
|
|
||||||
if (state.authToken) {
|
if (state.authToken) {
|
||||||
description = "Tap 'Next' to proceed"
|
description = "Tap 'Next' to proceed"
|
||||||
@@ -113,13 +113,13 @@ def oauthInitUrl() {
|
|||||||
scope: "read_station"
|
scope: "read_station"
|
||||||
]
|
]
|
||||||
|
|
||||||
log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
|
// log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
|
||||||
|
|
||||||
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
|
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
def callback() {
|
def callback() {
|
||||||
log.debug "callback()>> params: $params, params.code ${params.code}"
|
// log.debug "callback()>> params: $params, params.code ${params.code}"
|
||||||
|
|
||||||
def code = params.code
|
def code = params.code
|
||||||
def oauthState = params.state
|
def oauthState = params.state
|
||||||
@@ -135,7 +135,7 @@ def callback() {
|
|||||||
scope: "read_station"
|
scope: "read_station"
|
||||||
]
|
]
|
||||||
|
|
||||||
log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
|
// log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
|
||||||
|
|
||||||
def tokenUrl = getVendorTokenPath()
|
def tokenUrl = getVendorTokenPath()
|
||||||
def params = [
|
def params = [
|
||||||
@@ -144,7 +144,7 @@ def callback() {
|
|||||||
body: tokenParams
|
body: tokenParams
|
||||||
]
|
]
|
||||||
|
|
||||||
log.debug "PARAMS: ${params}"
|
// log.debug "PARAMS: ${params}"
|
||||||
|
|
||||||
httpPost(params) { resp ->
|
httpPost(params) { resp ->
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ def callback() {
|
|||||||
state.refreshToken = data.refresh_token
|
state.refreshToken = data.refresh_token
|
||||||
state.authToken = data.access_token
|
state.authToken = data.access_token
|
||||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||||
log.debug "swapped token: $resp.data"
|
// log.debug "swapped token: $resp.data"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ def refreshToken() {
|
|||||||
|
|
||||||
response.data.each {key, value ->
|
response.data.each {key, value ->
|
||||||
def data = slurper.parseText(key);
|
def data = slurper.parseText(key);
|
||||||
log.debug "Data: $data"
|
// log.debug "Data: $data"
|
||||||
|
|
||||||
state.refreshToken = data.refresh_token
|
state.refreshToken = data.refresh_token
|
||||||
state.accessToken = data.access_token
|
state.accessToken = data.access_token
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
section("Temperature monitor?") {
|
section("Temperature monitor?") {
|
||||||
input "temp", "capability.temperatureMeasurement", title: "Temp Sensor", required: false
|
input "temp", "capability.temperatureMeasurement", title: "Temperature Sensor", required: false
|
||||||
input "maxTemp", "number", title: "Max Temp?", required: false
|
input "maxTemp", "number", title: "Max Temperature (°${location.temperatureScale})", required: false
|
||||||
input "minTemp", "number", title: "Min Temp?", required: false
|
input "minTemp", "number", title: "Min Temperature (°${location.temperatureScale})", required: false
|
||||||
}
|
}
|
||||||
|
|
||||||
section("When which people are away?") {
|
section("When which people are away?") {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ mappings {
|
|||||||
path("/receivedToken") { action: [ POST: "receivedToken", GET: "receivedToken"] }
|
path("/receivedToken") { action: [ POST: "receivedToken", GET: "receivedToken"] }
|
||||||
path("/receiveToken") { action: [ POST: "receiveToken", GET: "receiveToken"] }
|
path("/receiveToken") { action: [ POST: "receiveToken", GET: "receiveToken"] }
|
||||||
path("/hookCallback") { action: [ POST: "hookEventHandler", GET: "hookEventHandler"] }
|
path("/hookCallback") { action: [ POST: "hookEventHandler", GET: "hookEventHandler"] }
|
||||||
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
|
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
|
||||||
path("/oauth/callback") { action: [ GET: "callback" ] }
|
path("/oauth/callback") { action: [ GET: "callback" ] }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ def callback() {
|
|||||||
} else {
|
} else {
|
||||||
log.warn "No authQueryString"
|
log.warn "No authQueryString"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.JawboneAccessToken) {
|
if (state.JawboneAccessToken) {
|
||||||
log.debug "Access token already exists"
|
log.debug "Access token already exists"
|
||||||
setup()
|
setup()
|
||||||
@@ -73,7 +73,7 @@ def callback() {
|
|||||||
|
|
||||||
def authPage() {
|
def authPage() {
|
||||||
log.debug "authPage"
|
log.debug "authPage"
|
||||||
def description = null
|
def description = null
|
||||||
if (state.JawboneAccessToken == null) {
|
if (state.JawboneAccessToken == null) {
|
||||||
if (!state.accessToken) {
|
if (!state.accessToken) {
|
||||||
log.debug "About to create access token"
|
log.debug "About to create access token"
|
||||||
@@ -82,12 +82,13 @@ def authPage() {
|
|||||||
description = "Click to enter Jawbone Credentials"
|
description = "Click to enter Jawbone Credentials"
|
||||||
def redirectUrl = buildRedirectUrl
|
def redirectUrl = buildRedirectUrl
|
||||||
log.debug "RedirectURL = ${redirectUrl}"
|
log.debug "RedirectURL = ${redirectUrl}"
|
||||||
def donebutton= state.JawboneAccessToken != null
|
def donebutton= state.JawboneAccessToken != null
|
||||||
return dynamicPage(name: "Credentials", title: "Jawbone UP", nextPage: null, uninstall: true, install: donebutton) {
|
return dynamicPage(name: "Credentials", title: "Jawbone UP", nextPage: null, uninstall: true, install: donebutton) {
|
||||||
|
section { paragraph title: "Note:", "This device has not been officially tested and certified to “Work with SmartThings”. You can connect it to your SmartThings home but performance may vary and we will not be able to provide support or assistance." }
|
||||||
section { href url:redirectUrl, style:"embedded", required:true, title:"Jawbone UP", state: hast ,description:description }
|
section { href url:redirectUrl, style:"embedded", required:true, title:"Jawbone UP", state: hast ,description:description }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
description = "Jawbone Credentials Already Entered."
|
description = "Jawbone Credentials Already Entered."
|
||||||
return dynamicPage(name: "Credentials", title: "Jawbone UP", uninstall: true, install:true) {
|
return dynamicPage(name: "Credentials", title: "Jawbone UP", uninstall: true, install:true) {
|
||||||
section { href url: buildRedirectUrl("receivedToken"), style:"embedded", state: "complete", title:"Jawbone UP", description:description }
|
section { href url: buildRedirectUrl("receivedToken"), style:"embedded", state: "complete", title:"Jawbone UP", description:description }
|
||||||
}
|
}
|
||||||
@@ -107,7 +108,7 @@ def receiveToken(redirectUrl = null) {
|
|||||||
def params = [
|
def params = [
|
||||||
uri: "https://jawbone.com/auth/oauth2/token?${toQueryString(oauthParams)}",
|
uri: "https://jawbone.com/auth/oauth2/token?${toQueryString(oauthParams)}",
|
||||||
]
|
]
|
||||||
httpGet(params) { response ->
|
httpGet(params) { response ->
|
||||||
log.debug "${response.data}"
|
log.debug "${response.data}"
|
||||||
log.debug "Setting access token to ${response.data.access_token}, refresh token to ${response.data.refresh_token}"
|
log.debug "Setting access token to ${response.data.access_token}, refresh token to ${response.data.refresh_token}"
|
||||||
state.JawboneAccessToken = response.data.access_token
|
state.JawboneAccessToken = response.data.access_token
|
||||||
@@ -149,7 +150,7 @@ def connectionStatus(message, redirectUrl = null) {
|
|||||||
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
|
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
def html = """
|
def html = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -229,12 +230,12 @@ def validateCurrentToken() {
|
|||||||
log.debug "validateCurrentToken"
|
log.debug "validateCurrentToken"
|
||||||
def url = "https://jawbone.com/nudge/api/v.1.1/users/@me/refreshToken"
|
def url = "https://jawbone.com/nudge/api/v.1.1/users/@me/refreshToken"
|
||||||
def requestBody = "secret=${appSettings.clientSecret}"
|
def requestBody = "secret=${appSettings.clientSecret}"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
httpPost(uri: url, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ], body: requestBody) {response ->
|
httpPost(uri: url, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ], body: requestBody) {response ->
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
log.debug "${response.data}"
|
log.debug "${response.data}"
|
||||||
log.debug "Setting refresh token to ${response.data.data.refresh_token}"
|
log.debug "Setting refresh token"
|
||||||
state.refreshToken = response.data.data.refresh_token
|
state.refreshToken = response.data.data.refresh_token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,7 +259,7 @@ def validateCurrentToken() {
|
|||||||
state.remove("refreshToken")
|
state.remove("refreshToken")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug "Setting access token to ${data.access_token}, refresh token to ${data.refresh_token}"
|
log.debug "Setting access token"
|
||||||
state.JawboneAccessToken = data.access_token
|
state.JawboneAccessToken = data.access_token
|
||||||
state.refreshToken = data.refresh_token
|
state.refreshToken = data.refresh_token
|
||||||
}
|
}
|
||||||
@@ -271,10 +272,10 @@ def validateCurrentToken() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
log.debug "Callback URL - Webhook"
|
log.debug "Callback URL - Webhook"
|
||||||
def localServerUrl = getApiServerUrl()
|
def localServerUrl = getApiServerUrl()
|
||||||
def hookUrl = "${localServerUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/hookCallback"
|
def hookUrl = "${localServerUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/hookCallback"
|
||||||
def webhook = "https://jawbone.com/nudge/api/v.1.1/users/@me/pubsub?webhook=$hookUrl"
|
def webhook = "https://jawbone.com/nudge/api/v.1.1/users/@me/pubsub?webhook=$hookUrl"
|
||||||
httpPost(uri: webhook, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ])
|
httpPost(uri: webhook, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,16 +285,16 @@ def setup() {
|
|||||||
|
|
||||||
if (state.JawboneAccessToken) {
|
if (state.JawboneAccessToken) {
|
||||||
def urlmember = "https://jawbone.com/nudge/api/users/@me/"
|
def urlmember = "https://jawbone.com/nudge/api/users/@me/"
|
||||||
def member = null
|
def member = null
|
||||||
httpGet(uri: urlmember, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
httpGet(uri: urlmember, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||||
member = response.data.data
|
member = response.data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
if (member) {
|
if (member) {
|
||||||
state.member = member
|
state.member = member
|
||||||
def externalId = "${app.id}.${member.xid}"
|
def externalId = "${app.id}.${member.xid}"
|
||||||
|
|
||||||
// find the appropriate child device based on my app id and the device network id
|
// find the appropriate child device based on my app id and the device network id
|
||||||
def deviceWrapper = getChildDevice("${externalId}")
|
def deviceWrapper = getChildDevice("${externalId}")
|
||||||
|
|
||||||
// invoke the generatePresenceEvent method on the child device
|
// invoke the generatePresenceEvent method on the child device
|
||||||
@@ -312,7 +313,7 @@ def setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
|
|
||||||
if (!state.accessToken) {
|
if (!state.accessToken) {
|
||||||
log.debug "About to create access token"
|
log.debug "About to create access token"
|
||||||
createAccessToken()
|
createAccessToken()
|
||||||
@@ -324,7 +325,7 @@ def installed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
|
|
||||||
if (!state.accessToken) {
|
if (!state.accessToken) {
|
||||||
log.debug "About to create access token"
|
log.debug "About to create access token"
|
||||||
createAccessToken()
|
createAccessToken()
|
||||||
@@ -348,29 +349,29 @@ def uninstalled() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def pollChild(childDevice) {
|
def pollChild(childDevice) {
|
||||||
def member = state.member
|
def member = state.member
|
||||||
generatePollingEvents (member, childDevice)
|
generatePollingEvents (member, childDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
def generatePollingEvents (member, childDevice) {
|
def generatePollingEvents (member, childDevice) {
|
||||||
// lets figure out if the member is currently "home" (At the place)
|
// lets figure out if the member is currently "home" (At the place)
|
||||||
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
||||||
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
||||||
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
|
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
|
||||||
def goals = null
|
def goals = null
|
||||||
def moves = null
|
def moves = null
|
||||||
def sleeps = null
|
def sleeps = null
|
||||||
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||||
goals = response.data.data
|
goals = response.data.data
|
||||||
}
|
}
|
||||||
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||||
moves = response.data.data.items[0]
|
moves = response.data.data.items[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
try { // we are going to just ignore any errors
|
try { // we are going to just ignore any errors
|
||||||
log.debug "Member = ${member.first}"
|
log.debug "Member = ${member.first}"
|
||||||
log.debug "Moves Goal = ${goals.move_steps} Steps"
|
log.debug "Moves Goal = ${goals.move_steps} Steps"
|
||||||
log.debug "Moves = ${moves.details.steps} Steps"
|
log.debug "Moves = ${moves.details.steps} Steps"
|
||||||
|
|
||||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||||
@@ -378,29 +379,29 @@ def generatePollingEvents (member, childDevice) {
|
|||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
// eat it
|
// eat it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateInitialEvent (member, childDevice) {
|
def generateInitialEvent (member, childDevice) {
|
||||||
// lets figure out if the member is currently "home" (At the place)
|
// lets figure out if the member is currently "home" (At the place)
|
||||||
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
||||||
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
||||||
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
|
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
|
||||||
def goals = null
|
def goals = null
|
||||||
def moves = null
|
def moves = null
|
||||||
def sleeps = null
|
def sleeps = null
|
||||||
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||||
goals = response.data.data
|
goals = response.data.data
|
||||||
}
|
}
|
||||||
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||||
moves = response.data.data.items[0]
|
moves = response.data.data.items[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
try { // we are going to just ignore any errors
|
try { // we are going to just ignore any errors
|
||||||
log.debug "Member = ${member.first}"
|
log.debug "Member = ${member.first}"
|
||||||
log.debug "Moves Goal = ${goals.move_steps} Steps"
|
log.debug "Moves Goal = ${goals.move_steps} Steps"
|
||||||
log.debug "Moves = ${moves.details.steps} Steps"
|
log.debug "Moves = ${moves.details.steps} Steps"
|
||||||
log.debug "Sleeping state = false"
|
log.debug "Sleeping state = false"
|
||||||
childDevice?.generateSleepingEvent(false)
|
childDevice?.generateSleepingEvent(false)
|
||||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||||
@@ -408,27 +409,27 @@ def generateInitialEvent (member, childDevice) {
|
|||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
// eat it
|
// eat it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColor (steps,goal,childDevice) {
|
def setColor (steps,goal,childDevice) {
|
||||||
def result = steps * 100 / goal
|
def result = steps * 100 / goal
|
||||||
if (result < 25)
|
if (result < 25)
|
||||||
childDevice?.sendEvent(name:"steps", value: "steps", label: steps)
|
childDevice?.sendEvent(name:"steps", value: "steps", label: steps)
|
||||||
else if ((result >= 25) && (result < 50))
|
else if ((result >= 25) && (result < 50))
|
||||||
childDevice?.sendEvent(name:"steps", value: "steps1", label: steps)
|
childDevice?.sendEvent(name:"steps", value: "steps1", label: steps)
|
||||||
else if ((result >= 50) && (result < 75))
|
else if ((result >= 50) && (result < 75))
|
||||||
childDevice?.sendEvent(name:"steps", value: "steps1", label: steps)
|
childDevice?.sendEvent(name:"steps", value: "steps1", label: steps)
|
||||||
else if (result >= 75)
|
else if (result >= 75)
|
||||||
childDevice?.sendEvent(name:"steps", value: "stepsgoal", label: steps)
|
childDevice?.sendEvent(name:"steps", value: "stepsgoal", label: steps)
|
||||||
}
|
}
|
||||||
|
|
||||||
def hookEventHandler() {
|
def hookEventHandler() {
|
||||||
// log.debug "In hookEventHandler method."
|
// log.debug "In hookEventHandler method."
|
||||||
log.debug "request = ${request}"
|
log.debug "request = ${request}"
|
||||||
|
|
||||||
def json = request.JSON
|
def json = request.JSON
|
||||||
|
|
||||||
// get some stuff we need
|
// get some stuff we need
|
||||||
def userId = json.events.user_xid[0]
|
def userId = json.events.user_xid[0]
|
||||||
def json_type = json.events.type[0]
|
def json_type = json.events.type[0]
|
||||||
@@ -437,39 +438,39 @@ def hookEventHandler() {
|
|||||||
//log.debug json
|
//log.debug json
|
||||||
log.debug "Userid = ${userId}"
|
log.debug "Userid = ${userId}"
|
||||||
log.debug "Notification Type: " + json_type
|
log.debug "Notification Type: " + json_type
|
||||||
log.debug "Notification Action: " + json_action
|
log.debug "Notification Action: " + json_action
|
||||||
|
|
||||||
// find the appropriate child device based on my app id and the device network id
|
// find the appropriate child device based on my app id and the device network id
|
||||||
def externalId = "${app.id}.${userId}"
|
def externalId = "${app.id}.${userId}"
|
||||||
def childDevice = getChildDevice("${externalId}")
|
def childDevice = getChildDevice("${externalId}")
|
||||||
|
|
||||||
if (childDevice) {
|
if (childDevice) {
|
||||||
switch (json_action) {
|
switch (json_action) {
|
||||||
case "enter_sleep_mode":
|
case "enter_sleep_mode":
|
||||||
childDevice?.generateSleepingEvent(true)
|
childDevice?.generateSleepingEvent(true)
|
||||||
break
|
break
|
||||||
case "exit_sleep_mode":
|
case "exit_sleep_mode":
|
||||||
childDevice?.generateSleepingEvent(false)
|
childDevice?.generateSleepingEvent(false)
|
||||||
break
|
break
|
||||||
case "creation":
|
case "creation":
|
||||||
childDevice?.sendEvent(name:"steps", value: 0)
|
childDevice?.sendEvent(name:"steps", value: 0)
|
||||||
break
|
break
|
||||||
case "updation":
|
case "updation":
|
||||||
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
||||||
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
||||||
def goals = null
|
def goals = null
|
||||||
def moves = null
|
def moves = null
|
||||||
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||||
goals = response.data.data
|
goals = response.data.data
|
||||||
}
|
}
|
||||||
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||||
moves = response.data.data.items[0]
|
moves = response.data.data.items[0]
|
||||||
}
|
}
|
||||||
log.debug "Goal = ${goals.move_steps} Steps"
|
log.debug "Goal = ${goals.move_steps} Steps"
|
||||||
log.debug "Steps = ${moves.details.steps} Steps"
|
log.debug "Steps = ${moves.details.steps} Steps"
|
||||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||||
//setColor(moves.details.steps,goals.move_steps,childDevice)
|
//setColor(moves.details.steps,goals.move_steps,childDevice)
|
||||||
break
|
break
|
||||||
case "deletion":
|
case "deletion":
|
||||||
app.delete()
|
app.delete()
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
definition(
|
definition(
|
||||||
name: "Smart Home Ventilation",
|
name: "Smart Home Ventilation",
|
||||||
namespace: "MichaelStruck",
|
namespace: "MichaelStruck",
|
||||||
@@ -164,7 +164,7 @@ def installed() {
|
|||||||
def updated() {
|
def updated() {
|
||||||
unschedule()
|
unschedule()
|
||||||
turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule
|
turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule
|
||||||
unsubscribe
|
unsubscribe()
|
||||||
log.debug "Updated with settings: ${settings}"
|
log.debug "Updated with settings: ${settings}"
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
@@ -174,12 +174,12 @@ def init() {
|
|||||||
schedule (midnightTime, midNight)
|
schedule (midnightTime, midNight)
|
||||||
subscribe(location, "mode", locationHandler)
|
subscribe(location, "mode", locationHandler)
|
||||||
startProcess()
|
startProcess()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common methods
|
// Common methods
|
||||||
|
|
||||||
def startProcess () {
|
def startProcess () {
|
||||||
createDayArray()
|
createDayArray()
|
||||||
state.dayCount=state.data.size()
|
state.dayCount=state.data.size()
|
||||||
if (state.dayCount){
|
if (state.dayCount){
|
||||||
state.counter = 0
|
state.counter = 0
|
||||||
@@ -190,7 +190,7 @@ def startProcess () {
|
|||||||
def startDay() {
|
def startDay() {
|
||||||
def start = convertEpoch(state.data[state.counter].start)
|
def start = convertEpoch(state.data[state.counter].start)
|
||||||
def stop = convertEpoch(state.data[state.counter].stop)
|
def stop = convertEpoch(state.data[state.counter].stop)
|
||||||
|
|
||||||
runOnce(start, turnOnSwitch, [overwrite: true])
|
runOnce(start, turnOnSwitch, [overwrite: true])
|
||||||
runOnce(stop, incDay, [overwrite: true])
|
runOnce(stop, incDay, [overwrite: true])
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ def locationHandler(evt) {
|
|||||||
}
|
}
|
||||||
if (!result) {
|
if (!result) {
|
||||||
startProcess()
|
startProcess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def midNight(){
|
def midNight(){
|
||||||
@@ -238,7 +238,7 @@ def turnOffSwitch() {
|
|||||||
}
|
}
|
||||||
log.debug "Home ventilation switches are off."
|
log.debug "Home ventilation switches are off."
|
||||||
}
|
}
|
||||||
|
|
||||||
def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
|
def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
|
||||||
def title = ""
|
def title = ""
|
||||||
def dayListClean = "On "
|
def dayListClean = "On "
|
||||||
@@ -252,7 +252,7 @@ def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
|
|||||||
dayListClean = "${dayListClean}, "
|
dayListClean = "${dayListClean}, "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dayListClean = "Every day"
|
dayListClean = "Every day"
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
|
|||||||
modeListClean = "${modeListClean} ${modePrefix}"
|
modeListClean = "${modeListClean} ${modePrefix}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
modeListClean = "${modeListClean}all modes"
|
modeListClean = "${modeListClean}all modes"
|
||||||
}
|
}
|
||||||
@@ -283,16 +283,16 @@ def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
|
|||||||
title += "\nSchedule 2: ${humanReadableTime(on2)} to ${humanReadableTime(off2)}"
|
title += "\nSchedule 2: ${humanReadableTime(on2)} to ${humanReadableTime(off2)}"
|
||||||
}
|
}
|
||||||
if (on3 && off3) {
|
if (on3 && off3) {
|
||||||
title += "\nSchedule 3: ${humanReadableTime(on3)} to ${humanReadableTime(off3)}"
|
title += "\nSchedule 3: ${humanReadableTime(on3)} to ${humanReadableTime(off3)}"
|
||||||
}
|
}
|
||||||
if (on4 && off4) {
|
if (on4 && off4) {
|
||||||
title += "\nSchedule 4: ${humanReadableTime(on4)} to ${humanReadableTime(off4)}"
|
title += "\nSchedule 4: ${humanReadableTime(on4)} to ${humanReadableTime(off4)}"
|
||||||
}
|
}
|
||||||
if (on1 || on2 || on3 || on4) {
|
if (on1 || on2 || on3 || on4) {
|
||||||
title += "\n$modeListClean"
|
title += "\n$modeListClean"
|
||||||
title += "\n$dayListClean"
|
title += "\n$dayListClean"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!on1 && !on2 && !on3 && !on4) {
|
if (!on1 && !on2 && !on3 && !on4) {
|
||||||
title="Click to configure scenario"
|
title="Click to configure scenario"
|
||||||
}
|
}
|
||||||
@@ -374,7 +374,7 @@ def createDayArray() {
|
|||||||
timeOk(timeOnD1, timeOffD1)
|
timeOk(timeOnD1, timeOffD1)
|
||||||
timeOk(timeOnD2, timeOffD2)
|
timeOk(timeOnD2, timeOffD2)
|
||||||
timeOk(timeOnD3, timeOffD3)
|
timeOk(timeOnD3, timeOffD3)
|
||||||
timeOk(timeOnD4, timeOffD4)
|
timeOk(timeOnD4, timeOffD4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.data.sort{it.start}
|
state.data.sort{it.start}
|
||||||
@@ -384,7 +384,7 @@ def createDayArray() {
|
|||||||
|
|
||||||
private def textAppName() {
|
private def textAppName() {
|
||||||
def text = "Smart Home Ventilation"
|
def text = "Smart Home Ventilation"
|
||||||
}
|
}
|
||||||
|
|
||||||
private def textVersion() {
|
private def textVersion() {
|
||||||
def text = "Version 2.1.2 (05/31/2015)"
|
def text = "Version 2.1.2 (05/31/2015)"
|
||||||
@@ -416,4 +416,4 @@ private def textHelp() {
|
|||||||
"that each scenario does not overlap and run in separate modes (i.e. Home, Out of town, etc). Also note that you should " +
|
"that each scenario does not overlap and run in separate modes (i.e. Home, Out of town, etc). Also note that you should " +
|
||||||
"avoid scheduling the ventilation fan at exactly midnight; the app resets itself at that time. It is suggested to start any new schedule " +
|
"avoid scheduling the ventilation fan at exactly midnight; the app resets itself at that time. It is suggested to start any new schedule " +
|
||||||
"at 12:15 am or later."
|
"at 12:15 am or later."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,13 +114,16 @@ def beaconHandler(evt) {
|
|||||||
|
|
||||||
if (allOk) {
|
if (allOk) {
|
||||||
def data = new groovy.json.JsonSlurper().parseText(evt.data)
|
def data = new groovy.json.JsonSlurper().parseText(evt.data)
|
||||||
log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
|
// removed logging of device names. can be added back for debugging
|
||||||
|
//log.debug "<beacon-control> data: $data - phones: " + phones*.deviceNetworkId
|
||||||
|
|
||||||
def beaconName = getBeaconName(evt)
|
def beaconName = getBeaconName(evt)
|
||||||
log.debug "<beacon-control> beaconName: $beaconName"
|
// removed logging of device names. can be added back for debugging
|
||||||
|
//log.debug "<beacon-control> beaconName: $beaconName"
|
||||||
|
|
||||||
def phoneName = getPhoneName(data)
|
def phoneName = getPhoneName(data)
|
||||||
log.debug "<beacon-control> phoneName: $phoneName"
|
// removed logging of device names. can be added back for debugging
|
||||||
|
//log.debug "<beacon-control> phoneName: $phoneName"
|
||||||
if (phoneName != null) {
|
if (phoneName != null) {
|
||||||
def action = data.presence == "1" ? "arrived" : "left"
|
def action = data.presence == "1" ? "arrived" : "left"
|
||||||
def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"
|
def msg = "$phoneName has $action ${action == 'arrived' ? 'at ' : ''}the $beaconName"
|
||||||
|
|||||||
@@ -49,13 +49,15 @@ preferences {
|
|||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
log.debug "Installed with settings: ${settings}"
|
log.debug "Installed with settings: ${settings}"
|
||||||
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
// commented out log statement because presence sensor label could contain user's name
|
||||||
|
//log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
log.debug "Updated with settings: ${settings}"
|
log.debug "Updated with settings: ${settings}"
|
||||||
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
// commented out log statement because presence sensor label could contain user's name
|
||||||
|
//log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ def waterWetHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1
|
def alreadySentSms = recentEvents.count { it.value && it.value == "wet" } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone within the last $deltaSeconds seconds"
|
log.debug "SMS already sent within the last $deltaSeconds seconds"
|
||||||
} else {
|
} else {
|
||||||
def msg = "${alarm.displayName} is wet!"
|
def msg = "${alarm.displayName} is wet!"
|
||||||
log.debug "$alarm is wet, texting $phone"
|
log.debug "$alarm is wet, texting phone number"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts(msg, recipients)
|
sendNotificationToContacts(msg, recipients)
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def takeAction(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
def sendTextMessage() {
|
def sendTextMessage() {
|
||||||
log.debug "$multisensor was open too long, texting $phone"
|
log.debug "$multisensor was open too long, texting phone"
|
||||||
|
|
||||||
updateSmsHistory()
|
updateSmsHistory()
|
||||||
def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1)
|
def openMinutes = maxOpenTime * (state.smsHistory?.size() ?: 1)
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ preferences {
|
|||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
log.debug "Installed with settings: ${settings}"
|
log.debug "Installed with settings: ${settings}"
|
||||||
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
// log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
log.debug "Updated with settings: ${settings}"
|
log.debug "Updated with settings: ${settings}"
|
||||||
log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
// log.debug "Current mode = ${location.mode}, people = ${people.collect{it.label + ': ' + it.currentPresence}}"
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
subscribe(people, "presence", presence)
|
subscribe(people, "presence", presence)
|
||||||
}
|
}
|
||||||
@@ -71,11 +71,10 @@ def presence(evt)
|
|||||||
def person = getPerson(evt)
|
def person = getPerson(evt)
|
||||||
def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"}
|
def recentNotPresent = person.statesSince("presence", t0).find{it.value == "not present"}
|
||||||
if (recentNotPresent) {
|
if (recentNotPresent) {
|
||||||
log.debug "skipping notification of arrival of ${person.displayName} because last departure was only ${now() - recentNotPresent.date.time} msec ago"
|
log.debug "skipping notification of arrival of Person because last departure was only ${now() - recentNotPresent.date.time} msec ago"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
def message = "${person.displayName} arrived at home, changing mode to '${newMode}'"
|
def message = "${person.displayName} arrived at home, changing mode to '${newMode}'"
|
||||||
log.info message
|
|
||||||
send(message)
|
send(message)
|
||||||
setLocationMode(newMode)
|
setLocationMode(newMode)
|
||||||
}
|
}
|
||||||
@@ -106,6 +105,4 @@ private send(msg) {
|
|||||||
sendSms(phone, msg)
|
sendSms(phone, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug msg
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,12 +57,11 @@ def scheduleCheck()
|
|||||||
def message = message1 ?: "SmartThings - Habit Helper Reminder!"
|
def message = message1 ?: "SmartThings - Habit Helper Reminder!"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
log.debug "Texting reminder: ($message) to contacts:${recipients?.size()}"
|
log.debug "Texting reminder to contacts:${recipients?.size()}"
|
||||||
sendNotificationToContacts(message, recipients)
|
sendNotificationToContacts(message, recipients)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
log.debug "Texting reminder"
|
||||||
log.debug "Texting reminder: ($message) to $phone1"
|
|
||||||
sendSms(phone1, message)
|
sendSms(phone1, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ def bridgeDiscovery(params=[:])
|
|||||||
|
|
||||||
return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) {
|
return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) {
|
||||||
section("Please wait while we discover your Hue Bridge. 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 Bridge. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options
|
input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options, submitOnChange: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,9 +333,9 @@ def bulbListHandler(hub, data = "") {
|
|||||||
def bridge = null
|
def bridge = null
|
||||||
if (selectedHue) {
|
if (selectedHue) {
|
||||||
bridge = getChildDevice(selectedHue)
|
bridge = getChildDevice(selectedHue)
|
||||||
|
bridge?.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
||||||
}
|
}
|
||||||
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
||||||
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,24 +490,25 @@ def ssdpBridgeHandler(evt) {
|
|||||||
def host = ip + ":" + port
|
def host = ip + ":" + port
|
||||||
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
|
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
|
||||||
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
|
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
|
||||||
def dni = "${parsedEvent.mac}"
|
def dniReceived = "${parsedEvent.mac}"
|
||||||
def d = getChildDevice(dni)
|
def currentDni = dstate.mac
|
||||||
|
def d = getChildDevice(dniReceived)
|
||||||
def networkAddress = null
|
def networkAddress = null
|
||||||
if (!d) {
|
if (!d) {
|
||||||
childDevices.each {
|
// There might be a mismatch between bridge DNI and the actual bridge mac address, correct that
|
||||||
if (it.getDeviceDataByName("mac")) {
|
log.debug "Bridge with $dniReceived not found"
|
||||||
def newDNI = "${it.getDeviceDataByName("mac")}"
|
def bridge = childDevices.find { it.deviceNetworkId == currentDni }
|
||||||
d = it
|
if (bridge != null) {
|
||||||
if (newDNI != it.deviceNetworkId) {
|
log.warn "Bridge is set to ${bridge.deviceNetworkId}, updating to $dniReceived"
|
||||||
def oldDNI = it.deviceNetworkId
|
bridge.setDeviceNetworkId("${dniReceived}")
|
||||||
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
dstate.mac = dniReceived
|
||||||
it.setDeviceNetworkId("${newDNI}")
|
// Check to see if selectedHue is a valid bridge, otherwise update it
|
||||||
if (oldDNI == selectedHue) {
|
def isSelectedValid = bridges?.find {it.value?.mac == selectedHue}
|
||||||
app.updateSetting("selectedHue", newDNI)
|
if (isSelectedValid == null) {
|
||||||
}
|
log.warn "Correcting selectedHue in state"
|
||||||
doDeviceSync()
|
app.updateSetting("selectedHue", dniReceived)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
doDeviceSync()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateBridgeStatus(d)
|
updateBridgeStatus(d)
|
||||||
@@ -525,6 +526,18 @@ def ssdpBridgeHandler(evt) {
|
|||||||
d.sendEvent(name:"networkAddress", value: host)
|
d.sendEvent(name:"networkAddress", value: host)
|
||||||
d.updateDataValue("networkAddress", host)
|
d.updateDataValue("networkAddress", host)
|
||||||
}
|
}
|
||||||
|
if (dstate.mac != dniReceived) {
|
||||||
|
log.warn "Correcting bridge mac address in state"
|
||||||
|
dstate.mac = dniReceived
|
||||||
|
}
|
||||||
|
if (selectedHue != dniReceived) {
|
||||||
|
// Check to see if selectedHue is a valid bridge, otherwise update it
|
||||||
|
def isSelectedValid = bridges?.find {it.value?.mac == selectedHue}
|
||||||
|
if (isSelectedValid == null) {
|
||||||
|
log.warn "Correcting selectedHue in state"
|
||||||
|
app.updateSetting("selectedHue", dniReceived)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -801,10 +814,12 @@ def parse(childDevice, description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Philips Hue priority for color is xy > ct > hs
|
// Philips Hue priority for color is xy > ct > hs
|
||||||
|
// For SmartThings, try to always send hue, sat and hex
|
||||||
private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
|
private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
|
||||||
if (device == null || (xy == null && hue == null && sat == null && ct == null))
|
if (device == null || (xy == null && hue == null && sat == null && ct == null))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def events = [:]
|
||||||
// For now, only care about changing color temperature if requested by user
|
// For now, only care about changing color temperature if requested by user
|
||||||
if (ct != null && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
|
if (ct != null && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
|
||||||
// for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below
|
// for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below
|
||||||
@@ -818,13 +833,13 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
|
|||||||
if (hue != null) {
|
if (hue != null) {
|
||||||
// 0-65535
|
// 0-65535
|
||||||
def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int
|
def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int
|
||||||
device.sendEvent([name: "hue", value: value, descriptionText: "Color has changed"])
|
events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sat != null) {
|
if (sat != null) {
|
||||||
// 0-254
|
// 0-254
|
||||||
def value = Math.round(sat * 100 / 254) as int
|
def value = Math.round(sat * 100 / 254) as int
|
||||||
device.sendEvent([name: "saturation", value: value, descriptionText: "Color has changed"])
|
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex
|
// Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex
|
||||||
@@ -836,17 +851,28 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
|
|||||||
def model = state.bulbs[id]?.modelid
|
def model = state.bulbs[id]?.modelid
|
||||||
def hex = colorFromXY(xy, model)
|
def hex = colorFromXY(xy, model)
|
||||||
|
|
||||||
// TODO Disabled until a solution for the jumping color picker can be figured out
|
// Create Hue and Saturation events if not previously existing
|
||||||
//device.sendEvent([name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed", displayed: false])
|
def hsv = hexToHsv(hex)
|
||||||
|
if (events["hue"] == null)
|
||||||
|
events["hue"] = [name: "hue", value: hsv[0], descriptionText: "Color has changed", displayed: false]
|
||||||
|
if (events["saturation"] == null)
|
||||||
|
events["saturation"] = [name: "saturation", value: hsv[1], descriptionText: "Color has changed", displayed: false]
|
||||||
|
|
||||||
|
events["color"] = [name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed", displayed: true]
|
||||||
} else if (colormode == "hs" || colormode == null) {
|
} else if (colormode == "hs" || colormode == null) {
|
||||||
// colormode is "hs" or "xy" is missing, default to follow hue/sat which is already handled above
|
// colormode is "hs" or "xy" is missing, default to follow hue/sat which is already handled above
|
||||||
|
def hueValue = (hue != null) ? events["hue"].value : Integer.parseInt("$device.currentHue")
|
||||||
|
def satValue = (sat != null) ? events["saturation"].value : Integer.parseInt("$device.currentSaturation")
|
||||||
|
|
||||||
// TODO Disabled until the standard behavior of lights is defined (hue and sat events are sent above)
|
|
||||||
//def hex = colorUtil.hslToHex((int) device.currentHue, (int) device.currentSaturation)
|
def hex = hsvToHex(hueValue, satValue)
|
||||||
// device.sendEvent([name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed"])
|
events["color"] = [name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed", displayed: true]
|
||||||
}
|
}
|
||||||
|
|
||||||
return debug
|
boolean sendColorChanged = false
|
||||||
|
events.each {
|
||||||
|
device.sendEvent(it.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendBasicEvents(device, param, value) {
|
private sendBasicEvents(device, param, value) {
|
||||||
@@ -887,8 +913,6 @@ private handleCommandResponse(body) {
|
|||||||
def updates = [:]
|
def updates = [:]
|
||||||
|
|
||||||
body.each { payload ->
|
body.each { payload ->
|
||||||
log.debug $payload
|
|
||||||
|
|
||||||
if (payload?.success) {
|
if (payload?.success) {
|
||||||
def childDeviceNetworkId = app.id + "/"
|
def childDeviceNetworkId = app.id + "/"
|
||||||
def eventType
|
def eventType
|
||||||
@@ -939,6 +963,14 @@ private handleCommandResponse(body) {
|
|||||||
* @return empty array
|
* @return empty array
|
||||||
*/
|
*/
|
||||||
private handlePoll(body) {
|
private handlePoll(body) {
|
||||||
|
// Used to track "unreachable" time
|
||||||
|
// Device is considered "offline" if it has been in the "unreachable" state for
|
||||||
|
// 11 minutes (e.g. two poll intervals)
|
||||||
|
// Note, Hue Bridge marks devices as "unreachable" often even when they accept commands
|
||||||
|
Calendar time11 = Calendar.getInstance()
|
||||||
|
time11.add(Calendar.MINUTE, -11)
|
||||||
|
Calendar currentTime = Calendar.getInstance()
|
||||||
|
|
||||||
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}"}
|
||||||
@@ -948,7 +980,10 @@ private handlePoll(body) {
|
|||||||
// light just came back online, notify device watch
|
// light just came back online, notify device watch
|
||||||
def lastActivity = now()
|
def lastActivity = now()
|
||||||
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
|
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
|
||||||
|
log.debug "$device is Online"
|
||||||
}
|
}
|
||||||
|
// Mark light as "online"
|
||||||
|
state.bulbs[bulb.key]?.unreachableSince = null
|
||||||
state.bulbs[bulb.key]?.online = 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 user just executed commands, then do not send events to avoid confusing the turning on/off state
|
||||||
@@ -958,9 +993,18 @@ private handlePoll(body) {
|
|||||||
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
|
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
|
if (state.bulbs[bulb.key]?.unreachableSince == null) {
|
||||||
log.warn "$device is not reachable by Hue bridge"
|
// Store the first time where device was reported as "unreachable"
|
||||||
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
|
state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis()
|
||||||
|
} else if (state.bulbs[bulb.key]?.online) {
|
||||||
|
// Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary
|
||||||
|
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) {
|
||||||
|
log.warn "$device went Offline"
|
||||||
|
state.bulbs[bulb.key]?.online = false
|
||||||
|
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.warn "$device may not reachable by Hue bridge"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -995,9 +1039,6 @@ def hubVerification(bodytext) {
|
|||||||
def on(childDevice) {
|
def on(childDevice) {
|
||||||
log.debug "Executing 'on'"
|
log.debug "Executing 'on'"
|
||||||
def id = getId(childDevice)
|
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/$id/state", [on: true])
|
||||||
@@ -1007,9 +1048,6 @@ def on(childDevice) {
|
|||||||
def off(childDevice) {
|
def off(childDevice) {
|
||||||
log.debug "Executing 'off'"
|
log.debug "Executing 'off'"
|
||||||
def id = getId(childDevice)
|
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/$id/state", [on: false])
|
||||||
@@ -1019,9 +1057,6 @@ def off(childDevice) {
|
|||||||
def setLevel(childDevice, percent) {
|
def setLevel(childDevice, percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 1 - 254
|
// 1 - 254
|
||||||
def level
|
def level
|
||||||
@@ -1046,10 +1081,6 @@ def setLevel(childDevice, percent) {
|
|||||||
def setSaturation(childDevice, percent) {
|
def setSaturation(childDevice, percent) {
|
||||||
log.debug "Executing 'setSaturation($percent)'"
|
log.debug "Executing 'setSaturation($percent)'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInProgress()
|
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)
|
||||||
@@ -1062,9 +1093,6 @@ def setSaturation(childDevice, percent) {
|
|||||||
def setHue(childDevice, percent) {
|
def setHue(childDevice, percent) {
|
||||||
log.debug "Executing 'setHue($percent)'"
|
log.debug "Executing 'setHue($percent)'"
|
||||||
def id = getId(childDevice)
|
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)
|
||||||
@@ -1077,9 +1105,6 @@ def setHue(childDevice, percent) {
|
|||||||
def setColorTemperature(childDevice, huesettings) {
|
def setColorTemperature(childDevice, huesettings) {
|
||||||
log.debug "Executing 'setColorTemperature($huesettings)'"
|
log.debug "Executing 'setColorTemperature($huesettings)'"
|
||||||
def id = getId(childDevice)
|
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)
|
||||||
@@ -1091,9 +1116,6 @@ def setColorTemperature(childDevice, huesettings) {
|
|||||||
def setColor(childDevice, huesettings) {
|
def setColor(childDevice, huesettings) {
|
||||||
log.debug "Executing 'setColor($huesettings)'"
|
log.debug "Executing 'setColor($huesettings)'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
if (!isOnline(id)) {
|
|
||||||
return "Bulb is unreachable"
|
|
||||||
}
|
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
|
|
||||||
def value = [:]
|
def value = [:]
|
||||||
@@ -1101,26 +1123,22 @@ def setColor(childDevice, huesettings) {
|
|||||||
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
|
// Prefer hue/sat over hex to make sure it works with the majority of the smartapps
|
||||||
// def model = state.bulbs[getId(childDevice)]?.modelid
|
if (huesettings.hue != null || huesettings.sat != null) {
|
||||||
if (huesettings.hex != null) {
|
// If both hex and hue/sat are set, send all values to bridge to get hue/sat in response from bridge to
|
||||||
|
// generate hue/sat events even though bridge will prioritize XY when setting color
|
||||||
|
if (huesettings.hue != null)
|
||||||
|
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||||
|
if (huesettings.saturation != null)
|
||||||
|
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
|
||||||
|
} else if (huesettings.hex != null) {
|
||||||
|
// For now ignore model to get a consistent color if same color is set across multiple devices
|
||||||
|
// def model = state.bulbs[getId(childDevice)]?.modelid
|
||||||
// value.xy = calculateXY(huesettings.hex, model)
|
// value.xy = calculateXY(huesettings.hex, model)
|
||||||
// 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(huesettings.hex)
|
value.xy = calculateXY(huesettings.hex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both hex and hue/sat are set, send all values to bridge to get hue/sat in response from bridge to
|
|
||||||
// generate hue/sat events even though bridge will prioritize XY when setting color
|
|
||||||
if (huesettings.hue != null)
|
|
||||||
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
|
||||||
else
|
|
||||||
value.hue = Math.min(Math.round(childDevice.device?.currentValue("hue") * 65535 / 100), 65535)
|
|
||||||
|
|
||||||
if (huesettings.saturation != null)
|
|
||||||
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
|
|
||||||
else
|
|
||||||
value.sat = Math.min(Math.round(childDevice.device?.currentValue("saturation") * 254 / 100), 254)
|
|
||||||
|
|
||||||
/* Disabled for now due to bad behavior via Lightning Wizard
|
/* 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
|
||||||
@@ -1175,9 +1193,8 @@ private poll() {
|
|||||||
def host = getBridgeIP()
|
def host = getBridgeIP()
|
||||||
def uri = "/api/${state.username}/lights/"
|
def uri = "/api/${state.username}/lights/"
|
||||||
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\r\n" +
|
||||||
HOST: ${host}
|
"HOST: ${host}\r\n\r\n", physicalgraph.device.Protocol.LAN, selectedHue))
|
||||||
""", physicalgraph.device.Protocol.LAN, selectedHue))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private isOnline(id) {
|
private isOnline(id) {
|
||||||
@@ -1193,13 +1210,11 @@ private put(path, body) {
|
|||||||
log.debug "PUT: $host$uri"
|
log.debug "PUT: $host$uri"
|
||||||
log.debug "BODY: ${bodyJSON}"
|
log.debug "BODY: ${bodyJSON}"
|
||||||
|
|
||||||
sendHubCommand(new physicalgraph.device.HubAction("""PUT $uri HTTP/1.1
|
sendHubCommand(new physicalgraph.device.HubAction("PUT $uri HTTP/1.1\r\n" +
|
||||||
HOST: ${host}
|
"HOST: ${host}\r\n" +
|
||||||
Content-Length: ${length}
|
"Content-Length: ${length}\r\n" +
|
||||||
|
"\r\n" +
|
||||||
${bodyJSON}
|
"${bodyJSON}", physicalgraph.device.Protocol.LAN, "${selectedHue}"))
|
||||||
""", physicalgraph.device.Protocol.LAN, "${selectedHue}"))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1220,7 +1235,7 @@ private getBridgeIP() {
|
|||||||
if (d) {
|
if (d) {
|
||||||
if (d.getDeviceDataByName("networkAddress"))
|
if (d.getDeviceDataByName("networkAddress"))
|
||||||
host = d.getDeviceDataByName("networkAddress")
|
host = d.getDeviceDataByName("networkAddress")
|
||||||
else
|
else
|
||||||
host = d.latestState('networkAddress').stringValue
|
host = d.latestState('networkAddress').stringValue
|
||||||
}
|
}
|
||||||
if (host == null || host == "") {
|
if (host == null || host == "") {
|
||||||
@@ -1657,3 +1672,101 @@ private boolean checkPointInLampsReach(p, colorPoints) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an RGB color in hex to HSV/HSB.
|
||||||
|
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
|
||||||
|
*
|
||||||
|
* @param colorStr color value in hex (#ff03d3)
|
||||||
|
*
|
||||||
|
* @return HSV representation in an array (0-100) [hue, sat, value]
|
||||||
|
*/
|
||||||
|
def hexToHsv(colorStr){
|
||||||
|
def r = Integer.valueOf( colorStr.substring( 1, 3 ), 16 ) / 255
|
||||||
|
def g = Integer.valueOf( colorStr.substring( 3, 5 ), 16 ) / 255
|
||||||
|
def b = Integer.valueOf( colorStr.substring( 5, 7 ), 16 ) / 255
|
||||||
|
|
||||||
|
def max = Math.max(Math.max(r, g), b)
|
||||||
|
def min = Math.min(Math.min(r, g), b)
|
||||||
|
|
||||||
|
def h, s, v = max
|
||||||
|
|
||||||
|
def d = max - min
|
||||||
|
s = max == 0 ? 0 : d / max
|
||||||
|
|
||||||
|
if(max == min){
|
||||||
|
h = 0
|
||||||
|
}else{
|
||||||
|
switch(max){
|
||||||
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break
|
||||||
|
case g: h = (b - r) / d + 2; break
|
||||||
|
case b: h = (r - g) / d + 4; break
|
||||||
|
}
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [Math.round(h * 100), Math.round(s * 100), Math.round(v * 100)]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts HSV/HSB color to RGB in hex.
|
||||||
|
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
|
||||||
|
*
|
||||||
|
* @param hue hue 0-100
|
||||||
|
* @param sat saturation 0-100
|
||||||
|
* @param value value 0-100 (defaults to 100)
|
||||||
|
|
||||||
|
* @return the color in hex (#ff03d3)
|
||||||
|
*/
|
||||||
|
def hsvToHex(hue, sat, value = 100){
|
||||||
|
def r, g, b;
|
||||||
|
def h = hue / 100
|
||||||
|
def s = sat / 100
|
||||||
|
def v = value / 100
|
||||||
|
|
||||||
|
def i = Math.floor(h * 6)
|
||||||
|
def f = h * 6 - i
|
||||||
|
def p = v * (1 - s)
|
||||||
|
def q = v * (1 - f * s)
|
||||||
|
def t = v * (1 - (1 - f) * s)
|
||||||
|
|
||||||
|
switch (i % 6) {
|
||||||
|
case 0:
|
||||||
|
r = v
|
||||||
|
g = t
|
||||||
|
b = p
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
r = q
|
||||||
|
g = v
|
||||||
|
b = p
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
r = p
|
||||||
|
g = v
|
||||||
|
b = t
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
r = p
|
||||||
|
g = q
|
||||||
|
b = v
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
r = t
|
||||||
|
g = p
|
||||||
|
b = v
|
||||||
|
break
|
||||||
|
case 5:
|
||||||
|
r = v
|
||||||
|
g = p
|
||||||
|
b = q
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converting float components to int components.
|
||||||
|
def r1 = String.format("%02X", (int) (r * 255.0f))
|
||||||
|
def g1 = String.format("%02X", (int) (g * 255.0f))
|
||||||
|
def b1 = String.format("%02X", (int) (b * 255.0f))
|
||||||
|
|
||||||
|
return "#$r1$g1$b1"
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,14 +53,14 @@ def accelerationActiveHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent within the last $deltaSeconds seconds"
|
||||||
} else {
|
} else {
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
log.debug "$accelerationSensor has moved, texting contacts: ${recipients?.size()}"
|
log.debug "accelerationSensor has moved, texting contacts: ${recipients?.size()}"
|
||||||
sendNotificationToContacts("${accelerationSensor.label ?: accelerationSensor.name} moved", recipients)
|
sendNotificationToContacts("${accelerationSensor.label ?: accelerationSensor.name} moved", recipients)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "$accelerationSensor has moved, texting $phone1"
|
log.debug "accelerationSensor has moved, sending text message"
|
||||||
sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved")
|
sendSms(phone1, "${accelerationSensor.label ?: accelerationSensor.name} moved")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ def temperatureHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 1
|
def alreadySentSms = recentEvents.count { it.doubleValue >= tooHot } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone1 within the last $deltaMinutes minutes"
|
log.debug "SMS already sent within the last $deltaMinutes minutes"
|
||||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||||
} else {
|
} else {
|
||||||
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
|
log.debug "Temperature rose above $tooHot: sending SMS and activating $mySwitch"
|
||||||
def tempScale = location.temperatureScale ?: "F"
|
def tempScale = location.temperatureScale ?: "F"
|
||||||
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
||||||
switch1?.on()
|
switch1?.on()
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ def authPage() {
|
|||||||
}
|
}
|
||||||
def description = "Tap to enter LIFX credentials"
|
def description = "Tap to enter LIFX credentials"
|
||||||
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
|
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
|
||||||
// def redirectUrl = "${apiServerUrl}"
|
// def redirectUrl = "${apiServerUrl}"
|
||||||
log.debug "app id: ${app.id}"
|
// log.debug "app id: ${app.id}"
|
||||||
log.debug "redirect url: ${redirectUrl}"
|
// log.debug "redirect url: ${redirectUrl}"s
|
||||||
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
|
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
|
||||||
section {
|
section {
|
||||||
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
|
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
|
||||||
@@ -372,7 +372,7 @@ def updateDevices() {
|
|||||||
def childDevice = getChildDevice(device.id)
|
def childDevice = getChildDevice(device.id)
|
||||||
selectors.add("${device.id}")
|
selectors.add("${device.id}")
|
||||||
if (!childDevice) {
|
if (!childDevice) {
|
||||||
log.info("Adding device ${device.id}: ${device.product}")
|
// log.info("Adding device ${device.id}: ${device.product}")
|
||||||
def data = [
|
def data = [
|
||||||
label: device.label,
|
label: device.label,
|
||||||
level: Math.round((device.brightness ?: 1) * 100),
|
level: Math.round((device.brightness ?: 1) * 100),
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ definition(
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferences(oauthPage: "deviceAuthorization") {
|
preferences(oauthPage: "deviceAuthorization") {
|
||||||
page(name: "Credentials", title: "Connect to your Logitech Harmony device", content: "authPage", install: false, nextPage: "deviceAuthorization")
|
page(name: "Credentials", title: "Connect to your Logitech Harmony device", content: "authPage", install: false, nextPage: "deviceAuthorization")
|
||||||
page(name: "deviceAuthorization", title: "Logitech Harmony device authorization", install: true) {
|
page(name: "deviceAuthorization", title: "Logitech Harmony device authorization", install: true) {
|
||||||
section("Allow Logitech Harmony to control these things...") {
|
section("Allow Logitech Harmony to control these things...") {
|
||||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||||
@@ -102,7 +102,8 @@ def authPage() {
|
|||||||
description = "Click to enter Harmony Credentials"
|
description = "Click to enter Harmony Credentials"
|
||||||
def redirectUrl = buildRedirectUrl
|
def redirectUrl = buildRedirectUrl
|
||||||
return dynamicPage(name: "Credentials", title: "Harmony", nextPage: null, uninstall: true, install:false) {
|
return dynamicPage(name: "Credentials", title: "Harmony", nextPage: null, uninstall: true, install:false) {
|
||||||
section { href url:redirectUrl, style:"embedded", required:true, title:"Harmony", description:description }
|
section { paragraph title: "Note:", "This device has not been officially tested and certified to “Work with SmartThings”. You can connect it to your SmartThings home but performance may vary and we will not be able to provide support or assistance." }
|
||||||
|
section { href url:redirectUrl, style:"embedded", required:true, title:"Harmony", description:description }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//device discovery request every 5 //25 seconds
|
//device discovery request every 5 //25 seconds
|
||||||
@@ -314,8 +315,6 @@ def installed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
unsubscribe()
|
|
||||||
unschedule()
|
|
||||||
if (!state.accessToken) {
|
if (!state.accessToken) {
|
||||||
log.debug "About to create access token"
|
log.debug "About to create access token"
|
||||||
createAccessToken()
|
createAccessToken()
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ def updated() {
|
|||||||
|
|
||||||
def presenceHandler(evt) {
|
def presenceHandler(evt) {
|
||||||
if (evt.value == "present") {
|
if (evt.value == "present") {
|
||||||
log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
|
// log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
|
||||||
sendPush("${presence.label ?: presence.name} has arrived at the ${location}")
|
sendPush("${presence.label ?: presence.name} has arrived at the ${location}")
|
||||||
} else if (evt.value == "not present") {
|
} else if (evt.value == "not present") {
|
||||||
log.debug "${presence.label ?: presence.name} has left the ${location}"
|
// log.debug "${presence.label ?: presence.name} has left the ${location}"
|
||||||
sendPush("${presence.label ?: presence.name} has left the ${location}")
|
sendPush("${presence.label ?: presence.name} has left the ${location}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def updated() {
|
|||||||
|
|
||||||
def presenceHandler(evt) {
|
def presenceHandler(evt) {
|
||||||
if (evt.value == "present") {
|
if (evt.value == "present") {
|
||||||
log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
|
// log.debug "${presence.label ?: presence.name} has arrived at the ${location}"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("${presence.label ?: presence.name} has arrived at the ${location}", recipients)
|
sendNotificationToContacts("${presence.label ?: presence.name} has arrived at the ${location}", recipients)
|
||||||
@@ -56,7 +56,7 @@ def presenceHandler(evt) {
|
|||||||
sendSms(phone1, "${presence.label ?: presence.name} has arrived at the ${location}")
|
sendSms(phone1, "${presence.label ?: presence.name} has arrived at the ${location}")
|
||||||
}
|
}
|
||||||
} else if (evt.value == "not present") {
|
} else if (evt.value == "not present") {
|
||||||
log.debug "${presence.label ?: presence.name} has left the ${location}"
|
// log.debug "${presence.label ?: presence.name} has left the ${location}"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients)
|
sendNotificationToContacts("${presence.label ?: presence.name} has left the ${location}", recipients)
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def subscribe() {
|
def subscribe() {
|
||||||
log.debug "present: ${cars.collect{it.displayName + ': ' + it.currentPresence}}"
|
// log.debug "present: ${cars.collect{it.displayName + ': ' + it.currentPresence}}"
|
||||||
subscribe(doorSensor, "contact", garageDoorContact)
|
subscribe(doorSensor, "contact", garageDoorContact)
|
||||||
|
|
||||||
subscribe(cars, "presence", carPresence)
|
subscribe(cars, "presence", carPresence)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def updated() {
|
|||||||
private subscribeToEvents()
|
private subscribeToEvents()
|
||||||
{
|
{
|
||||||
subscribe intrusionMotions, "motion", intruderMotion
|
subscribe intrusionMotions, "motion", intruderMotion
|
||||||
subscribe residentMotions, "motion", residentMotion
|
// subscribe residentMotions, "motion", residentMotion
|
||||||
subscribe intrusionContacts, "contact", contact
|
subscribe intrusionContacts, "contact", contact
|
||||||
subscribe alarms, "alarm", alarm
|
subscribe alarms, "alarm", alarm
|
||||||
subscribe(app, appTouch)
|
subscribe(app, appTouch)
|
||||||
@@ -156,6 +156,7 @@ def residentMotion(evt)
|
|||||||
// startReArmSequence()
|
// startReArmSequence()
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
unsubscribe(residentMotions)
|
||||||
}
|
}
|
||||||
|
|
||||||
def contact(evt)
|
def contact(evt)
|
||||||
@@ -214,7 +215,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, texting $phone1"
|
log.debug "$contact1 was opened, sending text"
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
|
sendNotificationToContacts("Your ${contact1.label ?: contact1.name} was opened", recipients)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 to $phone1 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent within the last $deltaSeconds seconds"
|
||||||
} else {
|
} else {
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
log.debug "$motion1 has moved while you were out, sending notifications to: ${recipients?.size()}"
|
log.debug "$motion1 has moved while you were out, sending notifications to: ${recipients?.size()}"
|
||||||
sendNotificationToContacts("${motion1.label} ${motion1.name} moved while you were out", recipients)
|
sendNotificationToContacts("${motion1.label} ${motion1.name} moved while you were out", recipients)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "$motion1 has moved while you were out, texting $phone1"
|
log.debug "$motion1 has moved while you were out, sending text"
|
||||||
sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out")
|
sendSms(phone1, "${motion1.label} ${motion1.name} moved while you were out")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ def accelerationActiveHandler(evt) {
|
|||||||
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
def alreadySentSms = recentEvents.count { it.value && it.value == "active" } > 1
|
||||||
|
|
||||||
if (alreadySentSms) {
|
if (alreadySentSms) {
|
||||||
log.debug "SMS already sent to $phone1 within the last $deltaSeconds seconds"
|
log.debug "SMS already sent to phone within the last $deltaSeconds seconds"
|
||||||
} else {
|
} else {
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts("Gun case has moved!", recipients)
|
sendNotificationToContacts("Gun case has moved!", recipients)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "$accelerationSensor has moved, texting $phone1"
|
log.debug "$accelerationSensor has moved, texting phone"
|
||||||
sendSms(phone1, "Gun case has moved!")
|
sendSms(phone1, "Gun case has moved!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ def firstPage()
|
|||||||
def lightSwitchesDiscovered = lightSwitchesDiscovered()
|
def lightSwitchesDiscovered = lightSwitchesDiscovered()
|
||||||
|
|
||||||
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
|
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
|
||||||
|
section { paragraph title: "Note:", "This device has not been officially tested and certified to “Work with SmartThings”. You can connect it to your SmartThings home but performance may vary and we will not be able to provide support or assistance." }
|
||||||
section("Select a device...") {
|
section("Select a device...") {
|
||||||
input "selectedSwitches", "enum", required:false, title:"Select Wemo Switches \n(${switchesDiscovered.size() ?: 0} found)", multiple:true, options:switchesDiscovered
|
input "selectedSwitches", "enum", required:false, title:"Select Wemo Switches \n(${switchesDiscovered.size() ?: 0} found)", multiple:true, options:switchesDiscovered
|
||||||
input "selectedMotions", "enum", required:false, title:"Select Wemo Motions \n(${motionsDiscovered.size() ?: 0} found)", multiple:true, options:motionsDiscovered
|
input "selectedMotions", "enum", required:false, title:"Select Wemo Motions \n(${motionsDiscovered.size() ?: 0} found)", multiple:true, options:motionsDiscovered
|
||||||
@@ -681,4 +682,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
|
|||||||
|
|
||||||
private List getRealHubFirmwareVersions() {
|
private List getRealHubFirmwareVersions() {
|
||||||
return location.hubs*.firmwareVersionString.findAll { it }
|
return location.hubs*.firmwareVersionString.findAll { it }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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