Zigbee Multi-Endport Switch support for Tuya 12 way relay

I'm using a C-8
It was just a fast click, I didn't see which relay it was. I couldn't re-create it.
I haven't been able to get anything to work, I've tried a bunch of the built in drivers.
Maybe its defective,
I guess I can always just use the RF remote..
If I get any different logs I'll look for the 'catchall'.
Jim

that a good idea.. try forcing a message to be sent with that RF remote. If you make a relay change state, I think it should send a zigbee message which will get caught when the driver is in debug mode. If you are not seeing any messages when you change state with the RF remote then something is off with communication between the hub and the device.

Hi, I am wondering if you were successful in getting this Tuya 8Ch Relay device to work with HE? I just received one but no luck with the various drivers I have tried.

Thanks
Ian

Zigbee model/manufacturer?

code attached in the details summary; completely unsupported. Got it working, IIRC it was still a buggy sometimes at start up. Lost interest and decided to go a different direction.

Summary

import hubitat.device.HubAction
import hubitat.device.Protocol
import groovy.transform.Field

/*

  • 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 - (dev. test version) - added TS0601 _TZE204_dqolcpcp (@alex1) (only the first 6 relays should be working)
  • revision 1.1.0 - 2023-11-29 - alex1 - reworked driver to use groovy list componentHexAddrMap to map 2 digit hex zigbee component IDs to buttons.

*/

//TODO: figure out how to read back status for relays 7 and higher.
static String version() { "1.1.0" }
static String timeStamp() {"2023/11/29 9:56 AM"}
// may want to choose a smaller default map in the future to avoid bugs
@Field componentHexAddrMap = ["00", "01", "02", "03", "04", "05", "06"]
@Field wallSwitchWithDimmerLED = true

