mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-22 05:10:52 +00:00
Compare commits
1 Commits
MSA-1502-1
...
DEVC-489-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22bbe08d0c |
@@ -106,7 +106,7 @@ def configure() {
|
|||||||
schedule("0 0/5 * * * ? *", "healthPoll")
|
schedule("0 0/5 * * * ? *", "healthPoll")
|
||||||
log.debug "Configuring Reporting and Bindings."
|
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"])
|
||||||
// minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// 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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// 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"])
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,9 +180,9 @@ private Map parseIasMessage(String description) {
|
|||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return Math.round(celsius)
|
return celsius
|
||||||
} else {
|
} else {
|
||||||
return Math.round(celsiusToFahrenheit(celsius))
|
return celsiusToFahrenheit(celsius) as Integer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +293,7 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// 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"])
|
||||||
|
|
||||||
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."
|
||||||
|
|||||||
@@ -194,9 +194,9 @@ private Map parseIasMessage(String description) {
|
|||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return Math.round(celsius)
|
return celsius
|
||||||
} else {
|
} else {
|
||||||
return Math.round(celsiusToFahrenheit(celsius))
|
return celsiusToFahrenheit(celsius) as Integer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// 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"])
|
||||||
|
|
||||||
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."
|
||||||
|
|||||||
@@ -261,9 +261,9 @@ def updated() {
|
|||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return Math.round(celsius)
|
return celsius
|
||||||
} else {
|
} else {
|
||||||
return Math.round(celsiusToFahrenheit(celsius))
|
return celsiusToFahrenheit(celsius) as Integer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +402,7 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// 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"])
|
||||||
|
|
||||||
log.debug "Configuring Reporting"
|
log.debug "Configuring Reporting"
|
||||||
|
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// 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"])
|
||||||
|
|
||||||
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."
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ def refresh()
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// 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"])
|
||||||
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
def humidityConfigCmds = [
|
def humidityConfigCmds = [
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Tyco Door Window Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Tyco Door Window Sensor](https://support.smartthings.com/hc/en-us/articles/204834100-Tyco-Door-Window-Sensor)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Contact Sensor** - can detect contact (open/close)
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - can measure the device temperature
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
Contact sensor with maxReportTime of 5 mins.
|
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 min
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
3V CR2032 battery is required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that either the sensor needs to be reseted or the sensor is out of range.
|
|
||||||
Reset needs to be done by inserting the battery in the sensor and then quickly pressing the adjacent black button 10 times. Pairing should be tried again now.
|
|
||||||
It may happen that sensor is out of range, then pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
|
|
||||||
for the different models:
|
|
||||||
* [Tyco Door Window Sensor (MCT-340)](https://support.smartthings.com/hc/en-us/articles/204834100-Tyco-Door-Window-Sensor)
|
|
||||||
@@ -16,13 +16,12 @@
|
|||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
@@ -230,42 +229,44 @@ private Map getContactResult(value) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.readAttribute(0x0402, 0x0000) // Read the Temperature Cluster
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh()
|
def refresh()
|
||||||
{
|
{
|
||||||
log.debug "Refreshing Temperature and Battery"
|
log.debug "Refreshing Temperature and Battery"
|
||||||
def refreshCmds = [
|
[
|
||||||
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 2 check-in misses from device
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
def enrollCmds = [
|
def configCmds = [
|
||||||
"delay 1000",
|
"delay 1000",
|
||||||
|
|
||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
|
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
|
|
||||||
//"raw 0x500 {01 23 00 00 00}", "delay 200",
|
//"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||||
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}",
|
||||||
|
|
||||||
|
"delay 500"
|
||||||
]
|
]
|
||||||
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
return configCmds + enrollResponse() + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
def enrollResponse() {
|
def enrollResponse() {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def refresh() {
|
|||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 2 check-in misses from device
|
// Device-Watch allows 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"])
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ def refresh() {
|
|||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 2 check-in misses from device
|
// Device-Watch allows 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"])
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ metadata {
|
|||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "000A", manufacturer: "HAI", model: "65A21-1", deviceJoinName: "Leviton Wireless Load Control Module-30amp"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -79,4 +80,4 @@ def refresh() {
|
|||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
zigbee.onOffConfig() + zigbee.onOffRefresh()
|
zigbee.onOffConfig() + zigbee.onOffRefresh()
|
||||||
}
|
}
|
||||||
@@ -114,7 +114,7 @@ def refresh() {
|
|||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 2 check-in misses from device
|
// Device-Watch allows 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"])
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,372 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
@@ -83,7 +83,7 @@ def bridgeDiscovery(params=[:])
|
|||||||
|
|
||||||
return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) {
|
return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) {
|
||||||
section("Please wait while we discover your Hue Bridge. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
section("Please wait while we discover your Hue Bridge. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options, submitOnChange: true
|
input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,9 +333,9 @@ def bulbListHandler(hub, data = "") {
|
|||||||
def bridge = null
|
def bridge = null
|
||||||
if (selectedHue) {
|
if (selectedHue) {
|
||||||
bridge = getChildDevice(selectedHue)
|
bridge = getChildDevice(selectedHue)
|
||||||
bridge?.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
|
||||||
}
|
}
|
||||||
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
||||||
|
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,25 +490,24 @@ def ssdpBridgeHandler(evt) {
|
|||||||
def host = ip + ":" + port
|
def host = ip + ":" + port
|
||||||
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
|
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
|
||||||
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
|
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
|
||||||
def dniReceived = "${parsedEvent.mac}"
|
def dni = "${parsedEvent.mac}"
|
||||||
def currentDni = dstate.mac
|
def d = getChildDevice(dni)
|
||||||
def d = getChildDevice(dniReceived)
|
|
||||||
def networkAddress = null
|
def networkAddress = null
|
||||||
if (!d) {
|
if (!d) {
|
||||||
// There might be a mismatch between bridge DNI and the actual bridge mac address, correct that
|
childDevices.each {
|
||||||
log.debug "Bridge with $dniReceived not found"
|
if (it.getDeviceDataByName("mac")) {
|
||||||
def bridge = childDevices.find { it.deviceNetworkId == currentDni }
|
def newDNI = "${it.getDeviceDataByName("mac")}"
|
||||||
if (bridge != null) {
|
d = it
|
||||||
log.warn "Bridge is set to ${bridge.deviceNetworkId}, updating to $dniReceived"
|
if (newDNI != it.deviceNetworkId) {
|
||||||
bridge.setDeviceNetworkId("${dniReceived}")
|
def oldDNI = it.deviceNetworkId
|
||||||
dstate.mac = dniReceived
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
||||||
// Check to see if selectedHue is a valid bridge, otherwise update it
|
it.setDeviceNetworkId("${newDNI}")
|
||||||
def isSelectedValid = bridges?.find {it.value?.mac == selectedHue}
|
if (oldDNI == selectedHue) {
|
||||||
if (isSelectedValid == null) {
|
app.updateSetting("selectedHue", newDNI)
|
||||||
log.warn "Correcting selectedHue in state"
|
}
|
||||||
app.updateSetting("selectedHue", dniReceived)
|
doDeviceSync()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
doDeviceSync()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateBridgeStatus(d)
|
updateBridgeStatus(d)
|
||||||
@@ -526,18 +525,6 @@ def ssdpBridgeHandler(evt) {
|
|||||||
d.sendEvent(name:"networkAddress", value: host)
|
d.sendEvent(name:"networkAddress", value: host)
|
||||||
d.updateDataValue("networkAddress", host)
|
d.updateDataValue("networkAddress", host)
|
||||||
}
|
}
|
||||||
if (dstate.mac != dniReceived) {
|
|
||||||
log.warn "Correcting bridge mac address in state"
|
|
||||||
dstate.mac = dniReceived
|
|
||||||
}
|
|
||||||
if (selectedHue != dniReceived) {
|
|
||||||
// Check to see if selectedHue is a valid bridge, otherwise update it
|
|
||||||
def isSelectedValid = bridges?.find {it.value?.mac == selectedHue}
|
|
||||||
if (isSelectedValid == null) {
|
|
||||||
log.warn "Correcting selectedHue in state"
|
|
||||||
app.updateSetting("selectedHue", dniReceived)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -963,14 +950,6 @@ private handleCommandResponse(body) {
|
|||||||
* @return empty array
|
* @return empty array
|
||||||
*/
|
*/
|
||||||
private handlePoll(body) {
|
private handlePoll(body) {
|
||||||
// Used to track "unreachable" time
|
|
||||||
// Device is considered "offline" if it has been in the "unreachable" state for
|
|
||||||
// 11 minutes (e.g. two poll intervals)
|
|
||||||
// Note, Hue Bridge marks devices as "unreachable" often even when they accept commands
|
|
||||||
Calendar time11 = Calendar.getInstance()
|
|
||||||
time11.add(Calendar.MINUTE, -11)
|
|
||||||
Calendar currentTime = Calendar.getInstance()
|
|
||||||
|
|
||||||
def bulbs = getChildDevices()
|
def bulbs = getChildDevices()
|
||||||
for (bulb in body) {
|
for (bulb in body) {
|
||||||
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
||||||
@@ -980,10 +959,7 @@ private handlePoll(body) {
|
|||||||
// light just came back online, notify device watch
|
// light just came back online, notify device watch
|
||||||
def lastActivity = now()
|
def lastActivity = now()
|
||||||
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
|
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
|
||||||
log.debug "$device is Online"
|
|
||||||
}
|
}
|
||||||
// Mark light as "online"
|
|
||||||
state.bulbs[bulb.key]?.unreachableSince = null
|
|
||||||
state.bulbs[bulb.key]?.online = true
|
state.bulbs[bulb.key]?.online = true
|
||||||
|
|
||||||
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
|
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
|
||||||
@@ -993,18 +969,9 @@ private handlePoll(body) {
|
|||||||
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
|
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (state.bulbs[bulb.key]?.unreachableSince == null) {
|
state.bulbs[bulb.key]?.online = false
|
||||||
// Store the first time where device was reported as "unreachable"
|
log.warn "$device is not reachable by Hue bridge"
|
||||||
state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis()
|
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
|
||||||
} else if (state.bulbs[bulb.key]?.online) {
|
|
||||||
// Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary
|
|
||||||
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) {
|
|
||||||
log.warn "$device went Offline"
|
|
||||||
state.bulbs[bulb.key]?.online = false
|
|
||||||
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.warn "$device may not reachable by Hue bridge"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1039,6 +1006,9 @@ def hubVerification(bodytext) {
|
|||||||
def on(childDevice) {
|
def on(childDevice) {
|
||||||
log.debug "Executing 'on'"
|
log.debug "Executing 'on'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
|
if (!isOnline(id)) {
|
||||||
|
return "Bulb is unreachable"
|
||||||
|
}
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
createSwitchEvent(childDevice, "on")
|
createSwitchEvent(childDevice, "on")
|
||||||
put("lights/$id/state", [on: true])
|
put("lights/$id/state", [on: true])
|
||||||
@@ -1048,6 +1018,9 @@ def on(childDevice) {
|
|||||||
def off(childDevice) {
|
def off(childDevice) {
|
||||||
log.debug "Executing 'off'"
|
log.debug "Executing 'off'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
|
if (!isOnline(id)) {
|
||||||
|
return "Bulb is unreachable"
|
||||||
|
}
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
createSwitchEvent(childDevice, "off")
|
createSwitchEvent(childDevice, "off")
|
||||||
put("lights/$id/state", [on: false])
|
put("lights/$id/state", [on: false])
|
||||||
@@ -1057,6 +1030,9 @@ def off(childDevice) {
|
|||||||
def setLevel(childDevice, percent) {
|
def setLevel(childDevice, percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
|
if (!isOnline(id)) {
|
||||||
|
return "Bulb is unreachable"
|
||||||
|
}
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 1 - 254
|
// 1 - 254
|
||||||
def level
|
def level
|
||||||
@@ -1081,6 +1057,10 @@ def setLevel(childDevice, percent) {
|
|||||||
def setSaturation(childDevice, percent) {
|
def setSaturation(childDevice, percent) {
|
||||||
log.debug "Executing 'setSaturation($percent)'"
|
log.debug "Executing 'setSaturation($percent)'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
|
if (!isOnline(id)) {
|
||||||
|
return "Bulb is unreachable"
|
||||||
|
}
|
||||||
|
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 0 - 254
|
// 0 - 254
|
||||||
def level = Math.min(Math.round(percent * 254 / 100), 254)
|
def level = Math.min(Math.round(percent * 254 / 100), 254)
|
||||||
@@ -1093,6 +1073,9 @@ def setSaturation(childDevice, percent) {
|
|||||||
def setHue(childDevice, percent) {
|
def setHue(childDevice, percent) {
|
||||||
log.debug "Executing 'setHue($percent)'"
|
log.debug "Executing 'setHue($percent)'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
|
if (!isOnline(id)) {
|
||||||
|
return "Bulb is unreachable"
|
||||||
|
}
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 0 - 65535
|
// 0 - 65535
|
||||||
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
|
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
|
||||||
@@ -1105,6 +1088,9 @@ def setHue(childDevice, percent) {
|
|||||||
def setColorTemperature(childDevice, huesettings) {
|
def setColorTemperature(childDevice, huesettings) {
|
||||||
log.debug "Executing 'setColorTemperature($huesettings)'"
|
log.debug "Executing 'setColorTemperature($huesettings)'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
|
if (!isOnline(id)) {
|
||||||
|
return "Bulb is unreachable"
|
||||||
|
}
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 153 (6500K) to 500 (2000K)
|
// 153 (6500K) to 500 (2000K)
|
||||||
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
|
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
|
||||||
@@ -1116,6 +1102,9 @@ def setColorTemperature(childDevice, huesettings) {
|
|||||||
def setColor(childDevice, huesettings) {
|
def setColor(childDevice, huesettings) {
|
||||||
log.debug "Executing 'setColor($huesettings)'"
|
log.debug "Executing 'setColor($huesettings)'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
|
if (!isOnline(id)) {
|
||||||
|
return "Bulb is unreachable"
|
||||||
|
}
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
|
|
||||||
def value = [:]
|
def value = [:]
|
||||||
@@ -1131,7 +1120,7 @@ def setColor(childDevice, huesettings) {
|
|||||||
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||||
if (huesettings.saturation != null)
|
if (huesettings.saturation != null)
|
||||||
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
|
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
|
||||||
} else if (huesettings.hex != null) {
|
} else if (huesettings.hex != null && false) {
|
||||||
// For now ignore model to get a consistent color if same color is set across multiple devices
|
// For now ignore model to get a consistent color if same color is set across multiple devices
|
||||||
// def model = state.bulbs[getId(childDevice)]?.modelid
|
// def model = state.bulbs[getId(childDevice)]?.modelid
|
||||||
// value.xy = calculateXY(huesettings.hex, model)
|
// value.xy = calculateXY(huesettings.hex, model)
|
||||||
@@ -1235,7 +1224,7 @@ private getBridgeIP() {
|
|||||||
if (d) {
|
if (d) {
|
||||||
if (d.getDeviceDataByName("networkAddress"))
|
if (d.getDeviceDataByName("networkAddress"))
|
||||||
host = d.getDeviceDataByName("networkAddress")
|
host = d.getDeviceDataByName("networkAddress")
|
||||||
else
|
else
|
||||||
host = d.latestState('networkAddress').stringValue
|
host = d.latestState('networkAddress').stringValue
|
||||||
}
|
}
|
||||||
if (host == null || host == "") {
|
if (host == null || host == "") {
|
||||||
@@ -1674,7 +1663,7 @@ private boolean checkPointInLampsReach(p, colorPoints) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an RGB color in hex to HSV/HSB.
|
* Converts an RGB color in hex to HSV.
|
||||||
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
|
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
|
||||||
*
|
*
|
||||||
* @param colorStr color value in hex (#ff03d3)
|
* @param colorStr color value in hex (#ff03d3)
|
||||||
@@ -1684,32 +1673,32 @@ private boolean checkPointInLampsReach(p, colorPoints) {
|
|||||||
def hexToHsv(colorStr){
|
def hexToHsv(colorStr){
|
||||||
def r = Integer.valueOf( colorStr.substring( 1, 3 ), 16 ) / 255
|
def r = Integer.valueOf( colorStr.substring( 1, 3 ), 16 ) / 255
|
||||||
def g = Integer.valueOf( colorStr.substring( 3, 5 ), 16 ) / 255
|
def g = Integer.valueOf( colorStr.substring( 3, 5 ), 16 ) / 255
|
||||||
def b = Integer.valueOf( colorStr.substring( 5, 7 ), 16 ) / 255
|
def b = Integer.valueOf( colorStr.substring( 5, 7 ), 16 ) / 255;
|
||||||
|
|
||||||
def max = Math.max(Math.max(r, g), b)
|
def max = Math.max(Math.max(r, g), b)
|
||||||
def min = Math.min(Math.min(r, g), b)
|
def min = Math.min(Math.min(r, g), b)
|
||||||
|
|
||||||
def h, s, v = max
|
def h, s, v = max;
|
||||||
|
|
||||||
def d = max - min
|
def d = max - min;
|
||||||
s = max == 0 ? 0 : d / max
|
s = max == 0 ? 0 : d / max;
|
||||||
|
|
||||||
if(max == min){
|
if(max == min){
|
||||||
h = 0
|
h = 0;
|
||||||
}else{
|
}else{
|
||||||
switch(max){
|
switch(max){
|
||||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
case g: h = (b - r) / d + 2; break
|
case g: h = (b - r) / d + 2; break;
|
||||||
case b: h = (r - g) / d + 4; break
|
case b: h = (r - g) / d + 4; break;
|
||||||
}
|
}
|
||||||
h /= 6;
|
h /= 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [Math.round(h * 100), Math.round(s * 100), Math.round(v * 100)]
|
return [(h * 100).round(), (s * 100).round(), (v * 100).round()];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts HSV/HSB color to RGB in hex.
|
* Converts HSV color to RGB in hex.
|
||||||
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
|
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
|
||||||
*
|
*
|
||||||
* @param hue hue 0-100
|
* @param hue hue 0-100
|
||||||
@@ -1724,11 +1713,11 @@ def hsvToHex(hue, sat, value = 100){
|
|||||||
def s = sat / 100
|
def s = sat / 100
|
||||||
def v = value / 100
|
def v = value / 100
|
||||||
|
|
||||||
def i = Math.floor(h * 6)
|
def i = Math.floor(h * 6);
|
||||||
def f = h * 6 - i
|
def f = h * 6 - i;
|
||||||
def p = v * (1 - s)
|
def p = v * (1 - s);
|
||||||
def q = v * (1 - f * s)
|
def q = v * (1 - f * s);
|
||||||
def t = v * (1 - (1 - f) * s)
|
def t = v * (1 - (1 - f) * s);
|
||||||
|
|
||||||
switch (i % 6) {
|
switch (i % 6) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -1764,9 +1753,9 @@ def hsvToHex(hue, sat, value = 100){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Converting float components to int components.
|
// Converting float components to int components.
|
||||||
def r1 = String.format("%02X", (int) (r * 255.0f))
|
def r1 = String.format("%02X", (int) (r * 255.0f));
|
||||||
def g1 = String.format("%02X", (int) (g * 255.0f))
|
def g1 = String.format("%02X", (int) (g * 255.0f));
|
||||||
def b1 = String.format("%02X", (int) (b * 255.0f))
|
def b1 = String.format("%02X", (int) (b * 255.0f));
|
||||||
|
|
||||||
return "#$r1$g1$b1"
|
return "#$r1$g1$b1"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user