[edit] Just realized that the info below is mostly relevant if you are using the classic app, with the new app it seems to recognize all the buttons appropriately without renaming them to 1/2/3/4 and then you can also map the ‘held’ command and whatnot! [/edit]
Hi All,
Pretty new to smart things and coming from a Hue only setup I had 2 switches laying around that I wanted to use. Found this script and noticed it all connected just fine and reported things neatly in the app but my log started throwing errors when I actually tried to hook it up with the smart lighting app.
After some digging I noticed it was trying to parse the on/up/down/off as a number, and since I noticed the automation refers to the buttons as numbers I tried to change what was passed on as an event. Long story short this fixed my issue! And it’s now working as intended. Here is the updated DH if anyone is interested. Also thanks a lot to the original creators!
/**
* Hue Dimmer Switch
*
* Copyright 2016 Stephen McLaughlin
*
* 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: "Hue Dimmer Switch (ZHA)", namespace: "digitalgecko", author: "Stephen McLaughlin") {
capability "Configuration"
capability "Battery"
capability "Refresh"
capability "Button"
capability "Sensor"
fingerprint profileId: "0104", endpointId: "02", application: "02", outClusters: "0019", inClusters: "0000,0001,0003,000F,FC00", manufacturer: "Philips", model: "RWL020", deviceJoinName: "Hue Dimmer Switch (ZHA)"
attribute "lastAction", "string"
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
// TODO: define your main and details tiles here
multiAttributeTile(name: "lastAction", type: "generic", width: 6, height: 4) {
tileAttribute("device.battery", key: "SECONDARY_CONTROL") {
attributeState "battery", label: '${currentValue}% battery', icon: "st.Outdoor.outdoor3", unit: "", backgroundColors: [
[value: 30, color: "#ff0000"],
[value: 40, color: "#760000"],
[value: 60, color: "#ff9900"],
[value: 80, color: "#007600"]
]
}
tileAttribute("device.lastAction", key: "PRIMARY_CONTROL") {
attributeState "active", label: '${currentValue}', icon: "st.Home.home30"
}
}
// valueTile("lastAction", "device.lastAction", width: 6, height: 2) {
// state("lastAction", label:'${currentValue}')
// }
valueTile("battery2", "device.battery", decoration: "flat", inactiveLabel: false, width: 5, height: 1) {
state("battery", label: '${currentValue}% battery', unit: "")
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
state "default", label: "", action: "refresh.refresh", icon: "st.secondary.refresh"
}
// standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
// state "default", label:"bind", action:"configure"
// }
}
main "lastAction"
details(["lastAction", "battery2", "refresh", "configure"])
}
// parse events into attributes
def parse(String description) {
def msg = zigbee.parse(description)
//log.warn msg
/// Actual code down here
List map = []
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
def result = map ? map : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
} else if (description?.startsWith('read attr -')) {
result = parseReportAttributeMessage(description).each { createEvent(it) }
}
return result
// TODO: handle 'numberOfButtons' attribute
}
/*
parseReportAttributeMessage
*/
private List parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
}
List result = []
// Battery
if (descMap.cluster == "0001" && descMap.attrId == "0020") {
// log.warn descMap
result << getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return result
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
/*
getBatteryResult
*/
//TODO: needs calibration
private Map getBatteryResult(rawValue) {
//log.debug "Battery rawValue = ${rawValue}"
def result = [
name : 'battery',
value : '--',
translatable: true
]
def volts = rawValue / 10
if (rawValue == 0 || rawValue == 255) {
} else {
if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
} else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28: 100, 27: 100, 26: 100, 25: 90, 24: 90, 23: 70,
22: 70, 21: 50, 20: 50, 19: 30, 18: 30, 17: 15, 16: 1, 15: 0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
} 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 = "{{ device.displayName }} battery was {{ value }}%"
}
}
}
return result
}
private List getButtonResult(rawValue) {
def result
def button = rawValue[0]
def buttonState = rawValue[4]
def buttonHoldTime = rawValue[6]
def hueStatus = (button as String) + "00" + (buttonState as String)
// This is the state in the HUE api
log.error "Button: " + button + " Hue Code: " + hueStatus + " Hold Time: " + buttonHoldTime + " Button State: " + buttonState
// result.data = ['buttonNumber': button]
def buttonName
// Name of the button
if (button == 1) {
buttonName = "1"
} else if (button == 2) {
buttonName = "2"
} else if (button == 3) {
buttonName = "3"
} else if (button == 4) {
buttonName = "4"
}
// The button is pressed, aka: pushed + released, with 0 hold time
if (buttonState == 0) {
result = [createEvent(name: "button", value: "pressed", data: [buttonNumber: buttonName], descriptionText: "$device.displayName button $button was pushed", isStateChange: true)]
sendEvent(name: "lastAction", value: buttonName + " pressed")
}
// The button is pressed, aka: pushed + released, with at least 1s hold time
else if (buttonState == 2) {
result = [
createEvent(name: "button", value: "pushed", data: [buttonNumber: buttonName], descriptionText: "$device.displayName button $button was pushed", isStateChange: true),
createEvent(name: "button", value: "released", data: [buttonNumber: buttonName], descriptionText: "$device.displayName button $button was pushed", isStateChange: true)
]
sendEvent(name: "lastAction", value: buttonName + " pushed")
sendEvent(name: "lastAction", value: buttonName + " released")
}
// The button is released, with at least 1s hold time. This code happens after the button is held
else if (buttonState == 3) {
result = [
createEvent(name: "button", value: "released", data: [buttonNumber: buttonName], descriptionText: "$device.displayName button $button was pushed", isStateChange: true)
]
sendEvent(name: "lastAction", value: buttonName + " released")
}
// The button is held
else if (buttonHoldTime == 8) {
result = [createEvent(name: "button", value: "held", data: [buttonNumber: buttonName], descriptionText: "$device.displayName button $button was held", isStateChange: true)]
sendEvent(name: "lastAction", value: buttonName + " held")
} else {
return
}
return result
}
/*
parseCatchAllMessage
*/
private List parseCatchAllMessage(String description) {
List resultMap = []
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch (cluster.clusterId) {
case 0x0001:
// 0x07 - configure reporting
if (cluster.command != 0x07) {
resultMap = [getBatteryResult(cluster.data.last())]
}
break
case 0xFC00:
if (cluster.command == 0x00) {
resultMap = getButtonResult(cluster.data);
}
break
}
}
return resultMap
}
def refresh() {
//log.debug "Refresh"
def refreshCmds = []
refreshCmds += "st rattr 0x${device.deviceNetworkId} 0x02 0x0001 0x0020";
// WORKS! - Fetches battery from 0x02
// configCmds += zigbee.configureReporting(0x406,0x0000, 0x18, 30, 600, null) // motion // confirmed
//refreshCmds += zigbee.configureReporting(0x000F, 0x0055, 0x10, 30, 30, null);
// refreshCmds += "zdo bind 0xDAD6 0x01 0x02 0x000F {00178801103317AA} {}"
// refreshCmds += "delay 2000"
// refreshCmds += "st cr 0xDAD6 0x02 0x000F 0x0055 0x10 0x001E 0x001E {}"
// refreshCmds += "delay 2000"
//refreshCmds += zigbee.configureReporting(0x000F, 0x006F, 0x18, 0x30, 0x30);
// refreshCmds += "zdo bind 0x${device.deviceNetworkId} 0x02 0x02 0xFC00 {${device.zigbeeId}} {}"
// refreshCmds += "delay 2000"
// refreshCmds += "st cr 0x${device.deviceNetworkId} 0x02 0xFC00 0x0000 0x18 0x001E 0x001E {}"
// refreshCmds += "delay 2000"
//log.debug refreshCmds
return refreshCmds
}
def configure() {
// String zigbeeId = swapEndianHex(device.hub.zigbeeId)
//log.debug "Configiring Reporting and Bindings."
def configCmds = []
// Configure Button Count
sendEvent(name: "numberOfButtons", value: 4, displayed: false)
// Monitor Buttons
//TODO: This could be zigbee.configureReporting(0xFC00, 0x0000, 0x18, 0x001e, 0x001e); but no idea how to point it at a different endpoint
configCmds += "zdo bind 0x${device.deviceNetworkId} 0x02 0x02 0xFC00 {${device.zigbeeId}} {}"
configCmds += "delay 2000"
configCmds += "st cr 0x${device.deviceNetworkId} 0x02 0xFC00 0x0000 0x18 0x001E 0x001E {}"
configCmds += "delay 2000"
// Monitor Battery
//TODO: This could be zigbee.batteryConfig(); but no idea how to point it at a different endpoint
configCmds += "zdo bind 0x${device.deviceNetworkId} 0x02 0x02 0x0001 {${device.zigbeeId}} {}"
configCmds += "delay 2000"
configCmds += "st cr 0x${device.deviceNetworkId} 0x02 0x0001 0x0020 0x20 0x001E 0x0258 {}"
// configCmds += "st cr 0x${device.deviceNetworkId} 0x02 0x0001 0x0020 0x20 0x001E 0x001e {}"
configCmds += "delay 2000"
return configCmds + refresh()
}
def configureHealthCheck() {
Integer hcIntervalMinutes = 12
refresh()
sendEvent(name: "checkInterval", value: hcIntervalMinutes * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
def updated() {
// log.debug "in updated()"
configureHealthCheck()
}