Compare commits

..

9 Commits

Author SHA1 Message Date
Jungle
5dedaf2eb6 MSA-1534: Leedarson CCT Bulb 2016-10-17 22:25:42 -05:00
Zach Varberg
723ef7e7e6 Merge pull request #1343 from varzac/fix-button-zone-status
[DPROT-180] Correctly parse zone status in zigbee-button
2016-10-14 09:28:17 -05:00
Zach Varberg
84c72de640 Merge pull request #1345 from varzac/handle-both-mfg-temp-humidity
[DPROT-183] Handle both mfgIDs for temp-humidity sensors
2016-10-14 09:26:01 -05:00
Zach Varberg
570454e6c3 Merge pull request #1341 from varzac/swap-refresh-configure-order
[DPROT-177] Call refresh before configure
2016-10-14 09:25:44 -05:00
Zach Varberg
a5d95fb025 Correctly parse zone status in zigbee-button
Previously the DTH didn't handle the extended status from zone status
reporting.  This moves to use the library for parsing zone status
which will handle the extended status properly.

This resolves: https://smartthings.atlassian.net/browse/DPROT-180
2016-10-14 09:09:04 -05:00
Zach Varberg
50696902cf Handle both mfgIDs for temp-humidity sensors
With upcoming OTA support for ZigBee end devices centralite devices
change manufacturer IDs between versions.  As a result when reading a
manufacturer specific value, we have to handle the possibility of either
mfgID being correct.  The simplest solution (and the one employed here)
was to simply read the attribute using both mfgIDs and ignoring null
responses.

This resolves: https://smartthings.atlassian.net/browse/DPROT-183
2016-10-13 13:28:50 -05:00
Zach Varberg
409658e899 Call refresh before configure
With the changes in https://smartthings.atlassian.net/browse/DPROT-168
it can take a little longer for the configuration process for a device
to complete.  As a result sensor values may not be available immediately
by calling the refresh commands before the configure commands we can
populate those values sooner.

This resolves: https://smartthings.atlassian.net/browse/DPROT-177
2016-10-13 09:51:22 -05:00
Vinay Rao
1068a553f5 Merge pull request #1339 from workingmonk/feature/rgb_dth
DVCSMP-1447 Support for RGB ZigBee DTH
2016-10-12 12:40:20 -07:00
Vinay Rao
bbdf9ff02a DVCSMP-1447 Support for RGB ZigBee DTH 2016-10-12 12:39:54 -07:00
24 changed files with 597 additions and 111 deletions

View File

@@ -0,0 +1,267 @@
/**
* Leedarson Light
*
* Copyright 2016 Jungle
*
* 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.
*
*/
/*
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: "LEEDARSON LED Tunable White", namespace: "smartthings", author: "Yechi") {
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..5000)") {
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*23) + 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
}

View File

@@ -21,9 +21,6 @@ metadata {
attribute "colorName", "string"
command "setAdjustedColor"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
}
// simulator metadata

View File

@@ -133,7 +133,7 @@ def refresh() {
}
def configure() {
onOffConfig() + levelConfig() + powerConfig() + refresh()
refresh() + onOffConfig() + levelConfig() + powerConfig()
}

View File

@@ -143,10 +143,10 @@ def refresh() {
def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()
}
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)

View File

@@ -308,16 +308,9 @@ def configure() {
// 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])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -323,16 +323,9 @@ def configure() {
// 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])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -255,13 +255,9 @@ def refresh() {
}
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
@@ -270,7 +266,7 @@ def configure() {
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
return refresh() + configCmds // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -420,15 +420,14 @@ def configure() {
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
def configCmds = enrollResponse() +
zigbee.batteryConfig() +
def configCmds = zigbee.batteryConfig() +
zigbee.temperatureConfig(30, 300) +
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
return configCmds + refresh()
return refresh() + configCmds
}
private getEndpointId() {

View File

@@ -277,12 +277,8 @@ def getTemperature(value) {
def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
@@ -295,7 +291,7 @@ def configure() {
"zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
return refresh() + configCmds // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -270,16 +270,11 @@ def configure() {
// 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])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
}
def enrollResponse() {

View File

@@ -250,11 +250,7 @@ private Map getTemperatureResult(value) {
private Map getHumidityResult(value) {
log.debug 'Humidity'
return [
name: 'humidity',
value: value,
unit: '%'
]
return value ? [name: 'humidity', value: value, unit: '%'] : [:]
}
/**
@@ -267,15 +263,10 @@ def ping() {
def refresh()
{
log.debug "refresh temperature, humidity, and battery"
[
"zcl mfg-code 0xC2DF", "delay 1000",
"zcl global read 0xFC45 0", "delay 1000",
"send 0x${device.deviceNetworkId} 1 1", "delay 1000",
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
]
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware
zigbee.readAttribute(0x0402, 0x0000) +
zigbee.readAttribute(0x0001, 0x0020)
}
def configure() {
@@ -292,7 +283,7 @@ def configure() {
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default
return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
}
private hex(value) {

View File

@@ -89,14 +89,8 @@ def parse(String description) {
}
private Map parseIasButtonMessage(String description) {
int zoneInt = Integer.parseInt((description - "zone status 0x"), 16)
if (zoneInt & 0x02) {
resultMap = getButtonResult('press')
} else {
resultMap = getButtonResult('release')
}
return resultMap
def zs = zigbee.parseZoneStatus(description)
return zs.isAlarm2Set() ? getButtonResult("press") : getButtonResult("release")
}
private Map getBatteryResult(rawValue) {

View File

@@ -93,5 +93,5 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
refresh()
}

View File

@@ -104,8 +104,8 @@ def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
}

View File

@@ -90,7 +90,7 @@ def configure() {
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
TYPE_U8, 600, 21600, 0x01)
log.info "configure() --- cmds: $cmds"
return cmds + refresh() // send refresh cmds as part of config
return refresh() + cmds // send refresh cmds as part of config
}
def refresh() {

View File

@@ -0,0 +1,154 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Author: SmartThings
* Date: 2016-01-19
*
* This DTH should serve as the generic DTH to handle RGB ZigBee HA devices (For color bulbs with no color temperature)
*/
metadata {
definition (name: "ZigBee RGB Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Control"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
}
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"color control.setColor"
}
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "refresh"])
}
}
//Globals
private getATTRIBUTE_HUE() { 0x0000 }
private getATTRIBUTE_SATURATION() { 0x0001 }
private getHUE_COMMAND() { 0x00 }
private getSATURATION_COMMAND() { 0x03 }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
log.debug event
if (event.name=="level" && event.value==0) {}
else {
sendEvent(event)
}
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
def cluster = zigbee.parse(description)
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
}
}
else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "ON/OFF REPORTING CONFIG RESPONSE: $cluster"
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.info "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbeeMap
}
}
}
def on() {
zigbee.on()
}
def off() {
zigbee.off()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.onOffRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}
def setLevel(value) {
zigbee.setLevel(value)
}
def setColor(value){
log.trace "setColor($value)"
zigbee.on() + setHue(value.hue) + "delay 500" + setSaturation(value.saturation)
}
def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
}
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}