metadata {
definition (name: "Tuya ZigBee Multi Gang Switch", namespace: "martinkura", author: "Martin Kura", importUrl: "https://raw.githubusercontent.com/martinkura-svk/Hubitat/main/Moes%20ZigBee%20Wall%20Switch") {
capability "Initialize"
capability "Actuator"
capability "Refresh"
capability "Switch"

    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_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 Module"
    fingerprint profileId:"0104", model:"TS0601", manufacturer:"_TZE204_dvosyycn", endpointId:"01", inClusters:"0004,0005,EF00,0000", outClusters:"0019,000A", application:"42", deviceJoinName: "Tuya 8-way Relay Module"
}
attribute "switchLightMode","enum",["OFF", "ON", "Position"]
attribute "relayMode","enum",["OFF", "ON", "Last state"]
attribute "lastCheckin", "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: "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()
if(wallSwitchWithDimmerLED == true) device.updateSetting("switchLightMode", [type:"enum", value:"Position"])
device.updateSetting("relayMode", [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()
if(wallSwitchWithDimmerLED == true)device.updateSetting("switchLightMode", [type:"enum", value:"Position"])
device.updateSetting("relayMode", [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"
if(wallSwitchWithDimmerLED == true) switchLightModeConfig()
relayModeConfig()
refresh()

}

private getCLUSTER_TUYA() { 0xEF00 }

// Parse incoming device messages to generate events
def parse(String description) {
if (description?.startsWith('catchall:') || description?.startsWith('read attr -')) {
Map descMap = zigbee.parseDescriptionAsMap(description)
if (descMap?.clusterInt==CLUSTER_TUYA) {
if (debugLogging) log.debug descMap
if ( descMap?.command in ["00", "01", "02"] ) {
if (debugLogging) log.debug "Child switch ${(descMap?.data[2])}"
def switchFunc = (descMap?.data[2])
def switchAttr = (descMap?.data[3])
def switchState = (descMap?.data[6]) == "01" ? "on" : "off"
if (debugLogging) log.debug "Status for parsing Child switch ${switchFunc} switch state ${switchState} attrib ${switchAttr}"
if ( switchAttr == "01" ) {
if (debugLogging) log.debug "child ${switchFunc} recognized"
def cd = getChildDevice("${device.id}-${switchFunc}")
if (cd == null) {
if (debugLogging) log.debug "Got NULL child device!!!"
return createEvent(name: "switch", value: switchState)
}
if (descMap?.command == "00") {
// switch toggled
if (debugLogging) log.debug "Toggle Child switch ${switchFunc} turned $switchState"
cd.parse([[name: "switch", value: switchState, descriptionText: "Child switch ${switchFunc} turned $switchState"]])
}
else if (descMap?.command in ["01", "02"]) {
// report switch status
def statusIndexLen = descMap?.data.size() - 2
if (debugLogging) log.debug "statusIndexLen len is $statusIndexLen"
if (debugLogging) log.debug "status reported is for ${statusIndexLen/5} child switches"
for (funcIndex in 1..(statusIndexLen/5))
{
def switchFuncPtr = descMap?.data[2 + (5*(funcIndex-1))]
def cdPtr = getChildDevice("${device.id}-${switchFuncPtr}")
def switchFuncStatePtr = descMap?.data[6 + (5*(funcIndex-1))] == "01" ? "on" : "off"
if (debugLogging) log.debug "STATUS Child switch ${switchFuncPtr} is ${switchFuncStatePtr}"
cdPtr.parse([[name: "switch", value: switchFuncStatePtr, descriptionText: "Child switch ${switchFuncPtr} is ${switchFuncStatePtr}"]])
}
}

// this needs rework
/*

                if (switchState == "on") {
                    if (debugLogging) log.debug "Parent Switch ON"
                    return createEvent(name: "switch", value: "on")
                } 
                else if (switchState == "off") {
                    def cdsOn = 0
                    // find 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 [
// this assumes that there are only switchs 01-03. Should be reworked to iterate through actual number of child components
//"he cmd 0x${device.deviceNetworkId} 0x${device.endpointId} 0xEF00 0x00 {0001010100010002010001000301000100}","delay 200",
]
}

def on() {
if (infoLogging) log.info "Turn all switches ON"
return [
// this assumes that there are only switchs 01-03. Should be reworked to iterate through actual number of child components
//"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)
}

def componentOn(childDevice) {
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}}"
}

def 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}}"
}

def 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"
}

def setupChildDevices() {
if (debugLogging) log.debug "Parent setupChildDevices"
//deleteObsoleteChildren()
def 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' :
        buttons = 4
        break
    case '_TZE200_9mahtqtg' :
        buttons = 6
        break
    case '_TZE204_dqolcpcp' :
    _TZE204_dvosyycn
        buttons = 12
        componentHexAddrMap[7] = "65"
        componentHexAddrMap[8] = "66"
        componentHexAddrMap[9] = "67"
        componentHexAddrMap[10] = "68"
        componentHexAddrMap[11] = "69"
        componentHexAddrMap[12] = "6A"
        wallSwitchWithDimmerLED = false
        break
    case '_TZE204_dvosyycn' :
        buttons = 8
        componentHexAddrMap[7] = "65"
        componentHexAddrMap[8] = "66"
        wallSwitchWithDimmerLED = false
        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, componentHexAddrMap)

}

def createChildDevices(int buttons, componentHexAddrMap) {
if (debugLogging) log.debug "Parent createChildDevices"

if (buttons <= 1) {
    if (debugLogging) log.debug "This device have only: $buttons button, Child devices not needed."
    return 
} 
else {
    for (i in 1..buttons) {
        def childId = "${device.id}-${componentHexAddrMap[i]}"
        def existingChild = getChildDevices()?.find { it.deviceNetworkId == childId}
    
        if (existingChild) {
            if (infoLogging) log.info "Child device ${childId} already exists (${existingChild.deviceNetworkId})"
            if (infoLogging) log.info "Deleting and recreating ${existingChild.deviceNetworkId}"
            deleteChildDevice(existingChild.deviceNetworkId)
        } 
        
        if (infoLogging) log.info "Creating device ${childId}"
        addChildDevice("hubitat", "Generic Component Switch", childId, [isComponent: true, name: "Switch EP${componentHexAddrMap[i]}", label: "${device.displayName} EP${componentHexAddrMap[i]}"])
        
    }
}

}

def 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
}
}

1 Like

Thanks for replying

This is the link to the AliX item
https://www.aliexpress.com/item/1005005514624649.html?spm=a2g0o.order_list.order_list_main.4.29371802Addj65

endpointId: 01
application: 4A
inClusters: 0004,0005,EF00,0000
manufacturer: _TZE204_dvosyycn
model: TS0601
outClusters: 0019,000A

I can get it to pair but there are no child devices.

1 Like

@alex1 These Tuya multi-relays / multi-inputs boards were rather chatty, if I am not wrong?

sounds about right. I took it off my main Zigbee network and attached it to a spare hub on its own channel. It also needs to be paired pretty close to the hub initially. It behaved better on its own Zigbee. As stated, I was not confidence enough with it to continue with it and definitely not on my main Zigbee channel.

try pairing closer to the hub. Its was weird for me too. After driver load it then takes a while for it to start responding.

I was going to use one of these 12 relay units to replace my broken Orbitz sprinkler system. However, because my confidence was not high in its strange behavior I ended up going in different direction.

I ended up keeping the Orbitz sprinkler unit and just gutted out the Orbitz logic board which attaches via a ribbon cable to the rest of the HW. Soldered up and own little logic board with the ribbon connector header running an ESP32 MCU on Tasmota. I've got another Orbitz unit which as lets zones and I'll end up doing the same with it eventually. Please with the end result and no dependence on Orbitz Cloud as a bonus.

WetCoaster,

locate this code and try uncommenting the call to deleteObsoleteChildren().
This part never really made sense to me so I commented it out. Maybe it's to address the issue you are having for recreating the children upon new pairing.

Been trying it again today etc and finally the children showed up! It's been working fine all day.

Thanks to all
Ian

2 Likes

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.