Hi, I'd like to make a custom driver for my "Generic Multi-Endpoint Switch" devices that makes them work as PushableButtons. This will let me use them in lots of apps like Room Lighting where a button can be easier than a switch as its on/off state isn't important. Most of my switches are detached from the light as the lights are Zigbee CT and RGBW fittings.
I've done this with some of the Tuya switches I have by modifying the community driver, but I haven't found the code for the generic one. Is there something else around I can work with?
I'm pretty green to groovy coding so I don't have the skills to carve it from scratch.
Here is the modified code I've done for the Tuya ones, the bits I've changed have been tagged with "// LS"
Thanks to @kkossev and others for the original. If you're keen I'm happy to merge my changes into your project, but not sure where to start.
/* groovylint-disable BuilderMethodWithSideEffects, CompileStatic, DuplicateListLiteral, DuplicateMapLiteral, DuplicateNumberLiteral, DuplicateStringLiteral, FactoryMethodName, ImplicitClosureParameter, ImplicitReturnStatement, LineLength, MethodParameterTypeRequired, MethodReturnTypeRequired, NestedBlockDepth, NoDef, NoJavaUtilDate, PublicMethodsBeforeNonPublicMethods, UnnecessaryGetter, VariableTypeRequired */
/*
* revision 1.0.0 - 2021-05-25 - martinkura - latest original driver version update
* revision 1.0.1 - 2022-02-22 - kkossev - added Moes 4-Gang Switch / ZTS-EU4
* revision 1.0.2 - 2022-02-27 - kkossev - added more Tuya fingerprints for 1,2,3 and 4 gangs TS0601 wall switches
* revision 1.0.3 - 2022-09-26 - kkossev - added Zemismart 6 Gangs Wall Light Switch
* revision 1.0.4 - 2022-10-12 - kkossev - _TZE200_tz32mtza bug fix; code cleanup
* revision 1.0.5 - 2023-03-16 - kkossev - added OZ Smart 1-2-3-4 gang switches _TZE200_gbagoilo _TZE200_nh9m9emk _TZE200_go3tvswy _TZE200_mexisfik
* revision 1.0.6 - 2023-04-24 - kkossev - added importUrl; _TZE200_aqnazj70 _TZE200_wunufsil _TZE200_oisqyl4o _TZE200_atpwqgml
* revision 1.0.7 - 2023-07-18 - kkossev - added _TZE200_7deq70b8 (@pabutterworth)
* revision 1.0.8 - 2023-11-20 - kkossev - added TS0601 _TZE204_dqolcpcp (@alex1) (only the first 6 relays should be working)
* revision 1.0.9 - 2024-01-02 - kkossev - added TS0601 _TZE200_r731zlxk
* revision 1.1.0 - 2024-03-08 - kkossev - Groovy linting; addeed Zemismart 4-gang switch _TZE200_1n2kyphz; _TZE200_shkxsgis; _TZE204_shkxsgis; added testParse; Tuya cluster 0xEF00 data size check;
* revision 1.1.1 - 2024-04-03 - kkossev - added TS0601 _TZE200_m*******j @Joao;
* revision 1.1.2 - 2024-04-28 - kkossev - removed the T3E fingerprint; added _TZE204_aagrxlbd;
* revision 1.1.3 - 2024-05-06 - kkossev - added _TZE204_xjknlqz8
* revision 1.1.4 - 2024-05-17 - hhorigian - added _TZE204_4cl0dzt4
*
*/
static String version() { '1.1.4' }
static String timeStamp() { '2024/05/17 6:38 PM' }
import hubitat.device.HubAction
import hubitat.device.Protocol
import groovy.transform.Field
@Field static final Boolean _DEBUG = false
metadata {
definition(name: 'Moes ZigBee Wall Switch 1/2/3-Gang-LS', namespace: 'Lukes 1.31', author: 'Luke Smith', importUrl: '') {
capability 'Initialize'
capability 'Actuator'
capability 'Refresh'
capability 'Switch'
capability 'PushableButton'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_amp6tsvy', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 1-Gang Switch / ZTS-EU1'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_oisqyl4o', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'No Neutral Push Button Light Switch 1 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_g1ib5ldv', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 2-Gang Switch / ZTS-EU2'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_wunufsil', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'No Neutral Push Button Light Switch 2 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_tz32mtza', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 3-Gang Switch / ZTS-EU3'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_atpwqgml', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'No Neutral Push Button Light Switch 3 Gang'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_k6jhsr0q', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 4-Gang Switch / ZTS-EU4'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_aqnazj70', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Touch Switch 4 Gang No Neutral'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_9mahtqtg', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 6 Gangs Wall Light Switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_r731zlxk', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 6 Gangs Wall Light Switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_gbagoilo', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'OZ Smart Single Light Switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_nh9m9emk', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'OZ Smart Double Light Switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_go3tvswy', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'OZ Smart Triple Light Switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_mexisfik', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'46', deviceJoinName: 'OZ Smart Quad Light Switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_7deq70b8', endpointId:'01', inClusters:'0000,0004,0005,EF00', outClusters:'0019,000A', application:'42', deviceJoinName: 'Moes 2-gang switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_dqolcpcp', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 12-way Relay Moduleh'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_1n2kyphz', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 4-gang switch' // https://www.aliexpress.com/item/1005003972289459.html
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_shkxsgis', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 4-gang switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_shkxsgis', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 4-gang switch'
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_aagrxlbd', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 4-gang switch' // @Gabriel
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_xjknlqz8', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Tuya 6-gang switch' // @Vartan BR - TrTRON
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE204_4cl0dzt4', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'Zemismart 6 Gangs Wall Light Switch' // @Vartan BR - QA
fingerprint profileId:'0104', model:'TS0601', manufacturer:'_TZE200_wnp4d4va', endpointId:'01', inClusters:'0004,0005,EF00,0000', outClusters:'0019,000A', application:'42', deviceJoinName: 'LS Ikuu 6 Gang Wall Light Switch'
}
attribute 'switchLightMode', 'enum', ['OFF', 'ON', 'Position']
attribute 'relayMode', 'enum', ['OFF', 'ON', 'Last state']
attribute 'lastCheckin', 'string'
attribute 'buttonMode', 'enum', ['OFF', 'ON']
attribute 'numberOfButtons', 'number'
attribute 'pushed', 'number'
if (_DEBUG == true) {
command 'testParse', [[name:'val', type: 'STRING', description: 'description', constraints: ['STRING']]]
}
preferences {
input(name: 'switchLightMode', type: 'enum', title: ("Switch Backlight Mode"), description: ("- select type of backlight indicator (default: Position)"), options: ['OFF', 'ON', 'Position'], defaultValue: 'Position', submitOnChange: true)
input(name: 'relayMode', type: 'enum', title: ("Switch Relay Mode"), description: ("- select relay renew state after AC failed (default: OFF)"), options: ['OFF', 'ON', 'Last state'], defaultValue: 'OFF', submitOnChange: true)
input(name: 'buttonMode', type: 'enum', title: ("Switch Button Mode"), description: ("- make the switch operate like a button"), options: ['OFF', 'ON'], defaultValue: 'OFF', submitOnChange: true)
input(name: 'debugLogging', type: 'bool', title: ("Enable debug logging"), description: '', defaultValue: true, submitOnChange: true, displayDuringSetup: true)
input(name: 'infoLogging', type: 'bool', title: ("Enable info logging"), description: '', defaultValue: true, submitOnChange: true, displayDuringSetup: true)
}
}
def initialize() {
if (infoLogging) { log.info 'Initializing...' }
log.warn 'Debug logging will be automatically disabled after 30 minutes!'
setupChildDevices()
device.updateSetting('switchLightMode', [type:'enum', value:'Position'])
device.updateSetting('relayMode', [type:'enum', value:'OFF'])
device.updateSetting('buttonMode', [type:'enum', value:'OFF'])
device.updateSetting('debugLogging', [type:'bool', value:'true'])
device.updateSetting('infoLogging', [type:'bool', value:'true'])
if (debugLogging) { runIn(1800, logsOff) }
refresh()
}
void logsOff() {
log.warn 'Debug logging disabled...'
device.updateSetting('debugLogging', [value:'false' ,type:'bool'])
}
def installed() {
log.info 'Installing...'
log.warn 'Debug logging will be automatically disabled after 30 minutes!'
setupChildDevices()
device.updateSetting('switchLightMode', [type:'enum', value:'Position'])
device.updateSetting('relayMode', [type:'enum', value:'OFF'])
device.updateSetting('buttonMode', [type:'enum', value:'OFF'])
device.updateSetting('debugLogging', [type:'bool', value:'true'])
device.updateSetting('infoLogging', [type:'bool', value:'true'])
if (debugLogging) { runIn(1800, logsOff) }
refresh()
}
def updated() {
log.warn "debug logging is: ${debugLogging == true}"
log.warn "description logging is: ${infoLogging == true}"
if (infoLogging) { log.info 'Updated...' }
if (debugLogging) { log.debug 'Parent updated' }
switchLightModeConfig() + relayModeConfig() + refresh()
}
private getCLUSTER_TUYA() { 0xEF00 }
// Parse incoming device messages to generate events
def parse(String description) {
if (debugLogging) { log.debug "description: ${description}" }
if (description?.startsWith('catchall:') || description?.startsWith('read attr -')) {
Map descMap = zigbee.parseDescriptionAsMap(description)
if (descMap?.clusterInt == CLUSTER_TUYA) {
if (debugLogging) { log.debug "descMap: ${descMap}" }
if ((descMap?.command in ['00', '01', '02']) && descMap?.data?.size() >= 7) {
def switchFunc = (descMap?.data[2])
def switchAttr = (descMap?.data[3])
def switchState = (descMap?.data[6]) == '01' ? 'on' : 'off'
// LS verbose logging what these variables are
if (debugLogging) { log.debug "switchFunc: ${switchFunc}" }
if (debugLogging) { log.debug "switchAttr: ${switchAttr}" }
if (debugLogging) { log.debug "switchState: ${switchState}" }
// LS send a button push event for the switch that turned on
if (switchState == 'on') {sendEvent(name: "pushed", value:switchFunc, isStateChange:true)}
if (switchFunc in ['01', '02', '03', '04', '05', '06'] && switchAttr == '01') {
def cd = getChildDevice("${device.id}-${switchFunc}")
// LS if the phys switch turns on, and were in buttonMode, turn the phys switch off again
if (switchState == 'on' && buttonMode == 'ON') {
String fullDataOff = '0001' + getChildId(cd) + '01000100'
sendHubCommand(new HubAction("he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOff}}", Protocol.ZIGBEE))
}
if (cd == null) {
return createEvent(name: 'switch', value: switchState)
}
if (descMap?.command == '00') {
// switch toggled
cd.parse([[name: 'switch', value:switchState, descriptionText: "Child switch ${switchFunc} turned $switchState"]])
}
else if (descMap?.command in ['01', '02']) {
// report switch status
cd.parse([[name: 'switch', value:switchState, descriptionText: "Child switch ${switchFunc} is $switchState"]])
}
if (switchState == 'on') {
if (debugLogging) { log.debug 'Parent Switch ON' }
return createEvent(name: 'switch', value: 'on')
}
else if (switchState == 'off') {
def cdsOn = 0
// cound number of switches on
getChildDevices().each { child ->
if (getChildId(child) != switchFunc && child.currentValue('switch') == 'on') {
cdsOn++
}
}
if (cdsOn == 0) {
if (debugLogging) { log.debug 'Parent Switch OFF' }
return createEvent(name: 'switch', value: 'off')
}
}
}
}
}
}
}
def lastCheckin() { // send event for heartbeat
def now = new Date()
sendEvent(name: 'lastCheckin', value: now)
}
def off() {
if (infoLogging) { log.info 'Turn all switches OFF' }
return [
"he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {0001010100010002010001000301000100}", 'delay 200',
]
}
def on() {
if (infoLogging) { log.info 'Turn all switches ON' }
return [
"he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {0001010100010102010001010301000101}", 'delay 200',
]
}
def refresh() {
if (infoLogging) { log.info 'Refreshing...' }
return [
lastCheckin()
]
}
private String getChildId(childDevice) {
return childDevice.deviceNetworkId.substring(childDevice.deviceNetworkId.length() - 2)
}
void push(buttonNumber) {
log.debug "push event ${buttonNumber}"
sendEvent(name: "pushed", value:buttonNumber, isStateChange:true)
}
void componentOn(childDevice) {
// LS if were in button mode dont send an On to the physical switch when the soft swith is turned on
if (buttonMode != 'ON') {
if (debugLogging) { log.debug "component state is ON - ${childDevice} {${childDevice.deviceNetworkId}}" }
if (infoLogging) { log.info "${childDevice} is ON" }
String fullDataOn = '0001' + getChildId(childDevice) + '01000101'
sendHubCommand(new HubAction("he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOn}}", Protocol.ZIGBEE))
if (debugLogging) { log.debug { "{executed} 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOn}}" } }
}
// LS if the soft switch is turned on push the button
sendEvent(name: "pushed", value:getChildId(childDevice), isStateChange:true)
}
void componentOff(childDevice) {
if (debugLogging) { log.debug "component state is OFF - ${childDevice} {${childDevice.deviceNetworkId}}" }
if (infoLogging) { log.info "${childDevice} is OFF" }
String fullDataOff = '0001' + getChildId(childDevice) + '01000100'
sendHubCommand(new HubAction("he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOff}}", Protocol.ZIGBEE))
if (debugLogging) { log.debug "{executed} 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {${fullDataOff}}" }
}
void componentRefresh(childDevice) {
if (debugLogging) { log.debug "component refresh ${childDevice.deviceNetworkId} ${childDevice}" }
sendHubCommand(new HubAction("he rattr 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00", Protocol.ZIGBEE))
if (debugLogging) { log.debug "{executed} 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00" }
}
void setupChildDevices() {
if (debugLogging) { log.debug 'Parent setupChildDevices' }
deleteObsoleteChildren()
int buttons = 0
switch (device.data.manufacturer) {
case '_TZE200_amp6tsvy' :
case '_TZE200_oisqyl4o' :
case '_TZE200_wfxuhoea' :
case '_TZE200_gbagoilo' :
buttons = 1
break
case '_TZE200_g1ib5ldv' :
case '_TZE200_wunufsil' :
case '_TZE200_nh9m9emk' :
case '_TZE200_7deq70b8' :
buttons = 2
break
case '_TZE200_tz32mtza' :
case '_TZE200_kyfqmmyl' :
case '_TZE200_go3tvswy' :
case '_TZE200_atpwqgml' :
buttons = 3
break
case '_TZE200_k6jhsr0q' :
case '_TZE200_aqnazj70' :
case '_TZE200_1ozguk6x' :
case '_TZE200_mexisfik' :
case '_TZE200_1n2kyphz' :
case '_TZE200_shkxsgis' :
case '_TZE204_shkxsgis' :
case '_TZE200_mua6ucdj' :
case '_TZE204_aagrxlbd' :
buttons = 4
break
case '_TZE200_9mahtqtg' :
case '_TZE200_r731zlxk' :
case '_TZE204_xjknlqz8' :
case '_TZE204_4cl0dzt4' :
case '_TZE200_wnp4d4va' :
buttons = 6
break
case '_TZE204_dqolcpcp' :
buttons = 12
break
case '_TZE200_vhy3iakz' :
case '_TZ3000_uim07oem' :
default : // assume 4 buttons also for any unknown manufacturers codes!
buttons = 4
break
}
if (infoLogging) { log.info "model: ${device.data.manufacturer} buttons: $buttons" }
createChildDevices((int)buttons)
}
void createChildDevices(int buttons) {
if (debugLogging) { log.debug 'Parent createChildDevices' }
if (buttons <= 1) {
if (debugLogging) { log.debug "This device have only: $buttons button, Child devices not needed." }
return
}
/* groovylint-disable-next-line UnnecessaryElseStatement */
else {
for (i in 1..buttons) {
def childId = "${device.id}-0${i}"
def existingChild = getChildDevices()?.find { it.deviceNetworkId == childId }
if (existingChild) {
if (infoLogging) { log.info "Child device ${childId} already exists (${existingChild})" }
}
else {
if (infoLogging) { log.info "Creating device ${childId}" }
addChildDevice('hubitat', 'Generic Component Switch', childId, [isComponent: true, name: "Switch EP0${i}", label: "${device.displayName} EP0${i}"])
}
}
}
}
void deleteObsoleteChildren() {
if (debugLogging) { log.debug 'Parent deleteChildren' }
getChildDevices().each { child ->
if (!child.deviceNetworkId.startsWith(device.id) || child.deviceNetworkId == "${device.id}-00") {
if (infoLogging) { log.info "Deleting ${child.deviceNetworkId}" }
deleteChildDevice(child.deviceNetworkId)
}
}
}
def switchLightModeConfig() {
//def cmds = []
switch (switchLightMode) {
case 'OFF':
if (infoLogging) { log.info 'Backlight - OFF' }
zigbee.command(0xEF00, 0x0, '00010f04000100')
break
case 'ON':
if (infoLogging) { log.info 'Backlight - ON' }
zigbee.command(0xEF00, 0x0, '00010f04000101')
break
case 'Position':
if (infoLogging) { log.info 'Backlight - position' }
zigbee.command(0xEF00, 0x0, '00010f04000102')
break
}
}
def relayModeConfig() {
//def cmds = []
switch (relayMode) {
case 'OFF':
if (infoLogging) { log.info 'Relay state - OFF' }
zigbee.command(0xEF00, 0x0, '00010e04000100')
break
case 'ON':
if (infoLogging) { log.info 'Relay state - ON' }
zigbee.command(0xEF00, 0x0, '00010e04000101')
break
case 'Last state':
if (infoLogging) { log.info 'Relay state - last state' }
zigbee.command(0xEF00, 0x0, '00010e04000102')
break
}
}
void testParse(final String description) {
log.warn "testParse: ${description}"
parse(description)
log.trace '---end of testParse---'
}