mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-15 13:10:51 +00:00
Compare commits
191 Commits
netatmo-ap
...
MSA-1748-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
387e59a9bb | ||
|
|
54da556c17 | ||
|
|
8611d2e2d2 | ||
|
|
c650047f31 | ||
|
|
ded7501b84 | ||
|
|
eb4d5dcfb8 | ||
|
|
6385443f20 | ||
|
|
fd1ad51880 | ||
|
|
550214ceb5 | ||
|
|
a36500a216 | ||
|
|
445c115c14 | ||
|
|
9900e532a4 | ||
|
|
7648fd4a17 | ||
|
|
9686f3770b | ||
|
|
de37d0c813 | ||
|
|
bd3367fe0e | ||
|
|
30511d74af | ||
|
|
f0f02a2c00 | ||
|
|
57d20e2fca | ||
|
|
3216f63cc0 | ||
|
|
7cbc2d1780 | ||
|
|
5ad20fbd2a | ||
|
|
a17971d68c | ||
|
|
076ffecd19 | ||
|
|
52357e4c50 | ||
|
|
03a7991279 | ||
|
|
f969027191 | ||
|
|
8db0556696 | ||
|
|
5607a3e346 | ||
|
|
bd44027038 | ||
|
|
490ec329cb | ||
|
|
3109049122 | ||
|
|
930c4ed914 | ||
|
|
259516f21f | ||
|
|
b7a08a88e0 | ||
|
|
c028515fcd | ||
|
|
751c98d123 | ||
|
|
5b874e8f3a | ||
|
|
9e10405527 | ||
|
|
32ceaff54d | ||
|
|
5b1da30a47 | ||
|
|
76e139153a | ||
|
|
a4bc248006 | ||
|
|
e7eb461b4e | ||
|
|
0a82077b24 | ||
|
|
083ed7cc9a | ||
|
|
38ef9e5c77 | ||
|
|
6a71615ca5 | ||
|
|
9939591005 | ||
|
|
d7f2bc1d79 | ||
|
|
3c5d727d4c | ||
|
|
bbad6dfa7a | ||
|
|
df6c815aa4 | ||
|
|
d16ac00eb6 | ||
|
|
1941f56007 | ||
|
|
f34df19a65 | ||
|
|
af40246655 | ||
|
|
20239ca982 | ||
|
|
b20c0e371f | ||
|
|
60756e6dc6 | ||
|
|
ee2e1ee04f | ||
|
|
e443cb641c | ||
|
|
b9993a9bf2 | ||
|
|
092971c786 | ||
|
|
a8ee927893 | ||
|
|
51979f0030 | ||
|
|
61d1205e7d | ||
|
|
1dfd1818b4 | ||
|
|
1de73b643c | ||
|
|
e5c21ef720 | ||
|
|
fcb504f57e | ||
|
|
b9229c6ef8 | ||
|
|
91a9856a32 | ||
|
|
a84ffdde91 | ||
|
|
3034cc8bcb | ||
|
|
918e9d9397 | ||
|
|
0c040120cc | ||
|
|
6c84c052cb | ||
|
|
f017bff6ef | ||
|
|
65bb10d6d6 | ||
|
|
3f93de247b | ||
|
|
2b7af3ef8d | ||
|
|
cf9d123aa0 | ||
|
|
8e37b1fce7 | ||
|
|
439dd634bd | ||
|
|
1db5f75ec5 | ||
|
|
7978f45996 | ||
|
|
0ab657d5f2 | ||
|
|
3d88fc0413 | ||
|
|
ede1296b6b | ||
|
|
470cdc7d97 | ||
|
|
9bc3ff5103 | ||
|
|
bae79192c5 | ||
|
|
0ae836b023 | ||
|
|
0b4d555d33 | ||
|
|
0f8beee455 | ||
|
|
6ffdc02ef1 | ||
|
|
6beb8bb50c | ||
|
|
83a9df6557 | ||
|
|
af8590ab01 | ||
|
|
9599397db8 | ||
|
|
f7dbabb6c8 | ||
|
|
7016e234d2 | ||
|
|
cf119b1d15 | ||
|
|
3e88f3c4bd | ||
|
|
8ca20ce87e | ||
|
|
c1d520a578 | ||
|
|
c0bb0554d8 | ||
|
|
b6790729c6 | ||
|
|
3675332b75 | ||
|
|
7431346187 | ||
|
|
6aa0ff97b3 | ||
|
|
bd1ace96de | ||
|
|
40c4520d08 | ||
|
|
1f69a42341 | ||
|
|
969852602c | ||
|
|
4115d8c65f | ||
|
|
492315b78a | ||
|
|
40acb36009 | ||
|
|
0a040aa51b | ||
|
|
8986c4f5d6 | ||
|
|
58d8a7dac5 | ||
|
|
a98d3dc2d6 | ||
|
|
3a377ba147 | ||
|
|
2d25a0e63f | ||
|
|
633a179074 | ||
|
|
49bc42b4ab | ||
|
|
d258c46aee | ||
|
|
8a66742bb5 | ||
|
|
add519433c | ||
|
|
f6dcaf6d09 | ||
|
|
b07b34f66c | ||
|
|
96659f0a73 | ||
|
|
699f80e9f7 | ||
|
|
d21dfc09fe | ||
|
|
d196125092 | ||
|
|
44088d626a | ||
|
|
2966c4d5a1 | ||
|
|
3343273d40 | ||
|
|
5c70da54a4 | ||
|
|
687c64d29d | ||
|
|
c9d1b168f7 | ||
|
|
2f87309fdf | ||
|
|
37524f17b2 | ||
|
|
47522facc7 | ||
|
|
4363661157 | ||
|
|
330b41941a | ||
|
|
26d286e0a0 | ||
|
|
ef2323f1b1 | ||
|
|
51452bc095 | ||
|
|
b7b29d8dbc | ||
|
|
b8111e8760 | ||
|
|
24ea8269a3 | ||
|
|
20df244dca | ||
|
|
583d42df13 | ||
|
|
f1309b2ee2 | ||
|
|
ec1ae2d0b1 | ||
|
|
5e48e710d4 | ||
|
|
07c5a3533f | ||
|
|
72b2016b7d | ||
|
|
a9aee8fd96 | ||
|
|
5c015cf678 | ||
|
|
cf1a46e309 | ||
|
|
7c5438880d | ||
|
|
d9888b3184 | ||
|
|
b582c3d832 | ||
|
|
1ff77dc608 | ||
|
|
afbec02217 | ||
|
|
434a72bd13 | ||
|
|
3fba7c9422 | ||
|
|
b63d4a9156 | ||
|
|
6eb29ad019 | ||
|
|
711cdc3ebf | ||
|
|
f58a1ef589 | ||
|
|
db5237ca33 | ||
|
|
7791c68a8a | ||
|
|
db4140ffd6 | ||
|
|
c15b1e88e1 | ||
|
|
ac422076c8 | ||
|
|
94f57dd249 | ||
|
|
c11c146690 | ||
|
|
9a5d506668 | ||
|
|
b12df3f360 | ||
|
|
9dac541473 | ||
|
|
a6cc506803 | ||
|
|
aba8a7ad4b | ||
|
|
b4c912ab80 | ||
|
|
4b44460b0b | ||
|
|
9f5eb7b85a | ||
|
|
6a1a2b0ed9 | ||
|
|
aae7f23a22 |
@@ -1,10 +1,10 @@
|
|||||||
# SmartThings Public Github Repo
|
# SmartThings Public GitHub Repo
|
||||||
|
|
||||||
An official list of SmartApps and Device Types from SmartThings.
|
An official list of SmartApps and Device Types from SmartThings.
|
||||||
|
|
||||||
Here are some links to help you get started coding right away:
|
Here are some links to help you get started coding right away:
|
||||||
|
|
||||||
* [Github-specific Documentation](http://docs.smartthings.com/en/latest/tools-and-ide/github-integration.html)
|
* [GitHub-specific Documentation](http://docs.smartthings.com/en/latest/tools-and-ide/github-integration.html)
|
||||||
* [Full Documentation](http://docs.smartthings.com)
|
* [Full Documentation](http://docs.smartthings.com)
|
||||||
* [IDE & Simulator](http://ide.smartthings.com)
|
* [IDE & Simulator](http://ide.smartthings.com)
|
||||||
* [Community Forums](http://community.smartthings.com)
|
* [Community Forums](http://community.smartthings.com)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ metadata {
|
|||||||
definition (name: "Aeon Key Fob", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Aeon Key Fob", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Button"
|
capability "Button"
|
||||||
|
capability "Holdable Button"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
@@ -118,3 +119,16 @@ def configure() {
|
|||||||
log.debug("Sending configuration: $cmd")
|
log.debug("Sending configuration: $cmd")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
sendEvent(name: "numberOfButtons", value: 4)
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ metadata {
|
|||||||
definition (name: "Aeon Minimote", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Aeon Minimote", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Button"
|
capability "Button"
|
||||||
|
capability "Holdable Button"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
@@ -107,3 +108,16 @@ def configure() {
|
|||||||
log.debug("Sending configuration: $cmds")
|
log.debug("Sending configuration: $cmds")
|
||||||
return cmds
|
return cmds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
sendEvent(name: "numberOfButtons", value: 4)
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Music Player"
|
capability "Music Player"
|
||||||
capability "Polling"
|
capability "Health Check"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define all commands, ie, if you have a custom action not
|
* Define all commands, ie, if you have a custom action not
|
||||||
@@ -236,7 +236,33 @@ def parse(String event) {
|
|||||||
* @return action(s) to take or null
|
* @return action(s) to take or null
|
||||||
*/
|
*/
|
||||||
def installed() {
|
def installed() {
|
||||||
onAction("refresh")
|
// Notify health check about this device with timeout interval 12 minutes
|
||||||
|
sendEvent(name: "checkInterval", value: 12 * 60, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
||||||
|
startPoll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by health check if no events been generated in the last 12 minutes
|
||||||
|
* If device doesn't respond it will be marked offline (not available)
|
||||||
|
*/
|
||||||
|
def ping() {
|
||||||
|
TRACE("ping")
|
||||||
|
boseSendGetNowPlaying()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a 2 minute poll of the device to refresh the
|
||||||
|
* tiles so the user gets the correct information.
|
||||||
|
*/
|
||||||
|
def startPoll() {
|
||||||
|
TRACE("startPoll")
|
||||||
|
unschedule()
|
||||||
|
// Schedule 2 minute polling of speaker status (song average length is 3-4 minutes)
|
||||||
|
def sec = Math.round(Math.floor(Math.random() * 60))
|
||||||
|
//def cron = "$sec 0/5 * * * ?" // every 5 min
|
||||||
|
def cron = "$sec 0/2 * * * ?" // every 2 min
|
||||||
|
log.debug "schedule('$cron', boseSendGetNowPlaying)"
|
||||||
|
schedule(cron, boseSendGetNowPlaying)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -316,14 +342,6 @@ def onAction(String user, data=null) {
|
|||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called every so often (every 5 minutes actually) to refresh the
|
|
||||||
* tiles so the user gets the correct information.
|
|
||||||
*/
|
|
||||||
def poll() {
|
|
||||||
return boseRefreshNowPlaying()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Joins this speaker into the everywhere zone
|
* Joins this speaker into the everywhere zone
|
||||||
*/
|
*/
|
||||||
@@ -837,6 +855,10 @@ def boseRefreshNowPlaying(delay=0) {
|
|||||||
return boseGET("/now_playing")
|
return boseGET("/now_playing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def boseSendGetNowPlaying() {
|
||||||
|
sendHubCommand(boseGET("/now_playing"))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the list of presets
|
* Requests the list of presets
|
||||||
*
|
*
|
||||||
@@ -1014,4 +1036,8 @@ def boseGetDeviceID() {
|
|||||||
*/
|
*/
|
||||||
def getDeviceIP() {
|
def getDeviceIP() {
|
||||||
return parent.resolveDNI2Address(device.deviceNetworkId)
|
return parent.resolveDNI2Address(device.deviceNetworkId)
|
||||||
|
}
|
||||||
|
|
||||||
|
def TRACE(text) {
|
||||||
|
log.trace "${text}"
|
||||||
}
|
}
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 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.
|
|
||||||
*
|
|
||||||
* CentraLite Dimmer
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
* Date: 2013-12-04
|
|
||||||
*/
|
|
||||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
metadata {
|
|
||||||
definition (name: "CentraLite Dimmer", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Power Meter"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
status "on": "on/off: 1"
|
|
||||||
status "off": "on/off: 0"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "zcl on-off on": "on/off: 1"
|
|
||||||
reply "zcl on-off off": "on/off: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
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.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "power", label:'${currentValue} W'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "switch"
|
|
||||||
details(["switch","refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parse description $description"
|
|
||||||
def name = null
|
|
||||||
def value = null
|
|
||||||
if (description?.startsWith("catchall:")) {
|
|
||||||
def msg = zigbee.parse(description)
|
|
||||||
log.trace msg
|
|
||||||
log.trace "data: $msg.data"
|
|
||||||
} else if (description?.startsWith("read attr -")) {
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
log.debug "Read attr: $description"
|
|
||||||
if (descMap.cluster == "0006" && descMap.attrId == "0000") {
|
|
||||||
name = "switch"
|
|
||||||
value = descMap.value.endsWith("01") ? "on" : "off"
|
|
||||||
} else {
|
|
||||||
def reportValue = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()
|
|
||||||
name = "power"
|
|
||||||
// assume 16 bit signed for encoding and power divisor is 10
|
|
||||||
value = Integer.parseInt(reportValue, 16) / 10
|
|
||||||
}
|
|
||||||
} else if (description?.startsWith("on/off:")) {
|
|
||||||
log.debug "Switch command"
|
|
||||||
name = "switch"
|
|
||||||
value = description?.endsWith(" 1") ? "on" : "off"
|
|
||||||
}
|
|
||||||
|
|
||||||
def result = createEvent(name: name, value: value)
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands to device
|
|
||||||
def on() {
|
|
||||||
[
|
|
||||||
'zcl on-off on',
|
|
||||||
'delay 200',
|
|
||||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
|
||||||
'delay 500'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
[
|
|
||||||
'zcl on-off off',
|
|
||||||
'delay 200',
|
|
||||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
|
||||||
'delay 500'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
log.trace "setLevel($value)"
|
|
||||||
sendEvent(name: "level", value: value)
|
|
||||||
def level = hexString(Math.round(value * 255/100))
|
|
||||||
def cmd = "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 2000}"
|
|
||||||
log.debug cmd
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def meter() {
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0xB04 0x50B"
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0xB04 0x50B"
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
[
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0xB04 {${device.zigbeeId}} {}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private hex(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
@@ -81,51 +81,47 @@ metadata {
|
|||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "Parse description $description"
|
log.debug "Parse description $description"
|
||||||
def map = [:]
|
List result = []
|
||||||
if (description?.startsWith("read attr -")) {
|
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||||
def descMap = parseDescriptionAsMap(description)
|
log.debug "Desc Map: $descMap"
|
||||||
log.debug "Desc Map: $descMap"
|
List attrData = [[cluster: descMap.cluster ,attrId: descMap.attrId, value: descMap.value]]
|
||||||
if (descMap.cluster == "0201" && descMap.attrId == "0000") {
|
descMap.additionalAttrs.each {
|
||||||
|
attrData << [cluster: descMap.cluster, attrId: it.attrId, value: it.value]
|
||||||
|
}
|
||||||
|
attrData.each {
|
||||||
|
def map = [:]
|
||||||
|
if (it.cluster == "0201" && it.attrId == "0000") {
|
||||||
log.debug "TEMP"
|
log.debug "TEMP"
|
||||||
map.name = "temperature"
|
map.name = "temperature"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(it.value)
|
||||||
map.unit = temperatureScale
|
map.unit = temperatureScale
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
|
} else if (it.cluster == "0201" && it.attrId == "0011") {
|
||||||
log.debug "COOLING SETPOINT"
|
log.debug "COOLING SETPOINT"
|
||||||
map.name = "coolingSetpoint"
|
map.name = "coolingSetpoint"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(it.value)
|
||||||
map.unit = temperatureScale
|
map.unit = temperatureScale
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
|
} else if (it.cluster == "0201" && it.attrId == "0012") {
|
||||||
log.debug "HEATING SETPOINT"
|
log.debug "HEATING SETPOINT"
|
||||||
map.name = "heatingSetpoint"
|
map.name = "heatingSetpoint"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(it.value)
|
||||||
map.unit = temperatureScale
|
map.unit = temperatureScale
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
|
} else if (it.cluster == "0201" && it.attrId == "001c") {
|
||||||
log.debug "MODE"
|
log.debug "MODE"
|
||||||
map.name = "thermostatMode"
|
map.name = "thermostatMode"
|
||||||
map.value = getModeMap()[descMap.value]
|
map.value = getModeMap()[it.value]
|
||||||
} else if (descMap.cluster == "0202" && descMap.attrId == "0000") {
|
} else if (it.cluster == "0202" && it.attrId == "0000") {
|
||||||
log.debug "FAN MODE"
|
log.debug "FAN MODE"
|
||||||
map.name = "thermostatFanMode"
|
map.name = "thermostatFanMode"
|
||||||
map.value = getFanModeMap()[descMap.value]
|
map.value = getFanModeMap()[it.value]
|
||||||
}
|
}
|
||||||
|
if (map) {
|
||||||
|
result << createEvent(map)
|
||||||
|
}
|
||||||
|
log.debug "Parse returned $map"
|
||||||
}
|
}
|
||||||
|
|
||||||
def result = null
|
|
||||||
if (map) {
|
|
||||||
result = createEvent(map)
|
|
||||||
}
|
|
||||||
log.debug "Parse returned $map"
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getModeMap() { [
|
def getModeMap() { [
|
||||||
"00":"off",
|
"00":"off",
|
||||||
"03":"cool",
|
"03":"cool",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
capability "Button"
|
capability "Button"
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
|
|
||||||
//fingerprint deviceId: "0x1200", inClusters: "0x77 0x86 0x75 0x73 0x85 0x72 0xEF", outClusters: "0x26"
|
//fingerprint deviceId: "0x1200", inClusters: "0x77 0x86 0x75 0x73 0x85 0x72 0xEF", outClusters: "0x26"
|
||||||
}
|
}
|
||||||
@@ -74,20 +74,20 @@ def off() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def levelup() {
|
def levelup() {
|
||||||
def curlevel = device.currentValue('level') as Integer
|
def curlevel = device.currentValue('level') as Integer
|
||||||
if (curlevel <= 90)
|
if (curlevel <= 90)
|
||||||
setLevel(curlevel + 10);
|
setLevel(curlevel + 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
def leveldown() {
|
def leveldown() {
|
||||||
def curlevel = device.currentValue('level') as Integer
|
def curlevel = device.currentValue('level') as Integer
|
||||||
if (curlevel >= 10)
|
if (curlevel >= 10)
|
||||||
setLevel(curlevel - 10)
|
setLevel(curlevel - 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
log.trace "setLevel($value)"
|
log.trace "setLevel($value)"
|
||||||
sendEvent(name: "level", value: value)
|
sendEvent(name: "level", value: value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) {
|
||||||
@@ -106,11 +106,11 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelS
|
|||||||
if (cmd.upDown == true) {
|
if (cmd.upDown == true) {
|
||||||
Integer buttonid = 2
|
Integer buttonid = 2
|
||||||
leveldown()
|
leveldown()
|
||||||
checkbuttonEvent(buttonid)
|
checkbuttonEvent(buttonid)
|
||||||
} else if (cmd.upDown == false) {
|
} else if (cmd.upDown == false) {
|
||||||
Integer buttonid = 3
|
Integer buttonid = 3
|
||||||
levelup()
|
levelup()
|
||||||
checkbuttonEvent(buttonid)
|
checkbuttonEvent(buttonid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,12 +140,12 @@ def buttonEvent(button) {
|
|||||||
def result = []
|
def result = []
|
||||||
if (button == 1) {
|
if (button == 1) {
|
||||||
def mystate = device.currentValue('switch');
|
def mystate = device.currentValue('switch');
|
||||||
if (mystate == "on")
|
if (mystate == "on")
|
||||||
off()
|
off()
|
||||||
else
|
else
|
||||||
on()
|
on()
|
||||||
}
|
}
|
||||||
updateState("currentButton", "$button")
|
updateState("currentButton", "$button")
|
||||||
// update the device state, recording the button press
|
// update the device state, recording the button press
|
||||||
result << createEvent(name: "button", value: "pushed", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was pushed", isStateChange: true)
|
result << createEvent(name: "button", value: "pushed", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was pushed", isStateChange: true)
|
||||||
result
|
result
|
||||||
@@ -182,3 +182,16 @@ def updateState(String name, String value) {
|
|||||||
state[name] = value
|
state[name] = value
|
||||||
device.updateDataValue(name, value)
|
device.updateDataValue(name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
sendEvent(name: "numberOfButtons", value: 3)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Connected Cree LED Bulb
|
# Connected Cree LED Bulb
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -23,8 +23,10 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C6 Connected Cree LED Bulb with maxReportTime of 5 mins.
|
Connected Cree LED Bulb with cloud polling it every __5min__
|
||||||
Check-in interval = 12 mins
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
|
|
||||||
|
* __12min__ checkInterval
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ def healthPoll() {
|
|||||||
def configure() {
|
def configure() {
|
||||||
unschedule()
|
unschedule()
|
||||||
runEvery5Minutes("healthPoll")
|
runEvery5Minutes("healthPoll")
|
||||||
// Device-Watch allows 2 check-in misses from device
|
// Device-Watch allows 2 check-in misses from device + ping
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
}
|
}
|
||||||
|
|||||||
2
devicetypes/smartthings/dimmer-switch.src/.st-ignore
Normal file
2
devicetypes/smartthings/dimmer-switch.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
47
devicetypes/smartthings/dimmer-switch.src/README.md
Normal file
47
devicetypes/smartthings/dimmer-switch.src/README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Z-wave Dimmer Switch
|
||||||
|
|
||||||
|
Local Execution on V2 Hubs
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [GE Z-Wave In-Wall Smart Dimmer (GE 12724)](http://products.z-wavealliance.org/products/1197)
|
||||||
|
* [GE Z-Wave In-Wall Smart Dimmer (Toggle) (GE 12729)](http://products.z-wavealliance.org/products/1201)
|
||||||
|
* [GE Z-Wave Plug-in Smart Dimmer (GE 12718)](http://products.z-wavealliance.org/products/1191)
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health](#device-health)
|
||||||
|
* [Troubleshooting](#Troubleshooting)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Switch Level** - it's defined to accept two parameters, the level and the rate of dimming
|
||||||
|
* **Actuator** - represents that a Device has commands
|
||||||
|
* **Indicator** - gives you the ability to set the indicator LED light on a Z-Wave switch
|
||||||
|
* **Switch** - can detect state (possible values: on/off)
|
||||||
|
* **Polling** - represents that poll() can be implemented for the device
|
||||||
|
* **Refresh** - _refresh()_ command for status updates
|
||||||
|
* **Sensor** - detects sensor events
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
Z-Wave Smart Dimmers (In-Wall, In-Wall(Toggle), Plug-In) are polled by the hub.
|
||||||
|
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
|
||||||
|
Check-in interval = 32 mins.
|
||||||
|
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
|
||||||
|
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
|
||||||
|
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
|
||||||
|
|
||||||
|
* __32min__ checkInterval
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||||
|
Pairing needs to be tried again by placing the device closer to the hub.
|
||||||
|
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||||
|
* [General Z-Wave Dimmer/Switch Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200955890-Troubleshooting-GE-in-wall-switch-or-dimmer-won-t-respond-to-commands-or-automations-Z-Wave-)
|
||||||
|
* [GE Z-Wave In-Wall Smart Dimmer (GE 12724) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200902600-GE-In-Wall-Paddle-Dimmer-Switch-GE-12724-Z-Wave-)
|
||||||
|
* [GE Z-Wave In-Wall Smart Dimmer (Toggle) (GE 12729) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/207568463-GE-In-Wall-Smart-Toggle-Dimmer-GE-12729-Z-Wave-)
|
||||||
|
* [GE Z-Wave Plug-in Smart Dimmer (GE 12718) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202088474-GE-Plug-In-Smart-Dimmer-GE-12718-Z-Wave-)
|
||||||
@@ -20,10 +20,12 @@ metadata {
|
|||||||
capability "Polling"
|
capability "Polling"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "Z-Wave Wall Dimmer"
|
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "GE In-Wall Smart Dimmer "
|
||||||
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "Z-Wave Wall Dimmer"
|
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "GE In-Wall Smart Dimmer "
|
||||||
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "Z-Wave Plug-In Dimmer"
|
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "GE Plug-In Smart Dimmer "
|
||||||
|
fingerprint mfr:"0063", prod:"4944", model:"3034", deviceJoinName: "GE In-Wall Smart Fan Control"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -82,6 +84,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def updated(){
|
def updated(){
|
||||||
|
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||||
|
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||||
switch (ledIndicator) {
|
switch (ledIndicator) {
|
||||||
case "on":
|
case "on":
|
||||||
indicatorWhenOn()
|
indicatorWhenOn()
|
||||||
@@ -215,6 +219,13 @@ def poll() {
|
|||||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
* */
|
||||||
|
def ping() {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "refresh() is called"
|
log.debug "refresh() is called"
|
||||||
def commands = []
|
def commands = []
|
||||||
@@ -226,17 +237,17 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOn() {
|
void indicatorWhenOn() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
sendEvent(name: "indicatorStatus", value: "when on", displayed: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOff() {
|
void indicatorWhenOff() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
sendEvent(name: "indicatorStatus", value: "when off", displayed: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorNever() {
|
void indicatorNever() {
|
||||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
sendEvent(name: "indicatorStatus", value: "never", displayed: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ metadata {
|
|||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
command "generateEvent"
|
command "generateEvent"
|
||||||
command "raiseSetpoint"
|
command "raiseSetpoint"
|
||||||
@@ -38,6 +39,7 @@ metadata {
|
|||||||
attribute "maxCoolingSetpoint", "number"
|
attribute "maxCoolingSetpoint", "number"
|
||||||
attribute "minCoolingSetpoint", "number"
|
attribute "minCoolingSetpoint", "number"
|
||||||
attribute "deviceTemperatureUnit", "string"
|
attribute "deviceTemperatureUnit", "string"
|
||||||
|
attribute "deviceAlive", "enum", ["true", "false"]
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles {
|
tiles {
|
||||||
@@ -120,6 +122,21 @@ metadata {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void installed() {
|
||||||
|
// The device refreshes every 5 minutes by default so if we miss 2 refreshes we can consider it offline
|
||||||
|
// Using 12 minutes because in testing, device health team found that there could be "jitter"
|
||||||
|
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "cloud"], displayed: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device Watch will ping the device to proactively determine if the device has gone offline
|
||||||
|
// If the device was online the last time we refreshed, trigger another refresh as part of the ping.
|
||||||
|
def ping() {
|
||||||
|
def isAlive = device.currentValue("deviceAlive") == "true" ? true : false
|
||||||
|
if (isAlive) {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "Parsing '${description}'"
|
log.debug "Parsing '${description}'"
|
||||||
@@ -148,14 +165,12 @@ def generateEvent(Map results) {
|
|||||||
handlerName: name]
|
handlerName: name]
|
||||||
|
|
||||||
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
|
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
|
||||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
|
||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
|
||||||
isChange = isTemperatureStateChange(device, name, value.toString())
|
isChange = isTemperatureStateChange(device, name, value.toString())
|
||||||
isDisplayed = isChange
|
isDisplayed = isChange
|
||||||
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
|
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
|
||||||
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
||||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
|
||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
|
||||||
event << [value: sendValue, unit: temperatureScale, displayed: false]
|
event << [value: sendValue, unit: temperatureScale, displayed: false]
|
||||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||||
isChange = isStateChange(device, name, value.toString())
|
isChange = isStateChange(device, name, value.toString())
|
||||||
@@ -166,7 +181,11 @@ def generateEvent(Map results) {
|
|||||||
} else if (name=="humidity") {
|
} else if (name=="humidity") {
|
||||||
isChange = isStateChange(device, name, value.toString())
|
isChange = isStateChange(device, name, value.toString())
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
|
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
|
||||||
} else {
|
} else if (name == "deviceAlive") {
|
||||||
|
isChange = isStateChange(device, name, value.toString())
|
||||||
|
event['isStateChange'] = isChange
|
||||||
|
event['displayed'] = false
|
||||||
|
} else {
|
||||||
isChange = isStateChange(device, name, value.toString())
|
isChange = isStateChange(device, name, value.toString())
|
||||||
isDisplayed = isChange
|
isDisplayed = isChange
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed]
|
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed]
|
||||||
@@ -253,7 +272,6 @@ void setCoolingSetpoint(setpoint) {
|
|||||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||||
|
|
||||||
|
|
||||||
if (coolingSetpoint > maxCoolingSetpoint) {
|
if (coolingSetpoint > maxCoolingSetpoint) {
|
||||||
coolingSetpoint = maxCoolingSetpoint
|
coolingSetpoint = maxCoolingSetpoint
|
||||||
} else if (coolingSetpoint < minCoolingSetpoint) {
|
} else if (coolingSetpoint < minCoolingSetpoint) {
|
||||||
@@ -283,7 +301,6 @@ void setCoolingSetpoint(setpoint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void resumeProgram() {
|
void resumeProgram() {
|
||||||
|
|
||||||
log.debug "resumeProgram() is called"
|
log.debug "resumeProgram() is called"
|
||||||
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
|
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
@@ -354,7 +371,6 @@ def switchFanMode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def switchToFanMode(nextMode) {
|
def switchToFanMode(nextMode) {
|
||||||
|
|
||||||
log.debug "switching to fan mode: $nextMode"
|
log.debug "switching to fan mode: $nextMode"
|
||||||
def returnCommand
|
def returnCommand
|
||||||
|
|
||||||
@@ -520,63 +536,56 @@ def fanAuto() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generateSetpointEvent() {
|
def generateSetpointEvent() {
|
||||||
|
|
||||||
log.debug "Generate SetPoint Event"
|
log.debug "Generate SetPoint Event"
|
||||||
|
|
||||||
def mode = device.currentValue("thermostatMode")
|
def mode = device.currentValue("thermostatMode")
|
||||||
log.debug "Current Mode = ${mode}"
|
|
||||||
|
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||||
log.debug "Heating Setpoint = ${heatingSetpoint}"
|
|
||||||
|
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||||
log.debug "Cooling Setpoint = ${coolingSetpoint}"
|
|
||||||
|
|
||||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||||
|
|
||||||
if(location.temperatureScale == "C")
|
if(location.temperatureScale == "C") {
|
||||||
{
|
maxHeatingSetpoint = maxHeatingSetpoint > 40 ? roundC(convertFtoC(maxHeatingSetpoint)) : roundC(maxHeatingSetpoint)
|
||||||
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
|
maxCoolingSetpoint = maxCoolingSetpoint > 40 ? roundC(convertFtoC(maxCoolingSetpoint)) : roundC(maxCoolingSetpoint)
|
||||||
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
|
minHeatingSetpoint = minHeatingSetpoint > 40 ? roundC(convertFtoC(minHeatingSetpoint)) : roundC(minHeatingSetpoint)
|
||||||
minHeatingSetpoint = roundC(minHeatingSetpoint)
|
minCoolingSetpoint = minCoolingSetpoint > 40 ? roundC(convertFtoC(minCoolingSetpoint)) : roundC(minCoolingSetpoint)
|
||||||
minCoolingSetpoint = roundC(minCoolingSetpoint)
|
heatingSetpoint = heatingSetpoint > 40 ? roundC(convertFtoC(heatingSetpoint)) : roundC(heatingSetpoint)
|
||||||
heatingSetpoint = roundC(heatingSetpoint)
|
coolingSetpoint = coolingSetpoint > 40 ? roundC(convertFtoC(coolingSetpoint)) : roundC(coolingSetpoint)
|
||||||
coolingSetpoint = roundC(coolingSetpoint)
|
} else {
|
||||||
|
maxHeatingSetpoint = maxHeatingSetpoint < 40 ? roundC(convertCtoF(maxHeatingSetpoint)) : maxHeatingSetpoint
|
||||||
|
maxCoolingSetpoint = maxCoolingSetpoint < 40 ? roundC(convertCtoF(maxCoolingSetpoint)) : maxCoolingSetpoint
|
||||||
|
minHeatingSetpoint = minHeatingSetpoint < 40 ? roundC(convertCtoF(minHeatingSetpoint)) : minHeatingSetpoint
|
||||||
|
minCoolingSetpoint = minCoolingSetpoint < 40 ? roundC(convertCtoF(minCoolingSetpoint)) : minCoolingSetpoint
|
||||||
|
heatingSetpoint = heatingSetpoint < 40 ? roundC(convertCtoF(heatingSetpoint)) : heatingSetpoint
|
||||||
|
coolingSetpoint = coolingSetpoint < 40 ? roundC(convertCtoF(coolingSetpoint)) : coolingSetpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug "Current Mode = ${mode}"
|
||||||
|
log.debug "Heating Setpoint = ${heatingSetpoint}"
|
||||||
|
log.debug "Cooling Setpoint = ${coolingSetpoint}"
|
||||||
|
|
||||||
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
|
||||||
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
|
||||||
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
|
||||||
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
|
||||||
|
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||||
|
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
||||||
|
|
||||||
if (mode == "heat") {
|
if (mode == "heat") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (mode == "cool") {
|
else if (mode == "cool") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
||||||
|
|
||||||
} else if (mode == "auto") {
|
} else if (mode == "auto") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":"Auto")
|
sendEvent("name":"thermostatSetpoint", "value":"Auto")
|
||||||
|
|
||||||
} else if (mode == "off") {
|
} else if (mode == "off") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":"Off")
|
sendEvent("name":"thermostatSetpoint", "value":"Off")
|
||||||
|
|
||||||
} else if (mode == "auxHeatOnly") {
|
} else if (mode == "auxHeatOnly") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void raiseSetpoint() {
|
void raiseSetpoint() {
|
||||||
@@ -585,21 +594,31 @@ void raiseSetpoint() {
|
|||||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||||
|
|
||||||
|
|
||||||
if (mode == "off" || mode == "auto") {
|
if (mode == "off" || mode == "auto") {
|
||||||
log.warn "this mode: $mode does not allow raiseSetpoint"
|
log.warn "this mode: $mode does not allow raiseSetpoint"
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
||||||
|
|
||||||
|
if (location.temperatureScale == "C") {
|
||||||
|
maxHeatingSetpoint = maxHeatingSetpoint > 40 ? convertFtoC(maxHeatingSetpoint) : maxHeatingSetpoint
|
||||||
|
maxCoolingSetpoint = maxCoolingSetpoint > 40 ? convertFtoC(maxCoolingSetpoint) : maxCoolingSetpoint
|
||||||
|
heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint
|
||||||
|
coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint
|
||||||
|
thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint
|
||||||
|
} else {
|
||||||
|
maxHeatingSetpoint = maxHeatingSetpoint < 40 ? convertCtoF(maxHeatingSetpoint) : maxHeatingSetpoint
|
||||||
|
maxCoolingSetpoint = maxCoolingSetpoint < 40 ? convertCtoF(maxCoolingSetpoint) : maxCoolingSetpoint
|
||||||
|
heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||||
|
coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||||
|
thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint
|
||||||
|
}
|
||||||
|
|
||||||
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
||||||
|
|
||||||
if (device.latestState('thermostatSetpoint')) {
|
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
|
||||||
targetvalue = device.latestState('thermostatSetpoint').value
|
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
|
|
||||||
} else {
|
|
||||||
targetvalue = 0
|
|
||||||
}
|
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
|
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
|
||||||
|
|
||||||
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
|
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
|
||||||
@@ -622,20 +641,29 @@ void lowerSetpoint() {
|
|||||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||||
|
|
||||||
|
|
||||||
if (mode == "off" || mode == "auto") {
|
if (mode == "off" || mode == "auto") {
|
||||||
log.warn "this mode: $mode does not allow lowerSetpoint"
|
log.warn "this mode: $mode does not allow lowerSetpoint"
|
||||||
} else {
|
} else {
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
||||||
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
|
||||||
if (device.latestState('thermostatSetpoint')) {
|
if (location.temperatureScale == "C") {
|
||||||
targetvalue = device.latestState('thermostatSetpoint').value
|
minHeatingSetpoint = minHeatingSetpoint > 40 ? convertFtoC(minHeatingSetpoint) : minHeatingSetpoint
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
|
minCoolingSetpoint = minCoolingSetpoint > 40 ? convertFtoC(minCoolingSetpoint) : minCoolingSetpoint
|
||||||
|
heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint
|
||||||
|
coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint
|
||||||
|
thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint
|
||||||
} else {
|
} else {
|
||||||
targetvalue = 0
|
minHeatingSetpoint = minHeatingSetpoint < 40 ? convertCtoF(minHeatingSetpoint) : minHeatingSetpoint
|
||||||
|
minCoolingSetpoint = minCoolingSetpoint < 40 ? convertCtoF(minCoolingSetpoint) : minCoolingSetpoint
|
||||||
|
heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||||
|
coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||||
|
thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint
|
||||||
}
|
}
|
||||||
|
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
||||||
|
|
||||||
|
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
|
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
|
||||||
|
|
||||||
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
|
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
|
||||||
@@ -653,7 +681,6 @@ void lowerSetpoint() {
|
|||||||
|
|
||||||
//called by raiseSetpoint() and lowerSetpoint()
|
//called by raiseSetpoint() and lowerSetpoint()
|
||||||
void alterSetpoint(temp) {
|
void alterSetpoint(temp) {
|
||||||
|
|
||||||
def mode = device.currentValue("thermostatMode")
|
def mode = device.currentValue("thermostatMode")
|
||||||
|
|
||||||
if (mode == "off" || mode == "auto") {
|
if (mode == "off" || mode == "auto") {
|
||||||
@@ -666,6 +693,18 @@ void alterSetpoint(temp) {
|
|||||||
def targetHeatingSetpoint
|
def targetHeatingSetpoint
|
||||||
def targetCoolingSetpoint
|
def targetCoolingSetpoint
|
||||||
|
|
||||||
|
def temperatureScaleHasChanged = false
|
||||||
|
|
||||||
|
if (location.temperatureScale == "C") {
|
||||||
|
if ( heatingSetpoint > 40.0 || coolingSetpoint > 40.0 ) {
|
||||||
|
temperatureScaleHasChanged = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( heatingSetpoint < 40.0 || coolingSetpoint < 40.0 ) {
|
||||||
|
temperatureScaleHasChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//step1: check thermostatMode, enforce limits before sending request to cloud
|
//step1: check thermostatMode, enforce limits before sending request to cloud
|
||||||
if (mode == "heat" || mode == "auxHeatOnly"){
|
if (mode == "heat" || mode == "auxHeatOnly"){
|
||||||
if (temp.value > coolingSetpoint){
|
if (temp.value > coolingSetpoint){
|
||||||
@@ -707,17 +746,18 @@ void alterSetpoint(temp) {
|
|||||||
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
|
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( temperatureScaleHasChanged )
|
||||||
|
generateSetpointEvent()
|
||||||
generateStatusEvent()
|
generateStatusEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateStatusEvent() {
|
def generateStatusEvent() {
|
||||||
|
|
||||||
def mode = device.currentValue("thermostatMode")
|
def mode = device.currentValue("thermostatMode")
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||||
def temperature = device.currentValue("temperature")
|
def temperature = device.currentValue("temperature")
|
||||||
|
|
||||||
def statusText
|
def statusText
|
||||||
|
|
||||||
log.debug "Generate Status Event for Mode = ${mode}"
|
log.debug "Generate Status Event for Mode = ${mode}"
|
||||||
@@ -727,36 +767,25 @@ def generateStatusEvent() {
|
|||||||
log.debug "HVAC Mode = ${mode}"
|
log.debug "HVAC Mode = ${mode}"
|
||||||
|
|
||||||
if (mode == "heat") {
|
if (mode == "heat") {
|
||||||
|
|
||||||
if (temperature >= heatingSetpoint)
|
if (temperature >= heatingSetpoint)
|
||||||
statusText = "Right Now: Idle"
|
statusText = "Right Now: Idle"
|
||||||
else
|
else
|
||||||
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
|
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
|
||||||
|
|
||||||
} else if (mode == "cool") {
|
} else if (mode == "cool") {
|
||||||
|
|
||||||
if (temperature <= coolingSetpoint)
|
if (temperature <= coolingSetpoint)
|
||||||
statusText = "Right Now: Idle"
|
statusText = "Right Now: Idle"
|
||||||
else
|
else
|
||||||
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
|
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
|
||||||
|
|
||||||
} else if (mode == "auto") {
|
} else if (mode == "auto") {
|
||||||
|
|
||||||
statusText = "Right Now: Auto"
|
statusText = "Right Now: Auto"
|
||||||
|
|
||||||
} else if (mode == "off") {
|
} else if (mode == "off") {
|
||||||
|
|
||||||
statusText = "Right Now: Off"
|
statusText = "Right Now: Off"
|
||||||
|
|
||||||
} else if (mode == "auxHeatOnly") {
|
} else if (mode == "auxHeatOnly") {
|
||||||
|
|
||||||
statusText = "Emergency Heat"
|
statusText = "Emergency Heat"
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
statusText = "?"
|
statusText = "?"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Generate Status Event = ${statusText}"
|
log.debug "Generate Status Event = ${statusText}"
|
||||||
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
|
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
|
||||||
}
|
}
|
||||||
@@ -770,7 +799,7 @@ def roundC (tempC) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def convertFtoC (tempF) {
|
def convertFtoC (tempF) {
|
||||||
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
|
return ((Math.round(((tempF - 32)*(5/9)) * 2))/2).toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
def convertCtoF (tempC) {
|
def convertCtoF (tempC) {
|
||||||
|
|||||||
@@ -29,10 +29,10 @@
|
|||||||
capability "Polling"
|
capability "Polling"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Color Control"
|
capability "Color Control"
|
||||||
capability "Power Meter"
|
capability "Power Meter"
|
||||||
|
|
||||||
command "getDeviceData"
|
command "getDeviceData"
|
||||||
command "softwhite"
|
command "softwhite"
|
||||||
command "daylight"
|
command "daylight"
|
||||||
@@ -54,12 +54,12 @@
|
|||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
command "setWhiteLevel"
|
command "setWhiteLevel"
|
||||||
command "test"
|
command "test"
|
||||||
|
|
||||||
attribute "whiteLevel", "string"
|
attribute "whiteLevel", "string"
|
||||||
|
|
||||||
fingerprint deviceId: "0x1101", inClusters: "0x27,0x72,0x86,0x26,0x60,0x70,0x32,0x31,0x85,0x33"
|
fingerprint deviceId: "0x1101", inClusters: "0x27,0x72,0x86,0x26,0x60,0x70,0x32,0x31,0x85,0x33"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
status "on": "command: 2003, payload: FF"
|
status "on": "command: 2003, payload: FF"
|
||||||
status "off": "command: 2003, payload: 00"
|
status "off": "command: 2003, payload: 00"
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||||
state "level", action:"switch level.setLevel"
|
state "level", action:"switch level.setLevel"
|
||||||
}
|
}
|
||||||
controlTile("whiteSliderControl", "device.whiteLevel", "slider", height: 1, width: 3, inactiveLabel: false) {
|
controlTile("whiteSliderControl", "device.whiteLevel", "slider", height: 1, width: 3, inactiveLabel: false) {
|
||||||
state "whiteLevel", action:"setWhiteLevel", label:'White Level'
|
state "whiteLevel", action:"setWhiteLevel", label:'White Level'
|
||||||
}
|
}
|
||||||
@@ -183,24 +183,24 @@
|
|||||||
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
|
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
|
||||||
state "hue", label: 'Hue ${currentValue} '
|
state "hue", label: 'Hue ${currentValue} '
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["switch"])
|
main(["switch"])
|
||||||
details(["switch",
|
details(["switch",
|
||||||
"levelSliderControl",
|
"levelSliderControl",
|
||||||
"rgbSelector",
|
"rgbSelector",
|
||||||
"whiteSliderControl",
|
"whiteSliderControl",
|
||||||
/*"softwhite",
|
/*"softwhite",
|
||||||
"daylight",
|
"daylight",
|
||||||
"warmwhite",
|
"warmwhite",
|
||||||
"red",
|
"red",
|
||||||
"green",
|
"green",
|
||||||
"blue",
|
"blue",
|
||||||
"white",
|
"white",
|
||||||
"cyan",
|
"cyan",
|
||||||
"magenta",
|
"magenta",
|
||||||
"orange",
|
"orange",
|
||||||
"purple",
|
"purple",
|
||||||
"yellow",
|
"yellow",
|
||||||
"fireplace",
|
"fireplace",
|
||||||
"storm",
|
"storm",
|
||||||
"deepfade",
|
"deepfade",
|
||||||
@@ -214,7 +214,7 @@
|
|||||||
|
|
||||||
def setAdjustedColor(value) {
|
def setAdjustedColor(value) {
|
||||||
log.debug "setAdjustedColor: ${value}"
|
log.debug "setAdjustedColor: ${value}"
|
||||||
|
|
||||||
toggleTiles("off") //turn off the hard color tiles
|
toggleTiles("off") //turn off the hard color tiles
|
||||||
|
|
||||||
def level = device.latestValue("level")
|
def level = device.latestValue("level")
|
||||||
@@ -223,19 +223,19 @@ def setAdjustedColor(value) {
|
|||||||
log.debug "level is: ${level}"
|
log.debug "level is: ${level}"
|
||||||
value.level = level
|
value.level = level
|
||||||
|
|
||||||
def c = hexToRgb(value.hex)
|
def c = hexToRgb(value.hex)
|
||||||
value.rh = hex(c.r * (level/100))
|
value.rh = hex(c.r * (level/100))
|
||||||
value.gh = hex(c.g * (level/100))
|
value.gh = hex(c.g * (level/100))
|
||||||
value.bh = hex(c.b * (level/100))
|
value.bh = hex(c.b * (level/100))
|
||||||
|
|
||||||
setColor(value)
|
setColor(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColor(value) {
|
def setColor(value) {
|
||||||
log.debug "setColor: ${value}"
|
log.debug "setColor: ${value}"
|
||||||
log.debug "hue is: ${value.hue}"
|
log.debug "hue is: ${value.hue}"
|
||||||
log.debug "saturation is: ${value.saturation}"
|
log.debug "saturation is: ${value.saturation}"
|
||||||
|
|
||||||
if (value.size() < 8)
|
if (value.size() < 8)
|
||||||
toggleTiles("off")
|
toggleTiles("off")
|
||||||
|
|
||||||
@@ -246,22 +246,22 @@ def setColor(value) {
|
|||||||
value.gh = hex(rgb.g)
|
value.gh = hex(rgb.g)
|
||||||
value.bh = hex(rgb.b)
|
value.bh = hex(rgb.b)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((value.size() == 3) && (value.hue != null) && (value.saturation != null) && (value.level)) { //user passed in a level value too from outside (App)
|
if ((value.size() == 3) && (value.hue != null) && (value.saturation != null) && (value.level)) { //user passed in a level value too from outside (App)
|
||||||
def rgb = hslToRGB(value.hue, value.saturation, 0.5)
|
def rgb = hslToRGB(value.hue, value.saturation, 0.5)
|
||||||
value.hex = rgbToHex(rgb)
|
value.hex = rgbToHex(rgb)
|
||||||
value.rh = hex(rgb.r * value.level/100)
|
value.rh = hex(rgb.r * value.level/100)
|
||||||
value.gh = hex(rgb.g * value.level/100)
|
value.gh = hex(rgb.g * value.level/100)
|
||||||
value.bh = hex(rgb.b * value.level/100)
|
value.bh = hex(rgb.b * value.level/100)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (( value.size() == 1) && (value.hex)) { //being called from outside of device (App) with only hex
|
if (( value.size() == 1) && (value.hex)) { //being called from outside of device (App) with only hex
|
||||||
def rgbInt = hexToRgb(value.hex)
|
def rgbInt = hexToRgb(value.hex)
|
||||||
value.rh = hex(rgbInt.r)
|
value.rh = hex(rgbInt.r)
|
||||||
value.gh = hex(rgbInt.g)
|
value.gh = hex(rgbInt.g)
|
||||||
value.bh = hex(rgbInt.b)
|
value.bh = hex(rgbInt.b)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (( value.size() == 2) && (value.hex) && (value.level)) { //being called from outside of device (App) with only hex and level
|
if (( value.size() == 2) && (value.hex) && (value.level)) { //being called from outside of device (App) with only hex and level
|
||||||
|
|
||||||
def rgbInt = hexToRgb(value.hex)
|
def rgbInt = hexToRgb(value.hex)
|
||||||
@@ -269,7 +269,7 @@ def setColor(value) {
|
|||||||
value.gh = hex(rgbInt.g * value.level/100)
|
value.gh = hex(rgbInt.g * value.level/100)
|
||||||
value.bh = hex(rgbInt.b * value.level/100)
|
value.bh = hex(rgbInt.b * value.level/100)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (( value.size() == 1) && (value.colorName)) { //being called from outside of device (App) with only color name
|
if (( value.size() == 1) && (value.colorName)) { //being called from outside of device (App) with only color name
|
||||||
def colorData = getColorData(value.colorName)
|
def colorData = getColorData(value.colorName)
|
||||||
value.rh = colorData.rh
|
value.rh = colorData.rh
|
||||||
@@ -277,7 +277,7 @@ def setColor(value) {
|
|||||||
value.bh = colorData.bh
|
value.bh = colorData.bh
|
||||||
value.hex = "#${value.rh}${value.gh}${value.bh}"
|
value.hex = "#${value.rh}${value.gh}${value.bh}"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (( value.size() == 2) && (value.colorName) && (value.level)) { //being called from outside of device (App) with only color name and level
|
if (( value.size() == 2) && (value.colorName) && (value.level)) { //being called from outside of device (App) with only color name and level
|
||||||
def colorData = getColorData(value.colorName)
|
def colorData = getColorData(value.colorName)
|
||||||
value.rh = hex(colorData.r * value.level/100)
|
value.rh = hex(colorData.r * value.level/100)
|
||||||
@@ -285,7 +285,7 @@ def setColor(value) {
|
|||||||
value.bh = hex(colorData.b * value.level/100)
|
value.bh = hex(colorData.b * value.level/100)
|
||||||
value.hex = "#${hex(colorData.r)}${hex(colorData.g)}${hex(colorData.b)}"
|
value.hex = "#${hex(colorData.r)}${hex(colorData.g)}${hex(colorData.b)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (( value.size() == 3) && (value.red != null) && (value.green != null) && (value.blue != null)) { //being called from outside of device (App) with only color values (0-255)
|
if (( value.size() == 3) && (value.red != null) && (value.green != null) && (value.blue != null)) { //being called from outside of device (App) with only color values (0-255)
|
||||||
value.rh = hex(value.red)
|
value.rh = hex(value.red)
|
||||||
value.gh = hex(value.green)
|
value.gh = hex(value.green)
|
||||||
@@ -299,7 +299,7 @@ def setColor(value) {
|
|||||||
value.bh = hex(value.blue * value.level/100)
|
value.bh = hex(value.blue * value.level/100)
|
||||||
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
|
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(name: "hue", value: value.hue, displayed: false)
|
sendEvent(name: "hue", value: value.hue, displayed: false)
|
||||||
sendEvent(name: "saturation", value: value.saturation, displayed: false)
|
sendEvent(name: "saturation", value: value.saturation, displayed: false)
|
||||||
sendEvent(name: "color", value: value.hex, displayed: false)
|
sendEvent(name: "color", value: value.hex, displayed: false)
|
||||||
@@ -309,26 +309,26 @@ def setColor(value) {
|
|||||||
if (value.switch) {
|
if (value.switch) {
|
||||||
sendEvent(name: "switch", value: value.switch)
|
sendEvent(name: "switch", value: value.switch)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRGB(value.rh, value.gh, value.bh)
|
sendRGB(value.rh, value.gh, value.bh)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(level) {
|
def setLevel(level) {
|
||||||
log.debug "setLevel($level)"
|
log.debug "setLevel($level)"
|
||||||
|
|
||||||
if (level == 0) { off() }
|
if (level == 0) { off() }
|
||||||
else if (device.latestValue("switch") == "off") { on() }
|
else if (device.latestValue("switch") == "off") { on() }
|
||||||
|
|
||||||
def colorHex = device.latestValue("color")
|
def colorHex = device.latestValue("color")
|
||||||
if (colorHex == null)
|
if (colorHex == null)
|
||||||
colorHex = "#FFFFFF"
|
colorHex = "#FFFFFF"
|
||||||
|
|
||||||
def c = hexToRgb(colorHex)
|
def c = hexToRgb(colorHex)
|
||||||
|
|
||||||
def r = hex(c.r * (level/100))
|
def r = hex(c.r * (level/100))
|
||||||
def g = hex(c.g * (level/100))
|
def g = hex(c.g * (level/100))
|
||||||
def b = hex(c.b * (level/100))
|
def b = hex(c.b * (level/100))
|
||||||
|
|
||||||
sendEvent(name: "level", value: level)
|
sendEvent(name: "level", value: level)
|
||||||
sendEvent(name: "setLevel", value: level, displayed: false)
|
sendEvent(name: "setLevel", value: level, displayed: false)
|
||||||
sendRGB(r, g, b)
|
sendRGB(r, g, b)
|
||||||
@@ -337,14 +337,14 @@ def setLevel(level) {
|
|||||||
|
|
||||||
def setWhiteLevel(value) {
|
def setWhiteLevel(value) {
|
||||||
log.debug "setWhiteLevel: ${value}"
|
log.debug "setWhiteLevel: ${value}"
|
||||||
def level = Math.min(value as Integer, 99)
|
def level = Math.min(value as Integer, 99)
|
||||||
level = 255 * level/99 as Integer
|
level = 255 * level/99 as Integer
|
||||||
def channel = 0
|
def channel = 0
|
||||||
|
|
||||||
if (device.latestValue("switch") == "off") { on() }
|
if (device.latestValue("switch") == "off") { on() }
|
||||||
|
|
||||||
sendEvent(name: "whiteLevel", value: value)
|
sendEvent(name: "whiteLevel", value: value)
|
||||||
sendWhite(channel, value)
|
sendWhite(channel, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def sendWhite(channel, value) {
|
def sendWhite(channel, value) {
|
||||||
@@ -367,20 +367,20 @@ def sendRGBW(redHex, greenHex, blueHex, whiteHex) {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Device For SmartThings Use"
|
log.debug "Configuring Device For SmartThings Use"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def cmds = []
|
def cmds = []
|
||||||
|
|
||||||
// send associate to group 3 to get sensor data reported only to hub
|
// send associate to group 3 to get sensor data reported only to hub
|
||||||
cmds << zwave.associationV2.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format()
|
cmds << zwave.associationV2.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format()
|
||||||
|
|
||||||
|
|
||||||
//cmds << sendEvent(name: "level", value: 50)
|
//cmds << sendEvent(name: "level", value: 50)
|
||||||
//cmds << on()
|
//cmds << on()
|
||||||
//cmds << doColorButton("Green")
|
//cmds << doColorButton("Green")
|
||||||
delayBetween(cmds, 500)
|
delayBetween(cmds, 500)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
@@ -411,11 +411,11 @@ def parse(String description) {
|
|||||||
|
|
||||||
def getDeviceData() {
|
def getDeviceData() {
|
||||||
def cmd = []
|
def cmd = []
|
||||||
|
|
||||||
cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
cmd << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||||
cmd << response(zwave.versionV1.versionGet())
|
cmd << response(zwave.versionV1.versionGet())
|
||||||
cmd << response(zwave.firmwareUpdateMdV1.firmwareMdGet())
|
cmd << response(zwave.firmwareUpdateMdV1.firmwareMdGet())
|
||||||
|
|
||||||
delayBetween(cmd, 500)
|
delayBetween(cmd, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +426,7 @@ def createEvent(physicalgraph.zwave.commands.manufacturerspecificv2.Manufacturer
|
|||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) {
|
def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map item1) {
|
||||||
updateDataValue("applicationVersion", "${cmd.applicationVersion}")
|
updateDataValue("applicationVersion", "${cmd.applicationVersion}")
|
||||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
||||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
||||||
@@ -435,13 +435,13 @@ def createEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd, Map it
|
|||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) {
|
def createEvent(physicalgraph.zwave.commands.firmwareupdatemdv1.FirmwareMdReport cmd, Map item1) {
|
||||||
log.debug "checksum: ${cmd.checksum}"
|
log.debug "checksum: ${cmd.checksum}"
|
||||||
log.debug "firmwareId: ${cmd.firmwareId}"
|
log.debug "firmwareId: ${cmd.firmwareId}"
|
||||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.colorcontrolv1.CapabilityReport cmd, Map item1) {
|
def zwaveEvent(physicalgraph.zwave.commands.colorcontrolv1.CapabilityReport cmd, Map item1) {
|
||||||
|
|
||||||
log.debug "In CapabilityReport"
|
log.debug "In CapabilityReport"
|
||||||
}
|
}
|
||||||
@@ -546,7 +546,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
|
|||||||
def value = "when off"
|
def value = "when off"
|
||||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||||
[name: "indicatorStatus", value: value, display: false]
|
[name: "indicatorStatus", value: value, displayed: false]
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
def createEvent(physicalgraph.zwave.Command cmd, Map map) {
|
def createEvent(physicalgraph.zwave.Command cmd, Map map) {
|
||||||
@@ -557,7 +557,7 @@ def createEvent(physicalgraph.zwave.Command cmd, Map map) {
|
|||||||
def on() {
|
def on() {
|
||||||
log.debug "on()"
|
log.debug "on()"
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(),
|
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||||
zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,7 +593,7 @@ def refresh() {
|
|||||||
* @return none
|
* @return none
|
||||||
*/
|
*/
|
||||||
def updateZwaveParam(params) {
|
def updateZwaveParam(params) {
|
||||||
if ( params ) {
|
if ( params ) {
|
||||||
def pNumber = params.paramNumber
|
def pNumber = params.paramNumber
|
||||||
def pSize = params.size
|
def pSize = params.size
|
||||||
def pValue = [params.value]
|
def pValue = [params.value]
|
||||||
@@ -601,9 +601,9 @@ def updateZwaveParam(params) {
|
|||||||
|
|
||||||
def cmds = []
|
def cmds = []
|
||||||
cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format()
|
cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format()
|
||||||
|
|
||||||
cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format()
|
cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format()
|
||||||
delayBetween(cmds, 1500)
|
delayBetween(cmds, 1500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,22 +612,22 @@ def test() {
|
|||||||
//value = [hue: 0, saturation: 100, level: 5]
|
//value = [hue: 0, saturation: 100, level: 5]
|
||||||
//value = [red: 255, green: 0, blue: 255, level: 60]
|
//value = [red: 255, green: 0, blue: 255, level: 60]
|
||||||
//setColor(value)
|
//setColor(value)
|
||||||
|
|
||||||
def cmd = []
|
def cmd = []
|
||||||
|
|
||||||
if ( !state.cnt ) {
|
if ( !state.cnt ) {
|
||||||
state.cnt = 6
|
state.cnt = 6
|
||||||
} else {
|
} else {
|
||||||
state.cnt = state.cnt + 1
|
state.cnt = state.cnt + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( state.cnt > 10 )
|
if ( state.cnt > 10 )
|
||||||
state.cnt = 6
|
state.cnt = 6
|
||||||
|
|
||||||
// run programmed light show
|
// run programmed light show
|
||||||
cmd << zwave.configurationV1.configurationSet(configurationValue: [state.cnt], parameterNumber: 72, size: 1).format()
|
cmd << zwave.configurationV1.configurationSet(configurationValue: [state.cnt], parameterNumber: 72, size: 1).format()
|
||||||
cmd << zwave.configurationV1.configurationGet(parameterNumber: 72).format()
|
cmd << zwave.configurationV1.configurationGet(parameterNumber: 72).format()
|
||||||
|
|
||||||
delayBetween(cmd, 500)
|
delayBetween(cmd, 500)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -638,23 +638,23 @@ def colorNameToRgb(color) {
|
|||||||
[name:"Soft White", r: 255, g: 241, b: 224 ],
|
[name:"Soft White", r: 255, g: 241, b: 224 ],
|
||||||
[name:"Daylight", r: 255, g: 255, b: 251 ],
|
[name:"Daylight", r: 255, g: 255, b: 251 ],
|
||||||
[name:"Warm White", r: 255, g: 244, b: 229 ],
|
[name:"Warm White", r: 255, g: 244, b: 229 ],
|
||||||
|
|
||||||
[name:"Red", r: 255, g: 0, b: 0 ],
|
[name:"Red", r: 255, g: 0, b: 0 ],
|
||||||
[name:"Green", r: 0, g: 255, b: 0 ],
|
[name:"Green", r: 0, g: 255, b: 0 ],
|
||||||
[name:"Blue", r: 0, g: 0, b: 255 ],
|
[name:"Blue", r: 0, g: 0, b: 255 ],
|
||||||
|
|
||||||
[name:"Cyan", r: 0, g: 255, b: 255 ],
|
[name:"Cyan", r: 0, g: 255, b: 255 ],
|
||||||
[name:"Magenta", r: 255, g: 0, b: 33 ],
|
[name:"Magenta", r: 255, g: 0, b: 33 ],
|
||||||
[name:"Orange", r: 255, g: 102, b: 0 ],
|
[name:"Orange", r: 255, g: 102, b: 0 ],
|
||||||
|
|
||||||
[name:"Purple", r: 170, g: 0, b: 255 ],
|
[name:"Purple", r: 170, g: 0, b: 255 ],
|
||||||
[name:"Yellow", r: 255, g: 255, b: 0 ],
|
[name:"Yellow", r: 255, g: 255, b: 0 ],
|
||||||
[name:"White", r: 255, g: 255, b: 255 ]
|
[name:"White", r: 255, g: 255, b: 255 ]
|
||||||
]
|
]
|
||||||
|
|
||||||
def colorData = [:]
|
def colorData = [:]
|
||||||
colorData = colors.find { it.name == color }
|
colorData = colors.find { it.name == color }
|
||||||
|
|
||||||
colorData
|
colorData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,7 +670,7 @@ def hexToRgb(colorHex) {
|
|||||||
def rrInt = Integer.parseInt(colorHex.substring(1,3),16)
|
def rrInt = Integer.parseInt(colorHex.substring(1,3),16)
|
||||||
def ggInt = Integer.parseInt(colorHex.substring(3,5),16)
|
def ggInt = Integer.parseInt(colorHex.substring(3,5),16)
|
||||||
def bbInt = Integer.parseInt(colorHex.substring(5,7),16)
|
def bbInt = Integer.parseInt(colorHex.substring(5,7),16)
|
||||||
|
|
||||||
def colorData = [:]
|
def colorData = [:]
|
||||||
colorData = [r: rrInt, g: ggInt, b: bbInt]
|
colorData = [r: rrInt, g: ggInt, b: bbInt]
|
||||||
colorData
|
colorData
|
||||||
@@ -681,7 +681,7 @@ def rgbToHex(rgb) {
|
|||||||
def g = hex(rgb.g)
|
def g = hex(rgb.g)
|
||||||
def b = hex(rgb.b)
|
def b = hex(rgb.b)
|
||||||
def hexColor = "#${r}${g}${b}"
|
def hexColor = "#${r}${g}${b}"
|
||||||
|
|
||||||
hexColor
|
hexColor
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,11 +689,11 @@ def hslToRGB(float var_h, float var_s, float var_l) {
|
|||||||
float h = var_h / 100
|
float h = var_h / 100
|
||||||
float s = var_s / 100
|
float s = var_s / 100
|
||||||
float l = var_l
|
float l = var_l
|
||||||
|
|
||||||
def r = 0
|
def r = 0
|
||||||
def g = 0
|
def g = 0
|
||||||
def b = 0
|
def b = 0
|
||||||
|
|
||||||
if (s == 0) {
|
if (s == 0) {
|
||||||
r = l * 255
|
r = l * 255
|
||||||
g = l * 255
|
g = l * 255
|
||||||
@@ -705,26 +705,26 @@ def hslToRGB(float var_h, float var_s, float var_l) {
|
|||||||
} else {
|
} else {
|
||||||
var_2 = (l + s) - (s * l)
|
var_2 = (l + s) - (s * l)
|
||||||
}
|
}
|
||||||
|
|
||||||
float var_1 = 2 * l - var_2
|
float var_1 = 2 * l - var_2
|
||||||
|
|
||||||
r = 255 * hueToRgb(var_1, var_2, h + (1 / 3))
|
r = 255 * hueToRgb(var_1, var_2, h + (1 / 3))
|
||||||
g = 255 * hueToRgb(var_1, var_2, h)
|
g = 255 * hueToRgb(var_1, var_2, h)
|
||||||
b = 255 * hueToRgb(var_1, var_2, h - (1 / 3))
|
b = 255 * hueToRgb(var_1, var_2, h - (1 / 3))
|
||||||
}
|
}
|
||||||
|
|
||||||
def rgb = [:]
|
def rgb = [:]
|
||||||
rgb = [r: r, g: g, b: b]
|
rgb = [r: r, g: g, b: b]
|
||||||
|
|
||||||
rgb
|
rgb
|
||||||
}
|
}
|
||||||
|
|
||||||
def hueToRgb(v1, v2, vh) {
|
def hueToRgb(v1, v2, vh) {
|
||||||
if (vh < 0) { vh += 1 }
|
if (vh < 0) { vh += 1 }
|
||||||
if (vh > 1) { vh -= 1 }
|
if (vh > 1) { vh -= 1 }
|
||||||
if ((6 * vh) < 1) { return (v1 + (v2 - v1) * 6 * vh) }
|
if ((6 * vh) < 1) { return (v1 + (v2 - v1) * 6 * vh) }
|
||||||
if ((2 * vh) < 1) { return (v2) }
|
if ((2 * vh) < 1) { return (v2) }
|
||||||
if ((3 * vh) < 2) { return (v1 + (v2 - $v1) * ((2 / 3 - vh) * 6)) }
|
if ((3 * vh) < 2) { return (v1 + (v2 - $v1) * ((2 / 3 - vh) * 6)) }
|
||||||
return (v1)
|
return (v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,49 +735,49 @@ def rgbToHSL(rgb) {
|
|||||||
def h = 0
|
def h = 0
|
||||||
def s = 0
|
def s = 0
|
||||||
def l = 0
|
def l = 0
|
||||||
|
|
||||||
def var_min = [r,g,b].min()
|
def var_min = [r,g,b].min()
|
||||||
def var_max = [r,g,b].max()
|
def var_max = [r,g,b].max()
|
||||||
def del_max = var_max - var_min
|
def del_max = var_max - var_min
|
||||||
|
|
||||||
l = (var_max + var_min) / 2
|
l = (var_max + var_min) / 2
|
||||||
|
|
||||||
if (del_max == 0) {
|
if (del_max == 0) {
|
||||||
h = 0
|
h = 0
|
||||||
s = 0
|
s = 0
|
||||||
} else {
|
} else {
|
||||||
if (l < 0.5) { s = del_max / (var_max + var_min) }
|
if (l < 0.5) { s = del_max / (var_max + var_min) }
|
||||||
else { s = del_max / (2 - var_max - var_min) }
|
else { s = del_max / (2 - var_max - var_min) }
|
||||||
|
|
||||||
def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max
|
def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max
|
||||||
def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max
|
def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max
|
||||||
def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max
|
def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max
|
||||||
|
|
||||||
if (r == var_max) { h = del_b - del_g }
|
if (r == var_max) { h = del_b - del_g }
|
||||||
else if (g == var_max) { h = (1 / 3) + del_r - del_b }
|
else if (g == var_max) { h = (1 / 3) + del_r - del_b }
|
||||||
else if (b == var_max) { h = (2 / 3) + del_g - del_r }
|
else if (b == var_max) { h = (2 / 3) + del_g - del_r }
|
||||||
|
|
||||||
if (h < 0) { h += 1 }
|
if (h < 0) { h += 1 }
|
||||||
if (h > 1) { h -= 1 }
|
if (h > 1) { h -= 1 }
|
||||||
}
|
}
|
||||||
def hsl = [:]
|
def hsl = [:]
|
||||||
hsl = [h: h * 100, s: s * 100, l: l]
|
hsl = [h: h * 100, s: s * 100, l: l]
|
||||||
|
|
||||||
hsl
|
hsl
|
||||||
}
|
}
|
||||||
|
|
||||||
def getColorData(colorName) {
|
def getColorData(colorName) {
|
||||||
log.debug "getColorData: ${colorName}"
|
log.debug "getColorData: ${colorName}"
|
||||||
|
|
||||||
def colorRGB = colorNameToRgb(colorName)
|
def colorRGB = colorNameToRgb(colorName)
|
||||||
def colorHex = rgbToHex(colorRGB)
|
def colorHex = rgbToHex(colorRGB)
|
||||||
def colorHSL = rgbToHSL(colorRGB)
|
def colorHSL = rgbToHSL(colorRGB)
|
||||||
|
|
||||||
def colorData = [:]
|
def colorData = [:]
|
||||||
colorData = [h: colorHSL.h,
|
colorData = [h: colorHSL.h,
|
||||||
s: colorHSL.s,
|
s: colorHSL.s,
|
||||||
l: device.latestValue("level"),
|
l: device.latestValue("level"),
|
||||||
r: colorRGB.r,
|
r: colorRGB.r,
|
||||||
g: colorRGB.g,
|
g: colorRGB.g,
|
||||||
b: colorRGB.b,
|
b: colorRGB.b,
|
||||||
rh: hex(colorRGB.r),
|
rh: hex(colorRGB.r),
|
||||||
@@ -785,8 +785,8 @@ def getColorData(colorName) {
|
|||||||
bh: hex(colorRGB.b),
|
bh: hex(colorRGB.b),
|
||||||
hex: colorHex,
|
hex: colorHex,
|
||||||
alpha: 1]
|
alpha: 1]
|
||||||
|
|
||||||
colorData
|
colorData
|
||||||
}
|
}
|
||||||
|
|
||||||
def doColorButton(colorName) {
|
def doColorButton(colorName) {
|
||||||
@@ -798,7 +798,7 @@ def doColorButton(colorName) {
|
|||||||
def maxLevel = hex(99)
|
def maxLevel = hex(99)
|
||||||
|
|
||||||
toggleTiles(colorName.toLowerCase().replaceAll("\\s",""))
|
toggleTiles(colorName.toLowerCase().replaceAll("\\s",""))
|
||||||
|
|
||||||
if ( colorName == "Fire Place" ) { updateZwaveParam([paramNumber:72, value:6, size:1]) }
|
if ( colorName == "Fire Place" ) { updateZwaveParam([paramNumber:72, value:6, size:1]) }
|
||||||
else if ( colorName == "Storm" ) { updateZwaveParam([paramNumber:72, value:7, size:1]) }
|
else if ( colorName == "Storm" ) { updateZwaveParam([paramNumber:72, value:7, size:1]) }
|
||||||
else if ( colorName == "Deep Fade" ) { updateZwaveParam([paramNumber:72, value:8, size:1]) }
|
else if ( colorName == "Deep Fade" ) { updateZwaveParam([paramNumber:72, value:8, size:1]) }
|
||||||
@@ -808,8 +808,8 @@ def doColorButton(colorName) {
|
|||||||
else if ( colorName == "Daylight" ) { String.format("33050400${maxLevel}02${maxLevel}03${maxLevel}04${maxLevel}%02X", 100) }
|
else if ( colorName == "Daylight" ) { String.format("33050400${maxLevel}02${maxLevel}03${maxLevel}04${maxLevel}%02X", 100) }
|
||||||
else {
|
else {
|
||||||
def c = getColorData(colorName)
|
def c = getColorData(colorName)
|
||||||
def newValue = ["hue": c.h, "saturation": c.s, "level": level, "red": c.r, "green": c.g, "blue": c.b, "hex": c.hex, "alpha": c.alpha]
|
def newValue = ["hue": c.h, "saturation": c.s, "level": level, "red": c.r, "green": c.g, "blue": c.b, "hex": c.hex, "alpha": c.alpha]
|
||||||
setColor(newValue)
|
setColor(newValue)
|
||||||
def r = hex(c.r * (level/100))
|
def r = hex(c.r * (level/100))
|
||||||
def g = hex(c.g * (level/100))
|
def g = hex(c.g * (level/100))
|
||||||
def b = hex(c.b * (level/100))
|
def b = hex(c.b * (level/100))
|
||||||
@@ -823,19 +823,19 @@ def toggleTiles(color) {
|
|||||||
if ( !state.colorTiles ) {
|
if ( !state.colorTiles ) {
|
||||||
state.colorTiles = ["softwhite","daylight","warmwhite","red","green","blue","cyan","magenta","orange","purple","yellow","white","fireplace","storm","deepfade","litefade","police"]
|
state.colorTiles = ["softwhite","daylight","warmwhite","red","green","blue","cyan","magenta","orange","purple","yellow","white","fireplace","storm","deepfade","litefade","police"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def cmds = []
|
def cmds = []
|
||||||
|
|
||||||
state.colorTiles.each({
|
state.colorTiles.each({
|
||||||
if ( it == color ) {
|
if ( it == color ) {
|
||||||
log.debug "Turning ${it} on"
|
log.debug "Turning ${it} on"
|
||||||
cmds << sendEvent(name: it, value: "on${it}", display: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true)
|
cmds << sendEvent(name: it, value: "on${it}", displayed: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true)
|
||||||
} else {
|
} else {
|
||||||
//log.debug "Turning ${it} off"
|
//log.debug "Turning ${it} off"
|
||||||
cmds << sendEvent(name: it, value: "off${it}", displayed: false)
|
cmds << sendEvent(name: it, value: "off${it}", displayed: false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
delayBetween(cmds, 2500)
|
delayBetween(cmds, 2500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def resultMap = zigbee.getEvent(description)
|
def resultMap = zigbee.getEvent(description)
|
||||||
if (resultMap) {
|
if (resultMap) {
|
||||||
if ((resultMap.name == "level" && state.trigger == "setLevel") || resultMap.name != "level") { //doing this to account for weird level reporting bug with GE Link Bulbs
|
if (resultMap.name != "level" || resultMap.value != 0) { // Ignore level reports of 0 sent when bulb turns off
|
||||||
sendEvent(resultMap)
|
sendEvent(resultMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ def parse(String description) {
|
|||||||
|
|
||||||
def poll() {
|
def poll() {
|
||||||
def refreshCmds = [
|
def refreshCmds = [
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
|
||||||
]
|
]
|
||||||
|
|
||||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
@@ -188,25 +188,22 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def on() {
|
def on() {
|
||||||
state.trigger = "on/off"
|
|
||||||
zigbee.on()
|
zigbee.on()
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
state.trigger = "on/off"
|
|
||||||
zigbee.off()
|
zigbee.off()
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
def refreshCmds = [
|
def refreshCmds = [
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
|
||||||
]
|
]
|
||||||
|
|
||||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
|
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
state.trigger = "setLevel"
|
|
||||||
def cmd
|
def cmd
|
||||||
def delayForRefresh = 500
|
def delayForRefresh = 500
|
||||||
if (dimRate && (state?.rate != null)) {
|
if (dimRate && (state?.rate != null)) {
|
||||||
|
|||||||
@@ -1,351 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 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.
|
|
||||||
*
|
|
||||||
* GE/Jasco ZigBee Dimmer
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
* Date: 2015-07-01
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "GE ZigBee Dimmer", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch"
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Power Meter"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
status "on": "on/off: 1"
|
|
||||||
status "off": "on/off: 0"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "zcl on-off on": "on/off: 1"
|
|
||||||
reply "zcl on-off off": "on/off: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
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.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "level", label: 'Level ${currentValue}%'
|
|
||||||
}
|
|
||||||
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "power", label:'${currentValue} W'
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
main "switch"
|
|
||||||
details(["switch", "level", "power","levelSliderControl","refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "description is $description"
|
|
||||||
|
|
||||||
def finalResult = isKnownDescription(description)
|
|
||||||
if (finalResult != "false") {
|
|
||||||
log.info finalResult
|
|
||||||
if (finalResult.type == "update") {
|
|
||||||
log.info "$device updates: ${finalResult.value}"
|
|
||||||
}
|
|
||||||
else if (finalResult.type == "power") {
|
|
||||||
def powerValue = (finalResult.value as Integer)/10
|
|
||||||
sendEvent(name: "power", value: powerValue)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
|
||||||
|
|
||||||
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
|
||||||
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendEvent(name: finalResult.type, value: finalResult.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
|
||||||
log.debug parseDescriptionAsMap(description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands to device
|
|
||||||
def zigbeeCommand(cluster, attribute){
|
|
||||||
["st cmd 0x${device.deviceNetworkId} ${endpointId} ${cluster} ${attribute} {}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
zigbeeCommand("6", "0")
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
zigbeeCommand("6", "1")
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
value = value as Integer
|
|
||||||
if (value == 0) {
|
|
||||||
off()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendEvent(name: "level", value: value)
|
|
||||||
setLevelWithRate(value, "0000") + ["delay 1000"] + on() //value is between 0 to 100; GE does NOT switch on if OFF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
[
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0702 0x0400", "delay 500"
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
onOffConfig() + levelConfig() + powerConfig() + refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private getEndpointId() {
|
|
||||||
new BigInteger(device.endpointId, 16).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private hex(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer convertHexToInt(hex) {
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need to reverse array of size 2
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
|
||||||
byte tmp;
|
|
||||||
tmp = array[1];
|
|
||||||
array[1] = array[0];
|
|
||||||
array[0] = tmp;
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
if (description?.startsWith("read attr -")) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (description?.startsWith("catchall: ")) {
|
|
||||||
def seg = (description - "catchall: ").split(" ")
|
|
||||||
def zigbeeMap = [:]
|
|
||||||
zigbeeMap += [raw: (description - "catchall: ")]
|
|
||||||
zigbeeMap += [profileId: seg[0]]
|
|
||||||
zigbeeMap += [clusterId: seg[1]]
|
|
||||||
zigbeeMap += [sourceEndpoint: seg[2]]
|
|
||||||
zigbeeMap += [destinationEndpoint: seg[3]]
|
|
||||||
zigbeeMap += [options: seg[4]]
|
|
||||||
zigbeeMap += [messageType: seg[5]]
|
|
||||||
zigbeeMap += [dni: seg[6]]
|
|
||||||
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
|
|
||||||
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
|
|
||||||
zigbeeMap += [manufacturerId: seg[9]]
|
|
||||||
zigbeeMap += [command: seg[10]]
|
|
||||||
zigbeeMap += [direction: seg[11]]
|
|
||||||
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
|
|
||||||
it.join('')
|
|
||||||
} : []]
|
|
||||||
|
|
||||||
zigbeeMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def isKnownDescription(description) {
|
|
||||||
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
|
|
||||||
isDescriptionOnOff(descMap)
|
|
||||||
}
|
|
||||||
else if (descMap.cluster == "0008" || descMap.clusterId == "0008"){
|
|
||||||
isDescriptionLevel(descMap)
|
|
||||||
}
|
|
||||||
else if (descMap.cluster == "0702" || descMap.clusterId == "0702"){
|
|
||||||
isDescriptionPower(descMap)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(description?.startsWith("on/off:")) {
|
|
||||||
def switchValue = description?.endsWith("1") ? "on" : "off"
|
|
||||||
return [type: "switch", value : switchValue]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def isDescriptionOnOff(descMap) {
|
|
||||||
def switchValue = "undefined"
|
|
||||||
if (descMap.cluster == "0006") { //cluster info from read attr
|
|
||||||
value = descMap.value
|
|
||||||
if (value == "01"){
|
|
||||||
switchValue = "on"
|
|
||||||
}
|
|
||||||
else if (value == "00"){
|
|
||||||
switchValue = "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (descMap.clusterId == "0006") {
|
|
||||||
//cluster info from catch all
|
|
||||||
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
|
|
||||||
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
|
|
||||||
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
|
|
||||||
switchValue = "on"
|
|
||||||
}
|
|
||||||
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
|
|
||||||
switchValue = "off"
|
|
||||||
}
|
|
||||||
else if(descMap.command=="07"){
|
|
||||||
return [type: "update", value : "switch (0006) capability configured successfully"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switchValue != "undefined"){
|
|
||||||
return [type: "switch", value : switchValue]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//@return - false or "success" or level [0-100]
|
|
||||||
def isDescriptionLevel(descMap) {
|
|
||||||
def dimmerValue = -1
|
|
||||||
if (descMap.cluster == "0008"){
|
|
||||||
//TODO: the message returned with catchall is command 0B with clusterId 0008. That is just a confirmation message
|
|
||||||
def value = convertHexToInt(descMap.value)
|
|
||||||
dimmerValue = Math.round(value * 100 / 255)
|
|
||||||
if(dimmerValue==0 && value > 0) {
|
|
||||||
dimmerValue = 1 //handling for non-zero hex value less than 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(descMap.clusterId == "0008") {
|
|
||||||
if(descMap.command=="0B"){
|
|
||||||
return [type: "update", value : "level updated successfully"] //device updating the level change was successful. no value sent.
|
|
||||||
}
|
|
||||||
else if(descMap.command=="07"){
|
|
||||||
return [type: "update", value : "level (0008) capability configured successfully"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dimmerValue != -1){
|
|
||||||
return [type: "level", value : dimmerValue]
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def isDescriptionPower(descMap) {
|
|
||||||
def powerValue = "undefined"
|
|
||||||
if (descMap.cluster == "0702") {
|
|
||||||
if (descMap.attrId == "0400") {
|
|
||||||
powerValue = convertHexToInt(descMap.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (descMap.clusterId == "0702") {
|
|
||||||
if(descMap.command=="07"){
|
|
||||||
return [type: "update", value : "power (0702) capability configured successfully"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (powerValue != "undefined"){
|
|
||||||
return [type: "power", value : powerValue]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def onOffConfig() {
|
|
||||||
[
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
|
|
||||||
//min level change is 01
|
|
||||||
def levelConfig() {
|
|
||||||
[
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zcl global send-me-a-report 8 0 0x20 1 3600 {01}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
|
|
||||||
//min change in value is 05
|
|
||||||
def powerConfig() {
|
|
||||||
[
|
|
||||||
//Meter (Power) Reporting
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0702 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zcl global send-me-a-report 0x0702 0x0400 0x2A 1 600 {05}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevelWithRate(level, rate) {
|
|
||||||
if(rate == null){
|
|
||||||
rate = "0000"
|
|
||||||
}
|
|
||||||
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
|
|
||||||
["st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
String convertToHexString(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 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.
|
|
||||||
*
|
|
||||||
* GE/Jasco ZigBee Switch
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
* Date: 2015-07-01
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
// Automatically generated. Make future change here.
|
|
||||||
definition (name: "GE ZigBee Switch", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch"
|
|
||||||
capability "Power Meter"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
status "on": "on/off: 1"
|
|
||||||
status "off": "on/off: 0"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "zcl on-off on": "on/off: 1"
|
|
||||||
reply "zcl on-off off": "on/off: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
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.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
valueTile("power", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "power", label:'${currentValue} Watts'
|
|
||||||
}
|
|
||||||
main "switch"
|
|
||||||
details(["switch", "power", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "description is $description"
|
|
||||||
|
|
||||||
def finalResult = isKnownDescription(description)
|
|
||||||
if (finalResult != "false") {
|
|
||||||
log.info finalResult
|
|
||||||
if (finalResult.type == "update") {
|
|
||||||
log.info "$device updates: ${finalResult.value}"
|
|
||||||
}
|
|
||||||
else if (finalResult.type == "power") {
|
|
||||||
def powerValue = (finalResult.value as Integer)/10
|
|
||||||
sendEvent(name: "power", value: powerValue)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
|
||||||
|
|
||||||
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
|
||||||
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendEvent(name: finalResult.type, value: finalResult.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
|
||||||
log.debug parseDescriptionAsMap(description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands to device
|
|
||||||
def zigbeeCommand(cluster, attribute){
|
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} ${cluster} ${attribute} {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
zigbeeCommand("6", "0")
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
zigbeeCommand("6", "1")
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
[
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0702 0x0400", "delay 500"
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
onOffConfig() + powerConfig() + refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private getEndpointId() {
|
|
||||||
new BigInteger(device.endpointId, 16).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private hex(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer convertHexToInt(hex) {
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need to reverse array of size 2
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
|
||||||
byte tmp;
|
|
||||||
tmp = array[1];
|
|
||||||
array[1] = array[0];
|
|
||||||
array[0] = tmp;
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
if (description?.startsWith("read attr -")) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (description?.startsWith("catchall: ")) {
|
|
||||||
def seg = (description - "catchall: ").split(" ")
|
|
||||||
def zigbeeMap = [:]
|
|
||||||
zigbeeMap += [raw: (description - "catchall: ")]
|
|
||||||
zigbeeMap += [profileId: seg[0]]
|
|
||||||
zigbeeMap += [clusterId: seg[1]]
|
|
||||||
zigbeeMap += [sourceEndpoint: seg[2]]
|
|
||||||
zigbeeMap += [destinationEndpoint: seg[3]]
|
|
||||||
zigbeeMap += [options: seg[4]]
|
|
||||||
zigbeeMap += [messageType: seg[5]]
|
|
||||||
zigbeeMap += [dni: seg[6]]
|
|
||||||
zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
|
|
||||||
zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
|
|
||||||
zigbeeMap += [manufacturerId: seg[9]]
|
|
||||||
zigbeeMap += [command: seg[10]]
|
|
||||||
zigbeeMap += [direction: seg[11]]
|
|
||||||
zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
|
|
||||||
it.join('')
|
|
||||||
} : []]
|
|
||||||
|
|
||||||
zigbeeMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def isKnownDescription(description) {
|
|
||||||
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
|
|
||||||
isDescriptionOnOff(descMap)
|
|
||||||
}
|
|
||||||
else if (descMap.cluster == "0702" || descMap.clusterId == "0702"){
|
|
||||||
isDescriptionPower(descMap)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(description?.startsWith("on/off:")) {
|
|
||||||
def switchValue = description?.endsWith("1") ? "on" : "off"
|
|
||||||
return [type: "switch", value : switchValue]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def isDescriptionOnOff(descMap) {
|
|
||||||
def switchValue = "undefined"
|
|
||||||
if (descMap.cluster == "0006") { //cluster info from read attr
|
|
||||||
value = descMap.value
|
|
||||||
if (value == "01"){
|
|
||||||
switchValue = "on"
|
|
||||||
}
|
|
||||||
else if (value == "00"){
|
|
||||||
switchValue = "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (descMap.clusterId == "0006") {
|
|
||||||
//cluster info from catch all
|
|
||||||
//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
|
|
||||||
//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
|
|
||||||
if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
|
|
||||||
switchValue = "on"
|
|
||||||
}
|
|
||||||
else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
|
|
||||||
switchValue = "off"
|
|
||||||
}
|
|
||||||
else if(descMap.command=="07"){
|
|
||||||
return [type: "update", value : "switch (0006) capability configured successfully"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (switchValue != "undefined"){
|
|
||||||
return [type: "switch", value : switchValue]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def isDescriptionPower(descMap) {
|
|
||||||
def powerValue = "undefined"
|
|
||||||
if (descMap.cluster == "0702") {
|
|
||||||
if (descMap.attrId == "0400") {
|
|
||||||
powerValue = convertHexToInt(descMap.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (descMap.clusterId == "0702") {
|
|
||||||
if(descMap.command=="07"){
|
|
||||||
return [type: "update", value : "power (0702) capability configured successfully"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (powerValue != "undefined"){
|
|
||||||
return [type: "power", value : powerValue]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def onOffConfig() {
|
|
||||||
[
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
|
|
||||||
//min change in value is 05
|
|
||||||
def powerConfig() {
|
|
||||||
[
|
|
||||||
//Meter (Power) Reporting
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0702 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zcl global send-me-a-report 0x0702 0x0400 0x2A 1 600 {05}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
String convertToHexString(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,7 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
capability "Health Check"
|
||||||
|
capability "Light"
|
||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
command "reset"
|
command "reset"
|
||||||
@@ -57,7 +58,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -172,6 +173,3 @@ def verifyPercent(percent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -87,6 +87,3 @@ def parse(description) {
|
|||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
capability "Health Check"
|
||||||
|
capability "Light"
|
||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
command "reset"
|
command "reset"
|
||||||
@@ -66,7 +67,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -188,6 +189,3 @@ def verifyPercent(percent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.trace "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
capability "Health Check"
|
||||||
|
capability "Light"
|
||||||
|
|
||||||
command "refresh"
|
command "refresh"
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -93,6 +94,3 @@ void refresh() {
|
|||||||
parent.manualRefresh()
|
parent.manualRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Health Check"
|
capability "Health Check"
|
||||||
|
capability "Light"
|
||||||
|
|
||||||
command "refresh"
|
command "refresh"
|
||||||
}
|
}
|
||||||
@@ -55,7 +56,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -107,6 +108,3 @@ void refresh() {
|
|||||||
parent.manualRefresh()
|
parent.manualRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generatePresenceEvent(boolean present) {
|
def generatePresenceEvent(boolean present) {
|
||||||
log.debug "Here in generatePresenceEvent!"
|
log.info "Life360 generatePresenceEvent($present)"
|
||||||
def value = formatValue(present)
|
def value = formatValue(present)
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
def descriptionText = formatDescriptionText(linkText, present)
|
def descriptionText = formatDescriptionText(linkText, present)
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ metadata {
|
|||||||
capability "Color Temperature"
|
capability "Color Temperature"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level" // brightness
|
capability "Switch Level" // brightness
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
capability "Health Check"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -23,7 +23,6 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
@@ -64,12 +63,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
void installed() {
|
||||||
def parse(String description) {
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}")
|
||||||
if (description == 'updated') {
|
|
||||||
return // don't poll when config settings is being updated as it may time out
|
|
||||||
}
|
|
||||||
poll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle commands
|
// handle commands
|
||||||
@@ -141,7 +136,6 @@ def setLevel(percentage) {
|
|||||||
percentage = 1 // clamp to 1%
|
percentage = 1 // clamp to 1%
|
||||||
}
|
}
|
||||||
if (percentage == 0) {
|
if (percentage == 0) {
|
||||||
sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update
|
|
||||||
return off() // if the brightness is set to 0, just turn it off
|
return off() // if the brightness is set to 0, just turn it off
|
||||||
}
|
}
|
||||||
parent.logErrors(logObject:log) {
|
parent.logErrors(logObject:log) {
|
||||||
@@ -193,14 +187,17 @@ def off() {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
def poll() {
|
def refresh() {
|
||||||
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
|
log.debug "Executing 'refresh'"
|
||||||
|
|
||||||
def resp = parent.apiGET("/lights/${selector()}")
|
def resp = parent.apiGET("/lights/${selector()}")
|
||||||
if (resp.status == 404) {
|
if (resp.status == 404) {
|
||||||
sendEvent(name: "switch", value: "unreachable")
|
state.online = false
|
||||||
|
sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false)
|
||||||
|
log.warn "$device is Offline"
|
||||||
return []
|
return []
|
||||||
} else if (resp.status != 200) {
|
} else if (resp.status != 200) {
|
||||||
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
|
log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}")
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
def data = resp.data[0]
|
def data = resp.data[0]
|
||||||
@@ -209,19 +206,20 @@ def poll() {
|
|||||||
sendEvent(name: "label", value: data.label)
|
sendEvent(name: "label", value: data.label)
|
||||||
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
|
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
|
||||||
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
|
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
|
||||||
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
|
sendEvent(name: "switch", value: data.power)
|
||||||
sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int))
|
sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int))
|
||||||
sendEvent(name: "hue", value: data.color.hue / 3.6)
|
sendEvent(name: "hue", value: data.color.hue / 3.6)
|
||||||
sendEvent(name: "saturation", value: data.color.saturation * 100)
|
sendEvent(name: "saturation", value: data.color.saturation * 100)
|
||||||
sendEvent(name: "colorTemperature", value: data.color.kelvin)
|
sendEvent(name: "colorTemperature", value: data.color.kelvin)
|
||||||
sendEvent(name: "model", value: "${data.product.company} ${data.product.name}")
|
sendEvent(name: "model", value: data.product.name)
|
||||||
|
|
||||||
return []
|
if (data.connected) {
|
||||||
|
sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
|
||||||
|
log.debug "$device is Online"
|
||||||
|
} else {
|
||||||
|
sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
|
||||||
|
log.warn "$device is Offline"
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "Executing 'refresh'"
|
|
||||||
poll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def selector() {
|
def selector() {
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ metadata {
|
|||||||
capability "Color Temperature"
|
capability "Color Temperature"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level" // brightness
|
capability "Switch Level" // brightness
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
capability "Health Check"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -22,13 +22,12 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState "level", action:"switch level.setLevel"
|
attributeState "level", action:"switch level.setLevel"
|
||||||
}
|
}
|
||||||
@@ -53,15 +52,10 @@ metadata {
|
|||||||
main "switch"
|
main "switch"
|
||||||
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
void installed() {
|
||||||
def parse(String description) {
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"cloud\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device?.hub?.hardwareID}\"}")
|
||||||
if (description == 'updated') {
|
|
||||||
return // don't poll when config settings is being updated as it may time out
|
|
||||||
}
|
|
||||||
poll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle commands
|
// handle commands
|
||||||
@@ -71,7 +65,6 @@ def setLevel(percentage) {
|
|||||||
percentage = 1 // clamp to 1%
|
percentage = 1 // clamp to 1%
|
||||||
}
|
}
|
||||||
if (percentage == 0) {
|
if (percentage == 0) {
|
||||||
sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update
|
|
||||||
return off() // if the brightness is set to 0, just turn it off
|
return off() // if the brightness is set to 0, just turn it off
|
||||||
}
|
}
|
||||||
parent.logErrors(logObject:log) {
|
parent.logErrors(logObject:log) {
|
||||||
@@ -123,14 +116,17 @@ def off() {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
def poll() {
|
def refresh() {
|
||||||
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
|
log.debug "Executing 'refresh'"
|
||||||
|
|
||||||
def resp = parent.apiGET("/lights/${selector()}")
|
def resp = parent.apiGET("/lights/${selector()}")
|
||||||
if (resp.status == 404) {
|
if (resp.status == 404) {
|
||||||
sendEvent(name: "switch", value: "unreachable")
|
state.online = false
|
||||||
|
sendEvent(name: "DeviceWatch-DeviceStatusUpdate", value: "offline", displayed: false)
|
||||||
|
log.warn "$device is Offline"
|
||||||
return []
|
return []
|
||||||
} else if (resp.status != 200) {
|
} else if (resp.status != 200) {
|
||||||
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
|
log.error("Unexpected result in refresh(): [${resp.status}] ${resp.data}")
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
def data = resp.data[0]
|
def data = resp.data[0]
|
||||||
@@ -138,16 +134,17 @@ def poll() {
|
|||||||
sendEvent(name: "label", value: data.label)
|
sendEvent(name: "label", value: data.label)
|
||||||
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
|
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
|
||||||
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
|
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
|
||||||
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
|
sendEvent(name: "switch", value: data.power)
|
||||||
sendEvent(name: "colorTemperature", value: data.color.kelvin)
|
sendEvent(name: "colorTemperature", value: data.color.kelvin)
|
||||||
sendEvent(name: "model", value: data.product.name)
|
sendEvent(name: "model", value: data.product.name)
|
||||||
|
|
||||||
return []
|
if (data.connected) {
|
||||||
}
|
sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
|
||||||
|
log.debug "$device is Online"
|
||||||
def refresh() {
|
} else {
|
||||||
log.debug "Executing 'refresh'"
|
sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
|
||||||
poll()
|
log.warn "$device is Offline"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def selector() {
|
def selector() {
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def push() {
|
def push() {
|
||||||
sendEvent(name: "switch", value: "on", isStateChange: true, display: false)
|
sendEvent(name: "switch", value: "on", isStateChange: true, displayed: false)
|
||||||
sendEvent(name: "switch", value: "off", isStateChange: true, display: false)
|
sendEvent(name: "switch", value: "off", isStateChange: true, displayed: false)
|
||||||
sendEvent(name: "momentary", value: "pushed", isStateChange: true)
|
sendEvent(name: "momentary", value: "pushed", isStateChange: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Nyce Door/Window Sensor (Open/Close Sensor)
|
# Nyce Door/Window Sensor (Open/Close Sensor)
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -23,7 +23,11 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C2 Nyce Door/Window sensor that has 12min check-in interval
|
Nyce Door/Window sensor with reporting interval of 5 min.
|
||||||
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
|
|
||||||
|
* __12min__ checkInterval
|
||||||
|
|
||||||
|
|
||||||
## Battery Specification
|
## Battery Specification
|
||||||
|
|
||||||
|
|||||||
@@ -1,381 +0,0 @@
|
|||||||
/*
|
|
||||||
Osram Lightify Gardenspot Mini RGB
|
|
||||||
|
|
||||||
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
|
|
||||||
that issue by using state variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "OSRAM LIGHTIFY Gardenspot mini RGB", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Color Temperature"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Color Control"
|
|
||||||
|
|
||||||
attribute "colorName", "string"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
status "on": "on/off: 1"
|
|
||||||
status "off": "on/off: 0"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "zcl on-off on": "on/off: 1"
|
|
||||||
reply "zcl on-off off": "on/off: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles {
|
|
||||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
|
|
||||||
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
|
|
||||||
state "color", action:"setAdjustedColor"
|
|
||||||
}
|
|
||||||
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "colorName", label: '${currentValue}'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "level", label: 'Level ${currentValue}%'
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["switch"])
|
|
||||||
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "rgbSelector"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
//log.info "description is $description"
|
|
||||||
if (description?.startsWith("catchall:")) {
|
|
||||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
|
||||||
{
|
|
||||||
def result = createEvent(name: "switch", value: "on")
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
|
|
||||||
{
|
|
||||||
if(!(description?.startsWith("catchall: 0104 0300"))){
|
|
||||||
def result = createEvent(name: "switch", value: "off")
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (description?.startsWith("read attr -")) {
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
log.trace "descMap : $descMap"
|
|
||||||
|
|
||||||
if (descMap.cluster == "0300") {
|
|
||||||
if(descMap.attrId == "0000"){ //Hue Attribute
|
|
||||||
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
|
|
||||||
log.debug "Hue value returned is $hueValue"
|
|
||||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
|
||||||
}
|
|
||||||
else if(descMap.attrId == "0001"){ //Saturation Attribute
|
|
||||||
def saturationValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
|
|
||||||
log.debug "Saturation from refresh is $saturationValue"
|
|
||||||
sendEvent(name: "saturation", value: saturationValue, displayed:false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(descMap.cluster == "0008"){
|
|
||||||
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
|
|
||||||
log.debug "dimmer value is $dimmerValue"
|
|
||||||
sendEvent(name: "level", value: dimmerValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def name = description?.startsWith("on/off: ") ? "switch" : null
|
|
||||||
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
|
|
||||||
def result = createEvent(name: name, value: value)
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
log.debug "on()"
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
setLevel(state?.levelValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zigbeeOff() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "off()"
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
zigbeeOff()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
[
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1"
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
state.levelValue = 100
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
|
||||||
def configCmds = [
|
|
||||||
|
|
||||||
//Switch Reporting
|
|
||||||
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",
|
|
||||||
|
|
||||||
//Level Control Reporting
|
|
||||||
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
|
|
||||||
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",
|
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
|
|
||||||
]
|
|
||||||
return configCmds + refresh() // send refresh cmds as part of config
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll(){
|
|
||||||
log.debug "Poll is calling refresh"
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def zigbeeSetLevel(level) {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
state.levelValue = (value==null) ? 100 : value
|
|
||||||
log.trace "setLevel($value)"
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
if (value == 0) {
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
cmds << zigbeeOff()
|
|
||||||
}
|
|
||||||
else if (device.latestValue("switch") == "off") {
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEvent(name: "level", value: state.levelValue)
|
|
||||||
def level = hex(state.levelValue * 255 / 100)
|
|
||||||
cmds << zigbeeSetLevel(level)
|
|
||||||
|
|
||||||
//log.debug cmds
|
|
||||||
cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
//input Hue Integer values; returns color name for saturation 100%
|
|
||||||
private getColorName(hueValue){
|
|
||||||
if(hueValue>100 || hueValue<0)
|
|
||||||
return
|
|
||||||
|
|
||||||
hueValue = Math.round(hueValue / 100 * 360)
|
|
||||||
|
|
||||||
log.debug "hue value is $hueValue"
|
|
||||||
|
|
||||||
def colorName = "Color Mode"
|
|
||||||
if(hueValue>=0 && hueValue <= 4){
|
|
||||||
colorName = "Red"
|
|
||||||
}
|
|
||||||
else if (hueValue>=5 && hueValue <=21 ){
|
|
||||||
colorName = "Brick Red"
|
|
||||||
}
|
|
||||||
else if (hueValue>=22 && hueValue <=30 ){
|
|
||||||
colorName = "Safety Orange"
|
|
||||||
}
|
|
||||||
else if (hueValue>=31 && hueValue <=40 ){
|
|
||||||
colorName = "Dark Orange"
|
|
||||||
}
|
|
||||||
else if (hueValue>=41 && hueValue <=49 ){
|
|
||||||
colorName = "Amber"
|
|
||||||
}
|
|
||||||
else if (hueValue>=50 && hueValue <=56 ){
|
|
||||||
colorName = "Gold"
|
|
||||||
}
|
|
||||||
else if (hueValue>=57 && hueValue <=65 ){
|
|
||||||
colorName = "Yellow"
|
|
||||||
}
|
|
||||||
else if (hueValue>=66 && hueValue <=83 ){
|
|
||||||
colorName = "Electric Lime"
|
|
||||||
}
|
|
||||||
else if (hueValue>=84 && hueValue <=93 ){
|
|
||||||
colorName = "Lawn Green"
|
|
||||||
}
|
|
||||||
else if (hueValue>=94 && hueValue <=112 ){
|
|
||||||
colorName = "Bright Green"
|
|
||||||
}
|
|
||||||
else if (hueValue>=113 && hueValue <=135 ){
|
|
||||||
colorName = "Lime"
|
|
||||||
}
|
|
||||||
else if (hueValue>=136 && hueValue <=166 ){
|
|
||||||
colorName = "Spring Green"
|
|
||||||
}
|
|
||||||
else if (hueValue>=167 && hueValue <=171 ){
|
|
||||||
colorName = "Turquoise"
|
|
||||||
}
|
|
||||||
else if (hueValue>=172 && hueValue <=187 ){
|
|
||||||
colorName = "Aqua"
|
|
||||||
}
|
|
||||||
else if (hueValue>=188 && hueValue <=203 ){
|
|
||||||
colorName = "Sky Blue"
|
|
||||||
}
|
|
||||||
else if (hueValue>=204 && hueValue <=217 ){
|
|
||||||
colorName = "Dodger Blue"
|
|
||||||
}
|
|
||||||
else if (hueValue>=218 && hueValue <=223 ){
|
|
||||||
colorName = "Navy Blue"
|
|
||||||
}
|
|
||||||
else if (hueValue>=224 && hueValue <=251 ){
|
|
||||||
colorName = "Blue"
|
|
||||||
}
|
|
||||||
else if (hueValue>=252 && hueValue <=256 ){
|
|
||||||
colorName = "Han Purple"
|
|
||||||
}
|
|
||||||
else if (hueValue>=257 && hueValue <=274 ){
|
|
||||||
colorName = "Electric Indigo"
|
|
||||||
}
|
|
||||||
else if (hueValue>=275 && hueValue <=289 ){
|
|
||||||
colorName = "Electric Purple"
|
|
||||||
}
|
|
||||||
else if (hueValue>=290 && hueValue <=300 ){
|
|
||||||
colorName = "Orchid Purple"
|
|
||||||
}
|
|
||||||
else if (hueValue>=301 && hueValue <=315 ){
|
|
||||||
colorName = "Magenta"
|
|
||||||
}
|
|
||||||
else if (hueValue>=316 && hueValue <=326 ){
|
|
||||||
colorName = "Hot Pink"
|
|
||||||
}
|
|
||||||
else if (hueValue>=327 && hueValue <=335 ){
|
|
||||||
colorName = "Deep Pink"
|
|
||||||
}
|
|
||||||
else if (hueValue>=336 && hueValue <=339 ){
|
|
||||||
colorName = "Raspberry"
|
|
||||||
}
|
|
||||||
else if (hueValue>=340 && hueValue <=352 ){
|
|
||||||
colorName = "Crimson"
|
|
||||||
}
|
|
||||||
else if (hueValue>=353 && hueValue <=360 ){
|
|
||||||
colorName = "Red"
|
|
||||||
}
|
|
||||||
|
|
||||||
colorName
|
|
||||||
}
|
|
||||||
|
|
||||||
private getEndpointId() {
|
|
||||||
new BigInteger(device.endpointId, 16).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private hex(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
private evenHex(value){
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() % 2 != 0) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer convertHexToInt(hex) {
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need to reverse array of size 2
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
|
||||||
byte tmp;
|
|
||||||
tmp = array[1];
|
|
||||||
array[1] = array[0];
|
|
||||||
array[0] = tmp;
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
def setAdjustedColor(value) {
|
|
||||||
log.debug "setAdjustedColor: ${value}"
|
|
||||||
def adjusted = value + [:]
|
|
||||||
adjusted.level = null // needed because color picker always sends 100
|
|
||||||
setColor(adjusted)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColor(value){
|
|
||||||
log.trace "setColor($value)"
|
|
||||||
def max = 0xfe
|
|
||||||
|
|
||||||
if (value.hex) { sendEvent(name: "color", value: value.hex, displayed:false)}
|
|
||||||
|
|
||||||
def colorName = getColorName(value.hue)
|
|
||||||
sendEvent(name: "colorName", value: colorName)
|
|
||||||
|
|
||||||
log.debug "color name is : $colorName"
|
|
||||||
sendEvent(name: "hue", value: value.hue, displayed:false)
|
|
||||||
sendEvent(name: "saturation", value: value.saturation, displayed:false)
|
|
||||||
def scaledHueValue = evenHex(Math.round(value.hue * max / 100.0))
|
|
||||||
def scaledSatValue = evenHex(Math.round(value.saturation * max / 100.0))
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
if (value.switch != "off" && device.latestValue("switch") == "off") {
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${scaledHueValue} 00 0000}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${scaledSatValue} 0000}"
|
|
||||||
|
|
||||||
if (value.level) {
|
|
||||||
state.levelValue = value.level
|
|
||||||
sendEvent(name: "level", value: value.level)
|
|
||||||
def level = hex(value.level * 255 / 100)
|
|
||||||
cmd << zigbeeSetLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.switch == "off") {
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << off()
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
@@ -1,459 +0,0 @@
|
|||||||
/*
|
|
||||||
Osram Flex RGBW Light Strip
|
|
||||||
|
|
||||||
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
|
|
||||||
that issue by using state variables
|
|
||||||
*/
|
|
||||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Color Temperature"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Color Control"
|
|
||||||
|
|
||||||
attribute "colorName", "string"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
|
||||||
|
|
||||||
|
|
||||||
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
|
|
||||||
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
status "on": "on/off: 1"
|
|
||||||
status "off": "on/off: 0"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "zcl on-off on": "on/off: 1"
|
|
||||||
reply "zcl on-off off": "on/off: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles {
|
|
||||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
|
|
||||||
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
|
|
||||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
|
||||||
}
|
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "colorTemperature", label: '${currentValue} K'
|
|
||||||
}
|
|
||||||
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "colorName", label: '${currentValue}'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
|
|
||||||
state "color", action:"setAdjustedColor"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "level", label: 'Level ${currentValue}%'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
main(["switch"])
|
|
||||||
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "colorTempSliderControl", "colorTemp", "rgbSelector"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
//log.info "description is $description"
|
|
||||||
if (description?.startsWith("catchall:")) {
|
|
||||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
|
||||||
{
|
|
||||||
def result = createEvent(name: "switch", value: "on")
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
|
|
||||||
{
|
|
||||||
if(!(description?.startsWith("catchall: 0104 0300"))){
|
|
||||||
def result = createEvent(name: "switch", value: "off")
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (description?.startsWith("read attr -")) { //for values returned after hitting refresh
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
log.trace "descMap : $descMap"
|
|
||||||
|
|
||||||
if (descMap.cluster == "0300") {
|
|
||||||
if(descMap.attrId == "0007"){
|
|
||||||
log.debug "in read attr"
|
|
||||||
log.debug descMap.value
|
|
||||||
def tempInMired = convertHexToInt(descMap.value)
|
|
||||||
def tempInKelvin = Math.round(1000000/tempInMired)
|
|
||||||
log.trace "temp in kelvin: $tempInKelvin"
|
|
||||||
sendEvent(name: "colorTemperature", value: tempInKelvin, displayed:false)
|
|
||||||
}
|
|
||||||
else if(descMap.attrId == "0008"){ //Color mode attribute
|
|
||||||
if(descMap.value == "00"){
|
|
||||||
state.colorType = "rgb"
|
|
||||||
}else if(descMap.value == "02"){
|
|
||||||
state.colorType = "white"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(descMap.attrId == "0000"){ //Hue Attribute
|
|
||||||
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
|
|
||||||
log.debug "Hue value returned is $hueValue"
|
|
||||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
|
||||||
}
|
|
||||||
else if(descMap.attrId == "0001"){ //Saturation Attribute
|
|
||||||
def saturationValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
|
|
||||||
log.debug "Saturation from refresh is $saturationValue"
|
|
||||||
sendEvent(name: "saturation", value: saturationValue, displayed:false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(descMap.cluster == "0008"){
|
|
||||||
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
|
|
||||||
log.debug "dimmer value is $dimmerValue"
|
|
||||||
sendEvent(name: "level", value: dimmerValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def name = description?.startsWith("on/off: ") ? "switch" : null
|
|
||||||
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
|
|
||||||
def result = createEvent(name: name, value: value)
|
|
||||||
log.debug "description is $description"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
log.debug "on()"
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
setLevel(state?.levelValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zigbeeOff() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "off()"
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
zigbeeOff()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
[
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 7", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 8"
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
state.levelValue = 100
|
|
||||||
state.colorType = "white"
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
|
||||||
def configCmds = [
|
|
||||||
|
|
||||||
//Switch Reporting
|
|
||||||
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",
|
|
||||||
|
|
||||||
//Level Control Reporting
|
|
||||||
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
|
|
||||||
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",
|
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
|
|
||||||
]
|
|
||||||
return configCmds + refresh() // send refresh cmds as part of config
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColorTemperature(value) {
|
|
||||||
state?.colorType = "white"
|
|
||||||
if(value<101){
|
|
||||||
value = (value*38) + 2700 //Calculation of mapping 0-100 to 2700-6500
|
|
||||||
}
|
|
||||||
|
|
||||||
def tempInMired = Math.round(1000000/value)
|
|
||||||
def finalHex = swapEndianHex(hex(tempInMired, 4))
|
|
||||||
def genericName = getGenericName(value)
|
|
||||||
log.debug "generic name is : $genericName"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
sendEvent(name: "colorTemperature", value: value, displayed:false)
|
|
||||||
sendEvent(name: "colorName", value: genericName)
|
|
||||||
|
|
||||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0300 0x0a {${finalHex} 2000}"
|
|
||||||
|
|
||||||
cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll(){
|
|
||||||
log.debug "Poll is calling refresh"
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def zigbeeSetLevel(level) {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
state.levelValue = (value==null) ? 100 : value
|
|
||||||
log.trace "setLevel($value)"
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
if (value == 0) {
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
cmds << zigbeeOff()
|
|
||||||
}
|
|
||||||
else if (device.latestValue("switch") == "off") {
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEvent(name: "level", value: state.levelValue)
|
|
||||||
def level = hex(state.levelValue * 255 / 100)
|
|
||||||
cmds << zigbeeSetLevel(level)
|
|
||||||
|
|
||||||
//log.debug cmds
|
|
||||||
cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
|
||||||
private getGenericName(value){
|
|
||||||
def genericName = "White"
|
|
||||||
if(state?.colorType == "rgb"){
|
|
||||||
genericName = "Color Mode"
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if(value < 3300){
|
|
||||||
genericName = "Soft White"
|
|
||||||
} else if(value < 4150){
|
|
||||||
genericName = "Moonlight"
|
|
||||||
} else if(value < 5000){
|
|
||||||
genericName = "Cool White"
|
|
||||||
} else if(value <= 6500){
|
|
||||||
genericName = "Daylight"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
genericName
|
|
||||||
}
|
|
||||||
|
|
||||||
//input Hue Integer values; returns color name for saturation 100%
|
|
||||||
private getColorName(hueValue){
|
|
||||||
if(hueValue>100 || hueValue<0)
|
|
||||||
return
|
|
||||||
|
|
||||||
hueValue = Math.round(hueValue / 100 * 360)
|
|
||||||
|
|
||||||
log.debug "hue value is $hueValue"
|
|
||||||
|
|
||||||
def colorName = "Color Mode"
|
|
||||||
if(hueValue>=0 && hueValue <= 4){
|
|
||||||
colorName = "Red"
|
|
||||||
}
|
|
||||||
else if (hueValue>=5 && hueValue <=21 ){
|
|
||||||
colorName = "Brick Red"
|
|
||||||
}
|
|
||||||
else if (hueValue>=22 && hueValue <=30 ){
|
|
||||||
colorName = "Safety Orange"
|
|
||||||
}
|
|
||||||
else if (hueValue>=31 && hueValue <=40 ){
|
|
||||||
colorName = "Dark Orange"
|
|
||||||
}
|
|
||||||
else if (hueValue>=41 && hueValue <=49 ){
|
|
||||||
colorName = "Amber"
|
|
||||||
}
|
|
||||||
else if (hueValue>=50 && hueValue <=56 ){
|
|
||||||
colorName = "Gold"
|
|
||||||
}
|
|
||||||
else if (hueValue>=57 && hueValue <=65 ){
|
|
||||||
colorName = "Yellow"
|
|
||||||
}
|
|
||||||
else if (hueValue>=66 && hueValue <=83 ){
|
|
||||||
colorName = "Electric Lime"
|
|
||||||
}
|
|
||||||
else if (hueValue>=84 && hueValue <=93 ){
|
|
||||||
colorName = "Lawn Green"
|
|
||||||
}
|
|
||||||
else if (hueValue>=94 && hueValue <=112 ){
|
|
||||||
colorName = "Bright Green"
|
|
||||||
}
|
|
||||||
else if (hueValue>=113 && hueValue <=135 ){
|
|
||||||
colorName = "Lime"
|
|
||||||
}
|
|
||||||
else if (hueValue>=136 && hueValue <=166 ){
|
|
||||||
colorName = "Spring Green"
|
|
||||||
}
|
|
||||||
else if (hueValue>=167 && hueValue <=171 ){
|
|
||||||
colorName = "Turquoise"
|
|
||||||
}
|
|
||||||
else if (hueValue>=172 && hueValue <=187 ){
|
|
||||||
colorName = "Aqua"
|
|
||||||
}
|
|
||||||
else if (hueValue>=188 && hueValue <=203 ){
|
|
||||||
colorName = "Sky Blue"
|
|
||||||
}
|
|
||||||
else if (hueValue>=204 && hueValue <=217 ){
|
|
||||||
colorName = "Dodger Blue"
|
|
||||||
}
|
|
||||||
else if (hueValue>=218 && hueValue <=223 ){
|
|
||||||
colorName = "Navy Blue"
|
|
||||||
}
|
|
||||||
else if (hueValue>=224 && hueValue <=251 ){
|
|
||||||
colorName = "Blue"
|
|
||||||
}
|
|
||||||
else if (hueValue>=252 && hueValue <=256 ){
|
|
||||||
colorName = "Han Purple"
|
|
||||||
}
|
|
||||||
else if (hueValue>=257 && hueValue <=274 ){
|
|
||||||
colorName = "Electric Indigo"
|
|
||||||
}
|
|
||||||
else if (hueValue>=275 && hueValue <=289 ){
|
|
||||||
colorName = "Electric Purple"
|
|
||||||
}
|
|
||||||
else if (hueValue>=290 && hueValue <=300 ){
|
|
||||||
colorName = "Orchid Purple"
|
|
||||||
}
|
|
||||||
else if (hueValue>=301 && hueValue <=315 ){
|
|
||||||
colorName = "Magenta"
|
|
||||||
}
|
|
||||||
else if (hueValue>=316 && hueValue <=326 ){
|
|
||||||
colorName = "Hot Pink"
|
|
||||||
}
|
|
||||||
else if (hueValue>=327 && hueValue <=335 ){
|
|
||||||
colorName = "Deep Pink"
|
|
||||||
}
|
|
||||||
else if (hueValue>=336 && hueValue <=339 ){
|
|
||||||
colorName = "Raspberry"
|
|
||||||
}
|
|
||||||
else if (hueValue>=340 && hueValue <=352 ){
|
|
||||||
colorName = "Crimson"
|
|
||||||
}
|
|
||||||
else if (hueValue>=353 && hueValue <=360 ){
|
|
||||||
colorName = "Red"
|
|
||||||
}
|
|
||||||
|
|
||||||
colorName
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private getEndpointId() {
|
|
||||||
new BigInteger(device.endpointId, 16).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private hex(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
private evenHex(value){
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() % 2 != 0) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer convertHexToInt(hex) {
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need to reverse array of size 2
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
|
||||||
byte tmp;
|
|
||||||
tmp = array[1];
|
|
||||||
array[1] = array[0];
|
|
||||||
array[0] = tmp;
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
def setAdjustedColor(value) {
|
|
||||||
log.debug "setAdjustedColor: ${value}"
|
|
||||||
def adjusted = value + [:]
|
|
||||||
adjusted.level = null // needed because color picker always sends 100
|
|
||||||
setColor(adjusted)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColor(value){
|
|
||||||
state?.colorType = "rgb"
|
|
||||||
log.trace "setColor($value)"
|
|
||||||
def max = 0xfe
|
|
||||||
|
|
||||||
if (value.hex) { sendEvent(name: "color", value: value.hex, displayed:false)}
|
|
||||||
|
|
||||||
def colorName = getColorName(value.hue)
|
|
||||||
log.debug "color name is : $colorName"
|
|
||||||
sendEvent(name: "colorName", value: colorName)
|
|
||||||
sendEvent(name: "colorTemperature", value: "--", displayed:false)
|
|
||||||
|
|
||||||
|
|
||||||
sendEvent(name: "hue", value: value.hue, displayed:false)
|
|
||||||
sendEvent(name: "saturation", value: value.saturation, displayed:false)
|
|
||||||
def scaledHueValue = evenHex(Math.round(value.hue * max / 100.0))
|
|
||||||
def scaledSatValue = evenHex(Math.round(value.saturation * max / 100.0))
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
if (value.switch != "off" && device.latestValue("switch") == "off") {
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${scaledHueValue} 00 0000}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${scaledSatValue} 0000}"
|
|
||||||
|
|
||||||
if (value.level) {
|
|
||||||
state.levelValue = value.level
|
|
||||||
sendEvent(name: "level", value: value.level)
|
|
||||||
def level = hex(value.level * 255 / 100)
|
|
||||||
cmd << zigbeeSetLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.switch == "off") {
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << off()
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
/*
|
|
||||||
Osram Tunable White 60 A19 bulb
|
|
||||||
|
|
||||||
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
|
|
||||||
that issue by using state variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "OSRAM LIGHTIFY LED Tunable White 60W", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Color Temperature"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
attribute "colorName", "string"
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
status "on": "on/off: 1"
|
|
||||||
status "off": "on/off: 0"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "zcl on-off on": "on/off: 1"
|
|
||||||
reply "zcl on-off off": "on/off: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles {
|
|
||||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
|
|
||||||
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
|
|
||||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
|
||||||
}
|
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "colorTemperature", label: '${currentValue} K'
|
|
||||||
}
|
|
||||||
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "colorName", label: '${currentValue}'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "level", label: 'Level ${currentValue}%'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
main(["switch"])
|
|
||||||
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "colorTempSliderControl", "colorTemp"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
//log.trace description
|
|
||||||
|
|
||||||
if (description?.startsWith("catchall:")) {
|
|
||||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
|
||||||
{
|
|
||||||
def result = createEvent(name: "switch", value: "on")
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
|
|
||||||
{
|
|
||||||
def result = createEvent(name: "switch", value: "off")
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (description?.startsWith("read attr -")) {
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
log.trace "descMap : $descMap"
|
|
||||||
|
|
||||||
if (descMap.cluster == "0300") {
|
|
||||||
log.debug descMap.value
|
|
||||||
def tempInMired = convertHexToInt(descMap.value)
|
|
||||||
def tempInKelvin = Math.round(1000000/tempInMired)
|
|
||||||
log.trace "temp in kelvin: $tempInKelvin"
|
|
||||||
sendEvent(name: "colorTemperature", value: tempInKelvin, displayed:false)
|
|
||||||
}
|
|
||||||
else if(descMap.cluster == "0008"){
|
|
||||||
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
|
|
||||||
log.debug "dimmer value is $dimmerValue"
|
|
||||||
sendEvent(name: "level", value: dimmerValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def name = description?.startsWith("on/off: ") ? "switch" : null
|
|
||||||
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
|
|
||||||
def result = createEvent(name: name, value: value)
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
log.debug "on()"
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
setLevel(state?.levelValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "off()"
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
[
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 7"
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
state.levelValue = 100
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
|
||||||
def configCmds = [
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
|
|
||||||
]
|
|
||||||
return onOffConfig() + levelConfig() + configCmds + refresh() // send refresh cmds as part of config
|
|
||||||
}
|
|
||||||
|
|
||||||
def onOffConfig() {
|
|
||||||
[
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zcl global send-me-a-report 6 0 0x10 0 300 {01}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
|
|
||||||
//min level change is 01
|
|
||||||
def levelConfig() {
|
|
||||||
[
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColorTemperature(value) {
|
|
||||||
if(value<101){
|
|
||||||
value = (value*38) + 2700 //Calculation of mapping 0-100 to 2700-6500
|
|
||||||
}
|
|
||||||
|
|
||||||
def tempInMired = Math.round(1000000/value)
|
|
||||||
def finalHex = swapEndianHex(hex(tempInMired, 4))
|
|
||||||
def genericName = getGenericName(value)
|
|
||||||
log.debug "generic name is : $genericName"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
sendEvent(name: "colorTemperature", value: value, displayed:false)
|
|
||||||
sendEvent(name: "colorName", value: genericName)
|
|
||||||
|
|
||||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0300 0x0a {${finalHex} 2000}"
|
|
||||||
|
|
||||||
cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
state.levelValue = (value==null) ? 100 : value
|
|
||||||
log.trace "setLevel($value)"
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
if (value == 0) {
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
|
||||||
}
|
|
||||||
else if (device.latestValue("switch") == "off") {
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEvent(name: "level", value: state.levelValue)
|
|
||||||
def level = hex(state.levelValue * 254 / 100)
|
|
||||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
|
|
||||||
|
|
||||||
//log.debug cmds
|
|
||||||
cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
|
||||||
private getGenericName(value){
|
|
||||||
def genericName = "White"
|
|
||||||
if(value < 3300){
|
|
||||||
genericName = "Soft White"
|
|
||||||
} else if(value < 4150){
|
|
||||||
genericName = "Moonlight"
|
|
||||||
} else if(value < 5000){
|
|
||||||
genericName = "Cool White"
|
|
||||||
} else if(value <= 6500){
|
|
||||||
genericName = "Daylight"
|
|
||||||
}
|
|
||||||
|
|
||||||
genericName
|
|
||||||
}
|
|
||||||
|
|
||||||
private getEndpointId() {
|
|
||||||
new BigInteger(device.endpointId, 16).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private hex(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer convertHexToInt(hex) {
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need to reverse array of size 2
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
|
||||||
byte tmp;
|
|
||||||
tmp = array[1];
|
|
||||||
array[1] = array[0];
|
|
||||||
array[0] = tmp;
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
@@ -69,15 +69,17 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
|
|
||||||
|
def event = [:]
|
||||||
def finalResult = isKnownDescription(description)
|
def finalResult = isKnownDescription(description)
|
||||||
if (finalResult != "false") {
|
if (finalResult) {
|
||||||
log.info finalResult
|
log.info finalResult
|
||||||
if (finalResult.type == "update") {
|
if (finalResult.type == "update") {
|
||||||
log.info "$device updates: ${finalResult.value}"
|
log.info "$device updates: ${finalResult.value}"
|
||||||
|
event = null
|
||||||
}
|
}
|
||||||
else if (finalResult.type == "power") {
|
else if (finalResult.type == "power") {
|
||||||
def powerValue = (finalResult.value as Integer)/10
|
def powerValue = (finalResult.value as Integer)/10
|
||||||
sendEvent(name: "power", value: powerValue)
|
event = createEvent(name: "power", value: powerValue)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
||||||
@@ -87,13 +89,14 @@ def parse(String description) {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendEvent(name: finalResult.type, value: finalResult.value)
|
event = createEvent(name: finalResult.type, value: finalResult.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
log.debug parseDescriptionAsMap(description)
|
log.debug parseDescriptionAsMap(description)
|
||||||
}
|
}
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commands to device
|
// Commands to device
|
||||||
@@ -125,9 +128,9 @@ def setLevel(value) {
|
|||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
[
|
[
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 2000",
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 2000",
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0B04 0x050B", "delay 500"
|
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0B04 0x050B", "delay 2000"
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -209,13 +212,16 @@ def isKnownDescription(description) {
|
|||||||
else if (descMap.cluster == "0B04" || descMap.clusterId == "0B04"){
|
else if (descMap.cluster == "0B04" || descMap.clusterId == "0B04"){
|
||||||
isDescriptionPower(descMap)
|
isDescriptionPower(descMap)
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return [:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(description?.startsWith("on/off:")) {
|
else if(description?.startsWith("on/off:")) {
|
||||||
def switchValue = description?.endsWith("1") ? "on" : "off"
|
def switchValue = description?.endsWith("1") ? "on" : "off"
|
||||||
return [type: "switch", value : switchValue]
|
return [type: "switch", value : switchValue]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return "false"
|
return [:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +255,7 @@ def isDescriptionOnOff(descMap) {
|
|||||||
return [type: "switch", value : switchValue]
|
return [type: "switch", value : switchValue]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return "false"
|
return [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -276,10 +282,9 @@ def isDescriptionLevel(descMap) {
|
|||||||
|
|
||||||
if (dimmerValue != -1){
|
if (dimmerValue != -1){
|
||||||
return [type: "level", value : dimmerValue]
|
return [type: "level", value : dimmerValue]
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return "false"
|
return [:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,16 +306,16 @@ def isDescriptionPower(descMap) {
|
|||||||
return [type: "power", value : powerValue]
|
return [type: "power", value : powerValue]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return "false"
|
return [:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def onOffConfig() {
|
def onOffConfig() {
|
||||||
[
|
[
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
|
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 2000",
|
||||||
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
|
"zcl global send-me-a-report 6 0 0x10 0 600 {01}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,9 +323,9 @@ def onOffConfig() {
|
|||||||
//min level change is 01
|
//min level change is 01
|
||||||
def levelConfig() {
|
def levelConfig() {
|
||||||
[
|
[
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 200",
|
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 2000",
|
||||||
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}",
|
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,9 +333,10 @@ def levelConfig() {
|
|||||||
//min change in value is 05
|
//min change in value is 05
|
||||||
def powerConfig() {
|
def powerConfig() {
|
||||||
[
|
[
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
|
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000",
|
||||||
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
|
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
"delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +345,10 @@ def setLevelWithRate(level, rate) {
|
|||||||
rate = "0000"
|
rate = "0000"
|
||||||
}
|
}
|
||||||
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
|
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}"
|
[
|
||||||
|
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}",
|
||||||
|
"delay 2000"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
String convertToHexString(value, width=2) {
|
String convertToHexString(value, width=2) {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ def on() {
|
|||||||
'zcl on-off on',
|
'zcl on-off on',
|
||||||
'delay 200',
|
'delay 200',
|
||||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||||
'delay 500'
|
'delay 2000'
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -62,6 +62,6 @@ def off() {
|
|||||||
'zcl on-off off',
|
'zcl on-off off',
|
||||||
'delay 200',
|
'delay 200',
|
||||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||||
'delay 500'
|
'delay 2000'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# SmartPower Outlet
|
# SmartPower Outlet
|
||||||
|
|
||||||
|
Local Execution on V2 Hubs
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -23,10 +23,11 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C1 smart power outlet with maxReportTime of 5 mins.
|
SmartPower outlet with reporting interval of 5 mins
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
* V1, TV, HubV2 AppEngine < 1.5.1 - __21min__ checkInterval
|
||||||
|
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ def parse(String description) {
|
|||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
|
|
||||||
def finalResult = zigbee.getKnownDescription(description)
|
def finalResult = zigbee.getKnownDescription(description)
|
||||||
|
def event = [:]
|
||||||
|
|
||||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||||
if (!finalResult && description!="updated")
|
if (!finalResult && description!="updated")
|
||||||
@@ -88,10 +89,11 @@ def parse(String description) {
|
|||||||
log.info "final result = $finalResult"
|
log.info "final result = $finalResult"
|
||||||
if (finalResult.type == "update") {
|
if (finalResult.type == "update") {
|
||||||
log.info "$device updates: ${finalResult.value}"
|
log.info "$device updates: ${finalResult.value}"
|
||||||
|
event = null
|
||||||
}
|
}
|
||||||
else if (finalResult.type == "power") {
|
else if (finalResult.type == "power") {
|
||||||
def powerValue = (finalResult.value as Integer)/10
|
def powerValue = (finalResult.value as Integer)/10
|
||||||
sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true )
|
event = createEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true)
|
||||||
/*
|
/*
|
||||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
||||||
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
||||||
@@ -100,7 +102,7 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
||||||
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
|
event = createEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -109,10 +111,11 @@ def parse(String description) {
|
|||||||
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07){
|
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07){
|
||||||
if (cluster.data[0] == 0x00) {
|
if (cluster.data[0] == 0x00) {
|
||||||
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
event = createEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||||
|
event = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -120,6 +123,7 @@ def parse(String description) {
|
|||||||
log.debug "${cluster}"
|
log.debug "${cluster}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
@@ -141,9 +145,9 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 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
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()
|
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()
|
||||||
@@ -153,9 +157,10 @@ def configure() {
|
|||||||
//min change in value is 01
|
//min change in value is 01
|
||||||
def powerConfig() {
|
def powerConfig() {
|
||||||
[
|
[
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
|
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000",
|
||||||
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
|
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
"delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ metadata {
|
|||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "parse($description)"
|
log.debug "parse($description)"
|
||||||
def results = null
|
def results = [:]
|
||||||
|
|
||||||
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||||
// Ignore this in favor of orientation-based state
|
// Ignore this in favor of orientation-based state
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Smartsense Moisture Sensor
|
# Smartsense Moisture Sensor
|
||||||
|
|
||||||
|
Local Execution on V2 Hubs
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -23,10 +23,11 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C2 moisture sensor with maxReportTime of 5 mins.
|
SmartSense Moisture sensor with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
|
||||||
|
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
|
||||||
|
|
||||||
## Battery Specification
|
## Battery Specification
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ metadata {
|
|||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
|
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
|
||||||
|
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-L", deviceJoinName: "Iris Smart Water Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
|
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $map"
|
log.debug "Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : [:]
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
@@ -128,7 +129,7 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
if (cluster.command == 0x07) {
|
if (cluster.command == 0x07) {
|
||||||
if (cluster.data[0] == 0x00){
|
if (cluster.data[0] == 0x00){
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||||
@@ -202,48 +203,37 @@ private Map getBatteryResult(rawValue) {
|
|||||||
log.debug "Battery rawValue = ${rawValue}"
|
log.debug "Battery rawValue = ${rawValue}"
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
def result = [
|
def result = [:]
|
||||||
name: 'battery',
|
|
||||||
value: '--',
|
|
||||||
translatable: true
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
|
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (!(rawValue == 0 || rawValue == 255)) {
|
||||||
else {
|
result.name = 'battery'
|
||||||
if (volts > 3.5) {
|
result.translatable = true
|
||||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||||
else {
|
volts = rawValue // For the batteryMap to work the key needs to be an int
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
def batteryMap = [28: 100, 27: 100, 26: 100, 25: 90, 24: 90, 23: 70,
|
||||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
22: 70, 21: 50, 20: 50, 19: 30, 18: 30, 17: 15, 16: 1, 15: 0]
|
||||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
def minVolts = 15
|
||||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
def maxVolts = 28
|
||||||
def minVolts = 15
|
|
||||||
def maxVolts = 28
|
|
||||||
|
|
||||||
if (volts < minVolts)
|
if (volts < minVolts)
|
||||||
volts = minVolts
|
volts = minVolts
|
||||||
else if (volts > maxVolts)
|
else if (volts > maxVolts)
|
||||||
volts = maxVolts
|
volts = maxVolts
|
||||||
def pct = batteryMap[volts]
|
def pct = batteryMap[volts]
|
||||||
if (pct != null) {
|
result.value = pct
|
||||||
result.value = pct
|
} else {
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
def minVolts = 2.1
|
||||||
}
|
def maxVolts = 3.0
|
||||||
}
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
else {
|
def roundedPct = Math.round(pct * 100)
|
||||||
def minVolts = 2.1
|
if (roundedPct <= 0)
|
||||||
def maxVolts = 3.0
|
roundedPct = 1
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
result.value = Math.min(100, roundedPct)
|
||||||
def roundedPct = Math.round(pct * 100)
|
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -296,17 +286,17 @@ def ping() {
|
|||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Refreshing Temperature and Battery"
|
log.debug "Refreshing Temperature and Battery"
|
||||||
def refreshCmds = [
|
def refreshCmds = [
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
|
||||||
]
|
]
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
return refreshCmds + enrollResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
|
||||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||||
@@ -319,10 +309,10 @@ def enrollResponse() {
|
|||||||
[
|
[
|
||||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
//Resending the CIE in case the enroll request is sent before CIE is written
|
||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
|
||||||
//Enroll Response
|
//Enroll Response
|
||||||
"raw 0x500 {01 23 00 00 00}",
|
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Smartsense Motion Sensor
|
# Smartsense Motion Sensor
|
||||||
|
|
||||||
|
Local Execution on V2 Hubs
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -22,10 +22,12 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C2 motion sensor with maxReportTime of 5 mins.
|
SmartSense Motion sensor with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
|
||||||
|
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
|
||||||
|
|
||||||
|
|
||||||
## Battery Specification
|
## Battery Specification
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $map"
|
log.debug "Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : [:]
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
@@ -132,7 +132,7 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
if (cluster.command == 0x07) {
|
if (cluster.command == 0x07) {
|
||||||
if (cluster.data[0] == 0x00) {
|
if (cluster.data[0] == 0x00) {
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||||
@@ -220,48 +220,35 @@ private Map getBatteryResult(rawValue) {
|
|||||||
log.debug "Battery rawValue = ${rawValue}"
|
log.debug "Battery rawValue = ${rawValue}"
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
def result = [
|
def result = [:]
|
||||||
name: 'battery',
|
|
||||||
value: '--',
|
|
||||||
translatable: true
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
|
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (!(rawValue == 0 || rawValue == 255)) {
|
||||||
else {
|
result.name = 'battery'
|
||||||
if (volts > 3.5) {
|
result.translatable = true
|
||||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||||
else {
|
volts = rawValue // For the batteryMap to work the key needs to be an int
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
def batteryMap = [28: 100, 27: 100, 26: 100, 25: 90, 24: 90, 23: 70,
|
||||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
22: 70, 21: 50, 20: 50, 19: 30, 18: 30, 17: 15, 16: 1, 15: 0]
|
||||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
def minVolts = 15
|
||||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
def maxVolts = 28
|
||||||
def minVolts = 15
|
|
||||||
def maxVolts = 28
|
|
||||||
|
|
||||||
if (volts < minVolts)
|
if (volts < minVolts)
|
||||||
volts = minVolts
|
volts = minVolts
|
||||||
else if (volts > maxVolts)
|
else if (volts > maxVolts)
|
||||||
volts = maxVolts
|
volts = maxVolts
|
||||||
def pct = batteryMap[volts]
|
def pct = batteryMap[volts]
|
||||||
if (pct != null) {
|
result.value = pct
|
||||||
result.value = pct
|
} else {
|
||||||
def value = pct
|
def minVolts = 2.1
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
def maxVolts = 3.0
|
||||||
}
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
}
|
def roundedPct = Math.round(pct * 100)
|
||||||
else {
|
if (roundedPct <= 0)
|
||||||
def minVolts = 2.1
|
roundedPct = 1
|
||||||
def maxVolts = 3.0
|
result.value = Math.min(100, roundedPct)
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
|
||||||
def roundedPct = Math.round(pct * 100)
|
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,17 +298,17 @@ def ping() {
|
|||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "refresh called"
|
log.debug "refresh called"
|
||||||
def refreshCmds = [
|
def refreshCmds = [
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
|
||||||
]
|
]
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
return refreshCmds + enrollResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
|
||||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||||
@@ -334,10 +321,10 @@ def enrollResponse() {
|
|||||||
[
|
[
|
||||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
//Resending the CIE in case the enroll request is sent before CIE is written
|
||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
|
||||||
//Enroll Response
|
//Enroll Response
|
||||||
"raw 0x500 {01 23 00 00 00}",
|
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,309 +0,0 @@
|
|||||||
/**
|
|
||||||
* SmartSense Motion/Temp Sensor
|
|
||||||
*
|
|
||||||
* Copyright 2014 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Motion Sensor"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Battery"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
status "active": "zone report :: type: 19 value: 0031"
|
|
||||||
status "inactive": "zone report :: type: 19 value: 0030"
|
|
||||||
}
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
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:"motion", type: "generic", width: 6, height: 4){
|
|
||||||
tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
|
||||||
attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["motion", "temperature"])
|
|
||||||
details(["motion", "temperature", "battery", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 0x0406:
|
|
||||||
log.debug 'motion'
|
|
||||||
resultMap.name = 'motion'
|
|
||||||
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 == "0406" && descMap.attrId == "0000") {
|
|
||||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
|
||||||
resultMap = getMotionResult(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
|
||||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
|
||||||
}
|
|
||||||
|
|
||||||
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.debug 'Battery'
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
|
|
||||||
log.debug rawValue
|
|
||||||
|
|
||||||
def result = [
|
|
||||||
name: 'battery',
|
|
||||||
value: '--'
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
|
||||||
def descriptionText
|
|
||||||
|
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
|
||||||
else {
|
|
||||||
if (volts > 3.5) {
|
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
|
||||||
}
|
|
||||||
else if (volts > 0){
|
|
||||||
def minVolts = 2.1
|
|
||||||
def maxVolts = 3.0
|
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
|
||||||
def roundedPct = Math.round(pct * 100)
|
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getTemperatureResult(value) {
|
|
||||||
log.debug 'TEMP'
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
if (tempOffset) {
|
|
||||||
def offset = tempOffset as int
|
|
||||||
def v = value as int
|
|
||||||
value = v + offset
|
|
||||||
}
|
|
||||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
|
||||||
return [
|
|
||||||
name: 'temperature',
|
|
||||||
value: value,
|
|
||||||
descriptionText: descriptionText,
|
|
||||||
unit: temperatureScale
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getMotionResult(value) {
|
|
||||||
log.debug 'motion'
|
|
||||||
String linkText = getLinkText(device)
|
|
||||||
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
|
|
||||||
return [
|
|
||||||
name: 'motion',
|
|
||||||
value: value,
|
|
||||||
descriptionText: descriptionText
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "refresh called"
|
|
||||||
def refreshCmds = [
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
|
||||||
]
|
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
|
||||||
|
|
||||||
def configCmds = [
|
|
||||||
"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",
|
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
def enrollResponse() {
|
|
||||||
log.debug "Sending enroll response"
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
|
||||||
[
|
|
||||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
|
||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
|
||||||
//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
|
|
||||||
}
|
|
||||||
@@ -44,7 +44,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def results
|
def results = [:]
|
||||||
if (isZoneType19(description) || !isSupportedDescription(description)) {
|
if (isZoneType19(description) || !isSupportedDescription(description)) {
|
||||||
results = parseBasicMessage(description)
|
results = parseBasicMessage(description)
|
||||||
}
|
}
|
||||||
@@ -57,21 +57,24 @@ def parse(String description) {
|
|||||||
|
|
||||||
private Map parseBasicMessage(description) {
|
private Map parseBasicMessage(description) {
|
||||||
def name = parseName(description)
|
def name = parseName(description)
|
||||||
def value = parseValue(description)
|
def results = [:]
|
||||||
def linkText = getLinkText(device)
|
if (name != null) {
|
||||||
def descriptionText = parseDescriptionText(linkText, value, description)
|
def value = parseValue(description)
|
||||||
def handlerName = value
|
def linkText = getLinkText(device)
|
||||||
def isStateChange = isStateChange(device, name, value)
|
def descriptionText = parseDescriptionText(linkText, value, description)
|
||||||
|
def handlerName = value
|
||||||
|
def isStateChange = isStateChange(device, name, value)
|
||||||
|
|
||||||
def results = [
|
results = [
|
||||||
name: name,
|
name : name,
|
||||||
value: value,
|
value : value,
|
||||||
linkText: linkText,
|
linkText : linkText,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText,
|
||||||
handlerName: handlerName,
|
handlerName : handlerName,
|
||||||
isStateChange: isStateChange,
|
isStateChange : isStateChange,
|
||||||
displayed: displayed(description, isStateChange)
|
displayed : displayed(description, isStateChange)
|
||||||
]
|
]
|
||||||
|
}
|
||||||
log.debug "Parse returned $results.descriptionText"
|
log.debug "Parse returned $results.descriptionText"
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Smartsense Multi Sensor
|
# Smartsense Multi Sensor
|
||||||
|
|
||||||
|
Local Execution on V2 Hubs
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -26,10 +26,11 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C2 multi sensor with maxReportTime of 5 mins.
|
SmartSense Multi sensor with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
|
||||||
|
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
|
||||||
|
|
||||||
## Battery Specification
|
## Battery Specification
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ def parse(String description) {
|
|||||||
map = parseIasMessage(description)
|
map = parseIasMessage(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : [:]
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
@@ -161,7 +161,7 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
if (cluster.command == 0x07) {
|
if (cluster.command == 0x07) {
|
||||||
if(cluster.data[0] == 0x00) {
|
if(cluster.data[0] == 0x00) {
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||||
@@ -281,47 +281,35 @@ def getTemperature(value) {
|
|||||||
private Map getBatteryResult(rawValue) {
|
private Map getBatteryResult(rawValue) {
|
||||||
log.debug "Battery rawValue = ${rawValue}"
|
log.debug "Battery rawValue = ${rawValue}"
|
||||||
|
|
||||||
def result = [
|
def result = [:]
|
||||||
name: 'battery',
|
|
||||||
value: '--',
|
|
||||||
translatable: true
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
|
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (!(rawValue == 0 || rawValue == 255)) {
|
||||||
else {
|
result.name = 'battery'
|
||||||
if (volts > 3.5) {
|
result.translatable = true
|
||||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||||
else {
|
volts = rawValue // For the batteryMap to work the key needs to be an int
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
def batteryMap = [28: 100, 27: 100, 26: 100, 25: 90, 24: 90, 23: 70,
|
||||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
22: 70, 21: 50, 20: 50, 19: 30, 18: 30, 17: 15, 16: 1, 15: 0]
|
||||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
def minVolts = 15
|
||||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
def maxVolts = 28
|
||||||
def minVolts = 15
|
|
||||||
def maxVolts = 28
|
|
||||||
|
|
||||||
if (volts < minVolts)
|
if (volts < minVolts)
|
||||||
volts = minVolts
|
volts = minVolts
|
||||||
else if (volts > maxVolts)
|
else if (volts > maxVolts)
|
||||||
volts = maxVolts
|
volts = maxVolts
|
||||||
def pct = batteryMap[volts]
|
def pct = batteryMap[volts]
|
||||||
if (pct != null) {
|
result.value = pct
|
||||||
result.value = pct
|
} else {
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
def minVolts = 2.1
|
||||||
}
|
def maxVolts = 3.0
|
||||||
}
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
else {
|
def roundedPct = Math.round(pct * 100)
|
||||||
def minVolts = 2.1
|
if (roundedPct <= 0)
|
||||||
def maxVolts = 3.0
|
roundedPct = 1
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
result.value = Math.min(100, roundedPct)
|
||||||
def roundedPct = Math.round(pct * 100)
|
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +339,7 @@ private Map getContactResult(value) {
|
|||||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
log.debug "Contact: ${device.displayName} value = ${value}"
|
||||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
||||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
||||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
|
return [name: 'status', value: value, descriptionText: descriptionText, translatable: true]
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAccelerationResult(numValue) {
|
private getAccelerationResult(numValue) {
|
||||||
@@ -412,9 +400,9 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
|
||||||
log.debug "Configuring Reporting"
|
log.debug "Configuring Reporting"
|
||||||
|
|
||||||
@@ -440,10 +428,10 @@ def enrollResponse() {
|
|||||||
[
|
[
|
||||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
//Resending the CIE in case the enroll request is sent before CIE is written
|
||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
|
||||||
//Enroll Response
|
//Enroll Response
|
||||||
"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 200"
|
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,334 +0,0 @@
|
|||||||
/**
|
|
||||||
* SmartSense Open/Closed Accelerometer Sensor
|
|
||||||
*
|
|
||||||
* Copyright 2014 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
|
||||||
capability "Battery"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Contact Sensor"
|
|
||||||
capability "Acceleration Sensor"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
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:"#ffa81e"
|
|
||||||
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
|
||||||
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
|
||||||
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "temperature", label:'${currentValue}°',
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
main (["contact", "acceleration", "temperature"])
|
|
||||||
details(["contact", "acceleration", "temperature", "battery", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 0xFC02:
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0x0402:
|
|
||||||
log.debug 'TEMP'
|
|
||||||
// temp is last 2 data values. reverse to swap endian
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
def value = getTemperature(temp)
|
|
||||||
resultMap = getTemperatureResult(value)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 int getHumidity(value) {
|
|
||||||
return Math.round(Double.parseDouble(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
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 == "FC02" && descMap.attrId == "0002") {
|
|
||||||
Integer.parseInt(descMap.value,8)
|
|
||||||
}
|
|
||||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
|
||||||
resultMap = getBatteryResult(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) {
|
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
|
||||||
|
|
||||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
|
||||||
}
|
|
||||||
|
|
||||||
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.debug 'Battery'
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
|
|
||||||
def result = [
|
|
||||||
name: 'battery'
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
|
||||||
def descriptionText
|
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
|
||||||
else if (volts > 3.5) {
|
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def minVolts = 2.1
|
|
||||||
def maxVolts = 3.0
|
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
|
||||||
def roundedPct = Math.round(pct * 100)
|
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getTemperatureResult(value) {
|
|
||||||
log.debug 'TEMP'
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
if (tempOffset) {
|
|
||||||
def offset = tempOffset as int
|
|
||||||
def v = value as int
|
|
||||||
value = v + offset
|
|
||||||
}
|
|
||||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
|
||||||
return [
|
|
||||||
name: 'temperature',
|
|
||||||
value: value,
|
|
||||||
descriptionText: descriptionText,
|
|
||||||
unit: temperatureScale
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getContactResult(value) {
|
|
||||||
log.debug 'Contact Status'
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
|
||||||
return [
|
|
||||||
name: 'contact',
|
|
||||||
value: value,
|
|
||||||
descriptionText: descriptionText
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private getAccelerationResult(numValue) {
|
|
||||||
def name = "acceleration"
|
|
||||||
def value = numValue.endsWith("1") ? "active" : "inactive"
|
|
||||||
//def linkText = getLinkText(device)
|
|
||||||
def descriptionText = "$linkText was $value"
|
|
||||||
def isStateChange = isStateChange(device, name, value)
|
|
||||||
[
|
|
||||||
name: name,
|
|
||||||
value: value,
|
|
||||||
//unit: null,
|
|
||||||
//linkText: linkText,
|
|
||||||
descriptionText: descriptionText,
|
|
||||||
//handlerName: value,
|
|
||||||
isStateChange: isStateChange
|
|
||||||
// displayed: displayed(description, isStateChange)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "Refreshing Temperature and Battery "
|
|
||||||
def refreshCmds = [
|
|
||||||
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
|
||||||
//"st rattr 0x${device.deviceNetworkId} 1 0xFC02 2", "delay 200",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
|
||||||
|
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
|
||||||
def configCmds = [
|
|
||||||
"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",
|
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
|
||||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 500",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
def enrollResponse() {
|
|
||||||
log.debug "Sending enroll response"
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
|
||||||
[
|
|
||||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
|
||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
|
||||||
//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
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Smartsense Open/Closed Sensor
|
# Smartsense Open/Closed Sensor
|
||||||
|
|
||||||
|
Local Execution on V2 Hubs
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -24,10 +24,11 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C2 open/closed sensor with maxReportTime of 5 mins.
|
SmartSense Open Closed sensor with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
|
||||||
|
* HubV2 AppEngine 1.5.1 - __12min__ checkInterval
|
||||||
|
|
||||||
## Battery Specification
|
## Battery Specification
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $map"
|
log.debug "Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : [:]
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
@@ -119,7 +119,7 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
if (cluster.command == 0x07){
|
if (cluster.command == 0x07){
|
||||||
if (cluster.data[0] == 0x00) {
|
if (cluster.data[0] == 0x00) {
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||||
@@ -196,25 +196,19 @@ private Map getBatteryResult(rawValue) {
|
|||||||
log.debug 'Battery'
|
log.debug 'Battery'
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
def result = [
|
def result = [:]
|
||||||
name: 'battery'
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
def descriptionText
|
if (!(rawValue == 0 || rawValue == 255)) {
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
|
||||||
else if (volts > 3.5) {
|
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
if (roundedPct <= 0)
|
||||||
roundedPct = 1
|
roundedPct = 1
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
|
result.name = 'battery'
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -258,17 +252,17 @@ def ping() {
|
|||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Refreshing Temperature and Battery"
|
log.debug "Refreshing Temperature and Battery"
|
||||||
def refreshCmds = [
|
def refreshCmds = [
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
|
||||||
]
|
]
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
return refreshCmds + enrollResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
|
||||||
@@ -283,10 +277,10 @@ def enrollResponse() {
|
|||||||
[
|
[
|
||||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
//Resending the CIE in case the enroll request is sent before CIE is written
|
||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
|
||||||
//Enroll Response
|
//Enroll Response
|
||||||
"raw 0x500 {01 23 00 00 00}",
|
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# SmartSense Temp/Humidity Sensor
|
# SmartSense Temp/Humidity Sensor
|
||||||
|
|
||||||
|
Local Execution on V2 Hubs
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -24,10 +24,11 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 5 mins.
|
SmartSense Temp/Humidity Sensor with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
* V1, TV, HubV2 AppEngine < 1.5.1 - __121min__ checkInterval
|
||||||
|
* HubV2 AppEngine 1.5.1 - __12min__ checkIntervalr 5 min interval is confirmed
|
||||||
|
|
||||||
## Battery Specification
|
## Battery Specification
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $map"
|
log.debug "Parse returned $map"
|
||||||
return map ? createEvent(map) : null
|
return map ? createEvent(map) : [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCatchAllMessage(String description) {
|
private Map parseCatchAllMessage(String description) {
|
||||||
@@ -103,7 +103,7 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
if (cluster.command == 0x07) {
|
if (cluster.command == 0x07) {
|
||||||
if (cluster.data[0] == 0x00){
|
if (cluster.data[0] == 0x00){
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||||
@@ -207,25 +207,20 @@ private Map getBatteryResult(rawValue) {
|
|||||||
log.debug 'Battery'
|
log.debug 'Battery'
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
def result = [
|
def result = [:]
|
||||||
name: 'battery'
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
def descriptionText
|
if (!(rawValue == 0 || rawValue == 255)) {
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
|
||||||
else if (volts > 3.5) {
|
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
if (roundedPct <= 0)
|
if (roundedPct <= 0)
|
||||||
roundedPct = 1
|
roundedPct = 1
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
|
result.name = 'battery'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -264,21 +259,20 @@ def refresh()
|
|||||||
{
|
{
|
||||||
log.debug "refresh temperature, humidity, and battery"
|
log.debug "refresh temperature, humidity, and battery"
|
||||||
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
|
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
|
||||||
zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware
|
|
||||||
zigbee.readAttribute(0x0402, 0x0000) +
|
zigbee.readAttribute(0x0402, 0x0000) +
|
||||||
zigbee.readAttribute(0x0001, 0x0020)
|
zigbee.readAttribute(0x0001, 0x0020)
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
def humidityConfigCmds = [
|
def humidityConfigCmds = [
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
|
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 2000",
|
||||||
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
|
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
|
||||||
]
|
]
|
||||||
|
|
||||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ metadata {
|
|||||||
definition (name: "Simulated Minimote", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Minimote", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Button"
|
capability "Button"
|
||||||
|
capability "Holdable Button"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
command "push1"
|
command "push1"
|
||||||
command "push2"
|
command "push2"
|
||||||
command "push3"
|
command "push3"
|
||||||
@@ -45,42 +46,42 @@ metadata {
|
|||||||
}
|
}
|
||||||
standardTile("push1", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("push1", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: "Push 1", backgroundColor: "#ffffff", action: "push1"
|
state "default", label: "Push 1", backgroundColor: "#ffffff", action: "push1"
|
||||||
}
|
}
|
||||||
standardTile("push2", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("push2", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: "Push 2", backgroundColor: "#ffffff", action: "push2"
|
state "default", label: "Push 2", backgroundColor: "#ffffff", action: "push2"
|
||||||
}
|
}
|
||||||
standardTile("push3", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("push3", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: "Push 3", backgroundColor: "#ffffff", action: "push3"
|
state "default", label: "Push 3", backgroundColor: "#ffffff", action: "push3"
|
||||||
}
|
}
|
||||||
standardTile("push4", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("push4", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: "Push 4", backgroundColor: "#ffffff", action: "push4"
|
state "default", label: "Push 4", backgroundColor: "#ffffff", action: "push4"
|
||||||
}
|
}
|
||||||
standardTile("dummy1", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("dummy1", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
|
state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
|
||||||
}
|
}
|
||||||
standardTile("hold1", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("hold1", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: "Hold 1", backgroundColor: "#ffffff", action: "hold1"
|
state "default", label: "Hold 1", backgroundColor: "#ffffff", action: "hold1"
|
||||||
}
|
}
|
||||||
standardTile("hold2", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("hold2", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: "Hold 2", backgroundColor: "#ffffff", action: "hold2"
|
state "default", label: "Hold 2", backgroundColor: "#ffffff", action: "hold2"
|
||||||
}
|
}
|
||||||
standardTile("dummy2", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("dummy2", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
|
state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
|
||||||
}
|
}
|
||||||
standardTile("hold3", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("hold3", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: "Hold 3", backgroundColor: "#ffffff", action: "hold3"
|
state "default", label: "Hold 3", backgroundColor: "#ffffff", action: "hold3"
|
||||||
}
|
}
|
||||||
standardTile("hold4", "device.button", width: 1, height: 1, decoration: "flat") {
|
standardTile("hold4", "device.button", width: 1, height: 1, decoration: "flat") {
|
||||||
state "default", label: "Hold 4", backgroundColor: "#ffffff", action: "hold4"
|
state "default", label: "Hold 4", backgroundColor: "#ffffff", action: "hold4"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "button"
|
main "button"
|
||||||
details(["push1","push2","button","push3","push4","dummy1","hold1","hold2","dummy2","hold3","hold4"])
|
details(["push1","push2","button","push3","push4","dummy1","hold1","hold2","dummy2","hold3","hold4"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def push1() {
|
def push1() {
|
||||||
@@ -125,3 +126,15 @@ private hold(button) {
|
|||||||
sendEvent(name: "button", value: "held", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was held", isStateChange: true)
|
sendEvent(name: "button", value: "held", data: [buttonNumber: button], descriptionText: "$device.displayName button $button was held", isStateChange: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
sendEvent(name: "numberOfButtons", value: 4)
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
|
|
||||||
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Relay Switch"
|
capability "Relay Switch"
|
||||||
@@ -39,9 +39,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(description) {
|
||||||
def pair = description.split(":")
|
|
||||||
createEvent(name: pair[0].trim(), value: pair[1].trim())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def on() {
|
def on() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Tyco Door Window Sensor
|
# Tyco Door Window Sensor
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -23,10 +23,11 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
Contact sensor with maxReportTime of 5 mins.
|
Tyco Door Window Sensor with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime for Zigbee device.
|
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.
|
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||||
Check-in interval = 12 min
|
|
||||||
|
* __12min__ checkInterval
|
||||||
|
|
||||||
## Battery Specification
|
## Battery Specification
|
||||||
|
|
||||||
|
|||||||
@@ -181,22 +181,17 @@ private Map getBatteryResult(rawValue) {
|
|||||||
log.debug 'Battery'
|
log.debug 'Battery'
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
def result = [
|
def result = [:]
|
||||||
name: 'battery'
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
if (!(rawValue == 0 || rawValue == 255)) {
|
||||||
def descriptionText
|
def volts = rawValue / 10
|
||||||
if (volts > 3.5) {
|
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
def roundedPct = Math.round(pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
result.value = Math.min(100, roundedPct)
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
|
result.name = 'battery'
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def parse(String description) {
|
|||||||
def bodyString = msg.body
|
def bodyString = msg.body
|
||||||
if (bodyString) {
|
if (bodyString) {
|
||||||
unschedule("setOffline")
|
unschedule("setOffline")
|
||||||
def body = new XmlSlurper().parseText(bodyString)
|
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
|
||||||
|
|
||||||
if (body?.property?.TimeSyncRequest?.text()) {
|
if (body?.property?.TimeSyncRequest?.text()) {
|
||||||
log.trace "Got TimeSyncRequest"
|
log.trace "Got TimeSyncRequest"
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ def parse(String description) {
|
|||||||
def bodyString = msg.body
|
def bodyString = msg.body
|
||||||
if (bodyString) {
|
if (bodyString) {
|
||||||
unschedule("setOffline")
|
unschedule("setOffline")
|
||||||
def body = new XmlSlurper().parseText(bodyString)
|
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
|
||||||
if (body?.property?.TimeSyncRequest?.text()) {
|
if (body?.property?.TimeSyncRequest?.text()) {
|
||||||
log.trace "Got TimeSyncRequest"
|
log.trace "Got TimeSyncRequest"
|
||||||
result << timeSyncResponse()
|
result << timeSyncResponse()
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ def parse(String description) {
|
|||||||
def bodyString = msg.body
|
def bodyString = msg.body
|
||||||
if (bodyString) {
|
if (bodyString) {
|
||||||
unschedule("setOffline")
|
unschedule("setOffline")
|
||||||
def body = new XmlSlurper().parseText(bodyString)
|
def body = new XmlSlurper().parseText(bodyString.replaceAll("[^\\x20-\\x7e]", ""))
|
||||||
if (body?.property?.TimeSyncRequest?.text()) {
|
if (body?.property?.TimeSyncRequest?.text()) {
|
||||||
log.trace "Got TimeSyncRequest"
|
log.trace "Got TimeSyncRequest"
|
||||||
result << timeSyncResponse()
|
result << timeSyncResponse()
|
||||||
@@ -208,7 +208,7 @@ def subscribe(ip, port) {
|
|||||||
def existingIp = getDataValue("ip")
|
def existingIp = getDataValue("ip")
|
||||||
def existingPort = getDataValue("port")
|
def existingPort = getDataValue("port")
|
||||||
if (ip && ip != existingIp) {
|
if (ip && ip != existingIp) {
|
||||||
log.debug "Updating ip from $existingIp to $ip"
|
log.debug "Updating ip from $existingIp to $ip"
|
||||||
updateDataValue("ip", ip)
|
updateDataValue("ip", ip)
|
||||||
def ipvalue = convertHexToIP(getDataValue("ip"))
|
def ipvalue = convertHexToIP(getDataValue("ip"))
|
||||||
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
|
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
|
||||||
@@ -291,4 +291,4 @@ User-Agent: CyberGarage-HTTP/1.0
|
|||||||
</u:GetBinaryState>
|
</u:GetBinaryState>
|
||||||
</s:Body>
|
</s:Body>
|
||||||
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
|
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ metadata {
|
|||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Button"
|
capability "Button"
|
||||||
|
capability "Holdable Button"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
@@ -27,8 +28,8 @@ metadata {
|
|||||||
|
|
||||||
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch"
|
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch"
|
||||||
//fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant"
|
//fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant"
|
||||||
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
|
fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
|
||||||
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
|
fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {}
|
simulator {}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
41
devicetypes/smartthings/zigbee-dimmer-power.src/README.md
Normal file
41
devicetypes/smartthings/zigbee-dimmer-power.src/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# GE Plug-In/In-Wall Smart Dimmer (ZigBee)
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [GE In-Wall Smart Dimmer (ZigBee)](https://shop.smartthings.com/#!/products/ge-in-wall-smart-dimmer-switch)
|
||||||
|
* [GE Plug-In Smart Dimmer (ZigBee)](https://www.smartthings.com/works-with-smartthings/ge/ge-plug-in-smart-dimmer-zigbee)
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health](#device-health)
|
||||||
|
* [Troubleshooting](#Troubleshooting)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Actuator** - represents that a Device has commands
|
||||||
|
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||||
|
* **Refresh** - _refresh()_ command for status updates
|
||||||
|
* **Power Meter** - ability to check the power meter(energy consumption) of device
|
||||||
|
* **Sensor** - represents the device sensor capability
|
||||||
|
* **Switch** - can detect state (possible values: on/off)
|
||||||
|
* **Switch Level** - represents current light level, usually 0-100 in percent
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
A Zigbee Power Dimmer with reporting interval of 5 mins.
|
||||||
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
|
|
||||||
|
* __12min__ checkInterval
|
||||||
|
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||||
|
Pairing needs to be tried again by placing the device closer to the hub.
|
||||||
|
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||||
|
* [GE Z-Wave In-Wall Smart Dimmer (GE 45857) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204988564-GE-In-Wall-Smart-Dimmer-45857GE-ZigBee-)
|
||||||
|
* [GE Zigbee Plug-in Smart Dimmer (GE 45852) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205239280-GE-Plug-In-Smart-Dimmer-45852GE-ZigBee-)
|
||||||
@@ -21,6 +21,7 @@ metadata {
|
|||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
|
||||||
@@ -70,8 +71,20 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
def cluster = zigbee.parse(description)
|
||||||
log.debug zigbee.parseDescriptionAsMap(description)
|
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
|
||||||
|
if (cluster.data[0] == 0x00){
|
||||||
|
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
||||||
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
log.debug zigbee.parseDescriptionAsMap(description)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,11 +100,22 @@ def setLevel(value) {
|
|||||||
zigbee.setLevel(value)
|
zigbee.setLevel(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
* */
|
||||||
|
def ping() {
|
||||||
|
return zigbee.onOffRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
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 + ping (plus 1 min lag time)
|
||||||
|
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||||
|
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# OSRAM Lightify LED On/Off/Dim
|
# Zigbee Dimmer
|
||||||
|
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
* [OSRAM Lightify LED On/Off/Dim](https://shop.smartthings.com/#!/products/osram-led-smart-bulb-on-off-dim)
|
* [OSRAM Lightify LED On/Off/Dim](https://shop.smartthings.com/#!/products/osram-led-smart-bulb-on-off-dim)
|
||||||
|
* [WeMo LED Bulb](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-)
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
@@ -23,14 +24,16 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C1 Zigbee dimmer with maxReportTime of 5 mins.
|
ZigBee Dimmer with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
* __12min__ checkInterval
|
||||||
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||||
Pairing needs to be tried again by placing the device closer to the hub.
|
Pairing needs to be tried again by placing the device closer to the hub.
|
||||||
Other troubleshooting tips are listed as follows:
|
Other troubleshooting tips are listed as follows:
|
||||||
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)
|
* [OSRAM Lightify LED On/Off/Dim Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)
|
||||||
|
* [WeMo LED Bulb Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-)
|
||||||
|
|||||||
@@ -23,9 +23,15 @@ metadata {
|
|||||||
|
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart A19 Soft White"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart BR30 Soft White"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "E11-G13", deviceJoinName: "Sengled Element Classic"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL6HD", deviceJoinName: "Leviton Dimmer Switch"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL3HL", deviceJoinName: "Leviton Lumina RF Plug-In Dimmer"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL1KD", deviceJoinName: "Leviton Lumina RF Dimmer Switch"
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
@@ -102,9 +108,9 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 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
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
||||||
|
|||||||
@@ -1,243 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
//@Deprecated - Moved to zll-rgbw-bulb
|
|
||||||
|
|
||||||
/* Philips Hue (via Zigbee)
|
|
||||||
|
|
||||||
Capabilities:
|
|
||||||
Actuator
|
|
||||||
Color Control
|
|
||||||
Configuration
|
|
||||||
Polling
|
|
||||||
Refresh
|
|
||||||
Sensor
|
|
||||||
Switch
|
|
||||||
Switch Level
|
|
||||||
|
|
||||||
Custom Commands:
|
|
||||||
setAdjustedColor
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "Zigbee Hue Bulb", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Color Control"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
|
||||||
|
|
||||||
//fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000", outClusters: "0019"
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
status "on": "on/off: 1"
|
|
||||||
status "off": "on/off: 0"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "zcl on-off on": "on/off: 1"
|
|
||||||
reply "zcl on-off off": "on/off: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles {
|
|
||||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
|
||||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
|
|
||||||
state "color", action:"setAdjustedColor"
|
|
||||||
}
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "level", label: 'Level ${currentValue}%'
|
|
||||||
}
|
|
||||||
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
|
|
||||||
state "saturation", action:"color control.setSaturation"
|
|
||||||
}
|
|
||||||
valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "saturation", label: 'Sat ${currentValue} '
|
|
||||||
}
|
|
||||||
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
|
|
||||||
state "hue", action:"color control.setHue"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["switch"])
|
|
||||||
details(["switch", "levelSliderControl", "rgbSelector", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
//log.trace description
|
|
||||||
if (description?.startsWith("catchall:")) {
|
|
||||||
def msg = zigbee.parse(description)
|
|
||||||
//log.trace msg
|
|
||||||
//log.trace "data: $msg.data"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def name = description?.startsWith("on/off: ") ? "switch" : null
|
|
||||||
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
|
|
||||||
def result = createEvent(name: name, value: value)
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
// just assume it works for now
|
|
||||||
log.debug "on()"
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
// just assume it works for now
|
|
||||||
log.debug "off()"
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def setHue(value) {
|
|
||||||
def max = 0xfe
|
|
||||||
log.trace "setHue($value)"
|
|
||||||
sendEvent(name: "hue", value: value)
|
|
||||||
def scaledValue = Math.round(value * max / 100.0)
|
|
||||||
def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledValue)} 00 0000}"
|
|
||||||
//log.info cmd
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setAdjustedColor(value) {
|
|
||||||
log.debug "setAdjustedColor: ${value}"
|
|
||||||
def adjusted = value + [:]
|
|
||||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
|
||||||
adjusted.level = null // needed because color picker always sends 100
|
|
||||||
setColor(adjusted)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColor(value){
|
|
||||||
log.trace "setColor($value)"
|
|
||||||
def max = 0xfe
|
|
||||||
|
|
||||||
sendEvent(name: "hue", value: value.hue)
|
|
||||||
sendEvent(name: "saturation", value: value.saturation)
|
|
||||||
def scaledHueValue = Math.round(value.hue * max / 100.0)
|
|
||||||
def scaledSatValue = Math.round(value.saturation * max / 100.0)
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
if (value.switch != "off" && device.latestValue("switch") == "off") {
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledHueValue)} 00 0000}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledSatValue)} 0000}"
|
|
||||||
|
|
||||||
if (value.level != null) {
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd.addAll(setLevel(value.level))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.switch == "off") {
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << off()
|
|
||||||
}
|
|
||||||
log.info cmd
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setSaturation(value) {
|
|
||||||
def max = 0xfe
|
|
||||||
log.trace "setSaturation($value)"
|
|
||||||
sendEvent(name: "saturation", value: value)
|
|
||||||
def scaledValue = Math.round(value * max / 100.0)
|
|
||||||
def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledValue)} 0000}"
|
|
||||||
//log.info cmd
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 6 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll(){
|
|
||||||
log.debug "Poll is calling refresh"
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
log.trace "setLevel($value)"
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
if (value == 0) {
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
|
||||||
}
|
|
||||||
else if (device.latestValue("switch") == "off") {
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEvent(name: "level", value: value)
|
|
||||||
def level = hexString(Math.round(value * 255/100))
|
|
||||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
|
|
||||||
|
|
||||||
//log.debug cmds
|
|
||||||
cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
private getEndpointId() {
|
|
||||||
new BigInteger(device.endpointId, 16).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private hex(value, width=2) {
|
|
||||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
while (s.size() < width) {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
private adjustOutgoingHue(percent) {
|
|
||||||
def adjusted = percent
|
|
||||||
if (percent > 31) {
|
|
||||||
if (percent < 63.0) {
|
|
||||||
adjusted = percent + (7 * (percent -30 ) / 32)
|
|
||||||
}
|
|
||||||
else if (percent < 73.0) {
|
|
||||||
adjusted = 69 + (5 * (percent - 62) / 10)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
adjusted = percent + (2 * (100 - percent) / 28)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info "percent: $percent, adjusted: $adjusted"
|
|
||||||
adjusted
|
|
||||||
}
|
|
||||||
@@ -28,8 +28,8 @@ metadata {
|
|||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
capability "Health Check"
|
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: "Gardenspot RGB", deviceJoinName: "SYLVANIA Smart Gardenspot mini RGB"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB", deviceJoinName: "SYLVANIA Smart Gardenspot mini RGB"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# OSRAM LIGHTIFY LED RGBW Bulb
|
# OSRAM LIGHTIFY LED RGBW Bulb
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
@@ -27,11 +27,10 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C6 OSRAM LIGHTIFY LED RGBW Bulb with maxReportTime of 5 mins.
|
OSRAM LIGHTIFY LED RGBW Bulb with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
|
||||||
|
|
||||||
|
* __12min__ checkInterval
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,12 @@ metadata {
|
|||||||
attribute "colorName", "string"
|
attribute "colorName", "string"
|
||||||
command "setGenericName"
|
command "setGenericName"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "SYLVANIA Smart Flex RGBW"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Flex RGBW"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED A19 RGBW"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "SYLVANIA Smart A19 RGBW"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "OSRAM LIGHTIFY LED BR30 RGBW"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "SYLVANIA Smart BR30 RGBW"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "OSRAM LIGHTIFY LED RT 5/6 RGBW"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "SYLVANIA Smart RT5/6 RGBW"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY FLEX OUTDOOR RGBW", deviceJoinName: "SYLVANIA Smart Outdoor RGBW Flex"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
@@ -143,9 +144,9 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 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
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
refresh()
|
refresh()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ metadata {
|
|||||||
capability "Power Meter"
|
capability "Power Meter"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
|
||||||
@@ -77,10 +78,28 @@ def on() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
Integer reportIntervalMinutes = 5
|
||||||
|
zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig(0,reportIntervalMinutes * 60) + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "in configure()"
|
||||||
refresh()
|
return configureHealthCheck()
|
||||||
|
}
|
||||||
|
|
||||||
|
def configureHealthCheck() {
|
||||||
|
Integer hcIntervalMinutes = 12
|
||||||
|
sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
return refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "in updated()"
|
||||||
|
// updated() doesn't have it's return value processed as hub commands, so we have to send them explicitly
|
||||||
|
def cmds = configureHealthCheck()
|
||||||
|
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
def ping() {
|
||||||
|
return zigbee.onOffRefresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ 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"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006", outClusters: "0003, 0006, 0008, 0019, 0406", manufacturer: "Leviton", model: "DL15A", deviceJoinName: "Leviton Lumina RF Plug-In Appliance Module"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# OSRAM Lightify Tunable 60 White
|
# ZigBee White Color Temperature Bulb
|
||||||
|
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
* [OSRAM Lightify Tunable 60 White](http://www.osram.com/osram_com/tools-and-services/tools/lightify---smart-connected-light/lightify-for-home---what-is-light-to-you/lightify-products/lightify-classic-a60-tunable-white/index.jsp)
|
* [OSRAM Lightify Tunable 60 White](http://www.osram.com/osram_com/tools-and-services/tools/lightify---smart-connected-light/lightify-for-home---what-is-light-to-you/lightify-products/lightify-classic-a60-tunable-white/index.jsp)
|
||||||
|
* [OSRAM LIGHTIFY RT5/6 Tunable White](https://www.smartthings.com/works-with-smartthings/light-bulbs/osram-lightify-rt56-tunable-white)
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
@@ -24,14 +25,15 @@ Works with:
|
|||||||
|
|
||||||
## Device Health
|
## Device Health
|
||||||
|
|
||||||
A Category C1 OSRAM Lightify Tunable 60 White with maxReportTime of 5 mins.
|
Zigbee Bulb with reporting interval of 5 mins.
|
||||||
Check-in interval is double the value of maxReportTime.
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
*__12min__ checkInterval
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||||
Pairing needs to be tried again by placing the device closer to the hub.
|
Pairing needs to be tried again by placing the device closer to the hub.
|
||||||
Other troubleshooting tips are listed as follows:
|
Other troubleshooting tips are listed as follows:
|
||||||
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb)
|
* [OSRAM Lightify Tunable 60 White Troubleshooting](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb)
|
||||||
|
* [OSRAM LIGHTIFY RT5/6 Tunable White Troubleshooting](https://support.smartthings.com/hc/en-us/articles/214191863-How-to-connect-OSRAM-LIGHTIFY-Bulbs)
|
||||||
@@ -32,11 +32,12 @@ metadata {
|
|||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "SYLVANIA Smart BR30 Tunable White"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "SYLVANIA Smart RT5/6 Tunable White"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "SYLVANIA Smart A19 Tunable White"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-A19NAE26", deviceJoinName: "Sengled Element plus"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
@@ -126,9 +127,9 @@ def refresh() {
|
|||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
// 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])
|
sendEvent(name: "checkInterval", value: 2 * 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
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
refresh()
|
refresh()
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
import groovy.transform.Field
|
||||||
|
|
||||||
|
@Field Boolean hasConfiguredHealthCheck = false
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "ZLL Dimmer Bulb", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "ZLL Dimmer Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -21,6 +24,7 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019"
|
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019"
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019"
|
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019"
|
||||||
@@ -96,7 +100,38 @@ def poll() {
|
|||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
* */
|
||||||
|
def ping() {
|
||||||
|
return zigbee.levelRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
def healthPoll() {
|
||||||
|
log.debug "healthPoll()"
|
||||||
|
def cmds = refresh()
|
||||||
|
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
|
||||||
|
}
|
||||||
|
|
||||||
|
def configureHealthCheck() {
|
||||||
|
Integer hcIntervalMinutes = 12
|
||||||
|
if (!hasConfiguredHealthCheck) {
|
||||||
|
log.debug "Configuring Health Check, Reporting"
|
||||||
|
unschedule("healthPoll")
|
||||||
|
runEvery5Minutes("healthPoll")
|
||||||
|
// Device-Watch allows 2 check-in misses from device
|
||||||
|
sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
hasConfiguredHealthCheck = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "configure()"
|
||||||
|
configureHealthCheck()
|
||||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "updated()"
|
||||||
|
configureHealthCheck()
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ metadata {
|
|||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
|
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019"
|
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019"
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 RGBW"
|
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 RGBW"
|
||||||
|
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "PAR 16 50 RGBW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY RGBW PAR 16 50"
|
||||||
|
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Flex RGBW"
|
||||||
|
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenpole RGBW-Lightify", deviceJoinName: "OSRAM LIGHTIFY Gardenpole RGBW"
|
||||||
|
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Outdoor Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY Outdoor Flex RGBW"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
import groovy.transform.Field
|
||||||
|
|
||||||
|
@Field Boolean hasConfiguredHealthCheck = false
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "ZLL White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "ZLL White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -22,12 +25,14 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
attribute "colorName", "string"
|
attribute "colorName", "string"
|
||||||
command "setGenericName"
|
command "setGenericName"
|
||||||
|
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, 0B04, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
|
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, 0B04, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"PAR16 50 TW", deviceJoinName: "OSRAM LIGHTIFY LED PAR16 50 Tunable White"
|
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"PAR16 50 TW", deviceJoinName: "OSRAM LIGHTIFY LED PAR16 50 Tunable White"
|
||||||
|
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
@@ -96,9 +101,41 @@ def poll() {
|
|||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
* */
|
||||||
|
def ping() {
|
||||||
|
return zigbee.levelRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
def healthPoll() {
|
||||||
|
log.debug "healthPoll()"
|
||||||
|
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
|
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
|
||||||
|
}
|
||||||
|
|
||||||
|
def configureHealthCheck() {
|
||||||
|
Integer hcIntervalMinutes = 12
|
||||||
|
if (!hasConfiguredHealthCheck) {
|
||||||
|
log.debug "Configuring Health Check, Reporting"
|
||||||
|
unschedule("healthPoll")
|
||||||
|
runEvery5Minutes("healthPoll")
|
||||||
|
// Device-Watch allows 2 check-in misses from device
|
||||||
|
sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
hasConfiguredHealthCheck = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "configure()"
|
||||||
|
configureHealthCheck()
|
||||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "updated()"
|
||||||
|
configureHealthCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColorTemperature(value) {
|
def setColorTemperature(value) {
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Z-wave Dimmer Switch Generic
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-plug-in-lamp-dimmer-module)
|
||||||
|
* [Leviton Universal Dimmer (DZMX1-LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-universal-dimmer)
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health](#device-health)
|
||||||
|
* [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Switch Level** - it's defined to accept two parameters, the level and the rate of dimming
|
||||||
|
* **Actuator** - represents that a Device has commands
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
* **Switch** - can detect state (possible values: on/off)
|
||||||
|
* **Polling** - represents that poll() can be implemented for the device
|
||||||
|
* **Refresh** - _refresh()_ command for status updates
|
||||||
|
* **Sensor** - detects sensor events
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
Leviton Plug-in Lamp Dimmer Module (DZPA1-1LW) (Z-wave) and Leviton Universal Dimmer (DZMX1-LZ) (Z-Wave) are polled by the hub.
|
||||||
|
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
|
||||||
|
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||||
|
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
|
||||||
|
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
|
||||||
|
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
|
||||||
|
|
||||||
|
* __32min__ checkInterval
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||||
|
Pairing needs to be tried again by placing the device closer to the hub.
|
||||||
|
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||||
|
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||||
|
* [Leviton Universal Dimmer (DZMX1-LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||||
@@ -15,12 +15,19 @@ metadata {
|
|||||||
definition (name: "Z-Wave Dimmer Switch Generic", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Z-Wave Dimmer Switch Generic", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
|
capability "Health Check"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Polling"
|
capability "Polling"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
|
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
|
||||||
|
fingerprint mfr:"001D", prod:"1902", deviceJoinName: "Z-Wave Dimmer"
|
||||||
|
fingerprint mfr:"001D", prod:"1B03", model:"0334", deviceJoinName: "Leviton Universal Dimmer"
|
||||||
|
fingerprint mfr:"011A", prod:"0102", model:"0201", deviceJoinName: "Enerwave In-Wall Dimmer"
|
||||||
|
fingerprint mfr:"001D", prod:"1001", model:"0334", deviceJoinName: "Leviton 3-Speed Fan Controller"
|
||||||
|
fingerprint mfr:"001D", prod:"0602", model:"0334", deviceJoinName: "Leviton Magnetic Low Voltage Dimmer"
|
||||||
|
fingerprint mfr:"001D", prod:"0401", model:"0334", deviceJoinName: "Leviton 600W Incandescent Dimmer"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -68,6 +75,11 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def updated(){
|
||||||
|
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||||
|
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||||
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
if (description != "updated") {
|
if (description != "updated") {
|
||||||
@@ -185,6 +197,13 @@ def poll() {
|
|||||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
* */
|
||||||
|
def ping() {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "refresh() is called"
|
log.debug "refresh() is called"
|
||||||
def commands = []
|
def commands = []
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ metadata {
|
|||||||
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
|
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
|
||||||
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
|
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
|
||||||
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
|
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
|
||||||
|
fingerprint mfr: "0063", prod: "4953", model: "3133", deviceJoinName: "GE Smart Motion Sensor"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
|
|||||||
def value = "when off"
|
def value = "when off"
|
||||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||||
[name: "indicatorStatus", value: value, display: false]
|
[name: "indicatorStatus", value: value, displayed: false]
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
45
devicetypes/smartthings/zwave-switch-generic.src/README.md
Normal file
45
devicetypes/smartthings/zwave-switch-generic.src/README.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Z-wave Switch
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [Leviton Appliance Module (DZPA1-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-appliance-module)
|
||||||
|
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)](https://www.smartthings.com/works-with-smartthings/outlets/ge-plug-in-outdoor-smart-switch)
|
||||||
|
* [Leviton Outlet (DZR15-1LZ)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-outlet)
|
||||||
|
* [Leviton Switch (DZS15-1LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-switch)
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health](#device-health)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Actuator** - represents that a Device has commands
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
* **Switch** - can detect state (possible values: on/off)
|
||||||
|
* **Polling** - represents that poll() can be implemented for the device
|
||||||
|
* **Refresh** - _refresh()_ command for status updates
|
||||||
|
* **Sensor** - detects sensor events
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
Leviton Appliance Module (DZPA1-1LW), GE Plug-In Outdoor Smart Switch (GE 12720), Leviton Outlet (DZR15-1LZ) and Leviton Switch (DZS15-1LZ) (Z-Wave) are polled by the hub.
|
||||||
|
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
|
||||||
|
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||||
|
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
|
||||||
|
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
|
||||||
|
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
|
||||||
|
|
||||||
|
* __32min__ checkInterval
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||||
|
Pairing needs to be tried again by placing the device closer to the hub.
|
||||||
|
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||||
|
* [Leviton Appliance Module (DZPA1-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||||
|
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)
|
||||||
|
* [Leviton Outlet (DZR15-1LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||||
|
* [Leviton Switch (DZS15-1LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
|
||||||
@@ -14,12 +14,20 @@
|
|||||||
metadata {
|
metadata {
|
||||||
definition (name: "Z-Wave Switch Generic", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Z-Wave Switch Generic", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
|
capability "Health Check"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Polling"
|
capability "Polling"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
|
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
|
||||||
|
fingerprint mfr:"001D", prod:"1A02", model:"0334", deviceJoinName: "Leviton Appliance Module"
|
||||||
|
fingerprint mfr:"0063", prod:"4F50", model:"3031", deviceJoinName: "GE Plug-in Outdoor Switch"
|
||||||
|
fingerprint mfr:"001D", prod:"1D04", model:"0334", deviceJoinName: "Leviton Outlet"
|
||||||
|
fingerprint mfr:"001D", prod:"1C02", model:"0334", deviceJoinName: "Leviton Switch"
|
||||||
|
fingerprint mfr:"001D", prod:"0301", model:"0334", deviceJoinName: "Leviton 15A Switch"
|
||||||
|
fingerprint mfr:"011A", prod:"0101", model:"0102", deviceJoinName: "Enerwave On/Off Switch"
|
||||||
|
fingerprint mfr:"011A", prod:"0101", model:"0603", deviceJoinName: "Enerwave Duplex Receptacle"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -50,6 +58,11 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def updated(){
|
||||||
|
// Device-Watch simply pings if no device events received for checkInterval duration of 32min = 2 * 15min + 2min lag time
|
||||||
|
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||||
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
||||||
@@ -126,6 +139,13 @@ def poll() {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
* */
|
||||||
|
def ping() {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
delayBetween([
|
delayBetween([
|
||||||
zwave.switchBinaryV1.switchBinaryGet().format(),
|
zwave.switchBinaryV1.switchBinaryGet().format(),
|
||||||
|
|||||||
2
devicetypes/smartthings/zwave-switch.src/.st-ignore
Normal file
2
devicetypes/smartthings/zwave-switch.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
51
devicetypes/smartthings/zwave-switch.src/README.md
Normal file
51
devicetypes/smartthings/zwave-switch.src/README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Z-Wave Switch
|
||||||
|
|
||||||
|
Local Execution on V2 Hubs
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [GE Z-Wave Plug-In Smart Switch (12719)](http://products.z-wavealliance.org/products/1193)
|
||||||
|
* [GE Z-Wave In-Wall Smart Outlet (12721)](http://products.z-wavealliance.org/products/1195)
|
||||||
|
* [GE Z-Wave In-Wall Smart Switch (12722)](http://products.z-wavealliance.org/products/1196)
|
||||||
|
* [GE Z-Wave In-Wall Smart Toggle Switch (12727)](http://products.z-wavealliance.org/products/1200)
|
||||||
|
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health](#device-health)
|
||||||
|
* [Troubleshooting](#Troubleshooting)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Actuator** - represents that a Device has commands
|
||||||
|
* **Indicator** - gives you the ability to set the indicator LED light on a Z-Wave switch
|
||||||
|
* **Switch** - can detect state (possible values: on/off)
|
||||||
|
* **Polling** - represents that poll() can be implemented for the device
|
||||||
|
* **Refresh** - _refresh()_ command for status updates
|
||||||
|
* **Sensor** - detects sensor events
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
Z-Wave Switches (Plug-In, In-Wall(Toggle Switch, Switch, Outlet)) are polled by the hub.
|
||||||
|
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
|
||||||
|
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||||
|
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
|
||||||
|
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
|
||||||
|
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
|
||||||
|
|
||||||
|
* __32min__ checkInterval
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||||
|
Pairing needs to be tried again by placing the device closer to the hub.
|
||||||
|
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||||
|
* [General Z-Wave Dimmer/Switch Troubleshooting](https://support.smartthings.com/hc/en-us/articles/200955890-Troubleshooting-GE-in-wall-switch-or-dimmer-won-t-respond-to-commands-or-automations-Z-Wave-)
|
||||||
|
* [GE Z-Wave Plug-In Smart Switch (12719) Troubleshooting](https://support.smartthings.com/hc/en-us/articles/200903070-GE-Plug-In-Smart-Switch-GE-12719-Z-Wave)
|
||||||
|
* [GE Z-Wave In-Wall Smart Outlet (12721) Troubleshooting](https://support.smartthings.com/hc/en-us/articles/200903020-GE-In-Wall-Smart-Outlet-GE-12721-Z-Wave)
|
||||||
|
* [GE Z-Wave In-Wall Smart Switch (12722) Troubleshooting](https://support.smartthings.com/hc/en-us/articles/200902540-GE-In-Wall-Smart-Switch-GE-12722-Z-Wave)
|
||||||
|
* [GE Z-Wave In-Wall Smart Toggle Switch (12727) Troubleshooting](https://support.smartthings.com/hc/en-us/articles/207568933-GE-In-Wall-Smart-Toggle-Switch-GE-12727-Z-Wave)
|
||||||
|
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ metadata {
|
|||||||
capability "Polling"
|
capability "Polling"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
|
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
|
||||||
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
||||||
@@ -64,6 +65,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def updated(){
|
def updated(){
|
||||||
|
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||||
|
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||||
switch (ledIndicator) {
|
switch (ledIndicator) {
|
||||||
case "on":
|
case "on":
|
||||||
indicatorWhenOn()
|
indicatorWhenOn()
|
||||||
@@ -111,7 +114,7 @@ def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport
|
|||||||
def value = "when off"
|
def value = "when off"
|
||||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||||
[name: "indicatorStatus", value: value, display: false]
|
[name: "indicatorStatus", value: value, displayed: false]
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||||
@@ -156,6 +159,13 @@ def poll() {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
**/
|
||||||
|
def ping() {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
delayBetween([
|
delayBetween([
|
||||||
zwave.switchBinaryV1.switchBinaryGet().format(),
|
zwave.switchBinaryV1.switchBinaryGet().format(),
|
||||||
@@ -164,17 +174,17 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOn() {
|
void indicatorWhenOn() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
sendEvent(name: "indicatorStatus", value: "when on", displayed: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOff() {
|
void indicatorWhenOff() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
sendEvent(name: "indicatorStatus", value: "when off", displayed: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorNever() {
|
void indicatorNever() {
|
||||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
sendEvent(name: "indicatorStatus", value: "never", displayed: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -387,18 +387,18 @@ def getDeviceList() {
|
|||||||
state.deviceDetail = [:]
|
state.deviceDetail = [:]
|
||||||
state.deviceState = [:]
|
state.deviceState = [:]
|
||||||
|
|
||||||
apiGet("/api/getstationsdata") { response ->
|
apiGet("/api/devicelist") { response ->
|
||||||
response.data.body.devices.each { value ->
|
response.data.body.devices.each { value ->
|
||||||
def key = value._id
|
def key = value._id
|
||||||
deviceList[key] = "${value.station_name}: ${value.module_name}"
|
deviceList[key] = "${value.station_name}: ${value.module_name}"
|
||||||
state.deviceDetail[key] = value
|
state.deviceDetail[key] = value
|
||||||
state.deviceState[key] = value.dashboard_data
|
state.deviceState[key] = value.dashboard_data
|
||||||
value.modules.each { value2 ->
|
}
|
||||||
def key2 = value2._id
|
response.data.body.modules.each { value ->
|
||||||
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
|
def key = value._id
|
||||||
state.deviceDetail[key2] = value2
|
deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
|
||||||
state.deviceState[key2] = value2.dashboard_data
|
state.deviceDetail[key] = value
|
||||||
}
|
state.deviceState[key] = value.dashboard_data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Color Coordinator
|
* Color Coordinator
|
||||||
* Version 1.0.0 - 7/4/15
|
* Version 1.1.1 - 11/9/16
|
||||||
* By Michael Struck
|
* By Michael Struck
|
||||||
*
|
*
|
||||||
* 1.0.0 - Initial release
|
* 1.0.0 - Initial release
|
||||||
|
* 1.1.0 - Fixed issue where master can be part of slaves. This causes a loop that impacts SmartThings.
|
||||||
|
* 1.1.1 - Fix NPE being thrown for slave/master inputs being empty.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
@@ -31,27 +33,35 @@ preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def mainPage() {
|
def mainPage() {
|
||||||
dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) {
|
dynamicPage(name: "mainPage", title: "", install: true, uninstall: false) {
|
||||||
section("Master Light") {
|
def masterInList = slaves?.id?.find{it==master?.id}
|
||||||
input "master", "capability.colorControl", title: "Colored Light"
|
if (masterInList) {
|
||||||
|
section ("**WARNING**"){
|
||||||
|
paragraph "You have included the Master Light in the Slave Group. This will cause a loop in execution. Please remove this device from the Slave Group.", image: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/img/caution.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
section("Master Light") {
|
||||||
|
input "master", "capability.colorControl", title: "Colored Light", required: true
|
||||||
}
|
}
|
||||||
section("Lights that follow the master settings") {
|
section("Lights that follow the master settings") {
|
||||||
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: false
|
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: true, submitOnChange: true
|
||||||
}
|
}
|
||||||
section([mobileOnly:true], "Options") {
|
section([mobileOnly:true], "Options") {
|
||||||
label(title: "Assign a name", required: false)
|
input "randomYes", "bool",title: "When Master Turned On, Randomize Color", defaultValue: false
|
||||||
href "pageAbout", title: "About ${textAppName()}", description: "Tap to get application version, license and instructions"
|
href "pageAbout", title: "About ${textAppName()}", description: "Tap to get application version, license and instructions"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
page(name: "pageAbout", title: "About ${textAppName()}") {
|
page(name: "pageAbout", title: "About ${textAppName()}", uninstall: true) {
|
||||||
section {
|
section {
|
||||||
paragraph "${textVersion()}\n${textCopyright()}\n\n${textLicense()}\n"
|
paragraph "${textVersion()}\n${textCopyright()}\n\n${textLicense()}\n"
|
||||||
}
|
}
|
||||||
section("Instructions") {
|
section("Instructions") {
|
||||||
paragraph textHelp()
|
paragraph textHelp()
|
||||||
}
|
}
|
||||||
|
section("Tap button below to remove application"){
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
@@ -72,27 +82,61 @@ def init() {
|
|||||||
}
|
}
|
||||||
//-----------------------------------
|
//-----------------------------------
|
||||||
def onOffHandler(evt){
|
def onOffHandler(evt){
|
||||||
if (master.currentValue("switch") == "on"){
|
if (slaves && master) {
|
||||||
slaves?.on()
|
if (!slaves?.id.find{it==master?.id}){
|
||||||
}
|
if (master?.currentValue("switch") == "on"){
|
||||||
else {
|
if (randomYes) getRandomColorMaster()
|
||||||
slaves?.off()
|
else slaves?.on()
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
slaves?.off()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def colorHandler(evt) {
|
def colorHandler(evt) {
|
||||||
def dimLevel = master.currentValue("level")
|
if (slaves && master) {
|
||||||
def hueLevel = master.currentValue("hue")
|
if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){
|
||||||
def saturationLevel = master.currentValue("saturation")
|
log.debug "Changing Slave units H,S,L"
|
||||||
|
def dimLevel = master?.currentValue("level")
|
||||||
|
def hueLevel = master?.currentValue("hue")
|
||||||
|
def saturationLevel = master.currentValue("saturation")
|
||||||
|
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
|
||||||
|
slaves?.setColor(newValue)
|
||||||
|
try {
|
||||||
|
log.debug "Changing Slave color temp"
|
||||||
|
def tempLevel = master?.currentValue("colorTemperature")
|
||||||
|
slaves?.setColorTemperature(tempLevel)
|
||||||
|
}
|
||||||
|
catch (e){
|
||||||
|
log.debug "Color temp for master --"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getRandomColorMaster(){
|
||||||
|
def hueLevel = Math.floor(Math.random() *1000)
|
||||||
|
def saturationLevel = Math.floor(Math.random() * 100)
|
||||||
|
def dimLevel = master?.currentValue("level")
|
||||||
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
|
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
|
||||||
slaves?.setColor(newValue)
|
log.debug hueLevel
|
||||||
|
log.debug saturationLevel
|
||||||
|
master.setColor(newValue)
|
||||||
|
slaves?.setColor(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
def tempHandler(evt){
|
def tempHandler(evt){
|
||||||
if (evt.value != "--") {
|
if (slaves && master) {
|
||||||
def tempLevel = master.currentValue("colorTemperature")
|
if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){
|
||||||
slaves?.setColorTemperature(tempLevel)
|
if (evt.value != "--") {
|
||||||
}
|
log.debug "Changing Slave color temp based on Master change"
|
||||||
|
def tempLevel = master.currentValue("colorTemperature")
|
||||||
|
slaves?.setColorTemperature(tempLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Version/Copyright/Information/Help
|
//Version/Copyright/Information/Help
|
||||||
@@ -102,11 +146,11 @@ private def textAppName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def textVersion() {
|
private def textVersion() {
|
||||||
def text = "Version 1.0.0 (07/04/2015)"
|
def text = "Version 1.1.1 (12/13/2016)"
|
||||||
}
|
}
|
||||||
|
|
||||||
private def textCopyright() {
|
private def textCopyright() {
|
||||||
def text = "Copyright © 2015 Michael Struck"
|
def text = "Copyright © 2016 Michael Struck"
|
||||||
}
|
}
|
||||||
|
|
||||||
private def textLicense() {
|
private def textLicense() {
|
||||||
@@ -128,5 +172,5 @@ private def textHelp() {
|
|||||||
def text =
|
def text =
|
||||||
"This application will allow you to control the settings of multiple colored lights with one control. " +
|
"This application will allow you to control the settings of multiple colored lights with one control. " +
|
||||||
"Simply choose a master control light, and then choose the lights that will follow the settings of the master, "+
|
"Simply choose a master control light, and then choose the lights that will follow the settings of the master, "+
|
||||||
"including on/off conditions, hue, saturation, level and color temperature."
|
"including on/off conditions, hue, saturation, level and color temperature. Also includes a random color feature."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,461 @@
|
|||||||
|
/**
|
||||||
|
* OpenT2T SmartApp Test
|
||||||
|
*
|
||||||
|
* Copyright 2016 OpenT2T
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
definition(
|
||||||
|
name: "OpenT2T SmartApp Test",
|
||||||
|
namespace: "opent2t",
|
||||||
|
author: "OpenT2T",
|
||||||
|
description: "Test app to test end to end SmartThings scenarios via OpenT2T",
|
||||||
|
category: "SmartThings Labs",
|
||||||
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||||
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||||
|
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
|
||||||
|
|
||||||
|
/** --------------------+---------------+-----------------------+------------------------------------
|
||||||
|
* Device Type | Attribute Name| Commands | Attribute Values
|
||||||
|
* --------------------+---------------+-----------------------+------------------------------------
|
||||||
|
* switches | switch | on, off | on, off
|
||||||
|
* motionSensors | motion | | active, inactive
|
||||||
|
* contactSensors | contact | | open, closed
|
||||||
|
* presenceSensors | presence | | present, 'not present'
|
||||||
|
* temperatureSensors | temperature | | <numeric, F or C according to unit>
|
||||||
|
* accelerationSensors | acceleration | | active, inactive
|
||||||
|
* waterSensors | water | | wet, dry
|
||||||
|
* lightSensors | illuminance | | <numeric, lux>
|
||||||
|
* humiditySensors | humidity | | <numeric, percent>
|
||||||
|
* locks | lock | lock, unlock | locked, unlocked
|
||||||
|
* garageDoors | door | open, close | unknown, closed, open, closing, opening
|
||||||
|
* cameras | image | take | <String>
|
||||||
|
* thermostats | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint, coolingSetpoint,
|
||||||
|
* | | setCoolingSetpoint, | thermostatSetpoint, thermostatMode,
|
||||||
|
* | | off, heat, cool, auto,| thermostatFanMode, thermostatOperatingState
|
||||||
|
* | | emergencyHeat, |
|
||||||
|
* | | setThermostatMode, |
|
||||||
|
* | | fanOn, fanAuto, |
|
||||||
|
* | | fanCirculate, |
|
||||||
|
* | | setThermostatFanMode |
|
||||||
|
* --------------------+---------------+-----------------------+------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Device Inputs
|
||||||
|
preferences {
|
||||||
|
section("Allow <PLACEHOLDER: Your App Name> to control these things...") {
|
||||||
|
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false
|
||||||
|
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
|
||||||
|
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||||
|
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false
|
||||||
|
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
|
||||||
|
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false
|
||||||
|
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false
|
||||||
|
input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false
|
||||||
|
input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getInputs() {
|
||||||
|
def inputList = []
|
||||||
|
inputList += contactSensors ?: []
|
||||||
|
inputList += garageDoors ?: []
|
||||||
|
inputList += locks ?: []
|
||||||
|
inputList += cameras ?: []
|
||||||
|
inputList += motionSensors ?: []
|
||||||
|
inputList += presenceSensors ?: []
|
||||||
|
inputList += switches ?: []
|
||||||
|
inputList += thermostats ?: []
|
||||||
|
inputList += waterSensors ?: []
|
||||||
|
return inputList
|
||||||
|
}
|
||||||
|
|
||||||
|
//API external Endpoints
|
||||||
|
mappings {
|
||||||
|
path("/subscriptionURL/:url") {
|
||||||
|
action:
|
||||||
|
[
|
||||||
|
PUT: "updateEndpointURL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/connectionId/:connId") {
|
||||||
|
action:
|
||||||
|
[
|
||||||
|
PUT: "updateConnectionId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/devices") {
|
||||||
|
action:
|
||||||
|
[
|
||||||
|
GET: "getDevices"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/devices/:id") {
|
||||||
|
action:
|
||||||
|
[
|
||||||
|
GET: "getDevice"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/update/:id") {
|
||||||
|
action:
|
||||||
|
[
|
||||||
|
PUT: "updateDevice"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
path("/subscription/:id") {
|
||||||
|
action:
|
||||||
|
[
|
||||||
|
POST : "registerDeviceChange",
|
||||||
|
DELETE: "unregisterDeviceChange"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "Updated with settings: ${settings}"
|
||||||
|
unsubscribe()
|
||||||
|
registerSubscriptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
state.connectionId = ""
|
||||||
|
state.endpointURL = "https://ifs.windows-int.com/v1/cb/81C7E77B-EABC-488A-B2BF-FEC42F0DABD2/notify"
|
||||||
|
registerSubscriptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Subscribe events for all devices
|
||||||
|
def registerSubscriptions() {
|
||||||
|
registerChangeHandler(inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Subscribe to events from a list of devices
|
||||||
|
def registerChangeHandler(myList) {
|
||||||
|
myList.each { myDevice ->
|
||||||
|
def theAtts = myDevice.supportedAttributes
|
||||||
|
theAtts.each { att ->
|
||||||
|
subscribe(myDevice, att.name, eventHandler)
|
||||||
|
log.info "Registering ${myDevice.displayName}.${att.name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Endpoints function: Subscribe to events from a specific device
|
||||||
|
def registerDeviceChange() {
|
||||||
|
def myDevice = findDevice(params.id)
|
||||||
|
def theAtts = myDevice.supportedAttributes
|
||||||
|
try {
|
||||||
|
theAtts.each { att ->
|
||||||
|
subscribe(myDevice, att.name, eventHandler)
|
||||||
|
log.info "Registering ${myDevice.displayName}.${att.name}"
|
||||||
|
}
|
||||||
|
return ["succeed"]
|
||||||
|
} catch (e) {
|
||||||
|
httpError(500, "something went wrong: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Endpoints function: Unsubscribe to events from a specific device
|
||||||
|
def unregisterDeviceChange() {
|
||||||
|
def myDevice = findDevice(params.id)
|
||||||
|
try {
|
||||||
|
unsubscribe(myDevice)
|
||||||
|
log.info "Unregistering ${myDevice.displayName}"
|
||||||
|
return ["succeed"]
|
||||||
|
} catch (e) {
|
||||||
|
httpError(500, "something went wrong: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//When events are triggered, send HTTP post to web socket servers
|
||||||
|
def eventHandler(evt) {
|
||||||
|
def evt_device_id = evt.deviceId
|
||||||
|
def evt_device_value = evt.value
|
||||||
|
def evt_name = evt.name
|
||||||
|
def evt_device = evt.device
|
||||||
|
def evt_deviceType = getDeviceType(evt_device);
|
||||||
|
def params = [
|
||||||
|
uri : "${state.endpointURL}/${state.connectionId}",
|
||||||
|
body: [
|
||||||
|
name : evt_device.displayName,
|
||||||
|
id : evt_device.id,
|
||||||
|
deviceType : evt_deviceType,
|
||||||
|
manufacturer: evt_device.getManufacturerName(),
|
||||||
|
model : evt_device.getModelName(),
|
||||||
|
attributes : deviceAttributeList(evt_device)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
try {
|
||||||
|
log.trace "POST URI: ${params.uri}"
|
||||||
|
log.trace "Payload: ${params.body}"
|
||||||
|
httpPostJson(params) { resp ->
|
||||||
|
resp.headers.each {
|
||||||
|
log.debug "${it.name} : ${it.value}"
|
||||||
|
}
|
||||||
|
log.trace "response status code: ${resp.status}"
|
||||||
|
log.trace "response data: ${resp.data}"
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.debug "something went wrong: $e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Endpoints function: update subcription endpoint url [state.endpoint]
|
||||||
|
void updateEndpointURL() {
|
||||||
|
state.endpointURL = params.url
|
||||||
|
log.info "Updated EndpointURL to ${state.endpointURL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Endpoints function: update global variable [state.connectionId]
|
||||||
|
void updateConnectionId() {
|
||||||
|
def connId = params.connId
|
||||||
|
state.connectionId = connId
|
||||||
|
log.info "Updated ConnectionID to ${state.connectionId}"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Endpoints function: return all device data in json format
|
||||||
|
def getDevices() {
|
||||||
|
def deviceData = []
|
||||||
|
inputs?.each {
|
||||||
|
def deviceType = getDeviceType(it)
|
||||||
|
if (deviceType == "thermostat") {
|
||||||
|
deviceData << [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it), locationMode: getLocationModeInfo()]
|
||||||
|
} else {
|
||||||
|
deviceData << [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug "getDevices, return: ${deviceData}"
|
||||||
|
return deviceData
|
||||||
|
}
|
||||||
|
|
||||||
|
//Endpoints function: get device data
|
||||||
|
def getDevice() {
|
||||||
|
def it = findDevice(params.id)
|
||||||
|
def deviceType = getDeviceType(it)
|
||||||
|
def device
|
||||||
|
if (deviceType == "thermostat") {
|
||||||
|
device = [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it), locationMode: getLocationModeInfo()]
|
||||||
|
} else {
|
||||||
|
device = [name: it.displayName, id: it.id, deviceType: deviceType, manufacturer: it.getManufacturerName(), model: it.getModelName(), attributes: deviceAttributeList(it)]
|
||||||
|
}
|
||||||
|
log.debug "getDevice, return: ${device}"
|
||||||
|
return device
|
||||||
|
}
|
||||||
|
|
||||||
|
//Endpoints function: update device data
|
||||||
|
void updateDevice() {
|
||||||
|
def device = findDevice(params.id)
|
||||||
|
request.JSON.each {
|
||||||
|
def command = it.key
|
||||||
|
def value = it.value
|
||||||
|
if (command) {
|
||||||
|
def commandList = mapDeviceCommands(command, value)
|
||||||
|
command = commandList[0]
|
||||||
|
value = commandList[1]
|
||||||
|
|
||||||
|
if (command == "setAwayMode") {
|
||||||
|
log.info "Setting away mode to ${value}"
|
||||||
|
if (location.modes?.find { it.name == value }) {
|
||||||
|
location.setMode(value)
|
||||||
|
}
|
||||||
|
} else if (command == "thermostatSetpoint") {
|
||||||
|
switch (device.currentThermostatMode) {
|
||||||
|
case "cool":
|
||||||
|
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||||
|
device.setCoolingSetpoint(value)
|
||||||
|
break
|
||||||
|
case "heat":
|
||||||
|
case "emergency heat":
|
||||||
|
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||||
|
device.setHeatingSetpoint(value)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
httpError(501, "this mode: ${device.currentThermostatMode} does not allow changing thermostat setpoint.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if (!device) {
|
||||||
|
log.error "updateDevice, Device not found"
|
||||||
|
httpError(404, "Device not found")
|
||||||
|
} else if (!device.hasCommand(command)) {
|
||||||
|
log.error "updateDevice, Device does not have the command"
|
||||||
|
httpError(404, "Device does not have such command")
|
||||||
|
} else {
|
||||||
|
if (command == "setColor") {
|
||||||
|
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||||
|
device."$command"(hex: value)
|
||||||
|
} else if (value.isNumber()) {
|
||||||
|
def intValue = value as Integer
|
||||||
|
log.info "Update: ${device.displayName}, [${command}, ${intValue}(int)]"
|
||||||
|
device."$command"(intValue)
|
||||||
|
} else if (value) {
|
||||||
|
log.info "Update: ${device.displayName}, [${command}, ${value}]"
|
||||||
|
device."$command"(value)
|
||||||
|
} else {
|
||||||
|
log.info "Update: ${device.displayName}, [${command}]"
|
||||||
|
device."$command"()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Private Functions ***/
|
||||||
|
|
||||||
|
//Return current location mode info
|
||||||
|
private getLocationModeInfo() {
|
||||||
|
return [mode: location.mode, supported: location.modes.name]
|
||||||
|
}
|
||||||
|
|
||||||
|
//Map each device to a type given it's capabilities
|
||||||
|
private getDeviceType(device) {
|
||||||
|
def deviceType
|
||||||
|
def caps = device.capabilities
|
||||||
|
log.debug "capabilities: [${device}, ${caps}]"
|
||||||
|
log.debug "supported commands: [${device}, ${device.supportedCommands}]"
|
||||||
|
caps.each {
|
||||||
|
switch (it.name.toLowerCase()) {
|
||||||
|
case "switch":
|
||||||
|
deviceType = "switch"
|
||||||
|
break
|
||||||
|
case "switch level":
|
||||||
|
deviceType = "light"
|
||||||
|
break
|
||||||
|
case "contact sensor":
|
||||||
|
deviceType = "contactSensor"
|
||||||
|
break
|
||||||
|
case "garageDoorControl":
|
||||||
|
deviceType = "garageDoor"
|
||||||
|
break
|
||||||
|
case "lock":
|
||||||
|
deviceType = "lock"
|
||||||
|
break
|
||||||
|
case "video camera":
|
||||||
|
deviceType = "camera"
|
||||||
|
break
|
||||||
|
case "motion sensor":
|
||||||
|
deviceType = "motionSensor"
|
||||||
|
break
|
||||||
|
case "presence sensor":
|
||||||
|
deviceType = "presenceSensor"
|
||||||
|
break
|
||||||
|
case "thermostat":
|
||||||
|
deviceType = "thermostat"
|
||||||
|
break
|
||||||
|
case "water sensor":
|
||||||
|
deviceType = "waterSensor"
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deviceType
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return a specific device give the device ID.
|
||||||
|
private findDevice(deviceId) {
|
||||||
|
return inputs?.find { it.id == deviceId }
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return a list of device attributes
|
||||||
|
private deviceAttributeList(device) {
|
||||||
|
device.supportedAttributes.collectEntries { attribute ->
|
||||||
|
try {
|
||||||
|
[(attribute.name): device.currentValue(attribute.name)]
|
||||||
|
} catch (e) {
|
||||||
|
[(attribute.name): null]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Map device command and value.
|
||||||
|
//input command and value are from UWP,
|
||||||
|
//returns resultCommand and resultValue that corresponds with function and value in SmartApps
|
||||||
|
private mapDeviceCommands(command, value) {
|
||||||
|
log.debug "mapDeviceCommands: [${command}, ${value}]"
|
||||||
|
def resultCommand = command
|
||||||
|
def resultValue = value
|
||||||
|
switch (command) {
|
||||||
|
case "switch":
|
||||||
|
if (value == 1 || value == "1" || value == "on") {
|
||||||
|
resultCommand = "on"
|
||||||
|
resultValue = ""
|
||||||
|
} else if (value == 0 || value == "0" || value == "off") {
|
||||||
|
resultCommand = "off"
|
||||||
|
resultValue = ""
|
||||||
|
}
|
||||||
|
break
|
||||||
|
// light attributes
|
||||||
|
case "level":
|
||||||
|
resultCommand = "setLevel"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "hue":
|
||||||
|
resultCommand = "setHue"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "saturation":
|
||||||
|
resultCommand = "setSaturation"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "ct":
|
||||||
|
resultCommand = "setColorTemperature"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "color":
|
||||||
|
resultCommand = "setColor"
|
||||||
|
resultValue = value
|
||||||
|
// thermostat attributes
|
||||||
|
case "hvacMode":
|
||||||
|
resultCommand = "setThermostatMode"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "fanMode":
|
||||||
|
resultCommand = "setThermostatFanMode"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "awayMode":
|
||||||
|
resultCommand = "setAwayMode"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "coolingSetpoint":
|
||||||
|
resultCommand = "setCoolingSetpoint"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "heatingSetpoint":
|
||||||
|
resultCommand = "setHeatingSetpoint"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
case "thermostatSetpoint":
|
||||||
|
resultCommand = "thermostatSetpoint"
|
||||||
|
resultValue = value
|
||||||
|
break
|
||||||
|
// lock attributes
|
||||||
|
case "locked":
|
||||||
|
if (value == 1 || value == "1" || value == "lock") {
|
||||||
|
resultCommand = "lock"
|
||||||
|
resultValue = ""
|
||||||
|
} else if (value == 0 || value == "0" || value == "unlock") {
|
||||||
|
resultCommand = "unlock"
|
||||||
|
resultValue = ""
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return [resultCommand, resultValue]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
author: "SmartThings",
|
author: "SmartThings",
|
||||||
description: "Control your Bose SoundTouch speakers",
|
description: "Control your Bose SoundTouch speakers",
|
||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
|
||||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png",
|
||||||
singleInstance: true
|
singleInstance: true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ def deviceDiscovery()
|
|||||||
|
|
||||||
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||||
section("Please wait while we discover your ${getDeviceName()}. 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 ${getDeviceName()}. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices
|
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices, submitOnChange: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,6 +196,8 @@ def addDevice(){
|
|||||||
d = addChildDevice(getNameSpace(), getDeviceName(), dni, newDevice?.value.hub, [label:"${deviceName}"])
|
d = addChildDevice(getNameSpace(), getDeviceName(), dni, newDevice?.value.hub, [label:"${deviceName}"])
|
||||||
d.boseSetDeviceID(newDevice.value.deviceID)
|
d.boseSetDeviceID(newDevice.value.deviceID)
|
||||||
log.trace "Created ${d.displayName} with id $dni"
|
log.trace "Created ${d.displayName} with id $dni"
|
||||||
|
// sync DTH with device, done here as it currently don't work from the DTH's installed() method
|
||||||
|
d.refresh()
|
||||||
} else {
|
} else {
|
||||||
log.trace "${d.displayName} with id $dni already exists"
|
log.trace "${d.displayName} with id $dni already exists"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -842,6 +842,7 @@ private void storeThermostatData(thermostats) {
|
|||||||
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
|
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
|
||||||
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
|
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
|
||||||
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
|
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
|
||||||
|
deviceAlive: stat.runtime.connected == true ? "true" : "false",
|
||||||
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
|
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
|
||||||
temperature: (stat.runtime.actualTemperature / 10),
|
temperature: (stat.runtime.actualTemperature / 10),
|
||||||
heatingSetpoint: stat.runtime.desiredHeat / 10,
|
heatingSetpoint: stat.runtime.desiredHeat / 10,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* 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:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* 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
|
* 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
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
@@ -24,7 +24,7 @@ definition(
|
|||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
|
||||||
singleInstance: true
|
singleInstance: true
|
||||||
)
|
)
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
@@ -37,7 +37,10 @@ preferences {
|
|||||||
|
|
||||||
def mainPage() {
|
def mainPage() {
|
||||||
def bridges = bridgesDiscovered()
|
def bridges = bridgesDiscovered()
|
||||||
if (state.username && bridges) {
|
|
||||||
|
if (state.refreshUsernameNeeded) {
|
||||||
|
return bridgeLinking()
|
||||||
|
} else if (state.username && bridges) {
|
||||||
return bulbDiscovery()
|
return bulbDiscovery()
|
||||||
} else {
|
} else {
|
||||||
return bridgeDiscovery()
|
return bridgeDiscovery()
|
||||||
@@ -102,13 +105,22 @@ def bridgeLinking() {
|
|||||||
|
|
||||||
def nextPage = ""
|
def nextPage = ""
|
||||||
def title = "Linking with your Hue"
|
def title = "Linking with your Hue"
|
||||||
def paragraphText
|
def paragraphText
|
||||||
if (selectedHue) {
|
if (selectedHue) {
|
||||||
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
if (state.refreshUsernameNeeded) {
|
||||||
} else {
|
paragraphText = "The current Hue username is invalid.\n\nPlease press the button on your Hue Bridge to re-link. "
|
||||||
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
} else {
|
||||||
}
|
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
||||||
|
}
|
||||||
if (state.username) { //if discovery worked
|
if (state.username) { //if discovery worked
|
||||||
|
if (state.refreshUsernameNeeded) {
|
||||||
|
state.refreshUsernameNeeded = false
|
||||||
|
// Issue one poll with new username to cancel local polling with old username
|
||||||
|
poll()
|
||||||
|
}
|
||||||
nextPage = "bulbDiscovery"
|
nextPage = "bulbDiscovery"
|
||||||
title = "Success!"
|
title = "Success!"
|
||||||
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
|
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
|
||||||
@@ -160,18 +172,34 @@ def bulbDiscovery() {
|
|||||||
if (existingLightsDescription.isEmpty()) {
|
if (existingLightsDescription.isEmpty()) {
|
||||||
existingLightsDescription += it.value
|
existingLightsDescription += it.value
|
||||||
} else {
|
} else {
|
||||||
existingLightsDescription += ", ${it.value}"
|
existingLightsDescription += ", ${it.value}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
if (bulbRefreshCount > 200 && numFound == 0) {
|
||||||
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
// Time out to avoid endless discovery
|
||||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
|
state.inBulbDiscovery = false
|
||||||
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
|
bulbRefreshCount = 0
|
||||||
|
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Failed!", nextPage:"", refreshInterval:0, install:true, uninstall: true) {
|
||||||
|
section("Failed to discover any lights, please try again later. Click Done to exit.") {
|
||||||
|
//input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
|
||||||
|
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
section {
|
|
||||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
} else {
|
||||||
|
return dynamicPage(name:"bulbDiscovery", title:"Light Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||||
|
section("Please wait while we discover your Hue Lights. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
|
input "selectedBulbs", "enum", required:false, title:"Select Hue Lights to add (${numFound} found)", multiple:true, submitOnChange: true, options:newLights
|
||||||
|
paragraph title: "Previously added Hue Lights (${existingLights.size()} added)", existingLightsDescription
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +214,7 @@ void ssdpSubscribe() {
|
|||||||
|
|
||||||
private sendDeveloperReq() {
|
private sendDeveloperReq() {
|
||||||
def token = app.id
|
def token = app.id
|
||||||
def host = getBridgeIP()
|
def host = getBridgeIP()
|
||||||
sendHubCommand(new physicalgraph.device.HubAction([
|
sendHubCommand(new physicalgraph.device.HubAction([
|
||||||
method: "POST",
|
method: "POST",
|
||||||
path: "/api",
|
path: "/api",
|
||||||
@@ -197,7 +225,7 @@ private sendDeveloperReq() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private discoverHueBulbs() {
|
private discoverHueBulbs() {
|
||||||
def host = getBridgeIP()
|
def host = getBridgeIP()
|
||||||
sendHubCommand(new physicalgraph.device.HubAction([
|
sendHubCommand(new physicalgraph.device.HubAction([
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/api/${state.username}/lights",
|
path: "/api/${state.username}/lights",
|
||||||
@@ -219,8 +247,8 @@ private verifyHueBridge(String deviceNetworkId, String host) {
|
|||||||
private verifyHueBridges() {
|
private verifyHueBridges() {
|
||||||
def devices = getHueBridges().findAll { it?.value?.verified != true }
|
def devices = getHueBridges().findAll { it?.value?.verified != true }
|
||||||
devices.each {
|
devices.each {
|
||||||
def ip = convertHexToIP(it.value.networkAddress)
|
def ip = convertHexToIP(it.value.networkAddress)
|
||||||
def port = convertHexToInt(it.value.deviceAddress)
|
def port = convertHexToInt(it.value.deviceAddress)
|
||||||
verifyHueBridge("${it.value.mac}", (ip + ":" + port))
|
verifyHueBridge("${it.value.mac}", (ip + ":" + port))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +277,7 @@ Map bulbsDiscovered() {
|
|||||||
bulbs.each {
|
bulbs.each {
|
||||||
def value = "${it.name}"
|
def value = "${it.name}"
|
||||||
def key = app.id +"/"+ it.id
|
def key = app.id +"/"+ it.id
|
||||||
logg += "$value - $key, "
|
logg += "$value - $key, "
|
||||||
bulbmap["${key}"] = value
|
bulbmap["${key}"] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,22 +304,23 @@ def installed() {
|
|||||||
def updated() {
|
def updated() {
|
||||||
log.trace "Updated with settings: ${settings}"
|
log.trace "Updated with settings: ${settings}"
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
unschedule()
|
unschedule()
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
log.debug "Initializing"
|
log.debug "Initializing"
|
||||||
unsubscribe(bridge)
|
unsubscribe(bridge)
|
||||||
state.inBulbDiscovery = false
|
state.inBulbDiscovery = false
|
||||||
state.bridgeRefreshCount = 0
|
state.bridgeRefreshCount = 0
|
||||||
state.bulbRefreshCount = 0
|
state.bulbRefreshCount = 0
|
||||||
state.updating = false
|
state.updating = false
|
||||||
|
setupDeviceWatch()
|
||||||
if (selectedHue) {
|
if (selectedHue) {
|
||||||
addBridge()
|
addBridge()
|
||||||
addBulbs()
|
addBulbs()
|
||||||
doDeviceSync()
|
doDeviceSync()
|
||||||
runEvery5Minutes("doDeviceSync")
|
runEvery5Minutes("doDeviceSync")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +338,14 @@ def uninstalled(){
|
|||||||
state.username = null
|
state.username = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setupDeviceWatch() {
|
||||||
|
def hub = location.hubs[0]
|
||||||
|
// Make sure that all child devices are enrolled in device watch
|
||||||
|
getChildDevices().each {
|
||||||
|
it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private upgradeDeviceType(device, newHueType) {
|
private upgradeDeviceType(device, newHueType) {
|
||||||
def deviceType = getDeviceType(newHueType)
|
def deviceType = getDeviceType(newHueType)
|
||||||
|
|
||||||
@@ -357,7 +394,6 @@ def addBulbs() {
|
|||||||
if (d) {
|
if (d) {
|
||||||
log.debug "created ${d.displayName} with id $dni"
|
log.debug "created ${d.displayName} with id $dni"
|
||||||
d.completedSetup = true
|
d.completedSetup = true
|
||||||
d.refresh()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||||
@@ -387,23 +423,23 @@ def addBridge() {
|
|||||||
if(vbridge) {
|
if(vbridge) {
|
||||||
def d = getChildDevice(selectedHue)
|
def d = getChildDevice(selectedHue)
|
||||||
if(!d) {
|
if(!d) {
|
||||||
// compatibility with old devices
|
// compatibility with old devices
|
||||||
def newbridge = true
|
def newbridge = true
|
||||||
childDevices.each {
|
childDevices.each {
|
||||||
if (it.getDeviceDataByName("mac")) {
|
if (it.getDeviceDataByName("mac")) {
|
||||||
def newDNI = "${it.getDeviceDataByName("mac")}"
|
def newDNI = "${it.getDeviceDataByName("mac")}"
|
||||||
if (newDNI != it.deviceNetworkId) {
|
if (newDNI != it.deviceNetworkId) {
|
||||||
def oldDNI = it.deviceNetworkId
|
def oldDNI = it.deviceNetworkId
|
||||||
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
||||||
it.setDeviceNetworkId("${newDNI}")
|
it.setDeviceNetworkId("${newDNI}")
|
||||||
if (oldDNI == selectedHue) {
|
if (oldDNI == selectedHue) {
|
||||||
app.updateSetting("selectedHue", newDNI)
|
app.updateSetting("selectedHue", newDNI)
|
||||||
}
|
}
|
||||||
newbridge = false
|
newbridge = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newbridge) {
|
if (newbridge) {
|
||||||
// Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge
|
// Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge
|
||||||
def idNumber = getBridgeIdNumber(selectedHue)
|
def idNumber = getBridgeIdNumber(selectedHue)
|
||||||
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"])
|
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"])
|
||||||
@@ -414,9 +450,11 @@ def addBridge() {
|
|||||||
d.completedSetup = true
|
d.completedSetup = true
|
||||||
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
||||||
def childDevice = getChildDevice(d.deviceNetworkId)
|
def childDevice = getChildDevice(d.deviceNetworkId)
|
||||||
|
childDevice?.sendEvent(name: "status", value: "Online")
|
||||||
|
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
|
||||||
updateBridgeStatus(childDevice)
|
updateBridgeStatus(childDevice)
|
||||||
childDevice.sendEvent(name: "idNumber", value: idNumber)
|
|
||||||
|
|
||||||
|
childDevice?.sendEvent(name: "idNumber", value: idNumber)
|
||||||
if (vbridge.value.ip && vbridge.value.port) {
|
if (vbridge.value.ip && vbridge.value.port) {
|
||||||
if (vbridge.value.ip.contains(".")) {
|
if (vbridge.value.ip.contains(".")) {
|
||||||
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
|
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
|
||||||
@@ -568,47 +606,47 @@ void usernameHandler(physicalgraph.device.HubResponse hubResponse) {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
def locationHandler(evt) {
|
def locationHandler(evt) {
|
||||||
def description = evt.description
|
def description = evt.description
|
||||||
log.trace "Location: $description"
|
log.trace "Location: $description"
|
||||||
|
|
||||||
def hub = evt?.hubId
|
def hub = evt?.hubId
|
||||||
def parsedEvent = parseLanMessage(description)
|
def parsedEvent = parseLanMessage(description)
|
||||||
parsedEvent << ["hub":hub]
|
parsedEvent << ["hub":hub]
|
||||||
|
|
||||||
if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1")) {
|
if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1")) {
|
||||||
//SSDP DISCOVERY EVENTS
|
//SSDP DISCOVERY EVENTS
|
||||||
log.trace "SSDP DISCOVERY EVENTS"
|
log.trace "SSDP DISCOVERY EVENTS"
|
||||||
def bridges = getHueBridges()
|
def bridges = getHueBridges()
|
||||||
log.trace bridges.toString()
|
log.trace bridges.toString()
|
||||||
if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) {
|
if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) {
|
||||||
//bridge does not exist
|
//bridge does not exist
|
||||||
log.trace "Adding bridge ${parsedEvent.ssdpUSN}"
|
log.trace "Adding bridge ${parsedEvent.ssdpUSN}"
|
||||||
bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||||
} else {
|
} else {
|
||||||
// update the values
|
// update the values
|
||||||
def ip = convertHexToIP(parsedEvent.networkAddress)
|
def ip = convertHexToIP(parsedEvent.networkAddress)
|
||||||
def port = convertHexToInt(parsedEvent.deviceAddress)
|
def port = convertHexToInt(parsedEvent.deviceAddress)
|
||||||
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 dni = "${parsedEvent.mac}"
|
def dni = "${parsedEvent.mac}"
|
||||||
def d = getChildDevice(dni)
|
def d = getChildDevice(dni)
|
||||||
def networkAddress = null
|
def networkAddress = null
|
||||||
if (!d) {
|
if (!d) {
|
||||||
childDevices.each {
|
childDevices.each {
|
||||||
if (it.getDeviceDataByName("mac")) {
|
if (it.getDeviceDataByName("mac")) {
|
||||||
def newDNI = "${it.getDeviceDataByName("mac")}"
|
def newDNI = "${it.getDeviceDataByName("mac")}"
|
||||||
d = it
|
d = it
|
||||||
if (newDNI != it.deviceNetworkId) {
|
if (newDNI != it.deviceNetworkId) {
|
||||||
def oldDNI = it.deviceNetworkId
|
def oldDNI = it.deviceNetworkId
|
||||||
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
||||||
it.setDeviceNetworkId("${newDNI}")
|
it.setDeviceNetworkId("${newDNI}")
|
||||||
if (oldDNI == selectedHue) {
|
if (oldDNI == selectedHue) {
|
||||||
app.updateSetting("selectedHue", newDNI)
|
app.updateSetting("selectedHue", newDNI)
|
||||||
}
|
}
|
||||||
doDeviceSync()
|
doDeviceSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateBridgeStatus(d)
|
updateBridgeStatus(d)
|
||||||
if (d.getDeviceDataByName("networkAddress")) {
|
if (d.getDeviceDataByName("networkAddress")) {
|
||||||
@@ -616,22 +654,22 @@ def locationHandler(evt) {
|
|||||||
} else {
|
} else {
|
||||||
networkAddress = d.latestState('networkAddress').stringValue
|
networkAddress = d.latestState('networkAddress').stringValue
|
||||||
}
|
}
|
||||||
log.trace "Host: $host - $networkAddress"
|
log.trace "Host: $host - $networkAddress"
|
||||||
if(host != networkAddress) {
|
if(host != networkAddress) {
|
||||||
log.debug "Device's port or ip changed for device $d..."
|
log.debug "Device's port or ip changed for device $d..."
|
||||||
dstate.ip = ip
|
dstate.ip = ip
|
||||||
dstate.port = port
|
dstate.port = port
|
||||||
dstate.name = "Philips hue ($ip)"
|
dstate.name = "Philips hue ($ip)"
|
||||||
d.sendEvent(name:"networkAddress", value: host)
|
d.sendEvent(name:"networkAddress", value: host)
|
||||||
d.updateDataValue("networkAddress", host)
|
d.updateDataValue("networkAddress", host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (parsedEvent.headers && parsedEvent.body) {
|
} else if (parsedEvent.headers && parsedEvent.body) {
|
||||||
log.trace "HUE BRIDGE RESPONSES"
|
log.trace "HUE BRIDGE RESPONSES"
|
||||||
def headerString = parsedEvent.headers.toString()
|
def headerString = parsedEvent.headers.toString()
|
||||||
if (headerString?.contains("xml")) {
|
if (headerString?.contains("xml")) {
|
||||||
log.trace "description.xml response (application/xml)"
|
log.trace "description.xml response (application/xml)"
|
||||||
def body = new XmlSlurper().parseText(parsedEvent.body)
|
def body = new XmlSlurper().parseText(parsedEvent.body)
|
||||||
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
||||||
def bridges = getHueBridges()
|
def bridges = getHueBridges()
|
||||||
@@ -643,7 +681,7 @@ def locationHandler(evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
|
} else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
|
||||||
log.trace "description.xml response (application/json)"
|
log.trace "description.xml response (application/json)"
|
||||||
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
|
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
|
||||||
if (body.success != null) {
|
if (body.success != null) {
|
||||||
if (body.success[0] != null) {
|
if (body.success[0] != null) {
|
||||||
@@ -680,7 +718,7 @@ def doDeviceSync(){
|
|||||||
poll()
|
poll()
|
||||||
ssdpSubscribe()
|
ssdpSubscribe()
|
||||||
discoverBridges()
|
discoverBridges()
|
||||||
checkBridgeStatus()
|
checkBridgeStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -693,10 +731,14 @@ def doDeviceSync(){
|
|||||||
private void updateBridgeStatus(childDevice) {
|
private void updateBridgeStatus(childDevice) {
|
||||||
// Update activity timestamp if child device is a valid bridge
|
// Update activity timestamp if child device is a valid bridge
|
||||||
def vbridges = getVerifiedHueBridges()
|
def vbridges = getVerifiedHueBridges()
|
||||||
def vbridge = vbridges.find {"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()}
|
def vbridge = vbridges.find {
|
||||||
|
"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()
|
||||||
|
}
|
||||||
vbridge?.value?.lastActivity = now()
|
vbridge?.value?.lastActivity = now()
|
||||||
if(vbridge) {
|
if (vbridge && childDevice?.device?.currentValue("status") == "Offline") {
|
||||||
|
log.debug "$childDevice is back Online"
|
||||||
childDevice?.sendEvent(name: "status", value: "Online")
|
childDevice?.sendEvent(name: "status", value: "Online")
|
||||||
|
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,29 +747,37 @@ private void updateBridgeStatus(childDevice) {
|
|||||||
* for the bridge and all connected lights. Also, set ID number on bridge if not done previously.
|
* for the bridge and all connected lights. Also, set ID number on bridge if not done previously.
|
||||||
*/
|
*/
|
||||||
private void checkBridgeStatus() {
|
private void checkBridgeStatus() {
|
||||||
def bridges = getHueBridges()
|
def bridges = getHueBridges()
|
||||||
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
|
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
|
||||||
def time = now() - (1000 * 60 * 11)
|
def time = now() - (1000 * 60 * 11)
|
||||||
bridges.each {
|
bridges.each {
|
||||||
def d = getChildDevice(it.value.mac)
|
def d = getChildDevice(it.value.mac)
|
||||||
if(d) {
|
if (d) {
|
||||||
// Set id number on bridge if not done
|
// Set id number on bridge if not done
|
||||||
if (it.value.idNumber == null) {
|
if (it.value.idNumber == null) {
|
||||||
it.value.idNumber = getBridgeIdNumber(it.value.serialNumber)
|
it.value.idNumber = getBridgeIdNumber(it.value.serialNumber)
|
||||||
d.sendEvent(name: "idNumber", value: it.value.idNumber)
|
d.sendEvent(name: "idNumber", value: it.value.idNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
|
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
|
||||||
log.warn "Bridge $it.value.idNumber is Offline"
|
if (d.currentStatus == "Online") {
|
||||||
d.sendEvent(name: "status", value: "Offline")
|
log.warn "$d is Offline"
|
||||||
|
d.sendEvent(name: "status", value: "Offline")
|
||||||
|
d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
|
||||||
|
|
||||||
state.bulbs?.each {
|
Calendar currentTime = Calendar.getInstance()
|
||||||
it.value.online = false
|
getChildDevices().each {
|
||||||
|
def id = getId(it)
|
||||||
|
if (state.bulbs[id]?.online == true) {
|
||||||
|
state.bulbs[id]?.online = false
|
||||||
|
state.bulbs[id]?.unreachableSince = currentTime.getTimeInMillis()
|
||||||
|
it.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
getChildDevices().each {
|
} else if (d.currentStatus == "Offline") {
|
||||||
it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false)
|
log.debug "$d is back Online"
|
||||||
}
|
d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
|
||||||
} else {
|
|
||||||
d.sendEvent(name: "status", value: "Online")//setOnline(false)
|
d.sendEvent(name: "status", value: "Online")//setOnline(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -779,24 +829,23 @@ def parse(childDevice, description) {
|
|||||||
def parsedEvent = parseLanMessage(description)
|
def parsedEvent = parseLanMessage(description)
|
||||||
if (parsedEvent.headers && parsedEvent.body) {
|
if (parsedEvent.headers && parsedEvent.body) {
|
||||||
def headerString = parsedEvent.headers.toString()
|
def headerString = parsedEvent.headers.toString()
|
||||||
def bodyString = parsedEvent.body.toString()
|
def bodyString = parsedEvent.body.toString()
|
||||||
if (headerString?.contains("json")) {
|
if (headerString?.contains("json")) {
|
||||||
def body
|
def body
|
||||||
try {
|
try {
|
||||||
body = new groovy.json.JsonSlurper().parseText(bodyString)
|
body = new groovy.json.JsonSlurper().parseText(bodyString)
|
||||||
} catch (all) {
|
} catch (all) {
|
||||||
log.warn "Parsing Body failed - trying again..."
|
log.warn "Parsing Body failed"
|
||||||
poll()
|
}
|
||||||
}
|
if (body instanceof java.util.Map) {
|
||||||
if (body instanceof java.util.Map) {
|
// get (poll) reponse
|
||||||
// get (poll) reponse
|
return handlePoll(body)
|
||||||
return handlePoll(body)
|
|
||||||
} else {
|
} else {
|
||||||
//put response
|
//put response
|
||||||
return handleCommandResponse(body)
|
return handleCommandResponse(body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug "parse - got something other than headers,body..."
|
log.debug "parse - got something other than headers,body..."
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -810,26 +859,26 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
|
|||||||
|
|
||||||
def events = [:]
|
def events = [:]
|
||||||
// For now, only care about changing color temperature if requested by user
|
// For now, only care about changing color temperature if requested by user
|
||||||
if (ct != null && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
|
if (ct != null && ct != 0 && (colormode == "ct" || (xy == null && hue == null && sat == null))) {
|
||||||
// for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below
|
// for some reason setting Hue to their specified minimum off 153 yields 154, dealt with below
|
||||||
// 153 (6500K) to 500 (2000K)
|
// 153 (6500K) to 500 (2000K)
|
||||||
def temp = (ct == 154) ? 6500 : Math.round(1000000 / ct)
|
def temp = (ct == 154) ? 6500 : Math.round(1000000 / ct)
|
||||||
device.sendEvent([name: "colorTemperature", value: temp, descriptionText: "Color temperature has changed"])
|
device.sendEvent([name: "colorTemperature", value: temp, descriptionText: "Color temperature has changed"])
|
||||||
// Return because color temperature change is not counted as a color change in SmartThings so no hex update necessary
|
// Return because color temperature change is not counted as a color change in SmartThings so no hex update necessary
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hue != null) {
|
if (hue != null) {
|
||||||
// 0-65535
|
// 0-65535
|
||||||
def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int
|
def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int
|
||||||
events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false]
|
events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sat != null) {
|
if (sat != null) {
|
||||||
// 0-254
|
// 0-254
|
||||||
def value = Math.round(sat * 100 / 254) as int
|
def value = Math.round(sat * 100 / 254) as int
|
||||||
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
|
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex
|
// Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex
|
||||||
if (xy != null && colormode != "hs") {
|
if (xy != null && colormode != "hs") {
|
||||||
@@ -915,8 +964,15 @@ private handleCommandResponse(body) {
|
|||||||
updates[childDeviceNetworkId]."$eventType" = v
|
updates[childDeviceNetworkId]."$eventType" = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (payload.error) {
|
} else if (payload?.error) {
|
||||||
log.warn "Error returned from Hue bridge error = ${body?.error}"
|
log.warn "Error returned from Hue bridge, error = ${payload?.error}"
|
||||||
|
// Check for unauthorized user
|
||||||
|
if (payload?.error?.type?.value == 1) {
|
||||||
|
log.error "Hue username is not valid"
|
||||||
|
state.refreshUsernameNeeded = true
|
||||||
|
state.username = null
|
||||||
|
}
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -924,12 +980,9 @@ private handleCommandResponse(body) {
|
|||||||
updates.each { childDeviceNetworkId, params ->
|
updates.each { childDeviceNetworkId, params ->
|
||||||
def device = getChildDevice(childDeviceNetworkId)
|
def device = getChildDevice(childDeviceNetworkId)
|
||||||
def id = getId(device)
|
def id = getId(device)
|
||||||
// If device is offline, then don't send events which will update device watch
|
sendBasicEvents(device, "on", params.on)
|
||||||
if (isOnline(id)) {
|
sendBasicEvents(device, "bri", params.bri)
|
||||||
sendBasicEvents(device, "on", params.on)
|
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
|
||||||
sendBasicEvents(device, "bri", params.bri)
|
|
||||||
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -962,39 +1015,38 @@ private handlePoll(body) {
|
|||||||
|
|
||||||
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}" }
|
||||||
if (device) {
|
if (device) {
|
||||||
if (bulb.value.state?.reachable) {
|
if (bulb.value.state?.reachable) {
|
||||||
if (state.bulbs[bulb.key]?.online == false) {
|
if (state.bulbs[bulb.key]?.online == false || state.bulbs[bulb.key]?.online == null) {
|
||||||
// light just came back online, notify device watch
|
// light just came back online, notify device watch
|
||||||
def lastActivity = now()
|
device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", 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"
|
log.debug "$device is Online"
|
||||||
}
|
}
|
||||||
// Mark light as "online"
|
// Mark light as "online"
|
||||||
state.bulbs[bulb.key]?.unreachableSince = null
|
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 (!state.updating) {
|
|
||||||
sendBasicEvents(device, "on", bulb.value?.state?.on)
|
|
||||||
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
|
|
||||||
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) {
|
if (state.bulbs[bulb.key]?.unreachableSince == null) {
|
||||||
// Store the first time where device was reported as "unreachable"
|
// Store the first time where device was reported as "unreachable"
|
||||||
state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis()
|
state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis()
|
||||||
} else if (state.bulbs[bulb.key]?.online) {
|
}
|
||||||
|
if (state.bulbs[bulb.key]?.online || state.bulbs[bulb.key]?.online == null) {
|
||||||
// Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary
|
// Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary
|
||||||
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) {
|
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis() || state.bulbs[bulb.key]?.online == null) {
|
||||||
log.warn "$device went Offline"
|
log.warn "$device went Offline"
|
||||||
state.bulbs[bulb.key]?.online = false
|
state.bulbs[bulb.key]?.online = false
|
||||||
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
|
device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.warn "$device may not reachable by Hue bridge"
|
log.warn "$device may not reachable by Hue bridge"
|
||||||
}
|
}
|
||||||
|
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
|
||||||
|
if (!state.updating) {
|
||||||
|
sendBasicEvents(device, "on", bulb.value?.state?.on)
|
||||||
|
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
|
||||||
|
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
@@ -1013,16 +1065,16 @@ def updateHandler() {
|
|||||||
|
|
||||||
def hubVerification(bodytext) {
|
def hubVerification(bodytext) {
|
||||||
log.trace "Bridge sent back description.xml for verification"
|
log.trace "Bridge sent back description.xml for verification"
|
||||||
def body = new XmlSlurper().parseText(bodytext)
|
def body = new XmlSlurper().parseText(bodytext)
|
||||||
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
||||||
def bridges = getHueBridges()
|
def bridges = getHueBridges()
|
||||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||||
if (bridge) {
|
if (bridge) {
|
||||||
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||||
} else {
|
} else {
|
||||||
log.error "/description.xml returned a bridge that didn't exist"
|
log.error "/description.xml returned a bridge that didn't exist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def on(childDevice) {
|
def on(childDevice) {
|
||||||
@@ -1031,7 +1083,7 @@ def on(childDevice) {
|
|||||||
updateInProgress()
|
updateInProgress()
|
||||||
createSwitchEvent(childDevice, "on")
|
createSwitchEvent(childDevice, "on")
|
||||||
put("lights/$id/state", [on: true])
|
put("lights/$id/state", [on: true])
|
||||||
return "Bulb is turning On"
|
return "Bulb is turning On"
|
||||||
}
|
}
|
||||||
|
|
||||||
def off(childDevice) {
|
def off(childDevice) {
|
||||||
@@ -1040,15 +1092,15 @@ def off(childDevice) {
|
|||||||
updateInProgress()
|
updateInProgress()
|
||||||
createSwitchEvent(childDevice, "off")
|
createSwitchEvent(childDevice, "off")
|
||||||
put("lights/$id/state", [on: false])
|
put("lights/$id/state", [on: false])
|
||||||
return "Bulb is turning Off"
|
return "Bulb is turning Off"
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(childDevice, percent) {
|
def setLevel(childDevice, percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 1 - 254
|
// 1 - 254
|
||||||
def level
|
def level
|
||||||
if (percent == 1)
|
if (percent == 1)
|
||||||
level = 1
|
level = 1
|
||||||
else
|
else
|
||||||
@@ -1082,7 +1134,7 @@ 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)
|
||||||
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)
|
||||||
// TODO should this be done by app only or should we default to on?
|
// TODO should this be done by app only or should we default to on?
|
||||||
@@ -1094,7 +1146,7 @@ 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)
|
||||||
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)
|
||||||
createSwitchEvent(childDevice, "on")
|
createSwitchEvent(childDevice, "on")
|
||||||
@@ -1103,14 +1155,14 @@ 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)
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
|
|
||||||
def value = [:]
|
def value = [:]
|
||||||
def hue = null
|
def hue = null
|
||||||
def sat = null
|
def sat = null
|
||||||
def xy = null
|
def xy = null
|
||||||
|
|
||||||
// Prefer hue/sat over hex to make sure it works with the majority of the smartapps
|
// Prefer hue/sat over hex to make sure it works with the majority of the smartapps
|
||||||
if (huesettings.hue != null || huesettings.sat != null) {
|
if (huesettings.hue != null || huesettings.sat != null) {
|
||||||
@@ -1135,48 +1187,32 @@ def setColor(childDevice, huesettings) {
|
|||||||
// value.xy = calculateXY(hex, model)
|
// value.xy = calculateXY(hex, model)
|
||||||
// Once groups, or scenes are introduced it might be a good idea to use unique models again
|
// Once groups, or scenes are introduced it might be a good idea to use unique models again
|
||||||
value.xy = calculateXY(hex)
|
value.xy = calculateXY(hex)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Default behavior is to turn light on
|
// Default behavior is to turn light on
|
||||||
value.on = true
|
value.on = true
|
||||||
|
|
||||||
if (huesettings.level != null) {
|
if (huesettings.level != null) {
|
||||||
if (huesettings.level <= 0)
|
if (huesettings.level <= 0)
|
||||||
value.on = false
|
value.on = false
|
||||||
else if (huesettings.level == 1)
|
else if (huesettings.level == 1)
|
||||||
value.bri = 1
|
value.bri = 1
|
||||||
else
|
else
|
||||||
value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254)
|
value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254)
|
||||||
}
|
}
|
||||||
value.alert = huesettings.alert ? huesettings.alert : "none"
|
value.alert = huesettings.alert ? huesettings.alert : "none"
|
||||||
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
|
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
|
||||||
|
|
||||||
// Make sure to turn off light if requested
|
// Make sure to turn off light if requested
|
||||||
if (huesettings.switch == "off")
|
if (huesettings.switch == "off")
|
||||||
value.on = false
|
value.on = false
|
||||||
|
|
||||||
createSwitchEvent(childDevice, value.on ? "on" : "off")
|
createSwitchEvent(childDevice, value.on ? "on" : "off")
|
||||||
put("lights/$id/state", value)
|
put("lights/$id/state", value)
|
||||||
return "Setting color to $value"
|
return "Setting color to $value"
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping(childDevice) {
|
|
||||||
if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) {
|
|
||||||
if (childDevice.device?.currentValue("status")?.equalsIgnoreCase("Online")) {
|
|
||||||
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Bridge is reachable", displayed: false, isStateChange: true)
|
|
||||||
return "Bridge is Online"
|
|
||||||
} else {
|
|
||||||
return "Bridge is Offline"
|
|
||||||
}
|
|
||||||
} else if (isOnline(getId(childDevice))) {
|
|
||||||
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
|
|
||||||
return "Device is Online"
|
|
||||||
} else {
|
|
||||||
return "Device is Offline"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getId(childDevice) {
|
private getId(childDevice) {
|
||||||
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
|
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
|
||||||
return childDevice.device?.deviceNetworkId[3..-1]
|
return childDevice.device?.deviceNetworkId[3..-1]
|
||||||
@@ -1227,30 +1263,30 @@ private getBridgeIdNumber(serialNumber) {
|
|||||||
private getBridgeIP() {
|
private getBridgeIP() {
|
||||||
def host = null
|
def host = null
|
||||||
if (selectedHue) {
|
if (selectedHue) {
|
||||||
def d = getChildDevice(selectedHue)
|
def d = getChildDevice(selectedHue)
|
||||||
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 == "") {
|
||||||
def serialNumber = selectedHue
|
def serialNumber = selectedHue
|
||||||
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
|
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
|
||||||
if (!bridge) {
|
if (!bridge) {
|
||||||
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
|
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
|
||||||
}
|
}
|
||||||
if (bridge?.ip && bridge?.port) {
|
if (bridge?.ip && bridge?.port) {
|
||||||
if (bridge?.ip.contains("."))
|
if (bridge?.ip.contains("."))
|
||||||
host = "${bridge?.ip}:${bridge?.port}"
|
host = "${bridge?.ip}:${bridge?.port}"
|
||||||
else
|
else
|
||||||
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
|
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
|
||||||
} else if (bridge?.networkAddress && bridge?.deviceAddress)
|
} else if (bridge?.networkAddress && bridge?.deviceAddress)
|
||||||
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
|
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
|
||||||
}
|
}
|
||||||
log.trace "Bridge: $selectedHue - Host: $host"
|
log.trace "Bridge: $selectedHue - Host: $host"
|
||||||
}
|
}
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer convertHexToInt(hex) {
|
private Integer convertHexToInt(hex) {
|
||||||
@@ -1290,7 +1326,7 @@ private List getRealHubFirmwareVersions() {
|
|||||||
* @param childDevice device to send event for
|
* @param childDevice device to send event for
|
||||||
* @param setSwitch The new switch state, "on" or "off"
|
* @param setSwitch The new switch state, "on" or "off"
|
||||||
* @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since
|
* @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since
|
||||||
* that should generate "off" instead of level change
|
* that should generate "off" instead of level change
|
||||||
*/
|
*/
|
||||||
private void createSwitchEvent(childDevice, setSwitch, setLevel = null) {
|
private void createSwitchEvent(childDevice, setSwitch, setLevel = null) {
|
||||||
|
|
||||||
@@ -1433,8 +1469,8 @@ private float[] calculateXY(colorStr, model = null) {
|
|||||||
xy[0] = closestPoint.x;
|
xy[0] = closestPoint.x;
|
||||||
xy[1] = closestPoint.y;
|
xy[1] = closestPoint.y;
|
||||||
}
|
}
|
||||||
// xy[0] = PHHueHelper.precision(4, xy[0]);
|
// xy[0] = PHHueHelper.precision(4, xy[0]);
|
||||||
// xy[1] = PHHueHelper.precision(4, xy[1]);
|
// xy[1] = PHHueHelper.precision(4, xy[1]);
|
||||||
|
|
||||||
|
|
||||||
// TODO needed, assume it just sets number of decimals?
|
// TODO needed, assume it just sets number of decimals?
|
||||||
@@ -1452,7 +1488,7 @@ private float[] calculateXY(colorStr, model = null) {
|
|||||||
*
|
*
|
||||||
* @param points the float array contain x and the y value. [x,y]
|
* @param points the float array contain x and the y value. [x,y]
|
||||||
* @param model the model of the lamp, example: "LCT001" for hue bulb. Used to calculate the color gamut.
|
* @param model the model of the lamp, example: "LCT001" for hue bulb. Used to calculate the color gamut.
|
||||||
* If this value is empty the default gamut values are used.
|
* If this value is empty the default gamut values are used.
|
||||||
* @return the color value in hex (#ff03d3). If xy is null OR xy is not an array of size 2, Color. BLACK will be returned
|
* @return the color value in hex (#ff03d3). If xy is null OR xy is not an array of size 2, Color. BLACK will be returned
|
||||||
*/
|
*/
|
||||||
private String colorFromXY(points, model ) {
|
private String colorFromXY(points, model ) {
|
||||||
@@ -1766,3 +1802,4 @@ def hsvToHex(hue, sat, value = 100){
|
|||||||
|
|
||||||
return "#$r1$g1$b1"
|
return "#$r1$g1$b1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -289,12 +289,12 @@ def initializeLife360Connection() {
|
|||||||
state.life360AccessToken = result.data.access_token
|
state.life360AccessToken = result.data.access_token
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
log.debug "Response=${result.data}"
|
log.info "Life360 initializeLife360Connection, response=${result.data}"
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
log.debug e
|
log.error "Life360 initializeLife360Connection, error: $e"
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +656,7 @@ def generateInitialEvent (member, childDevice) {
|
|||||||
|
|
||||||
try { // we are going to just ignore any errors
|
try { // we are going to just ignore any errors
|
||||||
|
|
||||||
log.debug "Generate Initial Event for New Device for Member = ${member.id}"
|
log.info "Life360 generateInitialEvent($member, $childDevice)"
|
||||||
|
|
||||||
def place = state.places.find{it.id==settings.place}
|
def place = state.places.find{it.id==settings.place}
|
||||||
|
|
||||||
@@ -677,6 +677,8 @@ def generateInitialEvent (member, childDevice) {
|
|||||||
// log.debug "Distance Away = ${distanceAway}"
|
// log.debug "Distance Away = ${distanceAway}"
|
||||||
|
|
||||||
boolean isPresent = (distanceAway <= placeRadius)
|
boolean isPresent = (distanceAway <= placeRadius)
|
||||||
|
|
||||||
|
log.info "Life360 generateInitialEvent, member: ($memberLatitude, $memberLongitude), place: ($placeLatitude, $placeLongitude), radius: $placeRadius, dist: $distanceAway, present: $isPresent"
|
||||||
|
|
||||||
// log.debug "External Id=${app.id}:${member.id}"
|
// log.debug "External Id=${app.id}:${member.id}"
|
||||||
|
|
||||||
@@ -718,7 +720,7 @@ def haversine(lat1, lon1, lat2, lon2) {
|
|||||||
|
|
||||||
def placeEventHandler() {
|
def placeEventHandler() {
|
||||||
|
|
||||||
log.debug "In placeEventHandler method."
|
log.info "Life360 placeEventHandler: params=$params, settings.place=$settings.place"
|
||||||
|
|
||||||
// the POST to this end-point will look like:
|
// the POST to this end-point will look like:
|
||||||
// POST http://test.com/webhook?circleId=XXXX&placeId=XXXX&userId=XXXX&direction=arrive
|
// POST http://test.com/webhook?circleId=XXXX&placeId=XXXX&userId=XXXX&direction=arrive
|
||||||
@@ -729,8 +731,6 @@ def placeEventHandler() {
|
|||||||
def direction = params?.direction
|
def direction = params?.direction
|
||||||
def timestamp = params?.timestamp
|
def timestamp = params?.timestamp
|
||||||
|
|
||||||
log.debug "Life360 Event: Circle: ${circleId}, Place: ${placeId}, User: ${userId}, Direction: ${direction}"
|
|
||||||
|
|
||||||
if (placeId == settings.place) {
|
if (placeId == settings.place) {
|
||||||
|
|
||||||
def presenceState = (direction=="in")
|
def presenceState = (direction=="in")
|
||||||
@@ -745,10 +745,10 @@ def placeEventHandler() {
|
|||||||
|
|
||||||
if (deviceWrapper) {
|
if (deviceWrapper) {
|
||||||
deviceWrapper.generatePresenceEvent(presenceState)
|
deviceWrapper.generatePresenceEvent(presenceState)
|
||||||
log.debug "Event raised on child device: ${externalId}"
|
log.debug "Life360 event raised on child device: ${externalId}"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "Couldn't find child device associated with inbound Life360 event."
|
log.warn "Life360 couldn't find child device associated with inbound Life360 event."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,8 +242,6 @@ def installed() {
|
|||||||
} else {
|
} else {
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
// Check for new devices and remove old ones every 3 hours
|
|
||||||
runEvery3Hours('updateDevices')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// called after settings are changed
|
// called after settings are changed
|
||||||
@@ -271,9 +269,19 @@ private removeChildDevices(devices) {
|
|||||||
def initialize() {
|
def initialize() {
|
||||||
log.debug "initialize"
|
log.debug "initialize"
|
||||||
updateDevices()
|
updateDevices()
|
||||||
|
// Check for new devices and remove old ones every 3 hours
|
||||||
|
runEvery5Minutes('updateDevices')
|
||||||
|
setupDeviceWatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
|
private setupDeviceWatch() {
|
||||||
|
def hub = location.hubs[0]
|
||||||
|
// Make sure that all child devices are enrolled in device watch
|
||||||
|
getChildDevices().each {
|
||||||
|
it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Map apiRequestHeaders() {
|
Map apiRequestHeaders() {
|
||||||
return ["Authorization": "Bearer ${state.lifxAccessToken}",
|
return ["Authorization": "Bearer ${state.lifxAccessToken}",
|
||||||
@@ -373,32 +381,46 @@ def updateDevices() {
|
|||||||
selectors.add("${device.id}")
|
selectors.add("${device.id}")
|
||||||
if (!childDevice) {
|
if (!childDevice) {
|
||||||
// log.info("Adding device ${device.id}: ${device.product}")
|
// log.info("Adding device ${device.id}: ${device.product}")
|
||||||
def data = [
|
|
||||||
label: device.label,
|
|
||||||
level: Math.round((device.brightness ?: 1) * 100),
|
|
||||||
switch: device.connected ? device.power : "unreachable",
|
|
||||||
colorTemperature: device.color.kelvin
|
|
||||||
]
|
|
||||||
if (device.product.capabilities.has_color) {
|
if (device.product.capabilities.has_color) {
|
||||||
data["color"] = colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)
|
childDevice = addChildDevice(app.namespace, "LIFX Color Bulb", device.id, null, ["label": device.label, "completedSetup": true])
|
||||||
data["hue"] = device.color.hue / 3.6
|
|
||||||
data["saturation"] = device.color.saturation * 100
|
|
||||||
childDevice = addChildDevice(app.namespace, "LIFX Color Bulb", device.id, null, data)
|
|
||||||
} else {
|
} else {
|
||||||
childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data)
|
childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, ["label": device.label, "completedSetup": true])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (device.product.capabilities.has_color) {
|
||||||
|
childDevice.sendEvent(name: "color", value: colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int))
|
||||||
|
childDevice.sendEvent(name: "hue", value: device.color.hue / 3.6)
|
||||||
|
childDevice.sendEvent(name: "saturation", value: device.color.saturation * 100)
|
||||||
|
}
|
||||||
|
childDevice.sendEvent(name: "label", value: device.label)
|
||||||
|
childDevice.sendEvent(name: "level", value: Math.round((device.brightness ?: 1) * 100))
|
||||||
|
childDevice.sendEvent(name: "switch.setLevel", value: Math.round((device.brightness ?: 1) * 100))
|
||||||
|
childDevice.sendEvent(name: "switch", value: device.power)
|
||||||
|
childDevice.sendEvent(name: "colorTemperature", value: device.color.kelvin)
|
||||||
|
childDevice.sendEvent(name: "model", value: device.product.name)
|
||||||
|
|
||||||
|
if (state.devices[device.id] == null) {
|
||||||
|
// State missing, add it and set it to opposite status as current status to provoke event below
|
||||||
|
state.devices[device.id] = [online: !device.connected]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.devices[device.id]?.online && device.connected) {
|
||||||
|
// Device came online after being offline
|
||||||
|
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false)
|
||||||
|
log.debug "$device is back Online"
|
||||||
|
} else if (state.devices[device.id]?.online && !device.connected) {
|
||||||
|
// Device went offline after being online
|
||||||
|
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false)
|
||||||
|
log.debug "$device went Offline"
|
||||||
|
}
|
||||||
|
state.devices[device.id] = [online: device.connected]
|
||||||
}
|
}
|
||||||
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
|
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
|
||||||
log.info("Deleting ${it.deviceNetworkId}")
|
log.info("Deleting ${it.deviceNetworkId}")
|
||||||
|
state.devices[it.deviceNetworkId] = null
|
||||||
deleteChildDevice(it.deviceNetworkId)
|
deleteChildDevice(it.deviceNetworkId)
|
||||||
}
|
}
|
||||||
runIn(1, 'refreshDevices') // Asynchronously refresh devices so we don't block
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def refreshDevices() {
|
|
||||||
log.info("Refreshing all devices...")
|
|
||||||
getChildDevices().each { device ->
|
|
||||||
device.refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,6 +24,12 @@
|
|||||||
* switches | switch | on, off | on, off
|
* switches | switch | on, off | on, off
|
||||||
* motionSensors | motion | | active, inactive
|
* motionSensors | motion | | active, inactive
|
||||||
* contactSensors | contact | | open, closed
|
* contactSensors | contact | | open, closed
|
||||||
|
* thermostat | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint
|
||||||
|
* | | setCoolingSetpoint(number) | coolingSetpoint, thermostatSetpoint
|
||||||
|
* | | off, heat, emergencyHeat | thermostatMode — ["emergency heat", "auto", "cool", "off", "heat"]
|
||||||
|
* | | cool, setThermostatMode | thermostatFanMode — ["auto", "on", "circulate"]
|
||||||
|
* | | fanOn, fanAuto, fanCirculate| thermostatOperatingState — ["cooling", "heating", "pending heat",
|
||||||
|
* | | setThermostatFanMode, auto | "fan only", "vent economizer", "pending cool", "idle"]
|
||||||
* presenceSensors | presence | | present, 'not present'
|
* presenceSensors | presence | | present, 'not present'
|
||||||
* temperatureSensors | temperature | | <numeric, F or C according to unit>
|
* temperatureSensors | temperature | | <numeric, F or C according to unit>
|
||||||
* accelerationSensors | acceleration | | active, inactive
|
* accelerationSensors | acceleration | | active, inactive
|
||||||
@@ -58,6 +64,7 @@ preferences(oauthPage: "deviceAuthorization") {
|
|||||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||||
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
|
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
|
||||||
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors?", multiple: true, required: false
|
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors?", multiple: true, required: false
|
||||||
|
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
||||||
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors?", multiple: true, required: false
|
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors?", multiple: true, required: false
|
||||||
input "temperatureSensors", "capability.temperatureMeasurement", title: "Which Temperature Sensors?", multiple: true, required: false
|
input "temperatureSensors", "capability.temperatureMeasurement", title: "Which Temperature Sensors?", multiple: true, required: false
|
||||||
input "accelerationSensors", "capability.accelerationSensor", title: "Which Vibration Sensors?", multiple: true, required: false
|
input "accelerationSensors", "capability.accelerationSensor", title: "Which Vibration Sensors?", multiple: true, required: false
|
||||||
@@ -509,7 +516,7 @@ def pollResponse(response, data) {
|
|||||||
def hub = getChildDevice("harmony-${it.key}")
|
def hub = getChildDevice("harmony-${it.key}")
|
||||||
if (hub) {
|
if (hub) {
|
||||||
if (it.value.response.data.currentAvActivity == "-1") {
|
if (it.value.response.data.currentAvActivity == "-1") {
|
||||||
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
|
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", displayed: false)
|
||||||
} else {
|
} else {
|
||||||
def currentActivity
|
def currentActivity
|
||||||
def activityDTH = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}")
|
def activityDTH = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}")
|
||||||
@@ -517,7 +524,7 @@ def pollResponse(response, data) {
|
|||||||
currentActivity = activityDTH.device.displayName
|
currentActivity = activityDTH.device.displayName
|
||||||
else
|
else
|
||||||
currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
|
currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
|
||||||
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
|
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", displayed: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -569,7 +576,7 @@ def getActivityList() {
|
|||||||
}
|
}
|
||||||
activities += [id: "off", name: "Activity OFF", type: "0"]
|
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)
|
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", displayed: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -936,7 +943,7 @@ def deleteHarmony() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getAllDevices() {
|
private getAllDevices() {
|
||||||
([] + switches + motionSensors + contactSensors + presenceSensors + temperatureSensors + accelerationSensors + waterSensors + lightSensors + humiditySensors + alarms + locks)?.findAll()?.unique { it.id }
|
([] + switches + motionSensors + contactSensors + thermostats + presenceSensors + temperatureSensors + accelerationSensors + waterSensors + lightSensors + humiditySensors + alarms + locks)?.findAll()?.unique { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
private deviceItem(device) {
|
private deviceItem(device) {
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* Xandem Home Integration
|
||||||
|
*
|
||||||
|
* Copyright 2017 Derek Twaddle
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* README
|
||||||
|
*
|
||||||
|
* Hello, after purchsing Xandem Home for a rental property I noticed there were no SmartApps yet developed. This is my
|
||||||
|
* first attempt at integrating Xandem with SmartThings out of personal need and decided to share with the community.
|
||||||
|
*
|
||||||
|
* Depending on your ISP and setup, there are some preliminary steps that need to be performed before installing this app. Xandem is initially
|
||||||
|
* accessible only via local network so you will need to make some minor rule changes to your firewall / router.
|
||||||
|
*
|
||||||
|
* If you have a static IP assignment from your ISP, you may forgo the following step. If your ISP assigns an IP Address to you Dynamically
|
||||||
|
* which is what most do at Resedential installations, you will need a Dynamic DNS service. Dynamic DNS will map your randomly assigned IP
|
||||||
|
* address to a static name like (Ex: myhome.dyndns.com). No matter what your IP changes to, it will always be resolved using the same name you chose.
|
||||||
|
*
|
||||||
|
* Once the above is completed, you will need to configure your router to pass or port forward to your internal Xandem Hub. You will
|
||||||
|
* want to statically assign a local IP to the mac address of the Xandem Hub. This way you will always assign the same local IP to the Xandem Hub which can be mapped
|
||||||
|
* and used in port forwarding rules. After the IP has been assigned (Ex: 192.168.1.25) add a rule on your rounter to pass incoming Port 80 request
|
||||||
|
* to your Xandem Hub IP address on the same port. This effectively lets your router listen for and pass data from SmarthThings to your Xandem Hub.
|
||||||
|
*
|
||||||
|
* Once the above networking steps are completed, create a Xandem API key on your local Xandem Hub. Instructions can
|
||||||
|
* found here: http://documentation.xandem.com/api/#api-keys-and-authorization-header
|
||||||
|
*
|
||||||
|
* This SmartApp is a personal project you are free to use.
|
||||||
|
*/
|
||||||
|
|
||||||
|
definition(
|
||||||
|
name: "Xandem Home Integration",
|
||||||
|
namespace: "Xandem",
|
||||||
|
author: "Derek Twaddle",
|
||||||
|
description: "Integrates Xandem Home to SmartThings. Detects motion, if above a user set threshold, turn on light.",
|
||||||
|
category: "Safety & Security",
|
||||||
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||||
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||||
|
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
section("Network Configuration") {
|
||||||
|
input "ddns", "text", title: " External Hostname or Static IP", required: true
|
||||||
|
paragraph "Example: mynetwork.dyndns.org or 64.102.0.1"
|
||||||
|
input "apikey", "text", title: "API Key", required: true
|
||||||
|
paragraph "Enter the Xandem API Key generated on your local Hub"
|
||||||
|
}
|
||||||
|
section("Turn on when motion detected and at or above level:") {
|
||||||
|
input "thelevel", "text", title: "1 - 10", required: true
|
||||||
|
paragraph "Motion Level: 1 Minimal Motion - 10 High Motion"
|
||||||
|
}
|
||||||
|
section("Turn on this light") {
|
||||||
|
input "theswitch", "capability.switch", required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "Updated with settings: ${settings}"
|
||||||
|
unsubscribe()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
runEvery5Minutes(updateStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateStatus() {
|
||||||
|
def params = [
|
||||||
|
uri: "http://${ddns}/v1/data",
|
||||||
|
headers: [Authorization: "${apikey}"],
|
||||||
|
body: [data_fields: ["motion_score", "is_motion"]]
|
||||||
|
]
|
||||||
|
|
||||||
|
try {
|
||||||
|
httpPostJson(params) { resp ->
|
||||||
|
resp.headers.each {
|
||||||
|
}
|
||||||
|
|
||||||
|
// If motion detected and motion level is at or above chosen level, Light On
|
||||||
|
if (resp.data.is_motion != 0 && resp.data.motion_score >= thelevel){
|
||||||
|
theswitch.on()
|
||||||
|
log.debug "Motion detected, light on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.debug "something went wrong: $e"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user