Compare commits

..

1 Commits

Author SHA1 Message Date
Jay Simon
ed3472f8d2 MSA-1502: The SMC Networks Micro Door/Window Sensor consists of
a magnet that attaches to a door or window and a
Zigbee wireless sensor that attaches to the door or
window frame. The sensor detects intrusions in
monitored areas and notifies users via a home controller
or security panel.
The sensor consists of a high sensitivity MR
(magnetoresistive) sensor IC/magnet that provides
ample hysteresis for normal and wide air gap solutions.
When the magnet is moved away from the sensor, a
signal is sent to the home controller or security panel that
sets off an alarm when the security system is armed. It
also informs the user when a low-voltage battery
condition occurs.
The product is designed to be elegant, discreet, and
installer-friendly.
Key Features:
■ Micro form factor for discreet installation
■ Lithium battery for longer life & enhanced RF range
■ Device and wall tamper switch added for added
protection
■ Easy to mount with included dual-sided mounting
tape or screws
■ LED indicators for pairing status, low voltage early
detection
■ Zigbee HA 1.2 profile compatible
2016-10-01 07:58:11 -05:00
17 changed files with 637 additions and 366 deletions

View File

@@ -127,10 +127,9 @@ 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:
def cmdScale = cmd.scale == 1 ? "F" : "C"
map.name = "temperature" map.name = "temperature"
map.unit = getTemperatureScale() map.unit = cmd.scale == 1 ? "F" : "C"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision) map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, map.unit, 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.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
} }
def healthPoll() { def healthPoll() {
@@ -103,8 +103,10 @@ def healthPoll() {
def configure() { def configure() {
unschedule() unschedule()
runEvery5Minutes("healthPoll") schedule("0 0/5 * * * ? *", "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])
zigbee.onOffRefresh() + zigbee.levelRefresh() // minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -37,7 +37,7 @@ metadata {
attribute "minHeatingSetpoint", "number" attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number" attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number" attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "string" attribute "deviceTemperatureUnit", "number"
} }
tiles { tiles {

View File

@@ -7,8 +7,6 @@
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"
@@ -44,10 +42,6 @@ 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}'"
@@ -76,8 +70,13 @@ 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)
} }
@@ -86,7 +85,3 @@ 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") { definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings", category: "C1") {
capability "Actuator" capability "Actuator"
capability "Switch" capability "Switch"
capability "Power Meter" capability "Power Meter"
@@ -103,22 +103,9 @@ def parse(String description) {
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
} }
} }
else {
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 { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}" log.debug zigbee.parseDescriptionAsMap(description)
}
} }
} }
@@ -141,10 +128,8 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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") { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
capability "Refresh" capability "Refresh"
@@ -118,28 +118,14 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last()) resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0x0402: case 0x0402:
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 // temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp) def value = getTemperature(temp)
resultMap = getTemperatureResult(value) resultMap = getTemperatureResult(value)
}
break break
} }
} }
@@ -149,8 +135,10 @@ 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
} }
@@ -304,9 +292,8 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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") { definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Motion Sensor" capability "Motion Sensor"
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
@@ -122,37 +122,19 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last()) resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0x0402: case 0x0402:
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 // temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp) def value = getTemperature(temp)
resultMap = getTemperatureResult(value) resultMap = getTemperatureResult(value)
}
break break
case 0x0406: case 0x0406:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
log.debug 'motion' log.debug 'motion'
resultMap.name = 'motion' resultMap.name = 'motion'
}
break break
} }
} }
@@ -162,8 +144,10 @@ 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
} }
@@ -319,9 +303,8 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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") { definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Three Axis" capability "Three Axis"
capability "Battery" capability "Battery"
@@ -147,10 +147,7 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last()) resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0xFC02: case 0xFC02:
@@ -158,21 +155,11 @@ private Map parseCatchAllMessage(String description) {
break break
case 0x0402: case 0x0402:
if (cluster.command == 0x07) { log.debug 'TEMP'
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 // temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp) def value = getTemperature(temp)
resultMap = getTemperatureResult(value) resultMap = getTemperatureResult(value)
}
break break
} }
} }
@@ -182,8 +169,10 @@ 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
} }
@@ -412,9 +401,8 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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") { definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Battery" capability "Battery"
capability "Configuration" capability "Configuration"
capability "Contact Sensor" capability "Contact Sensor"
@@ -109,28 +109,15 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last()) resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0x0402: case 0x0402:
if (cluster.command == 0x07){ log.debug 'TEMP'
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 // temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp) def value = getTemperature(temp)
resultMap = getTemperatureResult(value) resultMap = getTemperatureResult(value)
}
break break
} }
} }
@@ -140,8 +127,10 @@ 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
} }
@@ -266,9 +255,8 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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") { definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
capability "Refresh" capability "Refresh"
@@ -93,37 +93,20 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last()) resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0x0402: case 0x0402:
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 // temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp) def value = getTemperature(temp)
resultMap = getTemperatureResult(value) resultMap = getTemperatureResult(value)
}
break break
case 0xFC45: case 0xFC45:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('') String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
String display = Math.round(Integer.valueOf(pctStr, 16) / 100) String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
resultMap = getHumidityResult(display) resultMap = getHumidityResult(display)
}
break break
} }
} }
@@ -133,8 +116,10 @@ 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
} }
@@ -279,9 +264,8 @@ def refresh()
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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") { definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings", category: "C1") {
capability "Actuator" capability "Actuator"
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Refresh"
@@ -59,22 +59,9 @@ def parse(String description) {
sendEvent(event) sendEvent(event)
} }
} }
else {
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 { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}" log.debug zigbee.parseDescriptionAsMap(description)
}
} }
} }
@@ -97,15 +84,13 @@ def ping() {
} }
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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") { definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings", category: "C6") {
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)
def cluster = zigbee.parse(description) log.trace "zigbeeMap : $zigbeeMap"
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,18 +107,8 @@ 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
} }
} }
} }
@@ -138,15 +128,13 @@ 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(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.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)
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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)
} }
@@ -189,5 +177,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") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
} }

