Compare commits

...

65 Commits

Author SHA1 Message Date
Vinay Rao
c15b1e88e1 Merge pull request #1362 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2016-10-18 14:03:11 -07:00
Vinay Rao
ac422076c8 Merge pull request #1361 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-10-18 14:02:37 -07:00
Lars Finander
c11c146690 Merge pull request #1349 from larsfinander/DVCSMP-2131_Philips_Hue_Handle_invalid_username_staging
DVCSMP-2131 Philips Hue: Handle case if bridge username becomes invalid
2016-10-18 10:39:02 -06:00
Lars Finander
9a5d506668 DVCSMP-2131 Philips Hue: Handle case if bridge username becomes invalid 2016-10-14 16:18:51 -06:00
Vinay Rao
9dac541473 Merge pull request #1338 from jackchi/health-checkInterval-value-fix
[CHF-417] Fix for onOffConfig older periodic values
2016-10-11 16:22:12 -07:00
jackchi
a6cc506803 [CHF-417] Fix for onOffConfig older periodic values 2016-10-11 16:18:06 -07:00
Vinay Rao
aba8a7ad4b Merge pull request #1337 from SmartThingsCommunity/master
Rolling up master to staging
2016-10-11 14:18:00 -07:00
Vinay Rao
b4c912ab80 Merge pull request #1336 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2016-10-11 13:23:51 -07:00
Vinay Rao
f5b7dfd4eb Merge branch 'staging'
# Conflicts:
#	devicetypes/smartthings/cree-bulb.src/cree-bulb.groovy
#	smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy
2016-10-11 12:12:31 -07:00
Jack Chi
3e0306e912 Merge pull request #1330 from jackchi/healthcheck-chf417
[DVCSMP-2117] Fix NPE for unknown descriptions in parse
2016-10-10 10:15:57 -07:00
jackchi
2c2d75ae37 [DVCSMP-2117] Fix NPE for unknown descriptions in parse 2016-10-10 09:02:59 -07:00
Lars Finander
61ef40831c Merge pull request #1327 from larsfinander/DVCSMP-2043_OSRAM_Saturation_fix_staging
DVCSMP-2043 OSRAM: Saturation values are not updated in older firmwares
2016-10-10 09:56:19 -06:00
Lars Finander
19169748df DVCSMP-2043 OSRAM: Saturation values are not updated in older firmwares 2016-10-10 09:55:04 -06:00
Jack Chi
0f5a2c5e21 Merge pull request #1320 from jackchi/healthcheck-chf417
[CHF-417] [CHF-416] Health Check devices enroll with right checkInterval
2016-10-07 15:49:06 -07:00
jackchi
6dbb61536b [CHF-417] [CHF-416] Health Check devices enroll with appropriate checkInterval 2016-10-07 15:18:28 -07:00
dsainteclaire
84323afa04 Merge pull request #1324 from james-smartthings/DVCSMP-2097-add-alterSetpoint-check
alterSetpoint - check for auto or off mode
2016-10-07 10:03:08 -07:00
Juan Pablo Risso
04a7627c21 DVCSMP-2104 - Harmony - Fix Exceptions, improve logging (#1322) 2016-10-07 11:34:44 -04:00
James Chen
12b09acfa8 alterSetpoint - check for auto or off mode
changed debug message
2016-10-06 16:02:09 -07:00
bflorian
9e8ad0dfdf Merge pull request #1323 from bflorian/PROB-1359-fibaro-motion-temperature
PROB-1359 set Fibaro motion ZW5 temperature scale properly
2016-10-06 11:29:48 -07:00
bflorian
80eb1e43b9 PROB-1359 set Fibaro motion ZW5 temperature scale properly 2016-10-06 10:22:51 -07:00
Lars Finander
af383de368 Merge pull request #1315 from larsfinander/DVCSMP-2101_Philips_Hue_Exception_lights_removed__staging
DVCSMP-2101 Philips Hue: Exception if lights removed outside of ST
2016-10-05 13:34:50 -06:00
Juan Pablo Risso
427fa88ed8 Harmony - Fix Exceptions (#1321)
response.status
2016-10-05 14:33:38 -04:00
Jack Chi
57514944d5 Merge pull request #1139 from parijatdas/CHF-156_NYCE_sensor
[CHF-156] Health Check NYCE Door/Window Sensor NCZ-3011
2016-10-05 09:34:01 -07:00
Parijat Das
823efed562 Added health checks for NYCE open/closed sensor
checkInterval value determined and added
Implemented ping functionality
Fixed indentation in the metadata section
2016-10-05 08:24:08 +05:30
Jack Chi
540db429f3 Merge pull request #1270 from jackchi/cree-schedule-fix
[CHF-374] Better healthPoll scheduling for Cree Bulb
2016-10-04 16:13:10 -07:00
Jack Chi
0c3a5de661 Merge pull request #1314 from parijatdas/category_removal
[CHF-402] Removing categorization from DTHs
2016-10-04 15:48:52 -07:00
Vinay Rao
989f08708b Merge pull request #1319 from SmartThingsCommunity/master
Rolling up master to staging for next week deploy
2016-10-04 15:05:01 -07:00
Vinay Rao
60e09c56b7 Merge pull request #1318 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-10-04 15:04:25 -07:00
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
62b37f5c3d DVCSMP-2101 Philips Hue: Exception if lights removed outside of ST 2016-10-04 14:08:57 -06: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
Parijat Das
c17830ab56 Removed categorization in DTHs 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. SmartSense Motion Sensor
7. OSRAM Lightify LED On/Off/Dim
8. OSRAM Lightify LED RGBW
9. OSRAM Lightify Tunable 60 White
10. Tyco Door/Window Sensor
2016-10-04 18:19:41 +05:30
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
Jack Chi
c7f78a69e4 Merge pull request #1307 from pchomal/tycosensor
CHF-273
2016-09-30 14:25:16 -07:00
Jack Chi
80500207a8 Merge pull request #1285 from jackchi/health-hardwareid
[CHF-392] Add Hub Hardware ID to Device-Watch
2016-09-29 14:54:40 -07:00
piyush.c
29db335e1c CHF-273
Updated Tyco Door/Window Sensor
2016-09-29 16:48:46 +05:30
Jack Chi
55b5b7d03d Merge pull request #1269 from pchomal/tyco_new
CHF-273
2016-09-28 16:51:14 -07:00
piyush.c
730ccccd45 CHF-273
Updated Health Check capability implementation for "Tyco-Door-Window-Sensor".
2016-09-28 12:35:02 +05:30
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
jackchi
7113d7470e [CHF-392] Add Hub Hardware ID to Device-Watch 2016-09-22 16:03:25 -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
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
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
e373b6f92e Merge pull request #1272 from SmartThingsCommunity/staging
Rolling up staging to production for deployment
2016-09-20 11:53:36 -07:00
jackchi
60a98e3074 [CHF-374] Better healthPoll scheduling for Cree Bulb 2016-09-20 11:10:31 -07:00
29 changed files with 770 additions and 465 deletions

View File

@@ -127,9 +127,10 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelR
def map = [ displayed: true ]
switch (cmd.sensorType) {
case 1:
map.name = "temperature"
map.unit = cmd.scale == 1 ? "F" : "C"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, map.unit, cmd.precision)
def cmdScale = cmd.scale == 1 ? "F" : "C"
map.name = "temperature"
map.unit = getTemperatureScale()
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
break
case 3:
map.name = "illuminance"

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

@@ -93,20 +93,19 @@ def ping() {
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
zigbee.onOffRefresh() + zigbee.levelRefresh()
}
def healthPoll() {
log.debug "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."
runEvery5Minutes("healthPoll")
// 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()
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
zigbee.onOffRefresh() + zigbee.levelRefresh()
}

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 {
@@ -655,55 +655,60 @@ void lowerSetpoint() {
void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint
def targetCoolingSetpoint
//step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat" || mode == "auxHeatOnly"){
if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = coolingSetpoint
}
} else if (mode == "cool") {
//enforce limits before sending request to cloud
if (temp.value < heatingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = heatingSetpoint
targetCoolingSetpoint = temp.value
}
}
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow alterSetpoint"
} else {
log.error "Error alterSetpoint()"
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint
def targetCoolingSetpoint
//step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat" || mode == "auxHeatOnly"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = coolingSetpoint
}
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
//enforce limits before sending request to cloud
if (temp.value < heatingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = heatingSetpoint
targetCoolingSetpoint = temp.value
}
}
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else {
log.error "Error alterSetpoint()"
if (mode == "heat" || mode == "auxHeatOnly"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
}
}
generateStatusEvent()
}
generateStatusEvent()
}
def generateStatusEvent() {

View File

@@ -57,7 +57,7 @@ metadata {
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], 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 * 12, data: [protocol: "lan"], displayed: false)
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], 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 * 12, data: [protocol: "lan"], displayed: false)
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
}
// parse events into attributes

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
# Nyce Door/Window Sensor (Open/Close Sensor)
Works with:
* [NYCE Door/Window Sensor NCZ-3011](https://support.smartthings.com/hc/en-us/articles/204576764-NYCE-Door-Window-Sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Contact Sensor** - can detect contact (with possible values - open/closed)
* **Battery** - defines device uses a battery
* **Refresh** - _refresh()_ command for status updates
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C2 Nyce Door/Window sensor that has 12min check-in interval
## Battery Specification
One 3V CR2032 battery required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
* [Nyce Door/Window Sensor](https://support.smartthings.com/hc/en-us/articles/204576764-NYCE-Door-Window-Sensor)

View File

@@ -19,25 +19,26 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
capability "Battery"
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
capability "Contact Sensor"
capability "Refresh"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
capability "Health Check"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
}
simulator {
}
tiles {
standardTile("contact", "device.contact", width: 2, height: 2) {
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
@@ -273,23 +274,28 @@ private List parseIasMessage(String description) {
return resultListMap
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
def configCmds = [
//battery reporting and heartbeat
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500",
def enrollCmds = [
// Writes CIE attribute on end device to direct reports to the hub's EUID
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
log.debug "configure: Write IAS CIE"
return configCmds
// battery minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
return enrollCmds + zigbee.batteryConfig(30, 300) + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
@@ -334,7 +340,8 @@ Integer convertHexToInt(hex) {
def refresh() {
log.debug "Refreshing Battery"
[
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
}

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

@@ -16,7 +16,7 @@
metadata {
// Automatically generated. Make future change here.
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings", category: "C1") {
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Power Meter"
@@ -104,8 +104,21 @@ def parse(String description) {
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
def cluster = zigbee.parse(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07){
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
}
}
@@ -128,8 +141,10 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
}

View File

@@ -17,7 +17,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
capability "Configuration"
capability "Battery"
capability "Refresh"
@@ -118,14 +118,28 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
}
}
@@ -135,10 +149,8 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -180,9 +192,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))
}
}
@@ -292,8 +304,9 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -17,7 +17,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor"
capability "Configuration"
capability "Battery"
@@ -122,19 +122,37 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
case 0x0406:
log.debug 'motion'
resultMap.name = 'motion'
// 0x07 - configure reporting
if (cluster.command != 0x07) {
log.debug 'motion'
resultMap.name = 'motion'
}
break
}
}
@@ -144,10 +162,8 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -194,9 +210,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))
}
}
@@ -303,8 +319,9 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -16,7 +16,7 @@
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Three Axis"
capability "Battery"
@@ -147,20 +147,33 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break
case 0xFC02:
log.debug 'ACCELERATION'
log.debug 'ACCELERATION'
break
case 0x0402:
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
if (cluster.command == 0x07) {
if(cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
}
}
@@ -169,10 +182,8 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -261,9 +272,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))
}
}
@@ -401,8 +412,9 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
log.debug "Configuring Reporting"

