Compare commits

...

21 Commits

Author SHA1 Message Date
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
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
Juan Pablo Risso
04a7627c21 DVCSMP-2104 - Harmony - Fix Exceptions, improve logging (#1322) 2016-10-07 11:34:44 -04: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
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
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
aae7f23a22 Merge pull request #1302 from SmartThingsCommunity/staging
Rolling up staging for production deployment
2016-09-27 14:14:57 -07:00
David Sainte-Claire
5b0ca4b815 changed deviceTemperatureUnit attribute to be type string 2016-09-27 07:48:54 -07:00
16 changed files with 366 additions and 265 deletions

View File

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

View File

@@ -93,7 +93,7 @@ def ping() {
} }
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh()
} }
def healthPoll() { def healthPoll() {
@@ -103,10 +103,8 @@ def healthPoll() {
def configure() { def configure() {
unschedule() unschedule()
schedule("0 0/5 * * * ? *", "healthPoll") runEvery5Minutes("healthPoll")
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device // Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity zigbee.onOffRefresh() + zigbee.levelRefresh()
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -31,13 +31,13 @@ metadata {
command "switchMode" command "switchMode"
command "switchFanMode" command "switchFanMode"
attribute "thermostatSetpoint","number" attribute "thermostatSetpoint", "number"
attribute "thermostatStatus","string" attribute "thermostatStatus", "string"
attribute "maxHeatingSetpoint", "number" attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number" attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number" attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number" attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "number" attribute "deviceTemperatureUnit", "string"
} }
tiles { tiles {

View File

@@ -7,9 +7,11 @@
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") { definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
capability "Health Check"
attribute "networkAddress", "string" attribute "networkAddress", "string"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network // Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline" // Possible values "Online" or "Offline"
attribute "status", "string" attribute "status", "string"
// Id is the number on the back of the hub, Hue uses last six digits of Mac address // 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 // 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 // parse events into attributes
def parse(description) { def parse(description) {
log.debug "Parsing '${description}'" log.debug "Parsing '${description}'"
@@ -70,13 +76,8 @@ def parse(description) {
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body) def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
if (bulbs.state) { if (bulbs.state) {
log.info "Bridge response: $msg.body" 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" log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body) parent.hubVerification(device.hub.id, msg.body)
} }
@@ -85,3 +86,7 @@ def parse(description) {
} }
results results
} }
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -174,7 +174,7 @@ void setColorTemperature(value) {
void refresh() { void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent?.manualRefresh()
} }
def verifyPercent(percent) { def verifyPercent(percent) {

View File

@@ -16,7 +16,7 @@
metadata { metadata {
// Automatically generated. Make future change here. // 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 "Actuator"
capability "Switch" capability "Switch"
capability "Power Meter" capability "Power Meter"
@@ -104,8 +104,21 @@ def parse(String description) {
} }
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" def cluster = zigbee.parse(description)
log.debug zigbee.parseDescriptionAsMap(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() { def configure() {
// Device-Watch allows 2 check-in misses from device // Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) // 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])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + powerConfig() + refresh() zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@
*/ */
metadata { metadata {
definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings", category: "C1") { definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Refresh"
@@ -60,8 +60,21 @@ def parse(String description) {
} }
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" def cluster = zigbee.parse(description)
log.debug zigbee.parseDescriptionAsMap(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() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device // Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) // 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])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -17,7 +17,7 @@
*/ */
metadata { metadata {
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings", category: "C6") { definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Color Control" capability "Color Control"
@@ -95,7 +95,7 @@ def parse(String description) {
} }
else { else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description) def zigbeeMap = zigbee.parseDescriptionAsMap(description)
log.trace "zigbeeMap : $zigbeeMap" def cluster = zigbee.parse(description)
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) { if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
@@ -107,8 +107,18 @@ def parse(String description) {
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false) 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 { else {
log.info "DID NOT PARSE MESSAGE for description : $description" log.info "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbeeMap
} }
} }
} }
@@ -128,13 +138,15 @@ def ping() {
} }
def refresh() { 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() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device // Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) // 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])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
} }
@@ -177,5 +189,5 @@ def setHue(value) {
def setSaturation(value) { def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2) 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 { 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 "Actuator"
capability "Color Temperature" capability "Color Temperature"
@@ -83,8 +83,21 @@ def parse(String description) {
} }
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" def cluster = zigbee.parse(description)
log.debug zigbee.parseDescriptionAsMap(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() { 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() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device // Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) // 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])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
} }

View File

@@ -131,10 +131,7 @@ def bulbDiscovery() {
def refreshInterval = 3 def refreshInterval = 3
state.inBulbDiscovery = true state.inBulbDiscovery = true
def bridge = null def bridge = null
if (selectedHue) {
bridge = getChildDevice(selectedHue)
subscribe(bridge, "bulbList", bulbListData)
}
state.bridgeRefreshCount = 0 state.bridgeRefreshCount = 0
def allLightsFound = bulbsDiscovered() ?: [:] def allLightsFound = bulbsDiscovered() ?: [:]
@@ -259,10 +256,6 @@ Map bulbsDiscovered() {
return bulbmap return bulbmap
} }
def bulbListData(evt) {
state.bulbs = evt.jsonData
}
Map getHueBulbs() { Map getHueBulbs() {
state.bulbs = state.bulbs ?: [:] state.bulbs = state.bulbs ?: [:]
} }
@@ -316,29 +309,6 @@ def uninstalled(){
state.username = null 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) { private upgradeDeviceType(device, newHueType) {
def deviceType = getDeviceType(newHueType) def deviceType = getDeviceType(newHueType)
@@ -570,11 +540,8 @@ void lightsHandler(physicalgraph.device.HubResponse hubResponse) {
if (isValidSource(hubResponse.mac)) { if (isValidSource(hubResponse.mac)) {
def body = hubResponse.json def body = hubResponse.json
if (!body?.state?.on) { //check if first time poll made it here by mistake if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!" log.debug "Adding bulbs to state!"
body.each { k, v -> updateBulbState(body, hubResponse.hubId)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId]
}
} }
} }
} }
@@ -690,11 +657,8 @@ def locationHandler(evt) {
} else { } else {
//GET /api/${state.username}/lights response (application/json) //GET /api/${state.username}/lights response (application/json)
if (!body?.state?.on) { //check if first time poll made it here by mistake if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!" log.debug "Adding bulbs to state!"
body.each { k,v -> updateBulbState(body, parsedEvent.hub)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub]
}
} }
} }
} }
@@ -754,7 +718,7 @@ private void checkBridgeStatus() {
} }
if (it.value.lastActivity < time) { // it.value.lastActivity != null && if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.key is Offline" log.warn "Bridge $it.value.idNumber is Offline"
d.sendEvent(name: "status", value: "Offline") d.sendEvent(name: "status", value: "Offline")
state.bulbs?.each { state.bulbs?.each {
@@ -779,6 +743,31 @@ def isInBulbDiscovery() {
return state.inBulbDiscovery 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 //CHILD DEVICE METHODS
///////////////////////////////////// /////////////////////////////////////
@@ -1173,7 +1162,14 @@ def setColor(childDevice, huesettings) {
} }
def ping(childDevice) { 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) childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online" return "Device is Online"
} else { } else {
@@ -1198,7 +1194,7 @@ private poll() {
} }
private isOnline(id) { 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) { private put(path, body) {
@@ -1265,7 +1261,7 @@ def convertBulbListToMap() {
try { try {
if (state.bulbs instanceof java.util.List) { if (state.bulbs instanceof java.util.List) {
def map = [:] def map = [:]
state.bulbs.unique {it.id}.each { bulb -> state.bulbs?.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]] map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]]
} }
state.bulbs = map state.bulbs = map

View File

@@ -97,7 +97,7 @@ def authPage() {
def description = null def description = null
if (!state.HarmonyAccessToken) { if (!state.HarmonyAccessToken) {
if (!state.accessToken) { if (!state.accessToken) {
log.debug "About to create access token" log.debug "Harmony - About to create access token"
createAccessToken() createAccessToken()
} }
description = "Click to enter Harmony Credentials" description = "Click to enter Harmony Credentials"
@@ -141,13 +141,13 @@ def callback() {
def redirectUrl = null def redirectUrl = null
if (params.authQueryString) { if (params.authQueryString) {
redirectUrl = URLDecoder.decode(params.authQueryString.replaceAll(".+&redirect_url=", "")) redirectUrl = URLDecoder.decode(params.authQueryString.replaceAll(".+&redirect_url=", ""))
log.debug "redirectUrl: ${redirectUrl}" log.debug "Harmony - redirectUrl: ${redirectUrl}"
} else { } else {
log.warn "No authQueryString" log.warn "Harmony - No authQueryString"
} }
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
log.debug "Access token already exists" log.debug "Harmony - Access token already exists"
discovery() discovery()
success() success()
} else { } else {
@@ -155,27 +155,27 @@ def callback() {
if (code) { if (code) {
if (code.size() > 6) { if (code.size() > 6) {
// Harmony code // Harmony code
log.debug "Exchanging code for access token" log.debug "Harmony - Exchanging code for access token"
receiveToken(redirectUrl) receiveToken(redirectUrl)
} else { } else {
// Initiate the Harmony OAuth flow. // Initiate the Harmony OAuth flow.
init() init()
} }
} else { } else {
log.debug "This code should be unreachable" log.debug "Harmony - This code should be unreachable"
success() success()
} }
} }
} }
def init() { 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}" ] def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${servercallbackUrl}" ]
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}") redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
} }
def receiveToken(redirectUrl = null) { 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 oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code ]
def params = [ def params = [
uri: "https://home.myharmony.com/oauth2/token?${toQueryString(oauthParams)}", uri: "https://home.myharmony.com/oauth2/token?${toQueryString(oauthParams)}",
@@ -186,7 +186,7 @@ def receiveToken(redirectUrl = null) {
} }
} catch (java.util.concurrent.TimeoutException e) { } catch (java.util.concurrent.TimeoutException e) {
fail(e) fail(e)
log.warn "Connection timed out, please try again later." log.warn "Harmony - Connection timed out, please try again later."
} }
discovery() discovery()
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
@@ -310,7 +310,7 @@ def buildRedirectUrl(page) {
def installed() { def installed() {
if (!state.accessToken) { if (!state.accessToken) {
log.debug "About to create access token" log.debug "Harmony - About to create access token"
createAccessToken() createAccessToken()
} else { } else {
initialize() initialize()
@@ -319,7 +319,7 @@ def installed() {
def updated() { def updated() {
if (!state.accessToken) { if (!state.accessToken) {
log.debug "About to create access token" log.debug "Harmony - About to create access token"
createAccessToken() createAccessToken()
} else { } else {
initialize() initialize()
@@ -330,9 +330,9 @@ def uninstalled() {
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
try { try {
state.HarmonyAccessToken = "" state.HarmonyAccessToken = ""
log.debug "Success disconnecting Harmony from SmartThings" log.debug "Harmony - Success disconnecting Harmony from SmartThings"
} catch (groovyx.net.http.HttpResponseException e) { } 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}"
} }
} }
} }
@@ -341,7 +341,8 @@ def initialize() {
state.aux = 0 state.aux = 0
if (selectedhubs || selectedactivities) { if (selectedhubs || selectedactivities) {
addDevice() addDevice()
runEvery5Minutes("poll") runEvery5Minutes("poll")
getActivityList()
} }
} }
@@ -350,7 +351,7 @@ def getHarmonydevices() {
} }
Map discoverDevices() { Map discoverDevices() {
log.trace "Discovering devices..." log.trace "Harmony - Discovering devices..."
discovery() discovery()
if (getHarmonydevices() != []) { if (getHarmonydevices() != []) {
def devices = state.Harmonydevices.hubs def devices = state.Harmonydevices.hubs
@@ -362,7 +363,7 @@ Map discoverDevices() {
def hubname = getHubName(it.key) def hubname = getHubName(it.key)
def hubvalue = "${hubname}" def hubvalue = "${hubname}"
hubs["harmony-${hubkey}"] = hubvalue hubs["harmony-${hubkey}"] = hubvalue
it.value.response.data.activities.each { it.value.response.data.activities.each {
def value = "${it.value.name}" def value = "${it.value.name}"
def key = "harmony-${hubkey}-${it.key}" def key = "harmony-${hubkey}-${it.key}"
activities["${key}"] = value activities["${key}"] = value
@@ -380,52 +381,52 @@ def discovery() {
try { try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response -> httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
if (response.status == 200) { if (response.status == 200) {
log.debug "valid Token" log.debug "Harmony - valid Token"
state.Harmonydevices = response.data state.Harmonydevices = response.data
state.resethub = false state.resethub = false
} else { } else {
log.debug "Error: $response.status" log.debug "Harmony - Error: $response.status"
} }
} }
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
if (e.statusCode == 401) { // token is expired if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken") state.remove("HarmonyAccessToken")
log.warn "Harmony Access token has expired" log.warn "Harmony - Harmony Access token has expired"
} }
} catch (java.net.SocketTimeoutException e) { } 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 state.resethub = true
} catch (e) { } catch (e) {
log.info "Logitech Harmony - Error: $e" log.info "Harmony - Error: $e"
} }
return null return null
} }
def addDevice() { def addDevice() {
log.trace "Adding Hubs" log.trace "Harmony - Adding Hubs"
selectedhubs.each { dni -> selectedhubs.each { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
def newAction = state.HarmonyHubs.find { it.key == dni } def newAction = state.HarmonyHubs.find { it.key == dni }
d = addChildDevice("smartthings", "Logitech Harmony Hub C2C", dni, null, [label:"${newAction.value}"]) 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() poll()
} else { } 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 -> selectedactivities.each { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
def newAction = state.HarmonyActivities.find { it.key == dni } def newAction = state.HarmonyActivities.find { it.key == dni }
if (newAction) { if (newAction) {
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"]) 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() poll()
} }
} else { } else {
log.trace "found ${d.displayName} with id $dni already exists" log.trace "Harmony - Found ${d.displayName} with id $dni already exists"
} }
} }
} }
@@ -455,28 +456,17 @@ def activity(dni,mode) {
def activityResponse(response, data) { def activityResponse(response, data) {
if (response.hasError()) { if (response.hasError()) {
log.error "Logitech Harmony - response has error: $response.errorMessage" log.error "Harmony - response has error: $response.errorMessage"
if (response.status == 401) { // token is expired if (response.status == 401) { // token is expired
state.remove("HarmonyAccessToken") state.remove("HarmonyAccessToken")
log.warn "Logitech Harmony - Access token has expired" log.warn "Harmony - Access token has expired"
} }
} else { } else {
def ResponseValues if (response.status == 200) {
try { log.trace "Harmony - Command sent succesfully"
// json response already parsed into JSONElement object poll()
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 { } else {
log.debug "Logitech Harmony - did not get json results from response body: $response.data" log.trace "Harmony - Command failed. Error: $response.status"
} }
} }
} }
@@ -484,7 +474,6 @@ def activityResponse(response, data) {
def poll() { def poll() {
// GET THE LIST OF ACTIVITIES // GET THE LIST OF ACTIVITIES
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
getActivityList()
def tokenParam = [auth: state.HarmonyAccessToken] def tokenParam = [auth: state.HarmonyAccessToken]
def params = [ def params = [
uri: "https://home.myharmony.com/cloudapi/state?${toQueryString(tokenParam)}", uri: "https://home.myharmony.com/cloudapi/state?${toQueryString(tokenParam)}",
@@ -493,16 +482,16 @@ def poll() {
] ]
asynchttp_v1.get('pollResponse', params) asynchttp_v1.get('pollResponse', params)
} else { } else {
log.warn "Logitech Harmony - Access token has expired" log.warn "Harmony - Access token has expired"
} }
} }
def pollResponse(response, data) { def pollResponse(response, data) {
if (response.hasError()) { if (response.hasError()) {
log.error "Logitech Harmony - response has error: $response.errorMessage" log.error "Harmony - response has error: $response.errorMessage"
if (response.status == 401) { // token is expired if (response.status == 401) { // token is expired
state.remove("HarmonyAccessToken") state.remove("HarmonyAccessToken")
log.warn "Logitech Harmony - Access token has expired" log.warn "Harmony - Access token has expired"
} }
} else { } else {
def ResponseValues def ResponseValues
@@ -510,7 +499,7 @@ def pollResponse(response, data) {
// json response already parsed into JSONElement object // json response already parsed into JSONElement object
ResponseValues = response.json ResponseValues = response.json
} catch (e) { } catch (e) {
log.error "Logitech Harmony - error parsing json from response: $e" log.error "Harmony - error parsing json from response: $e"
} }
if (ResponseValues) { if (ResponseValues) {
def map = [:] def map = [:]
@@ -522,12 +511,17 @@ def pollResponse(response, data) {
if (it.value.response.data.currentAvActivity == "-1") { if (it.value.response.data.currentAvActivity == "-1") {
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false) hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
} else { } else {
def currentActivity = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}").device.displayName 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) hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
} }
} }
} else { } else {
log.trace "Logitech Harmony - error response: $it.value.message" log.trace "Harmony - error response: $it.value.message"
} }
} }
def activities = getChildDevices() def activities = getChildDevices()
@@ -552,63 +546,43 @@ def pollResponse(response, data) {
} }
} }
} else { } else {
log.debug "Logitech Harmony - did not get json results from response body: $response.data" log.debug "Harmony - did not get json results from response body: $response.data"
} }
} }
} }
def getActivityList() { def getActivityList() {
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
def tokenParam = [auth: state.HarmonyAccessToken] def Params = [auth: state.HarmonyAccessToken]
def params = [ def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
uri: "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(tokenParam)}", try {
headers: ["Accept": "application/json"], httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
contentType: 'application/json' response.data.hubs.each {
] def hub = getChildDevice("harmony-${it.key}")
asynchttp_v1.get('activityListResponse', params) if (hub) {
} else { def hubname = getHubName("${it.key}")
log.warn "Logitech Harmony - Access token has expired" 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"]
}
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
}
} }
} }
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) { def getActivityName(activity,hubId) {
// GET ACTIVITY'S NAME // GET ACTIVITY'S NAME
def actname = activity def actname = activity
@@ -669,7 +643,7 @@ def sendNotification(msg) {
def hookEventHandler() { def hookEventHandler() {
// log.debug "In hookEventHandler method." // log.debug "In hookEventHandler method."
log.debug "request = ${request}" log.debug "Harmony - request = ${request}"
def json = request.JSON def json = request.JSON
@@ -678,14 +652,14 @@ def hookEventHandler() {
} }
def listDevices() { def listDevices() {
log.debug "getDevices, params: ${params}" log.debug "Harmony - getDevices(), params: ${params}"
allDevices.collect { allDevices.collect {
deviceItem(it) deviceItem(it)
} }
} }
def getDevice() { def getDevice() {
log.debug "getDevice, params: ${params}" log.debug "Harmony - getDevice(), params: ${params}"
def device = allDevices.find { it.id == params.id } def device = allDevices.find { it.id == params.id }
if (!device) { if (!device) {
render status: 404, data: '{"msg": "Device not found"}' render status: 404, data: '{"msg": "Device not found"}'
@@ -698,7 +672,7 @@ def updateDevice() {
def data = request.JSON def data = request.JSON
def command = data.command def command = data.command
def arguments = data.arguments def arguments = data.arguments
log.debug "updateDevice, params: ${params}, request: ${data}" log.debug "Harmony - updateDevice(), params: ${params}, request: ${data}"
if (!command) { if (!command) {
render status: 400, data: '{"msg": "command is required"}' render status: 400, data: '{"msg": "command is required"}'
} else { } else {
@@ -766,7 +740,7 @@ def getDeviceCapabilityCommands(deviceCapabilities) {
} }
def listSubscriptions() { def listSubscriptions() {
log.debug "listSubscriptions()" log.debug "Harmony - listSubscriptions()"
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect { app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
def deviceInfo = state[it.device.id] def deviceInfo = state[it.device.id]
def response = [ def response = [
@@ -787,17 +761,17 @@ def addSubscription() {
def attribute = data.attributeName def attribute = data.attributeName
def callbackUrl = data.callbackUrl def callbackUrl = data.callbackUrl
log.debug "Logitech Harmony - addSubscription, params: ${params}, request: ${data}" log.debug "Harmony - addSubscription, params: ${params}, request: ${data}"
if (!attribute) { if (!attribute) {
render status: 400, data: '{"msg": "attributeName is required"}' render status: 400, data: '{"msg": "attributeName is required"}'
} else { } else {
def device = allDevices.find { it.id == data.deviceId } def device = allDevices.find { it.id == data.deviceId }
if (device) { if (device) {
if (!state.harmonyHubs) { if (!state.harmonyHubs) {
log.debug "Adding callbackUrl: $callbackUrl" log.debug "Harmony - Adding callbackUrl: $callbackUrl"
state[device.id] = [callbackUrl: callbackUrl] state[device.id] = [callbackUrl: callbackUrl]
} }
log.debug "Adding subscription" log.debug "Harmony - Adding subscription"
def subscription = subscribe(device, attribute, deviceHandler) def subscription = subscribe(device, attribute, deviceHandler)
if (!subscription || !subscription.eventSubscription) { if (!subscription || !subscription.eventSubscription) {
subscription = app.subscriptions?.find { it.device?.device && it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' } subscription = app.subscriptions?.find { it.device?.device && it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' }
@@ -825,7 +799,7 @@ def removeSubscription() {
log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}" log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}"
if (device) { if (device) {
log.debug "Removing subscription for device: ${device.id}" log.debug "Harmony - Removing subscription for device: ${device.id}"
state.remove(device.id) state.remove(device.id)
unsubscribe(device) unsubscribe(device)
} }
@@ -849,17 +823,17 @@ def deviceHandler(evt) {
def deviceInfo = state[evt.deviceId] def deviceInfo = state[evt.deviceId]
if (state.harmonyHubs) { if (state.harmonyHubs) {
state.harmonyHubs.each { harmonyHub -> state.harmonyHubs.each { harmonyHub ->
log.trace "Logitech Harmony - Sending data to $harmonyHub.name" log.trace "Harmony - Sending data to $harmonyHub.name"
sendToHarmony(evt, harmonyHub.callbackUrl) sendToHarmony(evt, harmonyHub.callbackUrl)
} }
} else if (deviceInfo) { } else if (deviceInfo) {
if (deviceInfo.callbackUrl) { if (deviceInfo.callbackUrl) {
sendToHarmony(evt, deviceInfo.callbackUrl) sendToHarmony(evt, deviceInfo.callbackUrl)
} else { } else {
log.warn "No callbackUrl set for device: ${evt.deviceId}" log.warn "Harmony - No callbackUrl set for device: ${evt.deviceId}"
} }
} else { } else {
log.warn "No subscribed device found for device: ${evt.deviceId}" log.warn "Harmony - No subscribed device found for device: ${evt.deviceId}"
} }
} }
@@ -883,12 +857,12 @@ def sendToHarmony(evt, String callbackUrl) {
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]] body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
] ]
try { try {
log.debug "Sending data to Harmony Cloud: $params" log.debug "Harmony - Sending data to Harmony Cloud: $params"
httpPostJson(params) { resp -> httpPostJson(params) { resp ->
log.debug "Harmony Cloud - Response: ${resp.status}" log.debug "Harmony - Cloud Response: ${resp.status}"
} }
} catch (e) { } catch (e) {
log.error "Harmony Cloud - Something went wrong: $e" log.error "Harmony - Cloud Something went wrong: $e"
} }
} }
} }
@@ -913,10 +887,10 @@ def activityCallback() {
if (data.errorCode == "200") { if (data.errorCode == "200") {
device.setCurrentActivity(data.currentActivityId) device.setCurrentActivity(data.currentActivityId)
} else { } else {
log.warn "Activity callback error: ${data}" log.warn "Harmony - Activity callback error: ${data}"
} }
} else { } 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"}' render status: 200, data: '{"msg": "Successfully received callbackUrl"}'
} }
@@ -950,13 +924,13 @@ def harmony() {
} }
def deleteHarmony() { 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 } def harmonyHub = state.harmonyHubs?.find { it.mac == params.mac }
if (harmonyHub) { 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) state.harmonyHubs.remove(harmonyHub)
} else { } 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: "{}" render status: 204, data: "{}"
} }