View File

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

@@ -0,0 +1,372 @@
/**
* SMCDW30-Z
*
* Copyright 2016 Roy Chuang
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "SMC Door/Window Sensor", namespace: "SMC", author: "Roy Chuang") {
capability "Contact Sensor"
capability "Configuration"
capability "Battery"
capability "Temperature Measurement"
capability "Refresh"
capability "Tamper Alert"
capability "Sensor"
command "enrollResponse"
fingerprint profileId: "0104", deviceId: "0402", deviceVersion: "00", inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "ACCTON", model: "SMCDW30-Z"
}
simulator {
status "closed": "zone status 0x0000 -- extended status 0x00"
status "open": "zone status 0x0001 -- extended status 0x00"
status "tamper alert with closed": "zone status 0x0004 -- extended status 0x00"
status "tamper alert with open": "zone status 0x0005 -- extended status 0x00"
}
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://na.smc.com/site/wp-content/uploads/2016/06/SMCDW30-Z-Sensor-_Magnet-1-170x170.png"
])
}
section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#53a7c0"
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#ffffff"
attributeState "tamper alert with open", label:'tamper', icon:"st.contact.contact.open", backgroundColor:"#cc0000"
attributeState "tamper alert with closed", label:'tamper', icon:"st.contact.contact.closed", backgroundColor:"#cc0000"
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("rssi", "device.rssi", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "rssi", label:'${currentValue}% rssi', unit:""
}
main(["contact"])
details(["contact", "temperature", "battery", "refresh", "rssi"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
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
case 0x0b05:
log.debug 'Diag'
break
}
}
return resultMap
}
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
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
resultMap = getTemperatureResult(value)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
else if (descMap.cluster == "0B05" && descMap.attrId == "011d") {
resultMap = getRssiResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}
return resultMap
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0000': // Closed
resultMap = getContactResult('closed')
break
case '0x0001': // Open/Alarm1
resultMap = getContactResult('open')
break
case '0x0004': // Closed & Tamper
resultMap = getContactResult('tamper alert with closed')
break
case '0x0005': // Open & Tamper
resultMap = getContactResult('tamper alert with open')
break
default :
log.warn "ZoneStatus not support!"
break
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
} else {
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getBatteryResult(rawValue) {
log.info "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [
name: 'battery',
value: '--',
translatable: true
]
def volts = rawValue / 10
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${device.displayName} battery has too much power: (> 3.5) volts."
}
else {
def minVolts = 2.1
def maxVolts = 3.2
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${device.displayName} battery was ${result.value}%"
}
}
return result
}
private Map getRssiResult(rawValue) {
log.info "RSSI rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [
name: 'rssi',
value: '--',
translatable: true
]
def pct = rawValue / 255
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${device.displayName} RSSI was ${result.value}%"
return result
}
private Map getTemperatureResult(value) {
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
log.info "Temperature is ${value}"
def descriptionText
if ( temperatureScale == 'C' )
descriptionText = "${device.displayName} was ${value}°C"
else
descriptionText = "${device.displayName} was ${value}°F"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
translatable: true
]
}
private Map getContactResult(value) {
String descriptionText = "${device.displayName}"
return [
name: 'contact',
value: value,
descriptionText: descriptionText,
translatable: true
]
}
def refresh() {
log.debug "refresh called"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x001 0x20", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0xb05 0x11d", "delay 200"
]
return refreshCmds
}
def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
log.debug "${device.hub.zigbeeEui}. ${zigbeeEui}"
def configCmds = [
/* Write CIE */
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x0001 0x20 0x20 30 21600 {02}", //Battery Voltage 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x0402 0 0x29 30 3600 {C800}", //Temperature 1 hr
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1000",
//Set long poll interval to 5 min
"raw 0x0020 {11 00 02 b0 04 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug "Sending enroll response"
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
[
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -131,7 +131,10 @@ 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() ?: [:]
@@ -256,6 +259,10 @@ 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 ?: [:]
} }
@@ -309,6 +316,29 @@ 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)
@@ -540,8 +570,11 @@ 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!"
updateBulbState(body, hubResponse.hubId) body.each { k, v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId]
}
} }
} }
} }
@@ -657,8 +690,11 @@ 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!"
updateBulbState(body, parsedEvent.hub) body.each { k,v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub]
}
} }
} }
} }
@@ -718,7 +754,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.value.idNumber is Offline" log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline") d.sendEvent(name: "status", value: "Offline")
state.bulbs?.each { state.bulbs?.each {
@@ -743,31 +779,6 @@ 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
///////////////////////////////////// /////////////////////////////////////
@@ -1162,14 +1173,7 @@ def setColor(childDevice, huesettings) {
} }
def ping(childDevice) { def ping(childDevice) {
if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) { if (isOnline(getId(childDevice))) {
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 {
@@ -1194,7 +1198,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) {
@@ -1261,7 +1265,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 "Harmony - About to create access token" log.debug "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 "Harmony - redirectUrl: ${redirectUrl}" log.debug "redirectUrl: ${redirectUrl}"
} else { } else {
log.warn "Harmony - No authQueryString" log.warn "No authQueryString"
} }
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
log.debug "Harmony - Access token already exists" log.debug "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 "Harmony - Exchanging code for access token" log.debug "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 "Harmony - This code should be unreachable" log.debug "This code should be unreachable"
success() success()
} }
} }
} }
def init() { def init() {
log.debug "Harmony - Requesting Code" log.debug "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 "Harmony - receiveToken" log.debug "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 "Harmony - Connection timed out, please try again later." log.warn "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 "Harmony - About to create access token" log.debug "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 "Harmony - About to create access token" log.debug "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 "Harmony - Success disconnecting Harmony from SmartThings" log.debug "Success disconnecting Harmony from SmartThings"
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
log.error "Harmony - Error disconnecting Harmony from SmartThings: ${e.statusCode}" log.error "Error disconnecting Harmony from SmartThings: ${e.statusCode}"
} }
} }
} }
@@ -342,7 +342,6 @@ def initialize() {
if (selectedhubs || selectedactivities) { if (selectedhubs || selectedactivities) {
addDevice() addDevice()
runEvery5Minutes("poll") runEvery5Minutes("poll")
getActivityList()
} }
} }
@@ -351,7 +350,7 @@ def getHarmonydevices() {
} }
Map discoverDevices() { Map discoverDevices() {
log.trace "Harmony - Discovering devices..." log.trace "Discovering devices..."
discovery() discovery()
if (getHarmonydevices() != []) { if (getHarmonydevices() != []) {
def devices = state.Harmonydevices.hubs def devices = state.Harmonydevices.hubs
@@ -381,52 +380,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 "Harmony - valid Token" log.debug "valid Token"
state.Harmonydevices = response.data state.Harmonydevices = response.data
state.resethub = false state.resethub = false
} else { } else {
log.debug "Harmony - Error: $response.status" log.debug "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 - Harmony Access token has expired" log.warn "Harmony Access token has expired"
} }
} catch (java.net.SocketTimeoutException e) { } catch (java.net.SocketTimeoutException e) {
log.warn "Harmony - Connection to the hub timed out. Please restart the hub and try again." log.warn "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 "Harmony - Error: $e" log.info "Logitech Harmony - Error: $e"
} }
return null return null
} }
def addDevice() { def addDevice() {
log.trace "Harmony - Adding Hubs" log.trace "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 "Harmony - Created ${d.displayName} with id $dni" log.trace "created ${d.displayName} with id $dni"
poll() poll()
} else { } else {
log.trace "Harmony - Found ${d.displayName} with id $dni already exists" log.trace "found ${d.displayName} with id $dni already exists"
} }
} }
log.trace "Harmony - Adding Activities" log.trace "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 "Harmony - Created ${d.displayName} with id $dni" log.trace "created ${d.displayName} with id $dni"
poll() poll()
} }
} else { } else {
log.trace "Harmony - Found ${d.displayName} with id $dni already exists" log.trace "found ${d.displayName} with id $dni already exists"
} }
} }
} }
@@ -456,17 +455,28 @@ def activity(dni,mode) {
def activityResponse(response, data) { def activityResponse(response, data) {
if (response.hasError()) { if (response.hasError()) {
log.error "Harmony - response has error: $response.errorMessage" log.error "Logitech 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 "Harmony - Access token has expired" log.warn "Logitech Harmony - Access token has expired"
} }
} else { } else {
if (response.status == 200) { def ResponseValues
log.trace "Harmony - Command sent succesfully" try {
// json response already parsed into JSONElement object
ResponseValues = response.json
} catch (e) {
log.error "Logitech Harmony - error parsing json from response: $e"
}
if (ResponseValues) {
if (ResponseValues.code == 200) {
log.trace "Command sent succesfully"
poll() poll()
} else { } else {
log.trace "Harmony - Command failed. Error: $response.status" log.trace "Command failed. Error: $response.data.code"
}
} else {
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
} }
} }
} }
@@ -474,6 +484,7 @@ 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)}",
@@ -482,16 +493,16 @@ def poll() {
] ]
asynchttp_v1.get('pollResponse', params) asynchttp_v1.get('pollResponse', params)
} else { } else {
log.warn "Harmony - Access token has expired" log.warn "Logitech Harmony - Access token has expired"
} }
} }
def pollResponse(response, data) { def pollResponse(response, data) {
if (response.hasError()) { if (response.hasError()) {
log.error "Harmony - response has error: $response.errorMessage" log.error "Logitech 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 "Harmony - Access token has expired" log.warn "Logitech Harmony - Access token has expired"
} }
} else { } else {
def ResponseValues def ResponseValues
@@ -499,7 +510,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 "Harmony - error parsing json from response: $e" log.error "Logitech Harmony - error parsing json from response: $e"
} }
if (ResponseValues) { if (ResponseValues) {
def map = [:] def map = [:]
@@ -511,17 +522,12 @@ 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 def currentActivity = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}").device.displayName
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 "Harmony - error response: $it.value.message" log.trace "Logitech Harmony - error response: $it.value.message"
} }
} }
def activities = getChildDevices() def activities = getChildDevices()
@@ -546,39 +552,59 @@ def pollResponse(response, data) {
} }
} }
} else { } else {
log.debug "Harmony - did not get json results from response body: $response.data" log.debug "Logitech Harmony - did not get json results from response body: $response.data"
} }
} }
} }
def getActivityList() { def getActivityList() {
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
def Params = [auth: state.HarmonyAccessToken] def tokenParam = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}" def params = [
uri: "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(tokenParam)}",
headers: ["Accept": "application/json"],
contentType: 'application/json'
]
asynchttp_v1.get('activityListResponse', params)
} else {
log.warn "Logitech Harmony - Access token has expired"
}
}
def 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 { try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response -> // json response already parsed into JSONElement object
response.data.hubs.each { 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}") def hub = getChildDevice("harmony-${it.key}")
if (hub) { if (hub) {
def hubname = getHubName("${it.key}") def hubname = getHubName("${it.key}")
def activities = [] def activities = []
def aux = it.value.response.data.activities.size() def aux = it.value.response?.data.activities.size()
if (aux >= 1) { if (aux >= 1) {
activities = it.value.response.data.activities.collect { activities = it.value.response.data.activities.collect {
[id: it.key, name: it.value['name'], type: it.value['type']] [id: it.key, name: it.value['name'], type: it.value['type']]
} }
activities += [id: "off", name: "Activity OFF", type: "0"] 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) hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
} }
} }
} } else {
} catch (groovyx.net.http.HttpResponseException e) { log.debug "Logitech Harmony - did not get json results from response body: $response.data"
log.trace e
} catch (java.net.SocketTimeoutException e) {
log.trace e
} catch(Exception e) {
log.trace e
} }
} }
} }
@@ -643,7 +669,7 @@ def sendNotification(msg) {
def hookEventHandler() { def hookEventHandler() {
// log.debug "In hookEventHandler method." // log.debug "In hookEventHandler method."
log.debug "Harmony - request = ${request}" log.debug "request = ${request}"
def json = request.JSON def json = request.JSON
@@ -652,14 +678,14 @@ def hookEventHandler() {
} }
def listDevices() { def listDevices() {
log.debug "Harmony - getDevices(), params: ${params}" log.debug "getDevices, params: ${params}"
allDevices.collect { allDevices.collect {
deviceItem(it) deviceItem(it)
} }
} }
def getDevice() { def getDevice() {
log.debug "Harmony - getDevice(), params: ${params}" log.debug "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"}'
@@ -672,7 +698,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 "Harmony - updateDevice(), params: ${params}, request: ${data}" log.debug "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 {
@@ -740,7 +766,7 @@ def getDeviceCapabilityCommands(deviceCapabilities) {
} }
def listSubscriptions() { def listSubscriptions() {
log.debug "Harmony - listSubscriptions()" log.debug "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 = [
@@ -761,17 +787,17 @@ def addSubscription() {
def attribute = data.attributeName def attribute = data.attributeName
def callbackUrl = data.callbackUrl def callbackUrl = data.callbackUrl
log.debug "Harmony - addSubscription, params: ${params}, request: ${data}" log.debug "Logitech 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 "Harmony - Adding callbackUrl: $callbackUrl" log.debug "Adding callbackUrl: $callbackUrl"
state[device.id] = [callbackUrl: callbackUrl] state[device.id] = [callbackUrl: callbackUrl]
} }
log.debug "Harmony - Adding subscription" log.debug "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' }
@@ -799,7 +825,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 "Harmony - Removing subscription for device: ${device.id}" log.debug "Removing subscription for device: ${device.id}"
state.remove(device.id) state.remove(device.id)
unsubscribe(device) unsubscribe(device)
} }
@@ -823,17 +849,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 "Harmony - Sending data to $harmonyHub.name" log.trace "Logitech 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 "Harmony - No callbackUrl set for device: ${evt.deviceId}" log.warn "No callbackUrl set for device: ${evt.deviceId}"
} }
} else { } else {
log.warn "Harmony - No subscribed device found for device: ${evt.deviceId}" log.warn "No subscribed device found for device: ${evt.deviceId}"
} }
} }
@@ -857,12 +883,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 "Harmony - Sending data to Harmony Cloud: $params" log.debug "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"
} }
} }
} }
@@ -887,10 +913,10 @@ def activityCallback() {
if (data.errorCode == "200") { if (data.errorCode == "200") {
device.setCurrentActivity(data.currentActivityId) device.setCurrentActivity(data.currentActivityId)
} else { } else {
log.warn "Harmony - Activity callback error: ${data}" log.warn "Activity callback error: ${data}"
} }
} else { } else {
log.warn "Harmony - Activity callback sent to non-existant dni: ${params.dni}" log.warn "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"}'
} }
@@ -924,13 +950,13 @@ def harmony() {
} }
def deleteHarmony() { def deleteHarmony() {
log.debug "Harmony - Trying to delete Harmony hub with mac: ${params.mac}" log.debug "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 "Harmony - Deleting Harmony hub with mac: ${params.mac}" log.debug "Deleting Harmony hub with mac: ${params.mac}"
state.harmonyHubs.remove(harmonyHub) state.harmonyHubs.remove(harmonyHub)
} else { } else {
log.debug "Harmony - Couldn't find Harmony hub with mac: ${params.mac}" log.debug "Couldn't find Harmony hub with mac: ${params.mac}"
} }
render status: 204, data: "{}" render status: 204, data: "{}"
} }