View File

@@ -138,17 +138,17 @@ def ping() {
}
def refresh() {
zigbee.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
refresh()
}
def setColorTemperature(value) {

View File

@@ -82,5 +82,5 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
refresh()
}

View File

@@ -78,5 +78,5 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.onOffRefresh()
zigbee.onOffRefresh() + zigbee.onOffConfig()
}

View File

@@ -134,10 +134,5 @@ def refresh() {
def configure() {
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() +
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1) +
zigbee.onOffRefresh() +
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
refresh()
}

View File

@@ -37,6 +37,7 @@ metadata {
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: "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: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
fingerprint profileId: "0104", deviceId: "0102", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019", manufacturer: "Eaton", model: "Halo_LT01", deviceJoinName: "Halo_LT01"
}
// UI tile definitions
@@ -128,10 +129,10 @@ def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
refresh()
}
def setColorTemperature(value) {
@@ -154,4 +155,4 @@ def setGenericName(value){
}
sendEvent(name: "colorName", value: genericName)
}
}
}

View File

@@ -0,0 +1,134 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "ZLL RGB Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Control"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Switch"
capability "Switch Level"
}
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"color control.setColor"
}
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "refresh"])
}
}
//Globals
private getATTRIBUTE_HUE() { 0x0000 }
private getATTRIBUTE_SATURATION() { 0x0001 }
private getHUE_COMMAND() { 0x00 }
private getSATURATION_COMMAND() { 0x03 }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = zigbee.getEvent(description)
if (finalResult) {
log.debug finalResult
sendEvent(finalResult)
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
log.trace "zigbeeMap : $zigbeeMap"
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else {
log.info "DID NOT PARSE MESSAGE for description : $description"
}
}
}
def on() {
zigbee.on() + ["delay 1500"] + zigbee.onOffRefresh()
}
def off() {
zigbee.off() + ["delay 1500"] + zigbee.onOffRefresh()
}
def refresh() {
refreshAttributes() + configureAttributes()
}
def poll() {
refreshAttributes()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
configureAttributes() + refreshAttributes()
}
def configureAttributes() {
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def refreshAttributes() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}
def setLevel(value) {
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def setColor(value){
log.trace "setColor($value)"
zigbee.on() + setHue(value.hue) + ["delay 300"] + setSaturation(value.saturation) + ["delay 2000"] + refreshAttributes()
}
def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
}
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time
}

View File

@@ -123,7 +123,7 @@ def configureAttributes() {
}
def refreshAttributes() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION)
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}
def setColorTemperature(value) {
@@ -141,10 +141,10 @@ def setColor(value){
def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
}
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time
}

View File

@@ -37,10 +37,7 @@ preferences {
def mainPage() {
def bridges = bridgesDiscovered()
if (state.refreshUsernameNeeded) {
return bridgeLinking()
} else if (state.username && bridges) {
if (state.username && bridges) {
return bulbDiscovery()
} else {
return bridgeDiscovery()
@@ -105,22 +102,13 @@ def bridgeLinking() {
def nextPage = ""
def title = "Linking with your Hue"
def paragraphText
def paragraphText
if (selectedHue) {
if (state.refreshUsernameNeeded) {
paragraphText = "The current Hue username is invalid.\n\nPlease press the button on your Hue Bridge to re-link. "
} 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."
}
} 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.refreshUsernameNeeded) {
state.refreshUsernameNeeded = false
// Issue one poll with new username to cancel local polling with old username
poll()
}
nextPage = "bulbDiscovery"
title = "Success!"
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
@@ -927,15 +915,8 @@ private handleCommandResponse(body) {
updates[childDeviceNetworkId]."$eventType" = v
}
}
} else if (payload?.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 []
} else if (payload.error) {
log.warn "Error returned from Hue bridge error = ${body?.error}"
}
}