Compare commits

..

83 Commits

Author SHA1 Message Date
Vinay Rao
9f5eb7b85a Merge pull request #1316 from SmartThingsCommunity/staging
Rolling up staging to prod for deployment
2016-10-04 14:41:53 -07:00
Lars Finander
64e4ccc517 Merge pull request #1311 from larsfinander/CHF-413_hue_unavailable_16minutes_staging
CHF-413 Philips Hue bulb shows unavailable after 16minutes and
2016-10-04 10:33:00 -06:00
Lars Finander
aa890ae3d5 CHF-413 Philips Hue bulb shows unavailable after 16minutes and
CHF-412 Hue Bridge shows OFFLINE instead of "Unavailable"
2016-10-03 14:36:15 -06:00
Lars Finander
8d701b9fea Merge pull request #1298 from dsainteclaire/DVCSMP-2087-device-temperature-unit-wrong-type
DVCSMP-2087 changed deviceTemperatureUnit attribute to be type string
2016-10-03 09:39:50 -06:00
Vinay Rao
719b24ecd6 Merge pull request #1304 from SmartThingsCommunity/master
Rolling up master to staging
2016-09-27 14:39:24 -07:00
Vinay Rao
9d5ab3bfc8 Merge pull request #1303 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-27 14:38:13 -07:00
Vinay Rao
aae7f23a22 Merge pull request #1302 from SmartThingsCommunity/staging
Rolling up staging for production deployment
2016-09-27 14:14:57 -07:00
Juan Pablo Risso
218cc43520 DVCSMP-2089 - Harmony - Make HTTP Requests Async (#1299) 2016-09-27 15:43:37 -04:00
David Sainte-Claire
5b0ca4b815 changed deviceTemperatureUnit attribute to be type string 2016-09-27 07:48:54 -07:00
Zach Varberg
9ddc020f04 Merge pull request #1248 from varzac/add-send-to-raw-messages
Add explicit send commands for zigbee raw
2016-09-27 09:07:55 -05:00
Vinay Rao
aab3b8d7f8 Merge pull request #1297 from workingmonk/feature/temp_rounding
SSVD-2897 to round celsius and fix rounding on fahrenheit
2016-09-26 14:40:42 -07:00
Vinay Rao
a0ccf35eaa SSVD-2897 to round celsius and fix rounding on fahrenheit 2016-09-26 14:39:07 -07:00
Steve Vlaminck
9fbbaec8f6 Merge pull request #1296 from vlaminck/GWU-completion-percentage-fix
Fix: Cleanup event feed when dimming cycle ends
2016-09-26 14:21:31 -05:00
vlaminck
e4c1824afd Fix: reorder events so the feed makes more sense 2016-09-26 14:18:31 -05:00
vlaminck
797a58cb68 Fix: hide reset events 2016-09-26 14:14:55 -05:00
vlaminck
c428267d63 Fix: stop sending 100 percent from completion 2016-09-26 14:05:03 -05:00
Vinay Rao
02f30cf425 Merge pull request #1295 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-09-26 11:50:24 -07:00
Lars Finander
fea802ffce Merge pull request #1294 from larsfinander/DVCSMP-2070_Philips_Hue_unreachable_devices_staging
DVCSMP-2070 Philips Hue: No commands sent if light is unreachable
2016-09-26 12:06:57 -06:00
Lars Finander
6400d26f4a DVCSMP-2070 Philips Hue: No commands sent if light is unreachable
-PROB-1384
2016-09-26 11:59:48 -06:00
Lars Finander
5e3aaa3270 Merge pull request #1293 from larsfinander/DVCSMP-2081_Philips_Hue_650k_exceptions_staging
DVCSMP-2081 Philips Hue: Bridge is throwing 650k exceptions a day
2016-09-26 11:52:12 -06:00
Lars Finander
f5c3997679 DVCSMP-2081 Philips Hue: Bridge is throwing 650k exceptions a day 2016-09-26 10:21:03 -06:00
Jason Botello
81cf1179ef Merge pull request #985 from SmartThingsCommunity/MSA-1351-2
MSA-1351: Gideon AI implementation
2016-09-23 13:38:52 -07:00
Juan Pablo Risso
79d20b0edb SSVD-2740 - Remove zipcode input (#1267)
Limit to samsungtv channel
2016-09-22 18:59:07 -04:00
twack
b6d862fdd4 Merge pull request #1283 from twack/update_generic_zigbee_for_yale
(PROD-736) Update generic zigbee lock for Yale Key-Free Deadbolt. Requested changes incorporated.
2016-09-22 14:40:30 -07:00
twack
d58084c438 Update zigbee-lock.groovy 2016-09-22 12:06:29 -07:00
Juan Pablo Risso
dbfaef3e69 DVCSMP-2076 - Async Update (#1279) 2016-09-22 14:55:25 -04:00
twack
40ed88e7fd Changed name to be consistent
Changed name to be consistent with "Yale Touch Screen Deadbolt Lock"
2016-09-22 11:34:33 -07:00
twack
1d6e22dc16 removed tabs 2016-09-22 11:23:29 -07:00
Lars Finander
30993aa218 Merge pull request #1284 from larsfinander/SSVD-2798_philips_hue_discovery_bridge_staging
SSVD-2798 Philips Hue: Bridge keeps getting unchecked during discovery
2016-09-22 12:11:15 -06:00
Lars Finander
2f8ed277ff SSVD-2798 Philips Hue: Bridge keeps getting unchecked during discovery 2016-09-22 12:07:09 -06:00
twack
1d180ac487 update generic zigbee lock for Yale Key-Free Deadbolt (PROD-736) 2016-09-22 10:19:23 -07:00
Zach Varberg
230541a145 Add explicit send commands for zigbee raw
Previously a few places depended on the dev-conn behavior of
automatically appending a send command after a raw zigbee command.  We
intend to deprecate that behavior and so we are adding explicit sends
where they were missing.
2016-09-22 11:08:30 -05:00
Lars Finander
8c4f7edc83 Merge pull request #1276 from larsfinander/DVCSMP-2057_Philips_Hue_Correct_incorrect_bridge_mac_production
INC-6888 Philips Hue: Correct incorrect bridge mac
2016-09-21 13:11:12 -06:00
Lars Finander
4f188581df INC-6888 Philips Hue: Correct incorrect bridge mac 2016-09-21 11:14:11 -06:00
Jack Chi
71880e2644 Merge pull request #1246 from skt123/osram
CHF-356 : Modifying Readme for Core Devices
2016-09-20 14:24:27 -07:00
Vinay Rao
0b7bb40474 Merge pull request #1274 from SmartThingsCommunity/master
Rolling up master for next week deploy
2016-09-20 12:05:49 -07:00
Vinay Rao
8d920ea072 Merge pull request #1273 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-20 12:05:10 -07:00
Vinay Rao
e373b6f92e Merge pull request #1272 from SmartThingsCommunity/staging
Rolling up staging to production for deployment
2016-09-20 11:53:36 -07:00
Vinay Rao
43a1ae6371 Merge pull request #1271 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-09-20 11:52:40 -07:00
Vinay Rao
a441b94a33 Merge pull request #1264 from posborne/bugfix/fix-hue-connect-http-headers
PROB-1373: hue: fix HTTP request headers
2016-09-20 08:43:01 -07:00
sushant.k1
ced03d746d Added Readme files for the following devices:
1. OSRAM Lightify LED On/Off/Dim
2. OSRAM Lightify LED RGBW
3. OSRAM Lightify Tunable 60 White

Modified Readme files for the following devices:
1. SmartSense Moisture Sensor
2. SmartSense Temp/Humidity Sensor
3. SmartSense Multi Sensor
4. SmartSense Open/closed Sensor
5. SmartPower Outlet
6. Connected Cree Bulb
7. SmartSense Motion Sensor

Added Readme files for the following devices:
1. OSRAM Lightify LED On/Off/Dim
2. OSRAM Lightify LED RGBW
3. OSRAM Lightify Tunable 60 White

Modified Readme files for the following devices:
1. SmartSense Moisture Sensor
2. SmartSense Temp/Humidity Sensor
3. SmartSense Multi Sensor
4. SmartSense Open/closed Sensor
5. SmartPower Outlet
6. Connected Cree Bulb
7. SmartSense Motion Sensor

Added Readme files for the following devices:
1. OSRAM Lightify LED On/Off/Dim
2. OSRAM Lightify LED RGBW
3. OSRAM Lightify Tunable 60 White

Modified Readme files for the following devices:
1. SmartSense Moisture Sensor
2. SmartSense Temp/Humidity Sensor
3. SmartSense Multi Sensor
4. SmartSense Open/closed Sensor
5. SmartPower Outlet
6. Connected Cree Bulb
7. SmartSense Motion Sensor

Added Readme files for the following devices:
1. OSRAM Lightify LED On/Off/Dim
2. OSRAM Lightify LED RGBW
3. OSRAM Lightify Tunable 60 White

Modified Readme files for the following devices:
1. SmartSense Moisture Sensor
2. SmartSense Temp/Humidity Sensor
3. SmartSense Multi Sensor
4. SmartSense Open/closed Sensor
5. SmartPower Outlet
6. Connected Cree Bulb
7. SmartSense Motion Sensor
2016-09-20 17:20:50 +05:30
Vinay Rao
5341d0d06f Merge pull request #1268 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-19 17:22:19 -07:00
Vinay Rao
2a58d7ff62 Merge pull request #1266 from jimmyjames/revert-async-ecobee
Revert "[DVCSMP-1979] Use async http for polling and refresh tokens."
2016-09-19 15:22:20 -07:00
Juan Pablo Risso
260917d515 MKTP-829 - Moving disclaimer to first page (#1261) 2016-09-19 14:01:33 -04:00
Paul Osborne
c1478d3e96 hue: fix HTTP request headers
Previously, the whitespace characters from the file were used for
newlines in HTTP headers.  In order for the HTTP headers sent to
the hue to be valid, line separators must always be \r\n.  Oddly, the
hue seemed to accept and respond to requests with the invalid header
that was being sent but it would cause increased latency for all
other API clients.

In addition to the missing carriage returns, the GET request was also
missing the required blank line which marks the end of the request
headers.

https://smartthings.atlassian.net/browse/PROB-1366
http://status.smartthings.com/incidents/13j8g8g2w7ly
https://community.smartthings.com/t/new-hue-delay/57569
2016-09-19 11:01:52 -05:00
Jim Anderson
8b9bff15dc Revert "[DVCSMP-1979] Use async http for polling and refresh tokens."
This reverts commit 826993cc45.
2016-09-19 09:38:33 -05:00
Lars Finander
75c1ede16c Merge pull request #1260 from larsfinander/SSVD-2737_philips_hue_color_handling_staging
SSVD-2736 Philips Hue: Color Coordinator does not work
2016-09-16 11:33:14 -06:00
Lars Finander
a7acc384a2 SSVD-2736 Philips Hue: Color Coordinator does not work
-SSVD-2631 Double color events
-SSVD-2601 Color picker control does not show the current color
-Changed color model for Philips Hue to use hue/sat instead of x/y
-Added color events in hex
-Added HSV color conversion algorithms
2016-09-16 11:16:31 -06:00
Vinay Rao
c6998e5f1d Merge pull request #1249 from jackchi/healthcheck-12min-checkin
[CHF-363] Set HealthCheck interval to 12 min
2016-09-14 14:37:02 -07:00
jackchi
f95e906d6e [CHF-363] Set HealthCheck interval to 12 min 2016-09-14 13:46:54 -07:00
Juan Pablo Risso
a6c7ab49b6 SSVD-2737 - Temperature Unit (#1247) 2016-09-14 09:43:26 -04:00
Jack Chi
4891e3b947 Merge pull request #1245 from jackchi/healthcheck-5min-reporting
[CHF-353] Cree Bulb polling fix; reads status every 5 minutes
2016-09-13 17:03:19 -07:00
jackchi
ae91f9bff5 [CHF-353] Cree Bulb polling fix; reads status every 5 minutes 2016-09-13 17:01:19 -07:00
Vinay Rao
bb87ad2cf0 Merge pull request #1196 from juano2310/disclaimer
MKTP-829 - Adding disclaimer
2016-09-13 15:13:43 -07:00
Vinay Rao
5dff03fb69 Merge pull request #1244 from SmartThingsCommunity/master
Rolling up master to staging for next week's deploy
2016-09-13 13:46:05 -07:00
Vinay Rao
629d768575 Merge pull request #1243 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-13 13:44:38 -07:00
Vinay Rao
dd7c6b90d5 Merge pull request #1241 from SmartThingsCommunity/staging
Rolling up staging to prod for deploy
2016-09-13 12:28:13 -07:00
Andrew Bresee
4523498dab Add parens to unsubscribe method (#1231) 2016-09-13 13:23:55 -04:00
Jason Terhune
e89e45e000 Another circle fix: dependencies task needs artifactory creds too. 2016-09-13 08:47:22 -05:00
Jason Terhune
78ec280e83 Fix circle build. 2016-09-13 08:37:20 -05:00
Jason Terhune-Wold
1f144d36e4 Merge pull request #1221 from jterhune/gradle-compile
Configure gradle to compile devicetypes and smartapps.
2016-09-13 08:30:57 -05:00
Vinay Rao
f5bd580c9e Merge pull request #1240 from juano2310/staging
SSVD-2733 -  Typo fix
2016-09-12 18:00:11 -07:00
juano2310
d5329dbde3 SSVD-2733 - Typo fix 2016-09-12 20:58:36 -04:00
Vinay Rao
48818cfb06 Merge pull request #1239 from larsfinander/CHF-348_philips_hue_staging
CHF-348 Philips Hue: Change offline time to 16 min
2016-09-12 16:23:41 -07:00
Lars Finander
079919260b CHF-348 Philips Hue: Change offline time to 16 min 2016-09-12 17:19:14 -06:00
Vinay Rao
570922e2ac Merge pull request #1238 from jackchi/healthcheck-5min-reporting
[CHF-348] Set 10 VD required devices to report in at 5 minutes
2016-09-12 15:52:29 -07:00
Lars Finander
53ed9b4d2b Merge pull request #1237 from larsfinander/add_hue_offline_events_staging
Add offline events to Philips Hue
2016-09-12 16:49:57 -06:00
jackchi
7149a81c85 [CHF-348] Set 10 VD required devices to report in at 5 minutes 2016-09-12 14:51:57 -07:00
Vinay Rao
30274f0cd7 Merge pull request #914 from jazzslider/motion-labels
Newer Fibaro motion sensor handler needs motion state labels
2016-09-12 13:26:28 -07:00
Jack Chi
8869cd3af0 Merge pull request #1232 from jackchi/healthcheck-all
[CHF-201] Removing DTH workaround now that all events go to Kafka
2016-09-12 10:46:12 -07:00
Jack Chi
fb0caa6446 Merge pull request #1233 from jackchi/healthcheck-removehack
[CHF-201] Removing DTH workaround now that all events go to Kafka
2016-09-12 08:24:10 -07:00
Lars Finander
3d05d42cb8 Add offline events to Philips Hue
-Handles case when bridge goes offline
-Handles case when indiviudual lights go offline
2016-09-11 22:56:24 -06:00
Vinay Rao
3184615e87 Merge pull request #1235 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-09 16:50:50 -07:00
Vinay Rao
0f70362e0a Merge pull request #1234 from larsfinander/DVCSMP-2024_OSRAM_Hue_incorrect_staging
DVCSMP-2024 OSRAM: Hue is incorrectly set to be 0-360
2016-09-09 16:49:31 -07:00
Lars Finander
bc817f8530 DVCSMP-2024 OSRAM: Hue is incorrectly set to be 0-360
-Hue changed to handle 0-100 according to SmartThings API
2016-09-09 17:41:46 -06:00
jackchi
01b8399893 [CHF-201] Removing DTH workaround now that all events go to Kafka 2016-09-09 14:58:03 -07:00
jackchi
81318bafac [CHF-201] Removing DTH workaround now that all events go to Kafka 2016-09-09 14:48:19 -07:00
Lars Finander
60c2006bfc Merge pull request #1230 from larsfinander/SSVD-2798_Philips_Hue_discovery_issue
SSVD-2798 Philips Hue: Light discovery UI issue
2016-09-09 11:01:40 -06:00
Lars Finander
1e4f1223e7 SSVD-2798 Philips Hue: Light discovery UI issue
-Keep checked lights in "lights to add list" when page refreshes
2016-09-09 10:50:23 -06:00
Jason Terhune
b78bce55b2 Configure gradle to compile devicetypes and smartapps. 2016-09-09 09:41:11 -05:00
juano2310
fe2fbc3b97 MKTP-829 - Adding disclaimer 2016-09-06 14:01:20 -04:00
Nicola Russo
4ad0a6fd9d MSA-1351: The Gideon AI smart app allows you to connect and control all of your smartThings devices with the Gideon AI app. Gideon AI smart app makes your smartThings device even smarter adding features like energy monitoring, vdeo surveillance history etc.. 2016-06-12 08:35:14 -05:00
Adam Jensen
22185c5440 Added labels to the motion attribute in the main multiattributetile 2016-05-24 08:18:51 -05:00
52 changed files with 1299 additions and 933 deletions

View File

@@ -19,7 +19,7 @@ buildscript {
username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword
}
url "http://artifactory.smartthings.com/libs-release-local"
url "https://artifactory.smartthings.com/libs-release-local"
}
}
}
@@ -27,9 +27,37 @@ buildscript {
repositories {
mavenLocal()
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 {
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 {

View File

@@ -5,7 +5,9 @@ machine:
dependencies:
override:
- echo "Nothing to do."
- ./gradlew dependencies -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
post:
- ./gradlew compileSmartappsGroovy compileDevicetypesGroovy -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
test:
override:

View File

@@ -33,8 +33,8 @@ metadata {
tiles(scale: 2) {
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") {
attributeState("inactive", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
attributeState("active", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
}
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
@@ -278,4 +278,4 @@ private encap(physicalgraph.zwave.Command cmd) {
} else {
crc16(cmd)
}
}
}

View File

@@ -87,16 +87,27 @@ def beep() {
up to this long from the time you send the message to the time you hear a sound.
*/
// Used source endpoint of 0x02 because we are using smartthings manufacturer specific cluster.
[
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}"
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
]
}

View File

@@ -105,11 +105,21 @@ def parseDescriptionAsMap(description) {
// Commands to device
def on() {
'zcl on-off on'
[
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
}
def off() {
'zcl on-off off'
[
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
}
def setLevel(value) {

View File

@@ -23,10 +23,8 @@ Works with:
## Device Health
A Category C6 Connected Cree LED Bulb with maxReportTime of 10 min.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*10 = 20 min
A Category C6 Connected Cree LED Bulb with maxReportTime of 5 mins.
Check-in interval = 12 mins
## Troubleshooting

View File

@@ -19,7 +19,6 @@ metadata {
capability "Actuator"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Switch"
capability "Switch Level"
@@ -67,12 +66,6 @@ def parse(String description) {
def resultMap = zigbee.getEvent(description)
if (resultMap) {
sendEvent(resultMap)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
}
else {
log.debug "DID NOT PARSE MESSAGE for description : $description"
@@ -96,27 +89,24 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.levelRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.levelRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
}
def poll() {
zigbee.onOffRefresh() + zigbee.levelRefresh()
def healthPoll() {
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
}
def configure() {
unschedule()
schedule("0 0/5 * * * ? *", "healthPoll")
log.debug "Configuring Reporting and Bindings."
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

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

View File

@@ -31,13 +31,13 @@ metadata {
command "switchMode"
command "switchFanMode"
attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string"
attribute "thermostatSetpoint", "number"
attribute "thermostatStatus", "string"
attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "number"
attribute "deviceTemperatureUnit", "string"
}
tiles {
@@ -133,7 +133,7 @@ def refresh() {
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
parent.poll()
parent.pollChild()
}
def generateEvent(Map results) {

View File

@@ -57,7 +57,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes

View File

@@ -7,9 +7,11 @@
metadata {
// Automatically generated. Make future change here.
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
capability "Health Check"
attribute "networkAddress", "string"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline"
attribute "status", "string"
// Id is the number on the back of the hub, Hue uses last six digits of Mac address
// This is also used in the Hue application as ID
@@ -42,6 +44,10 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "Parsing '${description}'"
@@ -70,13 +76,8 @@ def parse(description) {
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
if (bulbs.state) {
log.info "Bridge response: $msg.body"
} else {
// Sending Bulbs List to parent"
if (parent.isInBulbDiscovery())
log.info parent.bulbListHandler(device.hub.id, msg.body)
}
}
else if (contentType?.contains("xml")) {
} else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body)
}
@@ -85,3 +86,7 @@ def parse(description) {
}
results
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -66,7 +66,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
@@ -174,7 +174,7 @@ void setColorTemperature(value) {
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
parent?.manualRefresh()
}
def verifyPercent(percent) {

View File

@@ -50,7 +50,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes

View File

@@ -55,7 +55,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes

View File

@@ -91,7 +91,7 @@ def parse(String description) {
if (descMap.cluster == "0300") {
if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false)
}
@@ -203,7 +203,7 @@ def setLevel(value) {
//input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){
if(hueValue>360 || hueValue<0)
if(hueValue>100 || hueValue<0)
return
hueValue = Math.round(hueValue / 100 * 360)

View File

@@ -1,4 +1,4 @@
/*
/*
Osram Flex RGBW Light Strip
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
@@ -8,7 +8,7 @@
metadata {
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
capability "Color Temperature"
capability "Actuator"
capability "Switch"
@@ -18,7 +18,7 @@ metadata {
capability "Refresh"
capability "Sensor"
capability "Color Control"
attribute "colorName", "string"
command "setAdjustedColor"
@@ -49,7 +49,7 @@ metadata {
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
@@ -118,7 +118,7 @@ def parse(String description) {
}
}
else if(descMap.attrId == "0000"){ //Hue Attribute
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
log.debug "Hue value returned is $hueValue"
sendEvent(name: "hue", value: hueValue, displayed:false)
}
@@ -274,7 +274,7 @@ private getGenericName(value){
//input Hue Integer values; returns color name for saturation 100%
private getColorName(hueValue){
if(hueValue>360 || hueValue<0)
if(hueValue>100 || hueValue<0)
return
hueValue = Math.round(hueValue / 100 * 360)
@@ -449,7 +449,7 @@ def setColor(value){
def level = hex(value.level * 255 / 100)
cmd << zigbeeSetLevel(level)
}
if (value.switch == "off") {
cmd << "delay 150"
cmd << off()

View File

@@ -47,9 +47,21 @@ def parse(String description) {
// Commands to device
def on() {
'zcl on-off on'
[
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
}
def off() {
'zcl on-off off'
[
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
}

View File

@@ -23,10 +23,10 @@ Works with:
## Device Health
A Category C1 smart power outlet with maxReportTime of 10 min.
Check-in interval is double the value of maxReportTime for Zigbee device.
A Category C1 smart power outlet with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*10 = 20 min
Check-in interval = 12 mins
## Troubleshooting

View File

@@ -101,12 +101,6 @@ def parse(String description) {
else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
}
}
else {
@@ -126,15 +120,7 @@ def on() {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.onOffRefresh()
}
def refresh() {
@@ -142,8 +128,10 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + powerConfig() + refresh()
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
}
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)

View File

@@ -23,10 +23,10 @@ Works with:
## Device Health
A Category C2 moisture sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
A Category C2 moisture sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
Check-in interval = 12 mins
## Battery Specification

View File

@@ -101,13 +101,6 @@ def parse(String description) {
map = parseIasMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
@@ -187,9 +180,9 @@ private Map parseIasMessage(String description) {
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
return Math.round(celsius)
} else {
return celsiusToFahrenheit(celsius) as Integer
return Math.round(celsiusToFahrenheit(celsius))
}
}
@@ -285,15 +278,7 @@ private Map getMoistureResult(value) {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh() {
@@ -307,23 +292,19 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -22,10 +22,10 @@ Works with:
## Device Health
A Category C2 motion sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
A Category C2 motion sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
Check-in interval = 12 mins
## Battery Specification

View File

@@ -105,13 +105,6 @@ def parse(String description) {
map = parseIasMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
@@ -201,9 +194,9 @@ private Map parseIasMessage(String description) {
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
return Math.round(celsius)
} else {
return celsiusToFahrenheit(celsius) as Integer
return Math.round(celsiusToFahrenheit(celsius))
}
}
@@ -296,15 +289,7 @@ private Map getMotionResult(value) {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh() {
@@ -318,24 +303,19 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -26,10 +26,10 @@ Works with:
## Device Health
A Category C2 multi sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
A Category C2 multi sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
Check-in interval = 12 mins
## Battery Specification

View File

@@ -127,13 +127,6 @@ def parse(String description) {
map = parseIasMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
@@ -268,9 +261,9 @@ def updated() {
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
return Math.round(celsius)
} else {
return celsiusToFahrenheit(celsius) as Integer
return Math.round(celsiusToFahrenheit(celsius))
}
}
@@ -378,15 +371,7 @@ private getAccelerationResult(numValue) {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh() {
@@ -416,13 +401,16 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
log.debug "Configuring Reporting"
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
def configCmds = enrollResponse() +
zigbee.batteryConfig() +
zigbee.temperatureConfig() +
zigbee.temperatureConfig(30, 300) +
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +

View File

@@ -24,10 +24,10 @@ Works with:
## Device Health
A Category C2 open/closed sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
A Category C2 open/closed sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
Check-in interval = 12 mins
## Battery Specification

View File

@@ -92,13 +92,6 @@ def parse(String description) {
map = parseIasMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
@@ -248,15 +241,7 @@ private Map getContactResult(value) {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh() {
@@ -270,23 +255,19 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -24,10 +24,10 @@ Works with:
## Device Health
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
Check-in interval = 12 mins
## Battery Specification

View File

@@ -83,13 +83,6 @@ def parse(String description) {
map = parseCustomMessage(description)
}
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map"
return map ? createEvent(map) : null
}
@@ -253,14 +246,7 @@ private Map getHumidityResult(value) {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def refresh()
@@ -278,23 +264,19 @@ def refresh()
}
def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
log.debug "Configuring Reporting and Bindings."
def configCmds = [
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
def humidityConfigCmds = [
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
}
private hex(value) {

View File

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

View File

@@ -0,0 +1,36 @@
# OSRAM Lightify LED On/Off/Dim
Works with:
* [OSRAM Lightify LED On/Off/Dim](https://shop.smartthings.com/#!/products/osram-led-smart-bulb-on-off-dim)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Refresh** - _refresh()_ command for status updates
* **Switch** - can detect state (possible values: on/off)
* **Switch Level** - represents current light level, usually 0-100 in percent
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C1 Zigbee dimmer with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Other troubleshooting tips are listed as follows:
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)

View File

@@ -13,7 +13,7 @@
*/
metadata {
definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings") {
definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings", category: "C1") {
capability "Actuator"
capability "Configuration"
capability "Refresh"
@@ -54,12 +54,6 @@ def parse(String description) {
def event = zigbee.getEvent(description)
if (event) {
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
sendEvent(event)
@@ -86,15 +80,7 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.onOffRefresh()
}
def refresh() {
@@ -103,7 +89,8 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

@@ -31,7 +31,8 @@
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
}
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
}
tiles(scale: 2) {
multiAttributeTile(name:"toggle", type:"generic", width:6, height:4){

View File

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

View File

@@ -0,0 +1,42 @@
# OSRAM LIGHTIFY LED RGBW Bulb
Works with:
* [OSRAM LIGHTIFY LED RGBW Bulb](https://support.smartthings.com/hc/en-us/articles/207728173-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-RGBW)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Actuator** - It represents that a device has commands.
* **Color Control** - It represents that the color attributes of a device can be controlled (hue, saturation, color value).
* **Color Temperature** - It represents color temperature capability measured in degree Kelvin.
* **Polling** - It represents that a device can be polled.
* **Switch** - can detect state (possible values: on/off)
* **Switch Level** - can detect current light level (0-100 in percent)
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Refresh** - _refresh()_ command for status updates
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C6 OSRAM LIGHTIFY LED RGBW Bulb with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
It may also happen that you need to reset the device.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/207728173-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-RGBW)

View File

@@ -17,7 +17,7 @@
*/
metadata {
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings", category: "C6") {
capability "Actuator"
capability "Color Control"
@@ -85,12 +85,6 @@ def parse(String description) {
def event = zigbee.getEvent(description)
if (event) {
log.debug event
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
if (event.name=="colorTemperature") {
@@ -105,7 +99,7 @@ def parse(String description) {
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
@@ -130,15 +124,7 @@ def off() {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.onOffRefresh()
}
def refresh() {
@@ -147,9 +133,10 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.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)
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// 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)
}
def setColorTemperature(value) {

View File

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

View File

@@ -0,0 +1,37 @@
# OSRAM Lightify Tunable 60 White
Works with:
* [OSRAM Lightify Tunable 60 White](http://www.osram.com/osram_com/tools-and-services/tools/lightify---smart-connected-light/lightify-for-home---what-is-light-to-you/lightify-products/lightify-classic-a60-tunable-white/index.jsp)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Color Temperature** - represents color temperature, measured in degrees Kelvin.
* **Configuration** - _configure()_ command called when device is installed or device preferences updated.
* **Refresh** - _refresh()_ command for status updates
* **Switch** - can detect state (possible values: on/off)
* **Switch Level** - represents current light level, usually 0-100 in percent
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C1 OSRAM Lightify Tunable 60 White with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Other troubleshooting tips are listed as follows:
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb)

View File

@@ -17,7 +17,7 @@
*/
metadata {
definition (name: "ZigBee White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings") {
definition (name: "ZigBee White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings", category: "C1") {
capability "Actuator"
capability "Color Temperature"
@@ -74,12 +74,6 @@ def parse(String description) {
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
if (event.name=="colorTemperature") {
@@ -110,15 +104,7 @@ def setLevel(value) {
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
return zigbee.onOffRefresh()
}
def refresh() {
@@ -127,9 +113,10 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// 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()
}
def setColorTemperature(value) {

View File

@@ -1,7 +1,7 @@
/**
* Smart Windows
* Compares two temperatures indoor vs outdoor, for example then sends an alert if windows are open (or closed!).
*
*
* Copyright 2014 Eric Gideon
*
* Based in part on the "When it's going to rain" SmartApp by the SmartThings team,
@@ -21,13 +21,18 @@ definition(
name: "Smart Windows",
namespace: "egid",
author: "Eric Gideon",
description: "Compares two temperatures indoor vs outdoor, for example then sends an alert if windows are open (or closed!). If you don't use an external temperature device, your zipcode will be used instead.",
description: "Compares two temperatures indoor vs outdoor, for example then sends an alert if windows are open (or closed!). If you don't use an external temperature device, your location will be used instead.",
iconUrl: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn.png",
iconX2Url: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn@2x.png"
)
preferences {
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
}
section( "Set the temperature range for your comfort zone..." ) {
input "minTemp", "number", title: "Minimum temperature"
input "maxTemp", "number", title: "Maximum temperature"
@@ -39,9 +44,11 @@ preferences {
input "inTemp", "capability.temperatureMeasurement", title: "Indoor"
input "outTemp", "capability.temperatureMeasurement", title: "Outdoor (optional)", required: false
}
section( "Set your location" ) {
input "zipCode", "text", title: "Zip code"
}
if (location.channelName != 'samsungtv') {
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
}
section( "Notifications" ) {
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
input "retryPeriod", "number", title: "Minutes between notifications:"
@@ -72,7 +79,7 @@ def temperatureHandler(evt) {
def currentInTemp = evt.doubleValue
def openWindows = sensors.findAll { it?.latestValue("contact") == 'open' }
log.trace "Temp event: $evt"
log.info "In: $currentInTemp; Out: $currentOutTemp"
@@ -98,7 +105,7 @@ def temperatureHandler(evt) {
if ( currentOutTemp < maxTemp && !openWindows ) {
send( "Open some windows to cool down the house! Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
} else if ( currentOutTemp > maxTemp && openWindows ) {
send( "It's gotten warmer outside! You should close these windows: ${openWindows.join(', ')}. Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
send( "It's gotten warmer outside! You should close these windows: ${openWindows.join(', ')}. Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
} else {
log.debug "No notifications sent. Everything is in the right place."
}
@@ -125,7 +132,11 @@ def temperatureHandler(evt) {
}
def weatherCheck() {
def json = getWeatherFeature("conditions", zipCode)
def json
if (location.channelName != 'samsungtv')
json = getWeatherFeature("conditions", zipCode)
else
json = getWeatherFeature("conditions")
def currentTemp = json?.current_observation?.temp_f
if ( currentTemp ) {
@@ -150,4 +161,4 @@ private send(msg) {
}
log.info msg
}
}

View File

@@ -0,0 +1,253 @@
/**
* Gideon
*
* Copyright 2016 Nicola Russo
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Gideon",
namespace: "gideon.api",
author: "Braindrain Solutions",
description: "Gideon AI Smart app allows you to connect and control all of your SmartThings devices through the Gideon AI app, making your SmartThings devices even smarter.",
category: "Family",
iconUrl: "http://s33.postimg.org/t77u7y7v3/logo.png",
iconX2Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
iconX3Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
oauth: [displayName: "Gideon AI API", displayLink: "gideon.ai"])
preferences {
section("Control these switches...") {
input "switches", "capability.switch", multiple:true
}
section("Control these motion sensors...") {
input "motions", "capability.motionSensor", multiple:true
}
section("Control these presence sensors...") {
input "presence_sensors", "capability.presenceSensor", multiple:true
}
section("Control these outlets...") {
input "outlets", "capability.switch", multiple:true
}
section("Control these locks...") {
input "locks", "capability.lock", multiple:true
}
section("Control these locks...") {
input "temperature_sensors", "capability.temperatureMeasurement"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
subscribe(outlet, "energy", outletHandler)
subscribe(outlet, "switch", outletHandler)
}
// TODO: implement event handlers
def outletHandler(evt) {
log.debug "$outlet.currentEnergy"
//TODO call G API
}
private device(it, type) {
it ? [id: it.id, label: it.label, type: type] : null
}
//API Mapping
mappings {
path("/getalldevices") {
action: [
GET: "getAllDevices"
]
}
path("/doorlocks/:id/:command") {
action: [
GET: "updateDoorLock"
]
}
path("/doorlocks/:id") {
action: [
GET: "getDoorLockStatus"
]
}
path("/tempsensors/:id") {
action: [
GET: "getTempSensorsStatus"
]
}
path("/presences/:id") {
action: [
GET: "getPresenceStatus"
]
}
path("/motions/:id") {
action: [
GET: "getMotionStatus"
]
}
path("/outlets/:id") {
action: [
GET: "getOutletStatus"
]
}
path("/outlets/:id/:command") {
action: [
GET: "updateOutlet"
]
}
path("/switches/:command") {
action: [
PUT: "updateSwitch"
]
}
}
//API Methods
def getAllDevices() {
def locks_list = locks.collect{device(it,"Lock")}
def presences_list = presence_sensors.collect{device(it,"Presence")}
def motions_list = motions.collect{device(it,"Motion")}
def outlets_list = outlets.collect{device(it,"Outlet")}
def switches_list = switches.collect{device(it,"Switch")}
def temp_list = temperature_sensors.collect{device(it,"Temperature")}
return [Locks: locks_list, Presences: presences_list, Motions: motions_list, Outlets: outlets_list, Switches: switches_list, Temperatures: temp_list]
}
//LOCKS
def getDoorLockStatus() {
def device = locks.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('lock')]
}
}
def updateDoorLock() {
def command = params.command
def device = locks.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentValue('lock') == "locked")
device.unlock();
else
device.lock();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//PRESENCE
def getPresenceStatus() {
def device = presence_sensors.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('presence')]
}
}
//MOTION
def getMotionStatus() {
def device = motions.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('motion')]
}
}
//OUTLET
def getOutletStatus() {
def device = outlets.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentSwitch, Current_watt: device.currentValue("energy")]
}
}
def updateOutlet() {
def command = params.command
def device = outlets.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentSwitch == "on")
device.off();
else
device.on();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//SWITCH
def updateSwitch() {
def command = params.command
def device = switches.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentSwitch == "on")
device.off();
else
device.on();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//TEMPERATURE
def getTempSensorsStatus() {
def device = temperature_sensors.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('temperature')]
}
}

View File

@@ -18,8 +18,13 @@ definition(
)
preferences {
section("Zip code?") {
input "zipcode", "text", title: "Zipcode?"
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
}
if (location.channelName != 'samsungtv') {
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
}
section("Things to check?") {
@@ -60,7 +65,11 @@ def scheduleCheck(evt) {
// Only need to poll if we haven't checked in a while - and if something is left open.
if((now() - (30 * 60 * 1000) > state.lastCheck["time"]) && open) {
log.info("Something's open - let's check the weather.")
def response = getWeatherFeature("forecast", zipcode)
def response
if (location.channelName != 'samsungtv')
response = getWeatherFeature("forecast", zipCode)
else
response = getWeatherFeature("forecast")
def weather = isStormy(response)
if(weather) {

View File

@@ -26,9 +26,9 @@ preferences {
}
section("Temperature monitor?") {
input "temp", "capability.temperatureMeasurement", title: "Temp Sensor", required: false
input "maxTemp", "number", title: "Max Temp?", required: false
input "minTemp", "number", title: "Min Temp?", required: false
input "temp", "capability.temperatureMeasurement", title: "Temperature Sensor", required: false
input "maxTemp", "number", title: "Max Temperature (°${location.temperatureScale})", required: false
input "minTemp", "number", title: "Min Temperature (°${location.temperatureScale})", required: false
}
section("When which people are away?") {

View File

@@ -4,6 +4,9 @@
* Author: Juan Risso
* Date: 2013-12-19
*/
include 'asynchttp_v1'
definition(
name: "Jawbone UP (Connect)",
namespace: "juano2310",
@@ -28,7 +31,7 @@ mappings {
path("/receivedToken") { action: [ POST: "receivedToken", GET: "receivedToken"] }
path("/receiveToken") { action: [ POST: "receiveToken", GET: "receiveToken"] }
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" ] }
}
@@ -44,7 +47,7 @@ def callback() {
} else {
log.warn "No authQueryString"
}
if (state.JawboneAccessToken) {
log.debug "Access token already exists"
setup()
@@ -73,7 +76,7 @@ def callback() {
def authPage() {
log.debug "authPage"
def description = null
def description = null
if (state.JawboneAccessToken == null) {
if (!state.accessToken) {
log.debug "About to create access token"
@@ -82,12 +85,13 @@ def authPage() {
description = "Click to enter Jawbone Credentials"
def redirectUrl = buildRedirectUrl
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) {
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 }
}
} else {
description = "Jawbone Credentials Already Entered."
description = "Jawbone Credentials Already Entered."
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 }
}
@@ -107,7 +111,7 @@ def receiveToken(redirectUrl = null) {
def params = [
uri: "https://jawbone.com/auth/oauth2/token?${toQueryString(oauthParams)}",
]
httpGet(params) { response ->
httpGet(params) { response ->
log.debug "${response.data}"
log.debug "Setting access token to ${response.data.access_token}, refresh token to ${response.data.refresh_token}"
state.JawboneAccessToken = response.data.access_token
@@ -149,7 +153,7 @@ def connectionStatus(message, redirectUrl = null) {
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
}
def html = """
<!DOCTYPE html>
<html>
@@ -229,12 +233,12 @@ def validateCurrentToken() {
log.debug "validateCurrentToken"
def url = "https://jawbone.com/nudge/api/v.1.1/users/@me/refreshToken"
def requestBody = "secret=${appSettings.clientSecret}"
try {
httpPost(uri: url, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ], body: requestBody) {response ->
if (response.status == 200) {
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
}
}
@@ -258,7 +262,7 @@ def validateCurrentToken() {
state.remove("refreshToken")
}
} 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.refreshToken = data.refresh_token
}
@@ -271,10 +275,10 @@ def validateCurrentToken() {
}
def initialize() {
log.debug "Callback URL - Webhook"
def localServerUrl = getApiServerUrl()
log.debug "Callback URL - Webhook"
def localServerUrl = getApiServerUrl()
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}" ])
}
@@ -284,16 +288,16 @@ def setup() {
if (state.JawboneAccessToken) {
def urlmember = "https://jawbone.com/nudge/api/users/@me/"
def member = null
httpGet(uri: urlmember, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
def member = null
httpGet(uri: urlmember, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
member = response.data.data
}
if (member) {
state.member = member
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}")
// invoke the generatePresenceEvent method on the child device
@@ -302,7 +306,8 @@ def setup() {
def childDevice = addChildDevice('juano2310', "Jawbone User", "${app.id}.${member.xid}",null,[name:"Jawbone UP - " + member.first, completedSetup: true])
if (childDevice) {
log.debug "Child Device Successfully Created"
generateInitialEvent (member, childDevice)
childDevice?.generateSleepingEvent(false)
pollChild(childDevice)
}
}
}
@@ -312,7 +317,7 @@ def setup() {
}
def installed() {
if (!state.accessToken) {
log.debug "About to create access token"
createAccessToken()
@@ -324,7 +329,7 @@ def installed() {
}
def updated() {
if (!state.accessToken) {
log.debug "About to create access token"
createAccessToken()
@@ -348,128 +353,128 @@ def uninstalled() {
}
def pollChild(childDevice) {
def member = state.member
generatePollingEvents (member, childDevice)
def childMap = [ value: "$childDevice.device.deviceNetworkId}"]
def params = [
uri: 'https://jawbone.com',
path: '/nudge/api/users/@me/goals',
headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ],
contentType: 'application/json'
]
asynchttp_v1.get('responseGoals', params, childMap)
def params2 = [
uri: 'https://jawbone.com',
path: '/nudge/api/users/@me/moves',
headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ],
contentType: 'application/json'
]
asynchttp_v1.get('responseMoves', params2, childMap)
}
def generatePollingEvents (member, childDevice) {
// lets figure out if the member is currently "home" (At the place)
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
def goals = null
def moves = null
def sleeps = null
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
goals = response.data.data
}
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
moves = response.data.data.items[0]
}
try { // we are going to just ignore any errors
log.debug "Member = ${member.first}"
log.debug "Moves Goal = ${goals.move_steps} Steps"
log.debug "Moves = ${moves.details.steps} Steps"
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
//setColor(moves.details.steps,goals.move_steps,childDevice)
}
catch (e) {
// eat it
}
def responseGoals(response, dni) {
if (response.hasError()) {
log.error "response has error: $response.errorMessage"
} else {
def goals
try {
// json response already parsed into JSONElement object
goals = response.json.data
} catch (e) {
log.error "error parsing json from response: $e"
}
if (goals) {
def childDevice = getChildDevice(dni.value)
log.debug "Goal = ${goals.move_steps} Steps"
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
} else {
log.debug "did not get json results from response body: $response.data"
}
}
}
def generateInitialEvent (member, childDevice) {
// lets figure out if the member is currently "home" (At the place)
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
def goals = null
def moves = null
def sleeps = null
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
goals = response.data.data
}
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
moves = response.data.data.items[0]
}
try { // we are going to just ignore any errors
log.debug "Member = ${member.first}"
log.debug "Moves Goal = ${goals.move_steps} Steps"
log.debug "Moves = ${moves.details.steps} Steps"
log.debug "Sleeping state = false"
childDevice?.generateSleepingEvent(false)
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
//setColor(moves.details.steps,goals.move_steps,childDevice)
}
catch (e) {
// eat it
}
def responseMoves(response, dni) {
if (response.hasError()) {
log.error "response has error: $response.errorMessage"
} else {
def moves
try {
// json response already parsed into JSONElement object
moves = response.json.data.items[0]
} catch (e) {
log.error "error parsing json from response: $e"
}
if (moves) {
def childDevice = getChildDevice(dni.value)
log.debug "Moves = ${moves.details.steps} Steps"
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
} else {
log.debug "did not get json results from response body: $response.data"
}
}
}
def setColor (steps,goal,childDevice) {
def result = steps * 100 / goal
if (result < 25)
if (result < 25)
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)
else if ((result >= 50) && (result < 75))
else if ((result >= 50) && (result < 75))
childDevice?.sendEvent(name:"steps", value: "steps1", label: steps)
else if (result >= 75)
childDevice?.sendEvent(name:"steps", value: "stepsgoal", label: steps)
else if (result >= 75)
childDevice?.sendEvent(name:"steps", value: "stepsgoal", label: steps)
}
def hookEventHandler() {
// log.debug "In hookEventHandler method."
log.debug "request = ${request}"
def json = request.JSON
def json = request.JSON
// get some stuff we need
def userId = json.events.user_xid[0]
def json_type = json.events.type[0]
def json_action = json.events.action[0]
def json_action = json.events.action[0]
//log.debug json
log.debug "Userid = ${userId}"
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
def externalId = "${app.id}.${userId}"
def childDevice = getChildDevice("${externalId}")
if (childDevice) {
switch (json_action) {
case "enter_sleep_mode":
childDevice?.generateSleepingEvent(true)
break
case "exit_sleep_mode":
childDevice?.generateSleepingEvent(false)
break
case "creation":
switch (json_action) {
case "enter_sleep_mode":
childDevice?.generateSleepingEvent(true)
break
case "exit_sleep_mode":
childDevice?.generateSleepingEvent(false)
break
case "creation":
childDevice?.sendEvent(name:"steps", value: 0)
break
case "updation":
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
def goals = null
def moves = null
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
def moves = null
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
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]
}
}
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:"goal", value: goals.move_steps)
//setColor(moves.details.steps,goals.move_steps,childDevice)
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
//setColor(moves.details.steps,goals.move_steps,childDevice)
break
case "deletion":
app.delete()

View File

@@ -14,7 +14,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Smart Home Ventilation",
namespace: "MichaelStruck",
@@ -164,7 +164,7 @@ def installed() {
def updated() {
unschedule()
turnOffSwitch() //Turn off all switches if the schedules are changed while in mid-schedule
unsubscribe
unsubscribe()
log.debug "Updated with settings: ${settings}"
init()
}
@@ -174,12 +174,12 @@ def init() {
schedule (midnightTime, midNight)
subscribe(location, "mode", locationHandler)
startProcess()
}
}
// Common methods
def startProcess () {
createDayArray()
createDayArray()
state.dayCount=state.data.size()
if (state.dayCount){
state.counter = 0
@@ -190,7 +190,7 @@ def startProcess () {
def startDay() {
def start = convertEpoch(state.data[state.counter].start)
def stop = convertEpoch(state.data[state.counter].stop)
runOnce(start, turnOnSwitch, [overwrite: true])
runOnce(stop, incDay, [overwrite: true])
}
@@ -218,7 +218,7 @@ def locationHandler(evt) {
}
if (!result) {
startProcess()
}
}
}
def midNight(){
@@ -238,7 +238,7 @@ def turnOffSwitch() {
}
log.debug "Home ventilation switches are off."
}
def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
def title = ""
def dayListClean = "On "
@@ -252,7 +252,7 @@ def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
dayListClean = "${dayListClean}, "
}
}
}
}
else {
dayListClean = "Every day"
}
@@ -272,7 +272,7 @@ def schedDesc(on1, off1, on2, off2, on3, off3, on4, off4, modeList, dayList) {
modeListClean = "${modeListClean} ${modePrefix}"
}
}
}
}
else {
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)}"
}
if (on3 && off3) {
title += "\nSchedule 3: ${humanReadableTime(on3)} to ${humanReadableTime(off3)}"
title += "\nSchedule 3: ${humanReadableTime(on3)} to ${humanReadableTime(off3)}"
}
if (on4 && off4) {
title += "\nSchedule 4: ${humanReadableTime(on4)} to ${humanReadableTime(off4)}"
title += "\nSchedule 4: ${humanReadableTime(on4)} to ${humanReadableTime(off4)}"
}
if (on1 || on2 || on3 || on4) {
title += "\n$modeListClean"
title += "\n$dayListClean"
title += "\n$dayListClean"
}
if (!on1 && !on2 && !on3 && !on4) {
title="Click to configure scenario"
}
@@ -374,7 +374,7 @@ def createDayArray() {
timeOk(timeOnD1, timeOffD1)
timeOk(timeOnD2, timeOffD2)
timeOk(timeOnD3, timeOffD3)
timeOk(timeOnD4, timeOffD4)
timeOk(timeOnD4, timeOffD4)
}
}
state.data.sort{it.start}
@@ -384,7 +384,7 @@ def createDayArray() {
private def textAppName() {
def text = "Smart Home Ventilation"
}
}
private def textVersion() {
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 " +
"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."
}
}

View File

@@ -20,8 +20,6 @@
* JLH - 02-15-2014 - Fuller use of ecobee API
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
*/
include 'asynchttp_v1'
definition(
name: "Ecobee (Connect)",
namespace: "smartthings",
@@ -246,7 +244,9 @@ def getEcobeeThermostats() {
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
query: [json: toJson(bodyParams)]
// TODO - the query string below is not consistent with the Ecobee docs:
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: toJson(bodyParams)]
]
def stats = [:]
@@ -265,8 +265,9 @@ def getEcobeeThermostats() {
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: false, nextAction: "getEcobeeThermostats"])
refreshAuthToken()
}
}
atomicState.thermostats = stats
@@ -357,22 +358,16 @@ def initialize() {
atomicState.timeSendPush = null
atomicState.reAttempt = 0
initialPoll() //first time polling data data from thermostat
pollHandler() //first time polling data data from thermostat
//automatically update devices status every 5 mins
runEvery5Minutes("poll")
}
/**
* Polls the child devices (synchronously).
* This is used during app install/update, and is synchronous
* to maintain current behavior that will cause install/update to fail
* if polling fails.
*/
def initialPoll() {
log.debug "initialPoll()"
pollChildrenSync() // Hit the ecobee API for update on all thermostats
def pollHandler() {
log.debug "pollHandler()"
pollChildren(null) // Hit the ecobee API for update on all thermostats
atomicState.thermostats.each {stat ->
def dni = stat.key
@@ -385,101 +380,10 @@ def initialPoll() {
}
}
/**
* Polls Ecobee (asynchronously) for updated device state data.
* Called from within this Connect SmartApp as well as the child
* devices.
*/
def poll() {
log.debug "polling asynchronously"
asynchttp_v1.get('asyncPollResponseHandler', getPollParams())
}
/**
* Makes a (synchronous) request to the Ecobee API to get the data for the thermostats.
* This request is made synchronously here because it is called as part of the
* install/updated lifecycle, and changing it to asynchronous during the install/update
* lifecycle may change the behavior if there is an error in polling.
*
* If further analysis shows that polling can be done asynchronously during
* install/update without any adverse consequences, this should then be made
* asynchronous just as the scheduled polling is.
*/
def pollChildrenSync() {
def pollChildren(child = null) {
def thermostatIdsString = getChildDeviceIdsString()
log.debug "polling children: $thermostatIdsString"
def params = getPollParams()
params.query << ["Content-Type": "application/json"]
def result = false
log.debug "making synchronous poll request"
try{
httpGet(params) { resp ->
if(resp.status == 200) {
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(resp.data.thermostatList)
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: false, nextAction: "pollChildrenSync"])
}
}
return result
}
/**
* Response handler for asynchronous request to get thermostat data.
* Given a successful response, updates the sensor data, stores the thermostat
* data, and generates child device events.
*
* If the access token has expired, will issue a request to refresh the token
* (and pending successful token refresh, the poll request will be made again).
*/
def asyncPollResponseHandler(response, data) {
log.trace "async poll response handler"
if (!response.hasError()) {
if (response.status == 200) {
def json
try {
json = response.getJson()
} catch (e) {
log.error ("error parsing JSON", e)
}
if (json) {
atomicState.remoteSensors = json.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(json.thermostatList)
generateChildThermostatEvent()
}
} else {
log.warn "Response returned non-200 response. Status: ${response.status}, data: ${response.getData()}"
}
} else {
log.trace "Exception polling children: ${response.getErrorMessage()}"
def errorJson
try {
errorJson = response.getErrorJson()
} catch (e) {
log.error("Unable to parse error json response", e)
}
if (errorJson?.status?.code == 14) {
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: true, nextAction: "poll"])
} else {
log.warn "Error polling children that is not due to an expired token. Response: ${response.getErrorData()}"
}
}
}
private getPollParams() {
def thermostatIdsString = getChildDeviceIdsString()
def requestBody = [
selection: [
selectionType: "thermostats",
@@ -490,32 +394,66 @@ private getPollParams() {
includeSensors: true
]
]
return [
def result = false
def pollParams = [
uri: apiEndpoint,
path: "/1/thermostat",
headers: ["Authorization": "Bearer ${atomicState.authToken}"],
query: [json: toJson(requestBody)]
headers: ["Content-Type": "text/json", "Authorization": "Bearer ${atomicState.authToken}"],
// TODO - the query string below is not consistent with the Ecobee docs:
// https://www.ecobee.com/home/developer/api/documentation/v1/operations/get-thermostats.shtml
query: [format: 'json', body: toJson(requestBody)]
]
try{
httpGet(pollParams) { resp ->
if(resp.status == 200) {
log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
updateSensorData()
storeThermostatData(resp.data.thermostatList)
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
}
return result
}
/**
* Calls each child thermostat device to generate an event with the thermostat
* data.
*/
def generateChildThermostatEvent() {
log.trace("generateChildThermostatEvent")
getChildDevices().each { child ->
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
def tData = atomicState.thermostats[child.device.deviceNetworkId]
log.debug "calling child.generateEvent($tData.data)"
child.generateEvent(tData.data) //parse received message from parent
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null
}
}
}
// Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild() {
def devices = getChildDevices()
if (pollChildren()) {
devices.each { child ->
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")) {
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
def tData = atomicState.thermostats[child.device.deviceNetworkId]
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
child.generateEvent(tData.data) //parse received message from parent
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null
}
}
}
} else {
log.info "ERROR: pollChildren()"
return null
}
}
void poll() {
pollChild()
}
def availableModes(child) {
@@ -615,104 +553,47 @@ def toQueryString(Map m) {
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}
/**
* Uses the refresh token to get a new access token, then executes the nextAction.
* @param options - a map of options. valid options are async: true/false, which
* specifies if the refresh token request will be done asynchronously or not (default is false)
* nextAction: "nameOfMethod" specifies what method to execute after
* the token is refreshed (not required).
* (note: using a map as the parameter because we need to call it from a schedueled
* execution and we can only pass a data map to scheduled executions)
*/
private void refreshAuthToken(options) {
if(!atomicState.refreshToken) {
log.warn "Cannot not refresh OAuth token since there is no refreshToken stored"
} else {
def refreshParams = [
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
if (options.async) {
refreshAuthTokenAsync(refreshParams, options.nextAction)
} else {
refreshAuthTokenSync(refreshParams, options.nextAction)
}
}
}
private refreshAuthToken() {
log.debug "refreshing auth token"
private void refreshAuthTokenSync(params, nextAction = null) {
try {
httpPost(refreshParams) { resp ->
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(resp.data, nextAction)
if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else {
def refreshParams = [
method: 'POST',
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost
try {
def jsonMap
httpPost(refreshParams) { resp ->
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(resp.data)
}
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
reauthTokenErrorHandler(e.statusCode)
}
}
private void refreshAuthTokenAsync(refreshParams, nextAction = null) {
log.debug "making asynchronous refresh request"
asynchttp_v1.post('refreshTokenResponseHandler', refreshParams, [nextAction: nextAction])
}
/**
* The response handler for the request to refresh the authorization handler.
* Stores the new authorization token and refresh token, and executes any action
* (method) that failed due to the authorization token expiring.
*/
private void refreshTokenResponseHandler(response, data) {
if (!response.hasError()) {
if (response.status == 200) {
def json
try {
json = response.getJson()
} catch (e) {
log.error "error parsing json from response data: $response.data"
}
if (json) {
log.debug "asnyc refreshTokenHandler: Token refreshed...calling saved RestAction now!"
debugEvent("async Token refreshed ... calling saved RestAction now!")
saveTokenAndResumeAction(json, data.nextAction)
} else {
log.warn "successfully parsed json but result is empty or null"
}
} else {
log.debug "Non 200 response returned. Response code: ${response.code}, data: ${response.getData()}"
}
} else {
log.debug "async refreshTokenHandler: RESPONSE ERROR: ${response.getErrorJson()}"
reauthTokenErrorHandler(response.getErrorJson().code)
}
}
/**
* Retries refreshing the authorization token. Will attempt to get the refresh
* token later, in case there were errors retrieving it.
* Will retry a fixed number of times before sending a push notification to the
* user instructing them to reauthenticate
*/
private void reauthTokenErrorHandler(responseCode) {
def retryInterval = 300 // in seconds
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
// might get non-401 error from exceeding 20 second app limit, connectivity issues, etc.
if (responseCode != 401) {
runIn(retryInterval, "refreshAuthToken", [async: true])
} else if (responseCode == 401) { // unauthorized
atomicState.reAttempt = atomicState.reAttempt + 1
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
if (atomicState.reAttempt <= 3) {
runIn(retryInterval, "refreshAuthToken", [async: true])
} else {
sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
def reAttemptPeriod = 300 // in sec
if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc.
runIn(reAttemptPeriod, "refreshAuthToken")
} else if (e.statusCode == 401) { // unauthorized
atomicState.reAttempt = atomicState.reAttempt + 1
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
if (atomicState.reAttempt <= 3) {
runIn(reAttemptPeriod, "refreshAuthToken")
} else {
sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0
}
}
}
}
}
/**
@@ -722,20 +603,20 @@ private void reauthTokenErrorHandler(responseCode) {
*
* @param json - an object representing the parsed JSON response from Ecobee
*/
private void saveTokenAndResumeAction(json, String nextAction) {
def debugMessage = "token response, scope: ${json?.scope}, expires_in: ${json?.expires_in}, token_type: ${json?.token_type}"
log.debug "debugMessage"
private void saveTokenAndResumeAction(json) {
log.debug "token response json: $json"
if (json) {
debugEvent(debugMessage)
debugEvent("Response = $json")
atomicState.refreshToken = json?.refresh_token
atomicState.authToken = json?.access_token
if (nextAction) {
log.debug "got refresh token, will execute next action (passed in!): $nextAction"
"$nextAction"()
if (atomicState.action) {
log.debug "got refresh token, executing next action: ${atomicState.action}"
"${atomicState.action}"()
}
} else {
log.warn "did not get response body from refresh token response"
}
atomicState.action = ""
}
/**
@@ -875,6 +756,7 @@ private boolean sendCommandToEcobee(Map bodyParams) {
try{
httpPost(cmdParams) { resp ->
if(resp.status == 200) {
log.debug "updated ${resp.data}"
def returnStatus = resp.data.status.code
if (returnStatus == 0) {
log.debug "Successful call to ecobee API."
@@ -889,10 +771,11 @@ private boolean sendCommandToEcobee(Map bodyParams) {
log.trace "Exception Sending Json: " + e.response.data.status
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
if (e.response.data.status.code == 14) {
// TODO - figure out why we're setting the next action to be poll
// TODO - figure out why we're setting the next action to be pollChildren
// after refreshing auth token. Is it to keep UI in sync, or just copy/paste error?
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken([async: true, nextAction: "poll"])
refreshAuthToken()
} else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc."

View File

@@ -64,7 +64,7 @@ def meterHandler(evt) {
def lastValue = atomicState.lastValue as double
atomicState.lastValue = meterValue
def dUnit ? evt.unit : "Watts"
def dUnit = evt.unit ?: "Watts"
def aboveThresholdValue = aboveThreshold as int
if (meterValue > aboveThresholdValue) {

View File

@@ -454,17 +454,23 @@ def sendStopEvent(source) {
eventData.value += "cancelled"
}
// send 100% completion event
sendTimeRemainingEvent(100)
// send a non-displayed 0% completion to reset tiles
sendTimeRemainingEvent(0, false)
// send sessionStatus event last so the event feed is ordered properly
sendControllerEvent(eventData)
sendTimeRemainingEvent(0)
}
def sendTimeRemainingEvent(percentComplete) {
def sendTimeRemainingEvent(percentComplete, displayed = true) {
log.trace "sendTimeRemainingEvent(${percentComplete})"
def percentCompleteEventData = [
name: "percentComplete",
value: percentComplete as int,
displayed: true,
displayed: displayed,
isStateChange: true
]
sendControllerEvent(percentCompleteEventData)
@@ -474,7 +480,7 @@ def sendTimeRemainingEvent(percentComplete) {
def timeRemainingEventData = [
name: "timeRemaining",
value: displayableTime(timeRemaining),
displayed: true,
displayed: displayed,
isStateChange: true
]
sendControllerEvent(timeRemainingEventData)
@@ -608,8 +614,6 @@ private completion() {
handleCompletionMessaging()
handleCompletionModesAndPhrases()
sendTimeRemainingEvent(100)
}
private handleCompletionSwitches() {

View File

@@ -83,7 +83,7 @@ def bridgeDiscovery(params=[:])
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.") {
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
}
}
}
@@ -131,10 +131,7 @@ def bulbDiscovery() {
def refreshInterval = 3
state.inBulbDiscovery = true
def bridge = null
if (selectedHue) {
bridge = getChildDevice(selectedHue)
subscribe(bridge, "bulbList", bulbListData)
}
state.bridgeRefreshCount = 0
def allLightsFound = bulbsDiscovered() ?: [:]
@@ -170,7 +167,7 @@ def bulbDiscovery() {
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, options:newLights
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
}
section {
@@ -259,10 +256,6 @@ Map bulbsDiscovered() {
return bulbmap
}
def bulbListData(evt) {
state.bulbs = evt.jsonData
}
Map getHueBulbs() {
state.bulbs = state.bulbs ?: [:]
}
@@ -316,29 +309,6 @@ def uninstalled(){
state.username = null
}
// Handles events to add new bulbs
def bulbListHandler(hub, data = "") {
def msg = "Bulbs list not processed. Only while in settings menu."
def bulbs = [:]
if (state.inBulbDiscovery) {
def logg = ""
log.trace "Adding bulbs to state..."
state.bridgeProcessedLightList = true
def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v ->
if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, online: v.state?.reachable]
}
}
def bridge = null
if (selectedHue) {
bridge = getChildDevice(selectedHue)
}
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
msg = "${bulbs.size()} bulbs found. ${bulbs}"
return msg
}
private upgradeDeviceType(device, newHueType) {
def deviceType = getDeviceType(newHueType)
@@ -490,24 +460,25 @@ def ssdpBridgeHandler(evt) {
def host = ip + ":" + port
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
def dni = "${parsedEvent.mac}"
def d = getChildDevice(dni)
def dniReceived = "${parsedEvent.mac}"
def currentDni = dstate.mac
def d = getChildDevice(dniReceived)
def networkAddress = null
if (!d) {
childDevices.each {
if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}"
d = it
if (newDNI != it.deviceNetworkId) {
def oldDNI = it.deviceNetworkId
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
it.setDeviceNetworkId("${newDNI}")
if (oldDNI == selectedHue) {
app.updateSetting("selectedHue", newDNI)
}
doDeviceSync()
}
// There might be a mismatch between bridge DNI and the actual bridge mac address, correct that
log.debug "Bridge with $dniReceived not found"
def bridge = childDevices.find { it.deviceNetworkId == currentDni }
if (bridge != null) {
log.warn "Bridge is set to ${bridge.deviceNetworkId}, updating to $dniReceived"
bridge.setDeviceNetworkId("${dniReceived}")
dstate.mac = 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)
}
doDeviceSync()
}
} else {
updateBridgeStatus(d)
@@ -525,6 +496,18 @@ def ssdpBridgeHandler(evt) {
d.sendEvent(name:"networkAddress", value: 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)
}
}
}
}
}
@@ -557,11 +540,8 @@ void lightsHandler(physicalgraph.device.HubResponse hubResponse) {
if (isValidSource(hubResponse.mac)) {
def body = hubResponse.json
if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!"
body.each { k, v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId]
}
updateBulbState(body, hubResponse.hubId)
}
}
}
@@ -677,11 +657,8 @@ def locationHandler(evt) {
} else {
//GET /api/${state.username}/lights response (application/json)
if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!"
body.each { k,v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub]
}
updateBulbState(body, parsedEvent.hub)
}
}
}
@@ -724,13 +701,13 @@ private void updateBridgeStatus(childDevice) {
}
/**
* Check if all Hue bridges have been heard from in the last 16 minutes, if not an Offline event will be sent
* for the bridge. Also, set ID number on bridge if not done previously.
* Check if all Hue bridges have been heard from in the last 11 minutes, if not an Offline event will be sent
* for the bridge and all connected lights. Also, set ID number on bridge if not done previously.
*/
private void checkBridgeStatus() {
def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 30)
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 11)
bridges.each {
def d = getChildDevice(it.value.mac)
if(d) {
@@ -740,16 +717,21 @@ private void checkBridgeStatus() {
d.sendEvent(name: "idNumber", value: it.value.idNumber)
}
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline")
// set all lights to offline since bridge is not reachable
state.bulbs?.each {it.value.online = false}
} else {
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.value.idNumber is Offline"
d.sendEvent(name: "status", value: "Offline")
state.bulbs?.each {
it.value.online = false
}
getChildDevices().each {
it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false)
}
} else {
d.sendEvent(name: "status", value: "Online")//setOnline(false)
}
}
}
}
}
}
}
def isValidSource(macAddress) {
@@ -761,6 +743,31 @@ def isInBulbDiscovery() {
return state.inBulbDiscovery
}
private updateBulbState(messageBody, hub) {
def bulbs = getHueBulbs()
// Copy of bulbs used to locate old lights in state that are no longer on bridge
def toRemove = [:]
toRemove << bulbs
messageBody.each { k,v ->
if (v instanceof Map) {
if (bulbs[k] == null) {
bulbs[k] = [:]
}
bulbs[k] << [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, remove: false]
toRemove.remove(k)
}
}
// Remove bulbs from state that are no longer discovered
toRemove.each { k,v ->
log.warn "${bulbs[k].name} no longer exists on bridge, removing"
bulbs.remove(k)
}
}
/////////////////////////////////////
//CHILD DEVICE METHODS
/////////////////////////////////////
@@ -796,10 +803,12 @@ def parse(childDevice, description) {
}
// 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) {
if (device == null || (xy == null && hue == null && sat == null && ct == null))
return
def events = [:]
// For now, only care about changing color temperature if requested by user
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
@@ -813,13 +822,13 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
if (hue != null) {
// 0-65535
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) {
// 0-254
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
@@ -831,17 +840,28 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
def model = state.bulbs[id]?.modelid
def hex = colorFromXY(xy, model)
// TODO Disabled until a solution for the jumping color picker can be figured out
//device.sendEvent([name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed", displayed: false])
// Create Hue and Saturation events if not previously existing
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) {
// 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)
// device.sendEvent([name: "color", value: hex.toUpperCase(), descriptionText: "Color has changed"])
def hex = hsvToHex(hueValue, satValue)
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) {
@@ -882,8 +902,6 @@ private handleCommandResponse(body) {
def updates = [:]
body.each { payload ->
log.debug $payload
if (payload?.success) {
def childDeviceNetworkId = app.id + "/"
def eventType
@@ -934,6 +952,14 @@ private handleCommandResponse(body) {
* @return empty array
*/
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()
for (bulb in body) {
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
@@ -943,7 +969,10 @@ private handlePoll(body) {
// light just came back online, notify device watch
def lastActivity = now()
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
log.debug "$device is Online"
}
// Mark light as "online"
state.bulbs[bulb.key]?.unreachableSince = null
state.bulbs[bulb.key]?.online = true
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
@@ -953,8 +982,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)
}
} else {
state.bulbs[bulb.key]?.online = false
log.warn "$device is not reachable by Hue bridge"
if (state.bulbs[bulb.key]?.unreachableSince == null) {
// Store the first time where device was reported as "unreachable"
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"
}
}
}
@@ -989,9 +1028,6 @@ def hubVerification(bodytext) {
def on(childDevice) {
log.debug "Executing 'on'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [on: true])
@@ -1001,9 +1037,6 @@ def on(childDevice) {
def off(childDevice) {
log.debug "Executing 'off'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
createSwitchEvent(childDevice, "off")
put("lights/$id/state", [on: false])
@@ -1013,9 +1046,6 @@ def off(childDevice) {
def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 1 - 254
def level
@@ -1040,10 +1070,6 @@ def setLevel(childDevice, percent) {
def setSaturation(childDevice, percent) {
log.debug "Executing 'setSaturation($percent)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 0 - 254
def level = Math.min(Math.round(percent * 254 / 100), 254)
@@ -1056,9 +1082,6 @@ def setSaturation(childDevice, percent) {
def setHue(childDevice, percent) {
log.debug "Executing 'setHue($percent)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
@@ -1071,9 +1094,6 @@ def setHue(childDevice, percent) {
def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
@@ -1085,9 +1105,6 @@ def setColorTemperature(childDevice, huesettings) {
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
def value = [:]
@@ -1095,26 +1112,22 @@ def setColor(childDevice, huesettings) {
def sat = null
def xy = 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
if (huesettings.hex != null) {
// Prefer hue/sat over hex to make sure it works with the majority of the smartapps
if (huesettings.hue != null || huesettings.sat != 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)
// Once groups, or scenes are introduced it might be a good idea to use unique models again
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
if (!value.xy) {
// Below will translate values to hex->XY to take into account the color support of the different hue types
@@ -1149,7 +1162,14 @@ def setColor(childDevice, huesettings) {
}
def ping(childDevice) {
if (isOnline(getId(childDevice))) {
if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) {
if (childDevice.device?.currentValue("status")?.equalsIgnoreCase("Online")) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Bridge is reachable", displayed: false, isStateChange: true)
return "Bridge is Online"
} else {
return "Bridge is Offline"
}
} else if (isOnline(getId(childDevice))) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online"
} else {
@@ -1169,9 +1189,8 @@ private poll() {
def host = getBridgeIP()
def uri = "/api/${state.username}/lights/"
log.debug "GET: $host$uri"
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
HOST: ${host}
""", physicalgraph.device.Protocol.LAN, selectedHue))
sendHubCommand(new physicalgraph.device.HubAction("GET ${uri} HTTP/1.1\r\n" +
"HOST: ${host}\r\n\r\n", physicalgraph.device.Protocol.LAN, selectedHue))
}
private isOnline(id) {
@@ -1187,13 +1206,11 @@ private put(path, body) {
log.debug "PUT: $host$uri"
log.debug "BODY: ${bodyJSON}"
sendHubCommand(new physicalgraph.device.HubAction("""PUT $uri HTTP/1.1
HOST: ${host}
Content-Length: ${length}
${bodyJSON}
""", physicalgraph.device.Protocol.LAN, "${selectedHue}"))
sendHubCommand(new physicalgraph.device.HubAction("PUT $uri HTTP/1.1\r\n" +
"HOST: ${host}\r\n" +
"Content-Length: ${length}\r\n" +
"\r\n" +
"${bodyJSON}", physicalgraph.device.Protocol.LAN, "${selectedHue}"))
}
/*
@@ -1214,7 +1231,7 @@ private getBridgeIP() {
if (d) {
if (d.getDeviceDataByName("networkAddress"))
host = d.getDeviceDataByName("networkAddress")
else
else
host = d.latestState('networkAddress').stringValue
}
if (host == null || host == "") {
@@ -1651,3 +1668,101 @@ private boolean checkPointInLampsReach(p, colorPoints) {
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"
}

View File

@@ -34,6 +34,7 @@
* locks | lock | lock, unlock | locked, unlocked
* ---------------------+-------------------+-----------------------------+------------------------------------
*/
include 'asynchttp_v1'
definition(
name: "Logitech Harmony (Connect)",
@@ -51,7 +52,7 @@ definition(
}
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) {
section("Allow Logitech Harmony to control these things...") {
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
@@ -102,32 +103,35 @@ def authPage() {
description = "Click to enter Harmony Credentials"
def redirectUrl = buildRedirectUrl
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 {
//device discovery request every 5 //25 seconds
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
state.deviceRefreshCount = deviceRefreshCount + 1
def refreshInterval = 3
def refreshInterval = 5
def huboptions = state.HarmonyHubs ?: []
def actoptions = state.HarmonyActivities ?: []
def numFoundHub = huboptions.size() ?: 0
def numFoundAct = actoptions.size() ?: 0
def numFoundAct = actoptions.size() ?: 0
if((deviceRefreshCount % 5) == 0) {
discoverDevices()
}
return dynamicPage(name:"Credentials", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Harmony Hubs and Activities. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, options:huboptions
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, submitOnChange: true, options:huboptions
}
// Virtual activity flag
if (numFoundHub > 0 && numFoundAct > 0 && true)
// Virtual activity flag
if (numFoundHub > 0 && numFoundAct > 0 && true)
section("You can also add activities as virtual switches for other convenient integrations") {
input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, options:actoptions
input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, submitOnChange: true, options:actoptions
}
if (state.resethub)
if (state.resethub)
section("Connection to the hub timed out. Please restart the hub and try again.") {}
}
}
@@ -314,8 +318,6 @@ def installed() {
}
def updated() {
unsubscribe()
unschedule()
if (!state.accessToken) {
log.debug "About to create access token"
createAccessToken()
@@ -381,8 +383,6 @@ def discovery() {
log.debug "valid Token"
state.Harmonydevices = response.data
state.resethub = false
getActivityList()
poll()
} else {
log.debug "Error: $response.status"
}
@@ -431,142 +431,182 @@ def addDevice() {
}
def activity(dni,mode) {
def Params = [auth: state.HarmonyAccessToken]
def msg = "Command failed"
def url = ''
def tokenParam = [auth: state.HarmonyAccessToken]
def url
if (dni == "all") {
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(tokenParam)}"
} else {
def aux = dni.split('-')
def hubId = aux[1]
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(tokenParam)}"
} else {
def activityId = aux[2]
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(Params)}"
def activityId = aux[2]
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(tokenParam)}"
}
}
try {
httpPostJson(uri: url) { response ->
if (response.data.code == 200 || dni == "all") {
msg = "Command sent succesfully"
state.aux = 0
} else {
msg = "Command failed. Error: $response.data.code"
}
}
} catch (groovyx.net.http.HttpResponseException ex) {
log.error ex
if (state.aux == 0) {
state.aux = 1
activity(dni,mode)
} else {
msg = ex
state.aux = 0
}
} catch(Exception ex) {
msg = ex
def params = [
uri: url,
contentType: 'application/json'
]
asynchttp_v1.post('activityResponse', params)
return "Command Sent"
}
def activityResponse(response, data) {
if (response.hasError()) {
log.error "Logitech Harmony - response has error: $response.errorMessage"
if (response.status == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Logitech Harmony - Access token has expired"
}
runIn(10, "poll", [overwrite: true])
return msg
} else {
def ResponseValues
try {
// json response already parsed into JSONElement object
ResponseValues = response.json
} catch (e) {
log.error "Logitech Harmony - error parsing json from response: $e"
}
if (ResponseValues) {
if (ResponseValues.code == 200) {
log.trace "Command sent succesfully"
poll()
} else {
log.trace "Command failed. Error: $response.data.code"
}
} else {
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
}
}
}
def poll() {
// GET THE LIST OF ACTIVITIES
if (state.HarmonyAccessToken) {
getActivityList()
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
def map = [:]
response.data.hubs.each {
if (it.value.message == "OK") {
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
def hub = getChildDevice("harmony-${it.key}")
if (hub) {
if (it.value.response.data.currentAvActivity == "-1") {
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
} else {
def currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
}
}
} else {
log.trace it.value.message
}
}
def activities = getChildDevices()
def activitynotrunning = true
activities.each { activity ->
def act = activity.deviceNetworkId.split('-')
if (act.size() > 2) {
def aux = map.find { it.key == act[1] }
if (aux) {
def aux2 = aux.value.split(',')
def childDevice = getChildDevice(activity.deviceNetworkId)
if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
childDevice?.sendEvent(name: "switch", value: "on")
if (aux2[1] == "1")
runIn(5, "poll", [overwrite: true])
} else {
childDevice?.sendEvent(name: "switch", value: "off")
if (aux2[1] == "3")
runIn(5, "poll", [overwrite: true])
}
}
def tokenParam = [auth: state.HarmonyAccessToken]
def params = [
uri: "https://home.myharmony.com/cloudapi/state?${toQueryString(tokenParam)}",
headers: ["Accept": "application/json"],
contentType: 'application/json'
]
asynchttp_v1.get('pollResponse', params)
} else {
log.warn "Logitech Harmony - Access token has expired"
}
}
def pollResponse(response, data) {
if (response.hasError()) {
log.error "Logitech Harmony - response has error: $response.errorMessage"
if (response.status == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Logitech Harmony - Access token has expired"
}
} else {
def ResponseValues
try {
// json response already parsed into JSONElement object
ResponseValues = response.json
} catch (e) {
log.error "Logitech Harmony - error parsing json from response: $e"
}
if (ResponseValues) {
def map = [:]
ResponseValues.hubs.each {
if (it.value.message == "OK") {
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
def hub = getChildDevice("harmony-${it.key}")
if (hub) {
if (it.value.response.data.currentAvActivity == "-1") {
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
} else {
def currentActivity = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}").device.displayName
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
}
}
} else {
log.trace "Logitech Harmony - error response: $it.value.message"
}
}
def activities = getChildDevices()
def activitynotrunning = true
activities.each { activity ->
def act = activity.deviceNetworkId.split('-')
if (act.size() > 2) {
def aux = map.find { it.key == act[1] }
if (aux) {
def aux2 = aux.value.split(',')
def childDevice = getChildDevice(activity.deviceNetworkId)
if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
childDevice?.sendEvent(name: "switch", value: "on")
if (aux2[1] == "1")
runIn(5, "poll", [overwrite: true])
} else {
childDevice?.sendEvent(name: "switch", value: "off")
if (aux2[1] == "3")
runIn(5, "poll", [overwrite: true])
}
}
return "Poll completed $map - $state.hubs"
}
} catch (groovyx.net.http.HttpResponseException e) {
if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Harmony Access token has expired"
}
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection to the hub timed out. Please restart the hub and try again."
state.resethub = true
} catch (e) {
log.info "Logitech Harmony - Error: $e"
}
} else {
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
}
}
}
def getActivityList() {
if (state.HarmonyAccessToken) {
def tokenParam = [auth: state.HarmonyAccessToken]
def params = [
uri: "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(tokenParam)}",
headers: ["Accept": "application/json"],
contentType: 'application/json'
]
asynchttp_v1.get('activityListResponse', params)
} else {
log.warn "Logitech Harmony - Access token has expired"
}
}
def getActivityList() {
// GET ACTIVITY'S NAME
if (state.HarmonyAccessToken) {
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
response.data.hubs.each {
def hub = getChildDevice("harmony-${it.key}")
if (hub) {
def hubname = getHubName("${it.key}")
def activities = []
def aux = it.value.response.data.activities.size()
if (aux >= 1) {
activities = it.value.response.data.activities.collect {
[id: it.key, name: it.value['name'], type: it.value['type']]
}
activities += [id: "off", name: "Activity OFF", type: "0"]
log.trace activities
}
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
}
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace e
} catch (java.net.SocketTimeoutException e) {
log.trace e
} catch(Exception e) {
log.trace e
}
}
return activity
def activityListResponse(response, data) {
if (response.hasError()) {
log.error "Logitech Harmony - response has error: $response.errorMessage"
if (response.status == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Logitech Harmony - Access token has expired"
}
} else {
def ResponseValues
try {
// json response already parsed into JSONElement object
ResponseValues = response.json
} catch (e) {
log.error "Logitech Harmony - error parsing json from response: $e"
}
if (ResponseValues) {
ResponseValues.hubs.each {
def hub = getChildDevice("harmony-${it.key}")
if (hub) {
def hubname = getHubName("${it.key}")
def activities = []
def aux = it.value.response?.data.activities.size()
if (aux >= 1) {
activities = it.value.response.data.activities.collect {
[id: it.key, name: it.value['name'], type: it.value['type']]
}
activities += [id: "off", name: "Activity OFF", type: "0"]
log.trace activities
}
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
}
}
} else {
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
}
}
}
def getActivityName(activity,hubId) {
@@ -747,7 +787,7 @@ def addSubscription() {
def attribute = data.attributeName
def callbackUrl = data.callbackUrl
log.debug "addSubscription, params: ${params}, request: ${data}"
log.debug "Logitech Harmony - addSubscription, params: ${params}, request: ${data}"
if (!attribute) {
render status: 400, data: '{"msg": "attributeName is required"}'
} else {
@@ -809,6 +849,7 @@ def deviceHandler(evt) {
def deviceInfo = state[evt.deviceId]
if (state.harmonyHubs) {
state.harmonyHubs.each { harmonyHub ->
log.trace "Logitech Harmony - Sending data to $harmonyHub.name"
sendToHarmony(evt, harmonyHub.callbackUrl)
}
} else if (deviceInfo) {

View File

@@ -26,17 +26,22 @@ definition(
)
preferences {
section ("In addition to push notifications, send text alerts to...") {
input("recipients", "contact", title: "Send notifications to") {
input "phone1", "phone", title: "Phone Number 1", required: false
input "phone2", "phone", title: "Phone Number 2", required: false
input "phone3", "phone", title: "Phone Number 3", required: false
}
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
}
section ("Zip code (optional, defaults to location coordinates)...") {
input "zipcode", "text", title: "Zip Code", required: false
}
if (location.channelName != 'samsungtv') {
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
}
section ("In addition to push notifications, send text alerts to...") {
input("recipients", "contact", title: "Send notifications to") {
input "phone1", "phone", title: "Phone Number 1", required: false
input "phone2", "phone", title: "Phone Number 2", required: false
input "phone3", "phone", title: "Phone Number 3", required: false
}
}
}
def installed() {
@@ -61,7 +66,7 @@ def checkForSevereWeather() {
def alerts
if(locationIsDefined()) {
if(zipcodeIsValid()) {
alerts = getWeatherFeature("alerts", zipcode)?.alerts
alerts = getWeatherFeature("alerts", zipCode)?.alerts
} else {
log.warn "Severe Weather Alert: Invalid zipcode entered, defaulting to location's zipcode"
alerts = getWeatherFeature("alerts")?.alerts

View File

@@ -86,6 +86,7 @@ def firstPage()
def lightSwitchesDiscovered = lightSwitchesDiscovered()
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...") {
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
@@ -681,4 +682,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}
}