Hello all, building on the original work by @andywebs - see thread at
I had a conversation with chatGPT about making the rotation work - essentially I have it move up/down at a quasi fixed value which you can adjust to your liking - Also I recommend hardcoding one of the buttons to reset the level to 0 (or mute if you are using it as a volume control as I am)
/*
Philips Hue Tap Dial
2022-11-11 A.Webster
-First WIP
- 1.03 - fixed configure/registration
- v3 - Fixed level change based on rotation
*/
metadata {
definition(name: "Philips Hue Tap Dial v3", namespace: "boundry", author: "Boris Tsipenyuk", component: true) {
capability "Refresh"
capability "Actuator"
capability "Configuration"
capability "PushableButton"
capability "HoldableButton"
capability "SwitchLevel"
capability "ChangeLevel"
command "push", ["NUMBER"]
command "hold", ["NUMBER"]
fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0003,FC00,1000", outClusters:"0019,0000,0003,0004,0006,0008,0005,1000", model:"RDM002", manufacturer:"Signify Netherlands B.V."
}
preferences {
input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
input name: "txtEnable", type: "bool", title: "Enable descriptionText logging", defaultValue: true
}
}
void updated() {
log.info "Updated..."
log.warn "description logging is: ${txtEnable == true}"
}
void installed() {
log.info "Installed..."
device.updateSetting("txtEnable",[type:"bool",value:true])
refresh()
}
void parse(List description) { log.warn "parse(List description) not implemented" }
// raw, clusterId, sourceEndpoint, destinationEndpoint, options, messageType, dni, isClusterSpecific,
// isManufacturerSpecific, manufacturerId, command, direction, data
def parse(String description) {
if (logEnable) log.debug "parse description: ${description}"
def descMap = zigbee.parseDescriptionAsMap(description)
def descriptionText
def rawValue = descMap.raw
def value = descMap.command
def data = descMap.data
switch (descMap.clusterInt){
case 5:
//if (logEnable) log.debug "Case 5: ${descMap.clusterInt}:${descMap.attrInt}${descMap.attrId} Value:${value} Data:${data}"
switch(data[2]) {
case "00":
push(2)
//if (logEnable) log.debug "Button 2"
break
case "01":
push(1)
//if (logEnable) log.debug "Button 1"
break
case "04":
push(4)
//if (logEnable) log.debug "Button 4"
break
case "05":
push(3)
//if (logEnable) log.debug "Button 3"
break
default:
if (logEnable) log.debug "Cluster 5: Unknown Button. Value:${value} Data:${data}"
break
}
break
case 8:
if (logEnable) log.debug "Case 8: ${descMap.clusterInt}: Value:${value} Data:${data}"
if(data != null && data[1] != null)
{
if (data[1] == "FF")
{
// For now, any held button sends out this command
hold(5)
return
}
else if(data[2] != null)
{
speed1 = Integer.parseInt(data[2],16)
speed2 = Integer.parseInt(data[1],16)
//speed3 = ((speed1 -4) * 16 + speed2) / 8
speed3 = speed1 * 16 + speed2
int speed4 = ((speed3 - 72) / 6) + 1
if(speed4 > 1)
{
speed = speed4 * (speed4 - 1)
}
else
{
speed = 1
}
// Dial
switch(data[0]) {
case "00":
// Increase
dialClockwise(speed)
return
//if (logEnable) log.debug "Increase ${speed} ( ${speed3} ${speed1} )"
break
case "01":
// Decrease
dialCounterclockwise(speed)
return
//if (logEnable) log.debug "Decrease ${speed} ( ${speed3} ${speed1} )"
break
}
}
}
break
default:
if (logEnable) log.debug "${descMap.clusterInt}:${descMap.attrInt}:${descMap.attrId}${rawValue}"
break
}
if (descriptionText){
if (txtEnable) log.info "${descriptionText}"
sendEvent(name:"Undefined",value:value,descriptionText:descriptionText)
}
}
void push(button){
sendButtonEvent("pushed", button, "physical")
}
void hold(button){
sendButtonEvent("held", button, "physical")
}
// Possibly do this programatically
//void doubleTap(button){
// sendButtonEvent("doubleTapped", button, "digital")
//}
void dialClockwise(number){
String descriptionText = "${device.displayName} [Clockwise]"
if (txtEnable) log.info descriptionText
adjustLevel(10) // Move level up by 10
}
void dialCounterclockwise(number){
String descriptionText = "${device.displayName} [CounterClockwise]"
if (txtEnable) log.info descriptionText
adjustLevel(-10) // Move level down by 10
}
void adjustLevel(int change){
def currentLevel = device.currentValue("level").toInteger()
int newLevel = currentLevel + change
// Ensure new level is within bounds (0-100)
newLevel = Math.max(0, Math.min(100, newLevel))
if (logEnable) log.debug "Adjusting level from ${currentLevel} to ${newLevel} by a fixed value of ${change}"
setLevel(newLevel) // Directly use the new level as it's already an integer
}
void dial(number){
def dimmer = device.currentValue("level")
if (dimmer == null) {
dimmer = 0
}
if (level == null) {
level = 0
}
log.debug "${level} ${number} ${dimmer}"
if (dimmer != null) {
if (logEnable) log.debug "Dial Value from: ${dimmer} by ${number}"
// Assuming setLevel expects an Integer, you might need to convert dimmer to Integer explicitly
setLevel((dimmer as Integer) + number)
} else {
if (logEnable) log.warn "Current value of 'level' is null"
// You might want to handle this case according to your requirements
}
}
void sendButtonEvent(action, button, type){
String descriptionText = "${device.displayName} button ${button} was ${action} [${type}]"
if (txtEnable) log.info descriptionText
sendEvent(name:action, value:button, descriptionText:descriptionText, isStateChange:true, type:type)
}
def setLevel(value, rate = null) {
if (value == null) return
Integer level = limitIntegerRange(value,0,100)
if (level == 0) {
sendEvent(name:"level", value:level, descriptionText:verb, isStateChange:true)
off()
return
}
if (device.currentValue("switch") != "on") on()
sendEvent(name:"level", value:level, descriptionText:"set to", isStateChange:true)
}
def configure(){
log.warn "configure..."
//runIn(1800,logsOff)
sendEvent(name: "numberOfButtons", value: 5)
sendEvent(name: "pushed", value: 1)
sendEvent(name: "level", value: 0)
sendEvent(name: "held", value: 5)
state."${1}" = 0
state."${2}" = 0
runIn(5, "refresh")
def cmds = [
//bindings
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 0x01 0x0006 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 0x01 0x0008 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 0x01 0x0005 {${device.zigbeeId}} {}", "delay 200",
//reporting
"he cr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x0006 0 0x10 0 0xFFFF {}","delay 200",
"he cr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x0008 0 0x20 0 0xFFFF {}", "delay 200",
"he cr 0x${device.deviceNetworkId} 0x${device.endpointId} 0x0005 0 0x30 0 0xFFFF {}", "delay 200",
] + refresh()
return cmds
}
void on() {
parent?.componentOn(this.device)
}
void off() {
parent?.componentOff(this.device)
}
void refresh() {
parent?.componentRefresh(this.device)
}
def logsOff(){
log.warn "debug logging disabled..."
device.updateSetting("logEnable",[value:"false",type:"bool"])
}
Integer limitIntegerRange(value,min,max) {
Integer limit = value.toInteger()
return (limit < min) ? min : (limit > max) ? max : limit
}