View File

@@ -16,7 +16,7 @@
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
@@ -109,15 +109,28 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break
case 0x0402:
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
if (cluster.command == 0x07){
if (cluster.data[0] == 0x00) {
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
}
}
@@ -127,10 +140,8 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -255,8 +266,9 @@ def refresh() {
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -14,7 +14,7 @@
*
*/
metadata {
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") {
capability "Configuration"
capability "Battery"
capability "Refresh"
@@ -93,20 +93,37 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
if (cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
case 0xFC45:
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
resultMap = getHumidityResult(display)
// 0x07 - configure reporting
if (cluster.command != 0x07) {
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
resultMap = getHumidityResult(display)
}
break
}
}
@@ -116,10 +133,8 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
@@ -264,8 +279,9 @@ def refresh()
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
log.debug "Configuring Reporting and Bindings."
def humidityConfigCmds = [

View File

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

View File

@@ -0,0 +1,42 @@
# Tyco Door Window Sensor
Works with:
* [Tyco Door Window Sensor](https://support.smartthings.com/hc/en-us/articles/204834100-Tyco-Door-Window-Sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Battery** - defines device uses a battery
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Contact Sensor** - can detect contact (open/close)
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - can measure the device temperature
* **Health Check** - indicates ability to get device health notifications
## Device Health
Contact sensor with maxReportTime of 5 mins.
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 = 12 min
## Battery Specification
3V CR2032 battery is required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that either the sensor needs to be reseted or the sensor is out of range.
Reset needs to be done by inserting the battery in the sensor and then quickly pressing the adjacent black button 10 times. Pairing should be tried again now.
It may happen that sensor is out of range, then pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
for the different models:
* [Tyco Door Window Sensor (MCT-340)](https://support.smartthings.com/hc/en-us/articles/204834100-Tyco-Door-Window-Sensor)

View File

@@ -22,6 +22,7 @@ metadata {
capability "Contact Sensor"
capability "Refresh"
capability "Temperature Measurement"
capability "Health Check"
command "enrollResponse"
@@ -229,44 +230,42 @@ private Map getContactResult(value) {
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x0402, 0x0000) // Read the Temperature Cluster
}
def refresh()
{
log.debug "Refreshing Temperature and Battery"
[
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
]
return refreshCmds + enrollResponse()
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
def enrollCmds = [
"delay 1000",
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
//"raw 0x500 {01 23 00 00 00}", "delay 200",
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}",
"delay 500"
]
return configCmds + enrollResponse() + refresh() // send refresh cmds as part of config
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -13,7 +13,7 @@
*/
metadata {
definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings", category: "C1") {
definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Configuration"
capability "Refresh"
@@ -60,8 +60,21 @@ def parse(String description) {
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
def cluster = zigbee.parse(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
}
}
@@ -84,13 +97,15 @@ def ping() {
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

@@ -17,7 +17,7 @@
*/
metadata {
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings", category: "C6") {
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Control"
@@ -95,7 +95,7 @@ def parse(String description) {
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
log.trace "zigbeeMap : $zigbeeMap"
def cluster = zigbee.parse(description)
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
@@ -107,8 +107,18 @@ def parse(String description) {
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
}
}
else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.info "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbeeMap
}
}
}
@@ -128,13 +138,15 @@ def ping() {
}
def refresh() {
zigbee.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + 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.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// 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)
}
@@ -177,5 +189,5 @@ def setHue(value) {
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}

View File

@@ -17,7 +17,7 @@
*/
metadata {
definition (name: "ZigBee White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings", category: "C1") {
definition (name: "ZigBee White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Temperature"
@@ -83,8 +83,21 @@ def parse(String description) {
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
def cluster = zigbee.parse(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
}
}
@@ -108,13 +121,15 @@ def ping() {
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// 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()
}

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

@@ -37,7 +37,10 @@ preferences {
def mainPage() {
def bridges = bridgesDiscovered()
if (state.username && bridges) {
if (state.refreshUsernameNeeded) {
return bridgeLinking()
} else if (state.username && bridges) {
return bulbDiscovery()
} else {
return bridgeDiscovery()
@@ -83,7 +86,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
}
}
}
@@ -102,13 +105,22 @@ def bridgeLinking() {
def nextPage = ""
def title = "Linking with your Hue"
def paragraphText
def paragraphText
if (selectedHue) {
if (state.refreshUsernameNeeded) {
paragraphText = "The current Hue username is invalid.\n\nPlease press the button on your Hue Bridge to re-link. "
} else {
paragraphText = "Press the button on your Hue Bridge to setup a link. "
} else {
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
}
}
} else {
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
}
if (state.username) { //if discovery worked
if (state.refreshUsernameNeeded) {
state.refreshUsernameNeeded = false
// Issue one poll with new username to cancel local polling with old username
poll()
}
nextPage = "bulbDiscovery"
title = "Success!"
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
@@ -131,10 +143,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() ?: [:]
@@ -259,10 +268,6 @@ Map bulbsDiscovered() {
return bulbmap
}
def bulbListData(evt) {
state.bulbs = evt.jsonData
}
Map getHueBulbs() {
state.bulbs = state.bulbs ?: [:]
}
@@ -316,29 +321,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 +472,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 +508,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 +552,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 +669,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)
}
}
}
@@ -741,7 +730,7 @@ private void checkBridgeStatus() {
}
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.key is Offline"
log.warn "Bridge $it.value.idNumber is Offline"
d.sendEvent(name: "status", value: "Offline")
state.bulbs?.each {
@@ -766,6 +755,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
/////////////////////////////////////
@@ -913,8 +927,15 @@ private handleCommandResponse(body) {
updates[childDeviceNetworkId]."$eventType" = v
}
}
} else if (payload.error) {
log.warn "Error returned from Hue bridge error = ${body?.error}"
} else if (payload?.error) {
log.warn "Error returned from Hue bridge, error = ${payload?.error}"
// Check for unauthorized user
if (payload?.error?.type?.value == 1) {
log.error "Hue username is not valid"
state.refreshUsernameNeeded = true
state.username = null
}
return []
}
}
@@ -950,6 +971,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}"}
@@ -959,7 +988,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
@@ -969,9 +1001,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"
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
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"
}
}
}
@@ -1006,9 +1047,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])
@@ -1018,9 +1056,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])
@@ -1030,9 +1065,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
@@ -1057,10 +1089,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)
@@ -1073,9 +1101,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)
@@ -1088,9 +1113,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)
@@ -1102,9 +1124,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 = [:]
@@ -1120,7 +1139,7 @@ def setColor(childDevice, huesettings) {
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 && false) {
} 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)
@@ -1162,7 +1181,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 {
@@ -1187,7 +1213,7 @@ private poll() {
}
private isOnline(id) {
return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null
return (state.bulbs[id]?.online != null && state.bulbs[id]?.online) || state.bulbs[id]?.online == null
}
private put(path, body) {
@@ -1224,7 +1250,7 @@ private getBridgeIP() {
if (d) {
if (d.getDeviceDataByName("networkAddress"))
host = d.getDeviceDataByName("networkAddress")
else
else
host = d.latestState('networkAddress').stringValue
}
if (host == null || host == "") {
@@ -1254,7 +1280,7 @@ def convertBulbListToMap() {
try {
if (state.bulbs instanceof java.util.List) {
def map = [:]
state.bulbs.unique {it.id}.each { bulb ->
state.bulbs?.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]]
}
state.bulbs = map
@@ -1663,7 +1689,7 @@ private boolean checkPointInLampsReach(p, colorPoints) {
}
/**
* Converts an RGB color in hex to HSV.
* 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)
@@ -1673,32 +1699,32 @@ private boolean checkPointInLampsReach(p, colorPoints) {
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 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 h, s, v = max
def d = max - min;
s = max == 0 ? 0 : d / max;
def d = max - min
s = max == 0 ? 0 : d / max
if(max == min){
h = 0;
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;
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 [(h * 100).round(), (s * 100).round(), (v * 100).round()];
return [Math.round(h * 100), Math.round(s * 100), Math.round(v * 100)]
}
/**
* Converts HSV color to RGB in hex.
* Converts HSV/HSB color to RGB in hex.
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
*
* @param hue hue 0-100
@@ -1713,11 +1739,11 @@ def hsvToHex(hue, sat, value = 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);
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:
@@ -1753,9 +1779,9 @@ def hsvToHex(hue, sat, value = 100){
}
// 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));
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)",
@@ -96,7 +97,7 @@ def authPage() {
def description = null
if (!state.HarmonyAccessToken) {
if (!state.accessToken) {
log.debug "About to create access token"
log.debug "Harmony - About to create access token"
createAccessToken()
}
description = "Click to enter Harmony Credentials"
@@ -109,26 +110,28 @@ def authPage() {
//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.") {}
}
}
@@ -138,13 +141,13 @@ def callback() {
def redirectUrl = null
if (params.authQueryString) {
redirectUrl = URLDecoder.decode(params.authQueryString.replaceAll(".+&redirect_url=", ""))
log.debug "redirectUrl: ${redirectUrl}"
log.debug "Harmony - redirectUrl: ${redirectUrl}"
} else {
log.warn "No authQueryString"
log.warn "Harmony - No authQueryString"
}
if (state.HarmonyAccessToken) {
log.debug "Access token already exists"
log.debug "Harmony - Access token already exists"
discovery()
success()
} else {
@@ -152,27 +155,27 @@ def callback() {
if (code) {
if (code.size() > 6) {
// Harmony code
log.debug "Exchanging code for access token"
log.debug "Harmony - Exchanging code for access token"
receiveToken(redirectUrl)
} else {
// Initiate the Harmony OAuth flow.
init()
}
} else {
log.debug "This code should be unreachable"
log.debug "Harmony - This code should be unreachable"
success()
}
}
}
def init() {
log.debug "Requesting Code"
log.debug "Harmony - Requesting Code"
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${servercallbackUrl}" ]
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
}
def receiveToken(redirectUrl = null) {
log.debug "receiveToken"
log.debug "Harmony - receiveToken"
def oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code ]
def params = [
uri: "https://home.myharmony.com/oauth2/token?${toQueryString(oauthParams)}",
@@ -183,7 +186,7 @@ def receiveToken(redirectUrl = null) {
}
} catch (java.util.concurrent.TimeoutException e) {
fail(e)
log.warn "Connection timed out, please try again later."
log.warn "Harmony - Connection timed out, please try again later."
}
discovery()
if (state.HarmonyAccessToken) {
@@ -307,7 +310,7 @@ def buildRedirectUrl(page) {
def installed() {
if (!state.accessToken) {
log.debug "About to create access token"
log.debug "Harmony - About to create access token"
createAccessToken()
} else {
initialize()
@@ -316,7 +319,7 @@ def installed() {
def updated() {
if (!state.accessToken) {
log.debug "About to create access token"
log.debug "Harmony - About to create access token"
createAccessToken()
} else {
initialize()
@@ -327,9 +330,9 @@ def uninstalled() {
if (state.HarmonyAccessToken) {
try {
state.HarmonyAccessToken = ""
log.debug "Success disconnecting Harmony from SmartThings"
log.debug "Harmony - Success disconnecting Harmony from SmartThings"
} catch (groovyx.net.http.HttpResponseException e) {
log.error "Error disconnecting Harmony from SmartThings: ${e.statusCode}"
log.error "Harmony - Error disconnecting Harmony from SmartThings: ${e.statusCode}"
}
}
}
@@ -338,7 +341,8 @@ def initialize() {
state.aux = 0
if (selectedhubs || selectedactivities) {
addDevice()
runEvery5Minutes("poll")
runEvery5Minutes("poll")
getActivityList()
}
}
@@ -347,7 +351,7 @@ def getHarmonydevices() {
}
Map discoverDevices() {
log.trace "Discovering devices..."
log.trace "Harmony - Discovering devices..."
discovery()
if (getHarmonydevices() != []) {
def devices = state.Harmonydevices.hubs
@@ -359,7 +363,7 @@ Map discoverDevices() {
def hubname = getHubName(it.key)
def hubvalue = "${hubname}"
hubs["harmony-${hubkey}"] = hubvalue
it.value.response.data.activities.each {
it.value.response.data.activities.each {
def value = "${it.value.name}"
def key = "harmony-${hubkey}-${it.key}"
activities["${key}"] = value
@@ -377,164 +381,177 @@ def discovery() {
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
if (response.status == 200) {
log.debug "valid Token"
log.debug "Harmony - valid Token"
state.Harmonydevices = response.data
state.resethub = false
getActivityList()
poll()
} else {
log.debug "Error: $response.status"
log.debug "Harmony - Error: $response.status"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Harmony Access token has expired"
log.warn "Harmony - 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."
log.warn "Harmony - Connection to the hub timed out. Please restart the hub and try again."
state.resethub = true
} catch (e) {
log.info "Logitech Harmony - Error: $e"
log.info "Harmony - Error: $e"
}
return null
}
def addDevice() {
log.trace "Adding Hubs"
log.trace "Harmony - Adding Hubs"
selectedhubs.each { dni ->
def d = getChildDevice(dni)
if(!d) {
def newAction = state.HarmonyHubs.find { it.key == dni }
d = addChildDevice("smartthings", "Logitech Harmony Hub C2C", dni, null, [label:"${newAction.value}"])
log.trace "created ${d.displayName} with id $dni"
log.trace "Harmony - Created ${d.displayName} with id $dni"
poll()
} else {
log.trace "found ${d.displayName} with id $dni already exists"
log.trace "Harmony - Found ${d.displayName} with id $dni already exists"
}
}
log.trace "Adding Activities"
log.trace "Harmony - Adding Activities"
selectedactivities.each { dni ->
def d = getChildDevice(dni)
if(!d) {
def newAction = state.HarmonyActivities.find { it.key == dni }
if (newAction) {
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
log.trace "created ${d.displayName} with id $dni"
log.trace "Harmony - Created ${d.displayName} with id $dni"
poll()
}
} else {
log.trace "found ${d.displayName} with id $dni already exists"
log.trace "Harmony - Found ${d.displayName} with id $dni already exists"
}
}
}
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 "Harmony - response has error: $response.errorMessage"
if (response.status == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Harmony - Access token has expired"
}
runIn(10, "poll", [overwrite: true])
return msg
} else {
if (response.status == 200) {
log.trace "Harmony - Command sent succesfully"
poll()
} else {
log.trace "Harmony - Command failed. Error: $response.status"
}
}
}
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])
}
}
}
}
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"
}
}
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 "Harmony - Access token has expired"
}
}
def pollResponse(response, data) {
if (response.hasError()) {
log.error "Harmony - response has error: $response.errorMessage"
if (response.status == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Harmony - Access token has expired"
}
} else {
def ResponseValues
try {
// json response already parsed into JSONElement object
ResponseValues = response.json
} catch (e) {
log.error "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
def activityDTH = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}")
if (activityDTH)
currentActivity = activityDTH.device.displayName
else
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 "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])
}
}
}
}
} else {
log.debug "Harmony - did not get json results from response body: $response.data"
}
}
}
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)}"
@@ -551,21 +568,19 @@ def getActivityList() {
[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) {
} catch(Exception e) {
log.trace e
}
}
}
return activity
}
def getActivityName(activity,hubId) {
@@ -628,7 +643,7 @@ def sendNotification(msg) {
def hookEventHandler() {
// log.debug "In hookEventHandler method."
log.debug "request = ${request}"
log.debug "Harmony - request = ${request}"
def json = request.JSON
@@ -637,14 +652,14 @@ def hookEventHandler() {
}
def listDevices() {
log.debug "getDevices, params: ${params}"
log.debug "Harmony - getDevices(), params: ${params}"
allDevices.collect {
deviceItem(it)
}
}
def getDevice() {
log.debug "getDevice, params: ${params}"
log.debug "Harmony - getDevice(), params: ${params}"
def device = allDevices.find { it.id == params.id }
if (!device) {
render status: 404, data: '{"msg": "Device not found"}'
@@ -657,7 +672,7 @@ def updateDevice() {
def data = request.JSON
def command = data.command
def arguments = data.arguments
log.debug "updateDevice, params: ${params}, request: ${data}"
log.debug "Harmony - updateDevice(), params: ${params}, request: ${data}"
if (!command) {
render status: 400, data: '{"msg": "command is required"}'
} else {
@@ -725,7 +740,7 @@ def getDeviceCapabilityCommands(deviceCapabilities) {
}
def listSubscriptions() {
log.debug "listSubscriptions()"
log.debug "Harmony - listSubscriptions()"
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
def deviceInfo = state[it.device.id]
def response = [
@@ -746,17 +761,17 @@ def addSubscription() {
def attribute = data.attributeName
def callbackUrl = data.callbackUrl
log.debug "addSubscription, params: ${params}, request: ${data}"
log.debug "Harmony - addSubscription, params: ${params}, request: ${data}"
if (!attribute) {
render status: 400, data: '{"msg": "attributeName is required"}'
} else {
def device = allDevices.find { it.id == data.deviceId }
if (device) {
if (!state.harmonyHubs) {
log.debug "Adding callbackUrl: $callbackUrl"
log.debug "Harmony - Adding callbackUrl: $callbackUrl"
state[device.id] = [callbackUrl: callbackUrl]
}
log.debug "Adding subscription"
log.debug "Harmony - Adding subscription"
def subscription = subscribe(device, attribute, deviceHandler)
if (!subscription || !subscription.eventSubscription) {
subscription = app.subscriptions?.find { it.device?.device && it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' }
@@ -784,7 +799,7 @@ def removeSubscription() {
log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}"
if (device) {
log.debug "Removing subscription for device: ${device.id}"
log.debug "Harmony - Removing subscription for device: ${device.id}"
state.remove(device.id)
unsubscribe(device)
}
@@ -808,16 +823,17 @@ def deviceHandler(evt) {
def deviceInfo = state[evt.deviceId]
if (state.harmonyHubs) {
state.harmonyHubs.each { harmonyHub ->
log.trace "Harmony - Sending data to $harmonyHub.name"
sendToHarmony(evt, harmonyHub.callbackUrl)
}
} else if (deviceInfo) {
if (deviceInfo.callbackUrl) {
sendToHarmony(evt, deviceInfo.callbackUrl)
} else {
log.warn "No callbackUrl set for device: ${evt.deviceId}"
log.warn "Harmony - No callbackUrl set for device: ${evt.deviceId}"
}
} else {
log.warn "No subscribed device found for device: ${evt.deviceId}"
log.warn "Harmony - No subscribed device found for device: ${evt.deviceId}"
}
}
@@ -841,12 +857,12 @@ def sendToHarmony(evt, String callbackUrl) {
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
]
try {
log.debug "Sending data to Harmony Cloud: $params"
log.debug "Harmony - Sending data to Harmony Cloud: $params"
httpPostJson(params) { resp ->
log.debug "Harmony Cloud - Response: ${resp.status}"
log.debug "Harmony - Cloud Response: ${resp.status}"
}
} catch (e) {
log.error "Harmony Cloud - Something went wrong: $e"
log.error "Harmony - Cloud Something went wrong: $e"
}
}
}
@@ -871,10 +887,10 @@ def activityCallback() {
if (data.errorCode == "200") {
device.setCurrentActivity(data.currentActivityId)
} else {
log.warn "Activity callback error: ${data}"
log.warn "Harmony - Activity callback error: ${data}"
}
} else {
log.warn "Activity callback sent to non-existant dni: ${params.dni}"
log.warn "Harmony - Activity callback sent to non-existant dni: ${params.dni}"
}
render status: 200, data: '{"msg": "Successfully received callbackUrl"}'
}
@@ -908,13 +924,13 @@ def harmony() {
}
def deleteHarmony() {
log.debug "Trying to delete Harmony hub with mac: ${params.mac}"
log.debug "Harmony - Trying to delete Harmony hub with mac: ${params.mac}"
def harmonyHub = state.harmonyHubs?.find { it.mac == params.mac }
if (harmonyHub) {
log.debug "Deleting Harmony hub with mac: ${params.mac}"
log.debug "Harmony - Deleting Harmony hub with mac: ${params.mac}"
state.harmonyHubs.remove(harmonyHub)
} else {
log.debug "Couldn't find Harmony hub with mac: ${params.mac}"
log.debug "Harmony - Couldn't find Harmony hub with mac: ${params.mac}"
}
render status: 204, data: "{}"
}