mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 21:03:00 +00:00
Compare commits
1 Commits
netatmo-ap
...
MSA-1517-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f383bb480 |
@@ -127,10 +127,9 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelR
|
||||
def map = [ displayed: true ]
|
||||
switch (cmd.sensorType) {
|
||||
case 1:
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
map.name = "temperature"
|
||||
map.unit = getTemperatureScale()
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
||||
map.name = "temperature"
|
||||
map.unit = cmd.scale == 1 ? "F" : "C"
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, map.unit, cmd.precision)
|
||||
break
|
||||
case 3:
|
||||
map.name = "illuminance"
|
||||
|
||||
@@ -93,7 +93,7 @@ def ping() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
}
|
||||
|
||||
def healthPoll() {
|
||||
@@ -105,7 +105,9 @@ def healthPoll() {
|
||||
def configure() {
|
||||
unschedule()
|
||||
runEvery5Minutes("healthPoll")
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", 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()
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ metadata {
|
||||
attribute "colorName", "string"
|
||||
|
||||
command "setAdjustedColor"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -133,7 +133,7 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
refresh() + onOffConfig() + levelConfig() + powerConfig()
|
||||
onOffConfig() + levelConfig() + powerConfig() + refresh()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -104,21 +104,8 @@ def parse(String description) {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug "${cluster}"
|
||||
}
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,12 +128,10 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()
|
||||
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
|
||||
}
|
||||
|
||||
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
|
||||
|
||||
@@ -118,28 +118,14 @@ private Map parseCatchAllMessage(String description) {
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
}
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
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
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -149,8 +135,10 @@ private Map parseCatchAllMessage(String description) {
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
@@ -304,13 +292,19 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
def enrollCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
]
|
||||
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
||||
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
|
||||
@@ -122,37 +122,19 @@ private Map parseCatchAllMessage(String description) {
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
}
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
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
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
|
||||
case 0x0406:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
}
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -162,8 +144,10 @@ private Map parseCatchAllMessage(String description) {
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
@@ -319,13 +303,19 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
def enrollCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
]
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
||||
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
|
||||
@@ -255,9 +255,13 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
def configCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
@@ -266,7 +270,7 @@ def configure() {
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
return refresh() + configCmds // send refresh cmds as part of config
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
|
||||
@@ -147,33 +147,20 @@ private Map parseCatchAllMessage(String description) {
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
}
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
case 0xFC02:
|
||||
log.debug 'ACCELERATION'
|
||||
log.debug 'ACCELERATION'
|
||||
break
|
||||
|
||||
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
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
break
|
||||
log.debug 'TEMP'
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,8 +169,10 @@ private Map parseCatchAllMessage(String description) {
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
@@ -412,22 +401,22 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
def configCmds = zigbee.batteryConfig() +
|
||||
def configCmds = enrollResponse() +
|
||||
zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig(30, 300) +
|
||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
||||
|
||||
return refresh() + configCmds
|
||||
return configCmds + refresh()
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
|
||||
@@ -277,8 +277,12 @@ def getTemperature(value) {
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
def configCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
@@ -291,7 +295,7 @@ def configure() {
|
||||
"zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
return refresh() + configCmds // send refresh cmds as part of config
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
|
||||
@@ -109,28 +109,15 @@ private Map parseCatchAllMessage(String description) {
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
}
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
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
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
log.debug 'TEMP'
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -140,8 +127,10 @@ private Map parseCatchAllMessage(String description) {
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
@@ -266,15 +255,19 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
def enrollCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
]
|
||||
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
||||
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
|
||||
@@ -93,37 +93,20 @@ private Map parseCatchAllMessage(String description) {
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
}
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
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
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
break
|
||||
// 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:
|
||||
// 0x07 - configure reporting
|
||||
if (cluster.command != 0x07) {
|
||||
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
|
||||
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
|
||||
resultMap = getHumidityResult(display)
|
||||
}
|
||||
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
|
||||
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
|
||||
resultMap = getHumidityResult(display)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -133,8 +116,10 @@ private Map parseCatchAllMessage(String description) {
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
@@ -250,7 +235,11 @@ private Map getTemperatureResult(value) {
|
||||
|
||||
private Map getHumidityResult(value) {
|
||||
log.debug 'Humidity'
|
||||
return value ? [name: 'humidity', value: value, unit: '%'] : [:]
|
||||
return [
|
||||
name: 'humidity',
|
||||
value: value,
|
||||
unit: '%'
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,16 +252,20 @@ def ping() {
|
||||
def refresh()
|
||||
{
|
||||
log.debug "refresh temperature, humidity, and battery"
|
||||
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
|
||||
zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware
|
||||
zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020)
|
||||
[
|
||||
|
||||
"zcl mfg-code 0xC2DF", "delay 1000",
|
||||
"zcl global read 0xFC45 0", "delay 1000",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1000",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def humidityConfigCmds = [
|
||||
@@ -283,7 +276,7 @@ def configure() {
|
||||
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
||||
return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
private hex(value) {
|
||||
|
||||
154
devicetypes/smartthings/virtual-foscam.src/virtual-foscam.groovy
Normal file
154
devicetypes/smartthings/virtual-foscam.src/virtual-foscam.groovy
Normal file
@@ -0,0 +1,154 @@
|
||||
import static java.util.UUID.randomUUID
|
||||
import java.security.MessageDigest
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import javax.crypto.Mac
|
||||
import java.security.SignatureException
|
||||
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Virtual Foscam", namespace: "smartthings", author: "sidoh") {
|
||||
capability "Actuator"
|
||||
capability "Image Capture"
|
||||
capability "Switch"
|
||||
|
||||
command "enableRecording"
|
||||
command "disableRecording"
|
||||
|
||||
attribute "preset", "enum", ["Door", "Couch", "Table"]
|
||||
command "setPresetDoor"
|
||||
command "setPresetCouch"
|
||||
command "setPresetTable"
|
||||
}
|
||||
|
||||
preferences {
|
||||
input "camera", "text", title: "Camera Identifier", displayDuringSetup: true
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles {
|
||||
standardTile("camera", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: true) {
|
||||
state "default", label: "", action: "", icon: "st.camera.dropcam-centered", backgroundColor: "#FFFFFF"
|
||||
}
|
||||
|
||||
carouselTile("cameraDetails", "device.image", width: 3, height: 2) { }
|
||||
|
||||
standardTile("take", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
|
||||
state "take", label: "Take", action: "Image Capture.take", icon: "st.camera.dropcam", backgroundColor: "#FFFFFF", nextState:"taking"
|
||||
state "taking", label:'Taking', action: "", icon: "st.camera.dropcam", backgroundColor: "#53a7c0"
|
||||
state "image", label: "Take", action: "Image Capture.take", icon: "st.camera.dropcam", backgroundColor: "#FFFFFF", nextState:"taking"
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.Entertainment.entertainment9", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.Entertainment.entertainment9", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', icon:"st.Entertainment.entertainment9", backgroundColor:"#79b821"
|
||||
state "turningOff", label:'${name}', icon:"st.Entertainment.entertainment9", backgroundColor:"#ffffff"
|
||||
}
|
||||
|
||||
standardTile("presetDoor", "device.preset", width: 1, height: 1, canChangeIcon: true) {
|
||||
state "Door", label: '${name}', backgroundColor:"#79b821", action: "setPresetDoor"
|
||||
}
|
||||
|
||||
standardTile("presetCouch", "device.preset", width: 1, height: 1, canChangeIcon: true) {
|
||||
state "Couch", label: '${name}', backgroundColor:"#79b821", action: "setPresetCouch"
|
||||
}
|
||||
|
||||
standardTile("presetTable", "device.preset", width: 1, height: 1, canChangeIcon: true) {
|
||||
state "Table", label: '${name}', backgroundColor:"#79b821", action: "setPresetTable"
|
||||
}
|
||||
|
||||
main "switch"
|
||||
details(["cameraDetails", "take", "switch", "presetDoor", "presetCouch", "presetTable"])
|
||||
}
|
||||
}
|
||||
|
||||
def setPresetDoor() {
|
||||
setPreset("Door")
|
||||
}
|
||||
|
||||
def setPresetCouch() {
|
||||
setPreset("Couch")
|
||||
}
|
||||
|
||||
def setPresetTable() {
|
||||
setPreset("Table")
|
||||
}
|
||||
|
||||
def on() {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
gwPost('', [recording: true])
|
||||
}
|
||||
|
||||
def off() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
gwPost('', [recording: false])
|
||||
}
|
||||
|
||||
def take() {
|
||||
log.info "Executing 'take'"
|
||||
|
||||
gwGet('/snapshot.jpg') {
|
||||
final def imageName = "${randomUUID() as String}.jpg"
|
||||
storeImage(imageName, it.data)
|
||||
}
|
||||
}
|
||||
|
||||
def hmac(String data, String key) throws SignatureException {
|
||||
final Mac hmacSha1;
|
||||
try {
|
||||
hmacSha1 = Mac.getInstance("HmacSHA1");
|
||||
} catch (Exception nsae) {
|
||||
hmacSha1 = Mac.getInstance("HMAC-SHA-1");
|
||||
}
|
||||
|
||||
final SecretKeySpec macKey = new SecretKeySpec(key.getBytes(), "RAW");
|
||||
hmacSha1.init(macKey);
|
||||
|
||||
final byte[] signature = hmacSha1.doFinal(data.getBytes());
|
||||
|
||||
return signature.encodeHex()
|
||||
}
|
||||
|
||||
def getHmacHeaders() {
|
||||
final def payload = randomUUID() as String
|
||||
long time = new Date().getTime()
|
||||
time /= 1000L
|
||||
|
||||
final String signature = hmac(payload + time, '<security_token>')
|
||||
|
||||
[
|
||||
'X-Signature-Timestamp': time,
|
||||
'X-Signature-Payload': payload,
|
||||
'X-Signature': signature
|
||||
]
|
||||
}
|
||||
|
||||
def setPreset(preset) {
|
||||
gwPost('', [preset: preset])
|
||||
runIn(2, take)
|
||||
sendEvent(name: 'preset', value: preset)
|
||||
}
|
||||
|
||||
def gwPost(path, params, success = {}) {
|
||||
httpPost(
|
||||
[
|
||||
uri: ("http://<ha_gateway_url>/camera/${settings.camera}${path}"),
|
||||
body: params,
|
||||
headers: getHmacHeaders(),
|
||||
success: success
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
def gwGet(path, success = {}) {
|
||||
httpGet(
|
||||
[
|
||||
uri: ("http://<ha_gateway_url>/camera/${settings.camera}${path}"),
|
||||
headers: getHmacHeaders(),
|
||||
success: success
|
||||
]
|
||||
)
|
||||
}
|
||||
@@ -89,8 +89,14 @@ def parse(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasButtonMessage(String description) {
|
||||
def zs = zigbee.parseZoneStatus(description)
|
||||
return zs.isAlarm2Set() ? getButtonResult("press") : getButtonResult("release")
|
||||
int zoneInt = Integer.parseInt((description - "zone status 0x"), 16)
|
||||
if (zoneInt & 0x02) {
|
||||
resultMap = getButtonResult('press')
|
||||
} else {
|
||||
resultMap = getButtonResult('release')
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
|
||||
@@ -93,5 +93,5 @@ def refresh() {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
refresh()
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
|
||||
}
|
||||
|
||||
@@ -60,21 +60,8 @@ def parse(String description) {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug "${cluster}"
|
||||
}
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,15 +84,13 @@ def ping() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ def configure() {
|
||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
|
||||
TYPE_U8, 600, 21600, 0x01)
|
||||
log.info "configure() --- cmds: $cmds"
|
||||
return refresh() + cmds // send refresh cmds as part of config
|
||||
return cmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Author: SmartThings
|
||||
* Date: 2016-01-19
|
||||
*
|
||||
* This DTH should serve as the generic DTH to handle RGB ZigBee HA devices (For color bulbs with no color temperature)
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee RGB Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Color Control"
|
||||
capability "Configuration"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"color control.setColor"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
//Globals
|
||||
private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
log.debug event
|
||||
if (event.name=="level" && event.value==0) {}
|
||||
else {
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
|
||||
def cluster = zigbee.parse(description)
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
|
||||
}
|
||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
|
||||
}
|
||||
}
|
||||
else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
|
||||
if (cluster.data[0] == 0x00){
|
||||
log.debug "ON/OFF REPORTING CONFIG RESPONSE: $cluster"
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
else {
|
||||
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbeeMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.onOffRefresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() + setHue(value.hue) + "delay 500" + setSaturation(value.saturation)
|
||||
}
|
||||
|
||||
def setHue(value) {
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
}
|
||||
|
||||
def setSaturation(value) {
|
||||
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)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ def parse(String description) {
|
||||
}
|
||||
else {
|
||||
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
|
||||
def cluster = zigbee.parse(description)
|
||||
log.trace "zigbeeMap : $zigbeeMap"
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
|
||||
if (cluster.data[0] == 0x00){
|
||||
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
else {
|
||||
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbeeMap
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,17 +128,15 @@ def ping() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + 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.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() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
refresh()
|
||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
@@ -189,5 +177,5 @@ def setHue(value) {
|
||||
|
||||
def setSaturation(value) {
|
||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
|
||||
}
|
||||
|
||||
@@ -82,5 +82,5 @@ def refresh() {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
refresh()
|
||||
zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
|
||||
}
|
||||
|
||||
@@ -78,5 +78,5 @@ def refresh() {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffRefresh() + zigbee.onOffConfig()
|
||||
zigbee.onOffConfig() + zigbee.onOffRefresh()
|
||||
}
|
||||
|
||||
@@ -134,5 +134,10 @@ def refresh() {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
refresh()
|
||||
zigbee.onOffConfig() +
|
||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
|
||||
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1) +
|
||||
zigbee.onOffRefresh() +
|
||||
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
|
||||
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
|
||||
}
|
||||
|
||||
@@ -83,21 +83,8 @@ def parse(String description) {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug "${cluster}"
|
||||
}
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,17 +108,15 @@ def ping() {
|
||||
}
|
||||
|
||||
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() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
refresh()
|
||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* 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: "ZLL RGB Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Color Control"
|
||||
capability "Configuration"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"color control.setColor"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
//Globals
|
||||
private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def finalResult = zigbee.getEvent(description)
|
||||
if (finalResult) {
|
||||
log.debug finalResult
|
||||
sendEvent(finalResult)
|
||||
}
|
||||
else {
|
||||
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
|
||||
log.trace "zigbeeMap : $zigbeeMap"
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
|
||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||
}
|
||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "saturation", value: saturationValue, displayed:false)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info "DID NOT PARSE MESSAGE for description : $description"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on() + ["delay 1500"] + zigbee.onOffRefresh()
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off() + ["delay 1500"] + zigbee.onOffRefresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
refreshAttributes() + configureAttributes()
|
||||
}
|
||||
|
||||
def poll() {
|
||||
refreshAttributes()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
configureAttributes() + refreshAttributes()
|
||||
}
|
||||
|
||||
def configureAttributes() {
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def refreshAttributes() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() + setHue(value.hue) + ["delay 300"] + setSaturation(value.saturation) + ["delay 2000"] + refreshAttributes()
|
||||
}
|
||||
|
||||
def setHue(value) {
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
}
|
||||
|
||||
def setSaturation(value) {
|
||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time
|
||||
}
|
||||
@@ -123,7 +123,7 @@ def configureAttributes() {
|
||||
}
|
||||
|
||||
def refreshAttributes() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
@@ -141,10 +141,10 @@ def setColor(value){
|
||||
|
||||
def setHue(value) {
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
}
|
||||
|
||||
def setSaturation(value) {
|
||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
|
||||
}
|
||||
|
||||
@@ -387,18 +387,18 @@ def getDeviceList() {
|
||||
state.deviceDetail = [:]
|
||||
state.deviceState = [:]
|
||||
|
||||
apiGet("/api/getstationsdata") { response ->
|
||||
apiGet("/api/devicelist") { response ->
|
||||
response.data.body.devices.each { value ->
|
||||
def key = value._id
|
||||
deviceList[key] = "${value.station_name}: ${value.module_name}"
|
||||
state.deviceDetail[key] = value
|
||||
state.deviceState[key] = value.dashboard_data
|
||||
value.modules.each { value2 ->
|
||||
def key2 = value2._id
|
||||
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
|
||||
state.deviceDetail[key2] = value2
|
||||
state.deviceState[key2] = value2.dashboard_data
|
||||
}
|
||||
}
|
||||
response.data.body.modules.each { value ->
|
||||
def key = value._id
|
||||
deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
|
||||
state.deviceDetail[key] = value
|
||||
state.deviceState[key] = value.dashboard_data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1194,7 +1194,7 @@ private poll() {
|
||||
}
|
||||
|
||||
private isOnline(id) {
|
||||
return (state.bulbs[id]?.online != null && state.bulbs[id]?.online) || state.bulbs[id]?.online == null
|
||||
return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null
|
||||
}
|
||||
|
||||
private put(path, body) {
|
||||
@@ -1261,7 +1261,7 @@ def convertBulbListToMap() {
|
||||
try {
|
||||
if (state.bulbs instanceof java.util.List) {
|
||||
def map = [:]
|
||||
state.bulbs?.unique {it.id}.each { bulb ->
|
||||
state.bulbs.unique {it.id}.each { bulb ->
|
||||
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]]
|
||||
}
|
||||
state.bulbs = map
|
||||
|
||||
@@ -97,7 +97,7 @@ def authPage() {
|
||||
def description = null
|
||||
if (!state.HarmonyAccessToken) {
|
||||
if (!state.accessToken) {
|
||||
log.debug "Harmony - About to create access token"
|
||||
log.debug "About to create access token"
|
||||
createAccessToken()
|
||||
}
|
||||
description = "Click to enter Harmony Credentials"
|
||||
@@ -141,13 +141,13 @@ def callback() {
|
||||
def redirectUrl = null
|
||||
if (params.authQueryString) {
|
||||
redirectUrl = URLDecoder.decode(params.authQueryString.replaceAll(".+&redirect_url=", ""))
|
||||
log.debug "Harmony - redirectUrl: ${redirectUrl}"
|
||||
log.debug "redirectUrl: ${redirectUrl}"
|
||||
} else {
|
||||
log.warn "Harmony - No authQueryString"
|
||||
log.warn "No authQueryString"
|
||||
}
|
||||
|
||||
if (state.HarmonyAccessToken) {
|
||||
log.debug "Harmony - Access token already exists"
|
||||
log.debug "Access token already exists"
|
||||
discovery()
|
||||
success()
|
||||
} else {
|
||||
@@ -155,27 +155,27 @@ def callback() {
|
||||
if (code) {
|
||||
if (code.size() > 6) {
|
||||
// Harmony code
|
||||
log.debug "Harmony - Exchanging code for access token"
|
||||
log.debug "Exchanging code for access token"
|
||||
receiveToken(redirectUrl)
|
||||
} else {
|
||||
// Initiate the Harmony OAuth flow.
|
||||
init()
|
||||
}
|
||||
} else {
|
||||
log.debug "Harmony - This code should be unreachable"
|
||||
log.debug "This code should be unreachable"
|
||||
success()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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}" ]
|
||||
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
|
||||
}
|
||||
|
||||
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 params = [
|
||||
uri: "https://home.myharmony.com/oauth2/token?${toQueryString(oauthParams)}",
|
||||
@@ -186,7 +186,7 @@ def receiveToken(redirectUrl = null) {
|
||||
}
|
||||
} catch (java.util.concurrent.TimeoutException e) {
|
||||
fail(e)
|
||||
log.warn "Harmony - Connection timed out, please try again later."
|
||||
log.warn "Connection timed out, please try again later."
|
||||
}
|
||||
discovery()
|
||||
if (state.HarmonyAccessToken) {
|
||||
@@ -310,7 +310,7 @@ def buildRedirectUrl(page) {
|
||||
|
||||
def installed() {
|
||||
if (!state.accessToken) {
|
||||
log.debug "Harmony - About to create access token"
|
||||
log.debug "About to create access token"
|
||||
createAccessToken()
|
||||
} else {
|
||||
initialize()
|
||||
@@ -319,7 +319,7 @@ def installed() {
|
||||
|
||||
def updated() {
|
||||
if (!state.accessToken) {
|
||||
log.debug "Harmony - About to create access token"
|
||||
log.debug "About to create access token"
|
||||
createAccessToken()
|
||||
} else {
|
||||
initialize()
|
||||
@@ -330,9 +330,9 @@ def uninstalled() {
|
||||
if (state.HarmonyAccessToken) {
|
||||
try {
|
||||
state.HarmonyAccessToken = ""
|
||||
log.debug "Harmony - Success disconnecting Harmony from SmartThings"
|
||||
log.debug "Success disconnecting Harmony from SmartThings"
|
||||
} 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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,8 +341,7 @@ def initialize() {
|
||||
state.aux = 0
|
||||
if (selectedhubs || selectedactivities) {
|
||||
addDevice()
|
||||
runEvery5Minutes("poll")
|
||||
getActivityList()
|
||||
runEvery5Minutes("poll")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,7 +350,7 @@ def getHarmonydevices() {
|
||||
}
|
||||
|
||||
Map discoverDevices() {
|
||||
log.trace "Harmony - Discovering devices..."
|
||||
log.trace "Discovering devices..."
|
||||
discovery()
|
||||
if (getHarmonydevices() != []) {
|
||||
def devices = state.Harmonydevices.hubs
|
||||
@@ -381,52 +380,52 @@ def discovery() {
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
if (response.status == 200) {
|
||||
log.debug "Harmony - valid Token"
|
||||
log.debug "valid Token"
|
||||
state.Harmonydevices = response.data
|
||||
state.resethub = false
|
||||
} else {
|
||||
log.debug "Harmony - Error: $response.status"
|
||||
log.debug "Error: $response.status"
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Harmony - Harmony Access token has expired"
|
||||
log.warn "Harmony Access token has expired"
|
||||
}
|
||||
} 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
|
||||
} catch (e) {
|
||||
log.info "Harmony - Error: $e"
|
||||
log.info "Logitech Harmony - Error: $e"
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
def addDevice() {
|
||||
log.trace "Harmony - Adding Hubs"
|
||||
log.trace "Adding Hubs"
|
||||
selectedhubs.each { dni ->
|
||||
def d = getChildDevice(dni)
|
||||
if(!d) {
|
||||
def newAction = state.HarmonyHubs.find { it.key == dni }
|
||||
d = addChildDevice("smartthings", "Logitech Harmony Hub C2C", dni, null, [label:"${newAction.value}"])
|
||||
log.trace "Harmony - Created ${d.displayName} with id $dni"
|
||||
log.trace "created ${d.displayName} with id $dni"
|
||||
poll()
|
||||
} 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 ->
|
||||
def d = getChildDevice(dni)
|
||||
if(!d) {
|
||||
def newAction = state.HarmonyActivities.find { it.key == dni }
|
||||
if (newAction) {
|
||||
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
|
||||
log.trace "Harmony - Created ${d.displayName} with id $dni"
|
||||
log.trace "created ${d.displayName} with id $dni"
|
||||
poll()
|
||||
}
|
||||
} 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,17 @@ def activity(dni,mode) {
|
||||
|
||||
def activityResponse(response, data) {
|
||||
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
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Harmony - Access token has expired"
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
} else {
|
||||
if (response.status == 200) {
|
||||
log.trace "Harmony - Command sent succesfully"
|
||||
log.trace "Command sent succesfully"
|
||||
poll()
|
||||
} else {
|
||||
log.trace "Harmony - Command failed. Error: $response.status"
|
||||
log.trace "Command failed. Error: $response.status"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,16 +481,16 @@ def poll() {
|
||||
]
|
||||
asynchttp_v1.get('pollResponse', params)
|
||||
} else {
|
||||
log.warn "Harmony - Access token has expired"
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
}
|
||||
|
||||
def pollResponse(response, data) {
|
||||
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
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Harmony - Access token has expired"
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
} else {
|
||||
def ResponseValues
|
||||
@@ -499,7 +498,7 @@ def pollResponse(response, data) {
|
||||
// json response already parsed into JSONElement object
|
||||
ResponseValues = response.json
|
||||
} catch (e) {
|
||||
log.error "Harmony - error parsing json from response: $e"
|
||||
log.error "Logitech Harmony - error parsing json from response: $e"
|
||||
}
|
||||
if (ResponseValues) {
|
||||
def map = [:]
|
||||
@@ -521,7 +520,7 @@ def pollResponse(response, data) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.trace "Harmony - error response: $it.value.message"
|
||||
log.trace "Logitech Harmony - error response: $it.value.message"
|
||||
}
|
||||
}
|
||||
def activities = getChildDevices()
|
||||
@@ -546,43 +545,11 @@ def pollResponse(response, data) {
|
||||
}
|
||||
}
|
||||
} 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() {
|
||||
if (state.HarmonyAccessToken) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
response.data.hubs.each {
|
||||
def hub = getChildDevice("harmony-${it.key}")
|
||||
if (hub) {
|
||||
def hubname = getHubName("${it.key}")
|
||||
def activities = []
|
||||
def aux = it.value.response.data.activities.size()
|
||||
if (aux >= 1) {
|
||||
activities = it.value.response.data.activities.collect {
|
||||
[id: it.key, name: it.value['name'], type: it.value['type']]
|
||||
}
|
||||
activities += [id: "off", name: "Activity OFF", type: "0"]
|
||||
}
|
||||
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 getActivityName(activity,hubId) {
|
||||
// GET ACTIVITY'S NAME
|
||||
def actname = activity
|
||||
@@ -643,7 +610,7 @@ def sendNotification(msg) {
|
||||
|
||||
def hookEventHandler() {
|
||||
// log.debug "In hookEventHandler method."
|
||||
log.debug "Harmony - request = ${request}"
|
||||
log.debug "request = ${request}"
|
||||
|
||||
def json = request.JSON
|
||||
|
||||
@@ -652,14 +619,14 @@ def hookEventHandler() {
|
||||
}
|
||||
|
||||
def listDevices() {
|
||||
log.debug "Harmony - getDevices(), params: ${params}"
|
||||
log.debug "getDevices, params: ${params}"
|
||||
allDevices.collect {
|
||||
deviceItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
def getDevice() {
|
||||
log.debug "Harmony - getDevice(), params: ${params}"
|
||||
log.debug "getDevice, params: ${params}"
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
@@ -672,7 +639,7 @@ def updateDevice() {
|
||||
def data = request.JSON
|
||||
def command = data.command
|
||||
def arguments = data.arguments
|
||||
log.debug "Harmony - updateDevice(), params: ${params}, request: ${data}"
|
||||
log.debug "updateDevice, params: ${params}, request: ${data}"
|
||||
if (!command) {
|
||||
render status: 400, data: '{"msg": "command is required"}'
|
||||
} else {
|
||||
@@ -740,7 +707,7 @@ def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
}
|
||||
|
||||
def listSubscriptions() {
|
||||
log.debug "Harmony - listSubscriptions()"
|
||||
log.debug "listSubscriptions()"
|
||||
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
|
||||
def deviceInfo = state[it.device.id]
|
||||
def response = [
|
||||
@@ -761,17 +728,17 @@ def addSubscription() {
|
||||
def attribute = data.attributeName
|
||||
def callbackUrl = data.callbackUrl
|
||||
|
||||
log.debug "Harmony - addSubscription, params: ${params}, request: ${data}"
|
||||
log.debug "Logitech Harmony - addSubscription, params: ${params}, request: ${data}"
|
||||
if (!attribute) {
|
||||
render status: 400, data: '{"msg": "attributeName is required"}'
|
||||
} else {
|
||||
def device = allDevices.find { it.id == data.deviceId }
|
||||
if (device) {
|
||||
if (!state.harmonyHubs) {
|
||||
log.debug "Harmony - Adding callbackUrl: $callbackUrl"
|
||||
log.debug "Adding callbackUrl: $callbackUrl"
|
||||
state[device.id] = [callbackUrl: callbackUrl]
|
||||
}
|
||||
log.debug "Harmony - Adding subscription"
|
||||
log.debug "Adding subscription"
|
||||
def subscription = subscribe(device, attribute, deviceHandler)
|
||||
if (!subscription || !subscription.eventSubscription) {
|
||||
subscription = app.subscriptions?.find { it.device?.device && it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' }
|
||||
@@ -799,7 +766,7 @@ def removeSubscription() {
|
||||
|
||||
log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}"
|
||||
if (device) {
|
||||
log.debug "Harmony - Removing subscription for device: ${device.id}"
|
||||
log.debug "Removing subscription for device: ${device.id}"
|
||||
state.remove(device.id)
|
||||
unsubscribe(device)
|
||||
}
|
||||
@@ -823,17 +790,17 @@ def deviceHandler(evt) {
|
||||
def deviceInfo = state[evt.deviceId]
|
||||
if (state.harmonyHubs) {
|
||||
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)
|
||||
}
|
||||
} else if (deviceInfo) {
|
||||
if (deviceInfo.callbackUrl) {
|
||||
sendToHarmony(evt, deviceInfo.callbackUrl)
|
||||
} else {
|
||||
log.warn "Harmony - No callbackUrl set for device: ${evt.deviceId}"
|
||||
log.warn "No callbackUrl set for device: ${evt.deviceId}"
|
||||
}
|
||||
} else {
|
||||
log.warn "Harmony - No subscribed device found for device: ${evt.deviceId}"
|
||||
log.warn "No subscribed device found for device: ${evt.deviceId}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,12 +824,12 @@ def sendToHarmony(evt, String callbackUrl) {
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
]
|
||||
try {
|
||||
log.debug "Harmony - Sending data to Harmony Cloud: $params"
|
||||
log.debug "Sending data to Harmony Cloud: $params"
|
||||
httpPostJson(params) { resp ->
|
||||
log.debug "Harmony - Cloud Response: ${resp.status}"
|
||||
log.debug "Harmony Cloud - Response: ${resp.status}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "Harmony - Cloud Something went wrong: $e"
|
||||
log.error "Harmony Cloud - Something went wrong: $e"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -887,10 +854,10 @@ def activityCallback() {
|
||||
if (data.errorCode == "200") {
|
||||
device.setCurrentActivity(data.currentActivityId)
|
||||
} else {
|
||||
log.warn "Harmony - Activity callback error: ${data}"
|
||||
log.warn "Activity callback error: ${data}"
|
||||
}
|
||||
} 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"}'
|
||||
}
|
||||
@@ -924,13 +891,13 @@ def harmony() {
|
||||
}
|
||||
|
||||
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 }
|
||||
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)
|
||||
} 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: "{}"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user