mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-16 13:10:51 +00:00
Compare commits
187 Commits
netatmo-ap
...
MSA-1739-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2fd0bed4a | ||
|
|
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)
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Hunter Douglas Platinum Gateway Scene Control Switch for SmartThings
|
||||||
|
* Schwark Satyavolu
|
||||||
|
* Originally based on: Allan Klein's (@allanak) and Mike Maxwell's code
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* 1. Add this code as a device handler in the SmartThings IDE
|
||||||
|
* 3. Create a device using PlatinumGatewaySceneSwitch as the device handler using a hexadecimal representation of IP:port as the device network ID value
|
||||||
|
* For example, a gateway at 192.168.1.222:522 would have a device network ID of C0A801DE:20A
|
||||||
|
* Note: Port 522 is the default Hunter Douglas Platinum Gateway port so you shouldn't need to change anything after the colon
|
||||||
|
* 4. Enjoy the new functionality of the SmartThings app
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing permissions and limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
definition (name: "Platinum Gateway Scene Switch", namespace: "schwark", author: "Schwark Satyavolu") {
|
||||||
|
capability "Switch"
|
||||||
|
command "setSceneNo", ["string"]
|
||||||
|
command "runScene"
|
||||||
|
}
|
||||||
|
|
||||||
|
simulator {
|
||||||
|
// TODO: define status and reply messages here
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
}
|
||||||
|
|
||||||
|
main "switch"
|
||||||
|
details(["switch"])
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
}
|
||||||
|
|
||||||
|
def runScene() {
|
||||||
|
parent.runScene(state.sceneNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
def on() {
|
||||||
|
runScene()
|
||||||
|
sendEvent(name: "switch", value: "on")
|
||||||
|
}
|
||||||
|
|
||||||
|
def off() {
|
||||||
|
runScene()
|
||||||
|
sendEvent(name: "switch", value: "off")
|
||||||
|
}
|
||||||
|
|
||||||
|
def setSceneNo(sceneNo) {
|
||||||
|
state.sceneNo = sceneNo
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Hunter Douglas Platinum Gateway Shade Control Switch for SmartThings
|
||||||
|
* Schwark Satyavolu
|
||||||
|
* Originally based on: Allan Klein's (@allanak) and Mike Maxwell's code
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* 1. Add this code as a device handler in the SmartThings IDE
|
||||||
|
* 3. Create a device using PlatinumGatewayShadeSwitch as the device handler using a hexadecimal representation of IP:port as the device network ID value
|
||||||
|
* For example, a gateway at 192.168.1.222:522 would have a device network ID of C0A801DE:20A
|
||||||
|
* Note: Port 522 is the default Hunter Douglas Platinum Gateway port so you shouldn't need to change anything after the colon
|
||||||
|
* 4. Enjoy the new functionality of the SmartThings app
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing permissions and limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
definition (name: "Platinum Gateway Shade Switch", namespace: "schwark", author: "Schwark Satyavolu") {
|
||||||
|
capability "Switch"
|
||||||
|
capability "Switch Level"
|
||||||
|
command "setShadeNo", ["string"]
|
||||||
|
}
|
||||||
|
|
||||||
|
simulator {
|
||||||
|
// TODO: define status and reply messages here
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||||
|
}
|
||||||
|
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||||
|
state "level", action:"switch level.setLevel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
}
|
||||||
|
|
||||||
|
main "switch"
|
||||||
|
details(["switch", "levelSliderControl"])
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
log.debug("installed Shade with settings ${settings}")
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
}
|
||||||
|
|
||||||
|
def on() {
|
||||||
|
return setLevel(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
def off() {
|
||||||
|
return setLevel(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setLevel(percent) {
|
||||||
|
parent.setShadeLevel(state.shadeNo, percent)
|
||||||
|
if(percent == 100) {
|
||||||
|
sendEvent(name: "switch", value: "on")
|
||||||
|
} else if (percent == 0) {
|
||||||
|
sendEvent(name: "switch", value: "off")
|
||||||
|
}
|
||||||
|
sendEvent(name: "level", value: percent)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setShadeNo(shadeNo) {
|
||||||
|
state.shadeNo = shadeNo
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
*
|
*
|
||||||
@@ -1015,3 +1037,7 @@ 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"
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -829,7 +829,7 @@ def toggleTiles(color) {
|
|||||||
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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -57,7 +57,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -172,6 +172,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)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -188,6 +188,3 @@ def verifyPercent(percent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.trace "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -93,6 +93,3 @@ void refresh() {
|
|||||||
parent.manualRefresh()
|
parent.manualRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
void installed() {
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -107,6 +107,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,6 +15,7 @@ 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"
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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]
|
||||||
|
log.debug hueLevel
|
||||||
|
log.debug saturationLevel
|
||||||
|
master.setColor(newValue)
|
||||||
slaves?.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]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
/**
|
||||||
|
* PlatinumGateway Service Manager
|
||||||
|
*
|
||||||
|
* Author: Schwark Satyavolu
|
||||||
|
*. nc -i3 <ip-address-of-gateway> 522 < input.txt > output.txt
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
definition(
|
||||||
|
name: "Hunter Douglas Platinum Gateway",
|
||||||
|
namespace: "schwark",
|
||||||
|
author: "Schwark Satyavolu",
|
||||||
|
description: "Allows you to connect your Hunter Douglas Platinum Gateway shades with SmartThings and control them from your Things area or Dashboard in the SmartThings Mobile app. Adjust colors by going to the Thing detail screen for your PlatinumGateway shades (tap the gear on PlatinumGateway tiles).",
|
||||||
|
category: "SmartThings Labs",
|
||||||
|
iconUrl: "https://lh5.ggpht.com/FN3-xG6R0q9VjJHYE1iK5K2J11rTphiDEePr8XluI6o_s52xfPoHwt0-TZxc0qlVSQ=w300",
|
||||||
|
iconX2Url: "https://lh5.ggpht.com/FN3-xG6R0q9VjJHYE1iK5K2J11rTphiDEePr8XluI6o_s52xfPoHwt0-TZxc0qlVSQ=w300",
|
||||||
|
singleInstance: true
|
||||||
|
)
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
input("gatewayIP", "string", title:"Gateway IP Address", description: "Please enter your gateway's IP Address", required: true, displayDuringSetup: true)
|
||||||
|
input("statusURL", "string", title:"Gateway Status URL", description: "Please enter the URL to download status", required: true, displayDuringSetup: true)
|
||||||
|
input("scenePrefix", "string", title:"Scene Name Prefix", description: "Please choose a prefix to add to all the Scenes", required: false, displayDuringSetup: true, defaultValue: "Shade Scene " )
|
||||||
|
input("shadePrefix", "string", title:"Shade Name Prefix", description: "Please choose a prefix to add to all the Shades", required: false, displayDuringSetup: true, defaultValue: "Shade " )
|
||||||
|
input("wantShades", "bool", title:"Do you want to add each Shade as a Switch?", description: "Turning this on will add one switch for EACH shade in your house", required: false, displayDuringSetup: true, defaultValue: false )
|
||||||
|
}
|
||||||
|
|
||||||
|
def makeNetworkId(ipaddr, port) {
|
||||||
|
String hexIp = ipaddr.tokenize('.').collect {String.format('%02X', it.toInteger()) }.join()
|
||||||
|
String hexPort = String.format('%04X', port.toInteger())
|
||||||
|
log.debug "The target device is configured as: ${hexIp}:${hexPort}"
|
||||||
|
return "${hexIp}:${hexPort}"
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def uninstalled() {
|
||||||
|
log.debug("Uninstalling with settings: ${settings}")
|
||||||
|
unschedule()
|
||||||
|
if(state.scenes) {
|
||||||
|
// remove scene child devices
|
||||||
|
state.scenes = [:]
|
||||||
|
}
|
||||||
|
if(state.shades) {
|
||||||
|
// remove window child devices
|
||||||
|
state.shades = [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
removeChildDevices(getChildDevices())
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
def updated() {
|
||||||
|
//log.debug "Updated with settings: ${settings}"
|
||||||
|
unsubscribe()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
def initialize() {
|
||||||
|
// remove location subscription aftwards
|
||||||
|
unsubscribe()
|
||||||
|
state.subscribe = false
|
||||||
|
log.debug("gatewayIP is ${gatewayIP}")
|
||||||
|
|
||||||
|
if (gatewayIP) {
|
||||||
|
addgateway()
|
||||||
|
}
|
||||||
|
|
||||||
|
runEvery5Minutes(doDeviceSync)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getHubId() {
|
||||||
|
return state.hubId ? state.hubId : location.hubs[0].id
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
def addgateway() {
|
||||||
|
if(!state.gatewayHex) {
|
||||||
|
state.gatewayHex = makeNetworkId(gatewayIP,522)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
def locationHandler(evt) {
|
||||||
|
log.debug "$locationHandler(evt.description)"
|
||||||
|
def description = evt.description
|
||||||
|
def hub = evt?.hubId
|
||||||
|
state.hubId = hub
|
||||||
|
log.debug("location handler: event description is ${description}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
private def parseEventMessage(Map event) {
|
||||||
|
//handles gateway attribute events
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
private def parseEventMessage(String description) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
def doDeviceSync(){
|
||||||
|
log.debug "Doing Platinum Gateway Device Sync!"
|
||||||
|
|
||||||
|
if(!state.subscribe) {
|
||||||
|
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||||
|
state.subscribe = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if(statusURL) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
httpGet(statusURL) { resp ->
|
||||||
|
resp.headers.each {
|
||||||
|
log.debug "${it.name} : ${it.value}"
|
||||||
|
}
|
||||||
|
log.debug "response contentType: ${resp.contentType}"
|
||||||
|
//log.trace "response data: ${resp.data}"
|
||||||
|
if(resp.status == 200) {
|
||||||
|
state.statusText = resp.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
log.error "something went wrong: $e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
def processState(info) {
|
||||||
|
log.debug("processing state...")
|
||||||
|
def DB = ['rooms':[:], 'shades':[:], 'scenes':[:]]
|
||||||
|
def prefix = ""
|
||||||
|
//def lines = info.split(/[\n\r]+/)
|
||||||
|
|
||||||
|
info.eachLine() { line ->
|
||||||
|
line = line.trim()
|
||||||
|
if(!prefix) {
|
||||||
|
prefix = line[0..1]
|
||||||
|
log.debug("prefix is set to ${prefix}")
|
||||||
|
}
|
||||||
|
else if(!line.startsWith(prefix)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line.drop(2)
|
||||||
|
//log.trace("processing line ${line}")
|
||||||
|
if(line.startsWith("\$cr")) {
|
||||||
|
// name of room
|
||||||
|
def room_id = line[3..4]
|
||||||
|
def room_name = line.split('-')[-1].trim()
|
||||||
|
log.debug("found room with ${room_id} and ${room_name}")
|
||||||
|
DB['rooms'][room_id] = ['name':room_name, 'id':room_id, 'search':room_name.toLowerCase()]
|
||||||
|
} else if(line.startsWith("\$cm")) {
|
||||||
|
// name of scene
|
||||||
|
def scene_id = line[3..4]
|
||||||
|
def scene_name = line.split('-')[-1].trim()
|
||||||
|
log.debug("found scene with ${scene_id} and ${scene_name}")
|
||||||
|
DB['scenes'][scene_id] = ['name':scene_name, 'id':scene_id, 'search':scene_name.toLowerCase()]
|
||||||
|
} else if(line.startsWith("\$cs")) {
|
||||||
|
// name of a shade
|
||||||
|
def parts = line.split('-')
|
||||||
|
def shade_id = line[3..4]
|
||||||
|
def shade_name = parts[-1].trim()
|
||||||
|
def room_id = parts[1]
|
||||||
|
log.debug("found shade with ${shade_id} and ${shade_name}")
|
||||||
|
DB['shades'][shade_id] = ['name':shade_name, 'id':shade_id, 'search':shade_name.toLowerCase(), 'room': room_id]
|
||||||
|
} else if(line.startsWith("\$cp")) {
|
||||||
|
// state of a shade
|
||||||
|
def shade_id = line[3..4]
|
||||||
|
def stateTxt = line[-4..-2]
|
||||||
|
def state = stateTxt.toInteger()/255.0
|
||||||
|
log.debug("found shade state with ${shade_id} and ${state}")
|
||||||
|
def shade = DB['shades'][shade_id]
|
||||||
|
if(shade) {
|
||||||
|
DB['shades'][shade_id]['state'] = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("DB is ${DB}")
|
||||||
|
return DB
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////
|
||||||
|
//CHILD DEVICE METHODS
|
||||||
|
/////////////////////////////////////
|
||||||
|
def parse(childDevice, description) {
|
||||||
|
def parsedEvent = parseEventMessage(description)
|
||||||
|
|
||||||
|
if (parsedEvent.headers && parsedEvent.body) {
|
||||||
|
def headerString = new String(parsedEvent.headers.decodeBase64())
|
||||||
|
def bodyString = new String(parsedEvent.body.decodeBase64())
|
||||||
|
log.debug "parse() - ${bodyString}"
|
||||||
|
} else {
|
||||||
|
log.debug "parse - got something other than headers,body..."
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def sendMessage(params) {
|
||||||
|
def newDNI = state.gatewayHex
|
||||||
|
if(newDNI) {
|
||||||
|
log.debug("sending ${params.msg} to ${newDNI}")
|
||||||
|
def ha = new physicalgraph.device.HubAction(params.msg,physicalgraph.device.Protocol.LAN, newDNI)
|
||||||
|
sendHubCommand(ha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
def runScene(sceneID) {
|
||||||
|
log.debug "Running Scene ${sceneID}"
|
||||||
|
sceneID = String.format('%02d',sceneID.toInteger())
|
||||||
|
def msg = "\$inm${sceneID}-"
|
||||||
|
sendMessage(["msg":msg])
|
||||||
|
}
|
||||||
|
|
||||||
|
def setShadeLevel(shadeNo, percent) {
|
||||||
|
log.debug "Setting Shade level on Shade ${shadeNo} to ${percent}%"
|
||||||
|
def shadeValue = 255 - (percent * 2.55).toInteger()
|
||||||
|
log.debug "Setting Shade level on Shade ${shadeNo} to ${shadeValue} value"
|
||||||
|
def msg = String.format("\$pss%s-04-%03d",shadeNo,shadeValue)
|
||||||
|
sendMessage(["msg":msg])
|
||||||
|
runIn(1, "sendMessage", [overwrite: false, data:["msg":"\$rls"]])
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateScenes(DB) {
|
||||||
|
log.debug("Updating Scenes...")
|
||||||
|
if(!state.scenes) {
|
||||||
|
state.scenes = [:]
|
||||||
|
}
|
||||||
|
state.scenes.each() { id, sceneDevice ->
|
||||||
|
if(DB['scenes'][id]) {
|
||||||
|
// update device
|
||||||
|
if(DB['scenes'][id]['name'] != sceneDevice.label) {
|
||||||
|
log.debug("processing scene ${id} from name ${sceneDevice.label} to ${DB['scenes'][id]['name']}")
|
||||||
|
sceneDevice.sendEvent(name:'label', value: DB['scenes'][id]['name'], isStateChange: true)
|
||||||
|
}
|
||||||
|
DB['scenes'].remove(id)
|
||||||
|
} else {
|
||||||
|
// remove device
|
||||||
|
log.debug("removing scene ${id} from name ${sceneDevice.displayName}")
|
||||||
|
deleteChildDevice(sceneDevice.deviceNetworkId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def namePrefix = scenePrefix
|
||||||
|
if(namePrefix) {
|
||||||
|
namePrefix = namePrefix.trim()+" "
|
||||||
|
}
|
||||||
|
DB['scenes']?.each() { id, sceneMap ->
|
||||||
|
def name = sceneMap['name']
|
||||||
|
log.debug("processing scene ${id} with name ${name}")
|
||||||
|
def PREFIX = "PLATINUMGATEWAYSCENE"
|
||||||
|
def hubId = getHubId()
|
||||||
|
def sceneDevice = addChildDevice("schwark", "Platinum Gateway Scene Switch", "${PREFIX}${id}", hubId, ["name": "PlatinumScene.${id}", "label": "${namePrefix}${name}", "completedSetup": true])
|
||||||
|
log.debug("created child device ${PREFIX}${id} for scene ${id} with name ${name} and hub ${hubId}")
|
||||||
|
sceneDevice.setSceneNo(id)
|
||||||
|
state.scenes[id] = sceneDevice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateShades(DB) {
|
||||||
|
if(!wantShades) return
|
||||||
|
log.debug("Updating Shades...")
|
||||||
|
|
||||||
|
if(!state.shades) {
|
||||||
|
state.shades = [:]
|
||||||
|
}
|
||||||
|
state.shades.each() { id, shadeDevice ->
|
||||||
|
if(DB['shades'][id]) {
|
||||||
|
// update device
|
||||||
|
if(DB['shades'][id]['name'] != shadeDevice.label) {
|
||||||
|
log.debug("processing shade rename ${id} from name ${shadeDevice.label} to ${DB['shades'][id]['name']}")
|
||||||
|
shadeDevice.sendEvent(name:'label', value: DB['shades'][id]['name'], isStateChange: true)
|
||||||
|
}
|
||||||
|
DB['shades'].remove(id)
|
||||||
|
} else {
|
||||||
|
// remove device
|
||||||
|
log.debug("removing shade ${id} from name ${shadeDevice.displayName}")
|
||||||
|
deleteChildDevice(shadeDevice.deviceNetworkId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def namePrefix = shadePrefix
|
||||||
|
if(namePrefix) {
|
||||||
|
namePrefix = namePrefix.trim()+" "
|
||||||
|
}
|
||||||
|
DB['shades']?.each() { id, shadeMap ->
|
||||||
|
def name = shadeMap['name']
|
||||||
|
log.debug("processing shade ${id} with name ${name}")
|
||||||
|
def PREFIX = "PLATINUMGATEWAYSHADE"
|
||||||
|
def hubId = getHubId()
|
||||||
|
def shadeDevice = addChildDevice("schwark", "Platinum Gateway Shade Switch", "${PREFIX}${id}", hubId, ["name": "PlatinumShade.${id}", "label": "${namePrefix}${name}", "completedSetup": true])
|
||||||
|
log.debug("created child device ${PREFIX}${id} for shade ${id} with name ${name} and hub ${hubId}")
|
||||||
|
shadeDevice.setShadeNo(id)
|
||||||
|
state.shades[id] = shadeDevice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateStatus() {
|
||||||
|
if(!state.statusText) {
|
||||||
|
log.debug("statusText is empty - ${state.statusText}.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.debug ("Updating status")
|
||||||
|
|
||||||
|
def DB = processState(state.statusText)
|
||||||
|
updateScenes(DB)
|
||||||
|
updateShades(DB)
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer convertHexToInt(hex) {
|
||||||
|
Integer.parseInt(hex,16)
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertHexToIP(hex) {
|
||||||
|
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean canInstallLabs()
|
||||||
|
{
|
||||||
|
return hasAllHubsOver("000.011.00603")
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean hasAllHubsOver(String desiredFirmware)
|
||||||
|
{
|
||||||
|
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
||||||
|
}
|
||||||
|
|
||||||
|
private List getRealHubFirmwareVersions()
|
||||||
|
{
|
||||||
|
return location.hubs*.firmwareVersionString.findAll { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeChildDevices(data) {
|
||||||
|
data.delete.each {
|
||||||
|
deleteChildDevice(it.deviceNetworkId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
|
||||||
@@ -678,6 +678,8 @@ def generateInitialEvent (member, childDevice) {
|
|||||||
|
|
||||||
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}"
|
||||||
|
|
||||||
// def childDevice2 = getChildDevice("${app.id}.${member.id}")
|
// def childDevice2 = getChildDevice("${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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user