Multi endpoint switch changing all children on 3 gang only

I have several 1, 2 and 3 gang zigbee switches from Yagusmart, so far going well.

The 2 gang switches work as expected with the child component switches changing state independently.

The 3 gang switches work independently only from the physical press. When any of the 3 switches is updated by hubitat, the parent state is updated which then cascades to all children. Is this a bug or can I prevent it? The 2 Gang switches don't have this issue.

Thanks for any advice,

Iain

Hi @iain,

Can you post the Model and the Manufacturer values from the device Data section?

I think this is it

endpointId: 01
application: 52
softwareBuild:
inClusters: 0003,0004,0005,0006,E000,E001,0000
outClusters: 0019,000A
model: TS0003
isMultiEP: true
manufacturer: _TZ3000_w58g68s3

I've replaced or of the switches with a different brand which works fine, so definitely seems to be this type of switch is the problem.

Thanks for your help,

Iain

1 Like

Can you try this mod of Muxa's driver?

Oh well done, fantastic, that works! Thank you very much for your help!!

1 Like

Hi there, sorry to hitch the thread. But I am facing the same issue too. ): Would you be able to help? Had amended the finger print to match but still when any switch if turned off using HE, all switches turn on for 3-gang. Works perfectly for 3-gang.

Here's my code which I had amended the fingerprint to match.

/**

  • Zemismart ZigBee Wall Switch Multi-Gang
  • Device Driver for Hubitat Elevation hub
  • Based on Muxa's driver Version 0.2.0, last updated Feb 5, 2020
  • Ver. 0.0.1 2019-08-21 Muxa - first version
  • Ver. 0.1.0 2020-02-05 Muxa - Driver name "Zemismart ZigBee Wall Switch Multi-Gang"
  • Ver. 0.2.1 2022-02-26 kkossev - TuyaBlackMagic for TS0003 _TZ3000_vjhcenzo
  • Ver. 0.2.2 2022-02-27 kkossev - TS0004 4-button, logEnable, txtEnable, ping(), intercept cluster: E000 attrId: D001 and D002 exceptions;
  • Ver. 0.2.3 2022-03-04 kkossev - powerOnState options
  • Ver. 0.2.4 2022-04-16 kkossev - _TZ3000_w58g68s3 Yagusmart 3 gang zigbee switch fingerprint
  • Ver. 0.2.5 2022-05-28 kkossev - _TYZB01_Lrjzz1UV Zemismart 3 gang zigbee switch fingerprint; added TS0011 TS0012 TS0013 models and fingerprints; more TS002, TS003, TS004 manufacturers
  • Ver. 0.2.6 2022-06-03 kkossev - powerOnState and Debug logs improvements; importUrl; singleThreaded
  • Ver. 0.2.7 2022-06-06 kkossev - command '0B' (command response) bug fix; added Tuya Zugbee mini switch TMZ02L (_TZ3000_txpirhfq); bug fix for TS0011 single-gang switches.
  • 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.
    */

import hubitat.device.HubAction
import hubitat.device.Protocol

def version() { "0.2.7" }
def timeStamp() {"2022/06/06 9:48 AM"}

metadata {
definition (name: "Moes ZigBee Wall Switch Multi-Gang", namespace: "muxa", author: "Muxa", importUrl: "https://raw.githubusercontent.com/kkossev/hubitat-muxa-fork/development/drivers/zemismart-zigbee-multigang-switch.groovy", singleThreaded: true ) {
capability "Initialize"
capability "Actuator"
capability "Configuration"
capability "Refresh"
capability "Switch"
capability "Health Check"

    fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0003,0004,0005,0006,E000,E001", outClusters:"0019,000A", model:"TS0012", manufacturer:"_TZ3000_18ejxno0", deviceJoinName: "Tuya Zigbee Switch Multi-Gang" // check! 
    fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0003,0004,0005,0006",           outClusters:"0019,000A", model:"TS0013", manufacturer:"_TZ3000_qewo8dlz", deviceJoinName: "Tuya Zigbee Switch Multi-Gang"

    
    command "powerOnState", [
        [name:"powerOnState",    type: "ENUM",   constraints: ["--- Select ---", "OFF", "ON", "Last state"], description: "Select Power On State"] 
    ]
    
    attribute "lastCheckin", "string"    
}
preferences {
    input (name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true)
    input (name: "txtEnable", type: "bool", title: "Enable description text logging", defaultValue: true)
}

}

// Parse incoming device messages to generate events

def parse(String description) {
checkDriverVersion()
//log.debug "${device.displayName} Parsing '${description}'"
def descMap = [:]
try {
descMap = zigbee.parseDescriptionAsMap(description)
}
catch ( e ) {
if (settings?.logEnable) log.warn "${device.displayName} exception caught while parsing description ${description} \r descMap: ${descMap}"
return null
}
logDebug "Parsed: $descMap"

Map map = null // [:]

if (descMap.cluster == "0006" && descMap.attrId == "0000") {
// descMap.command =="0A" - switch toggled physically
// descMap.command =="01" - get switch status
// descMap.command =="0B" - command response
def cd = getChildDevice("${device.id}-${descMap.endpoint}")
if (cd == null) {
if (!(device.data.model in ['TS0011'])) {
log.warn "${device.displayName} Child device ${device.id}-${descMap.endpoint} not found. Initialise parent device first"
return
}
}
def switchAttribute = descMap.value == "01" ? "on" : "off"
if (cd != null ) {
if (descMap.command in ["0A", "0B"]) {
// switch toggled
cd.parse([[name: "switch", value:switchAttribute, descriptionText: "Child switch ${descMap.endpoint} turned $switchAttribute"]])
}
else if (descMap.command =="01") {
// report switch status
cd.parse([[name: "switch", value:switchAttribute, descriptionText: "Child switch ${descMap.endpoint} is $switchAttribute"]])
}
}
if (switchAttribute == "on") {
logDebug "Parent switch on"
return createEvent(name: "switch", value: "on")
}
else if (switchAttribute == "off") {
def cdsOn = 0
// cound number of switches on
getChildDevices().each {child ->
if (getChildId(child) != descMap.endpoint && child.currentValue('switch') == "on") {
cdsOn++
}
}
if (cdsOn == 0) {
logDebug "Parent switch off"
return createEvent(name: "switch", value: "off")
}
}
} // OnOff cluster, attrId "0000"
else if (descMap.cluster == "0006" && descMap.attrId != "0000") { // other attr
processOnOfClusterOtherAttr( descMap )
}
else {
logDebug "${device.displayName} unprocessed EP: ${descMap.sourceEndpoint} cluster: ${descMap.clusterId} attrId: ${descMap.attrId}"
}
}

def off() {
if (settings?.txtEnable) log.info "${device.displayName} Turning all child switches off"
"he cmd 0x${device.deviceNetworkId} 0xFF 0x0006 0x0 {}"
}

def on() {
if (settings?.txtEnable) log.info "${device.displayName} Turning all child switches on"
"he cmd 0x${device.deviceNetworkId} 0xFF 0x0006 0x1 {}"
}

def refresh() {
logDebug "refreshing"
"he rattr 0x${device.deviceNetworkId} 0xFF 0x0006 0x0"
}

def ping() {
refresh()
}

private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}

private String getChildId(childDevice) {
return childDevice.deviceNetworkId.substring(childDevice.deviceNetworkId.length() - 2)
}

def componentOn(childDevice) {
logDebug "sending componentOn ${childDevice.deviceNetworkId}"
sendHubCommand(new HubAction("he cmd 0x${device.deviceNetworkId} 0x${getChildId(childDevice)} 0x0006 0x1 {}", Protocol.ZIGBEE))
}

def componentOff(childDevice) {
logDebug "sending componentOff ${childDevice.deviceNetworkId}"
sendHubCommand(new HubAction("he cmd 0x${device.deviceNetworkId} 0x${getChildId(childDevice)} 0x0006 0x0 {}", Protocol.ZIGBEE))
}

def componentRefresh(childDevice) {
logDebug "sending componentRefresh ${childDevice.deviceNetworkId} ${childDevice}"
sendHubCommand(new HubAction("he rattr 0x${device.deviceNetworkId} 0x${getChildId(childDevice)} 0x0006 0x0", Protocol.ZIGBEE))
}

def setupChildDevices() {
logDebug "Parent setupChildDevices"
deleteObsoleteChildren()
def buttons = 0
switch (device.data.model) {
case 'TS0004' :
case 'TS0014' :
buttons = 4
break
case 'TS0003' :
case 'TS0013' :
buttons = 3
break
case 'TS0002' :
case 'TS0012' :
buttons = 2
break
case 'TS0011' :
buttons = 0
break
default :
break
}
logDebug "model: ${device.data.model} buttons: $buttons"
createChildDevices((int)buttons)
}

def createChildDevices(int buttons) {
logDebug "Parent createChildDevices"

if (buttons == 0)
    return            

for (i in 1..buttons) {
    def childId = "${device.id}-0${i}"
    def existingChild = getChildDevices()?.find { it.deviceNetworkId == childId}

    if (existingChild) {
        log.info "${device.displayName} Child device ${childId} already exists (${existingChild})"
    } else {
        log.info "${device.displayName} Creatung device ${childId}"
        addChildDevice("hubitat", "Generic Component Switch", childId, [isComponent: true, name: "Switch EP0${i}", label: "${device.displayName} EP0${i}"])
    }
}

}

def deleteObsoleteChildren() {
logDebug "Parent deleteChildren"

getChildDevices().each {child->
    if (!child.deviceNetworkId.startsWith(device.id) || child.deviceNetworkId == "${device.id}-00") {
        log.info "${device.displayName} Deleting ${child.deviceNetworkId}"
	    deleteChildDevice(child.deviceNetworkId)
    }
}

}

def driverVersionAndTimeStamp() {version()+' '+timeStamp()}

def checkDriverVersion() {
if (state.driverVersion == null || (driverVersionAndTimeStamp() != state.driverVersion)) {
if (txtEnable==true) log.debug "${device.displayName} updating the settings from the current driver version ${state.driverVersion} to the new version ${driverVersionAndTimeStamp()}"
initializeVars( fullInit = false )
state.driverVersion = driverVersionAndTimeStamp()
}
}

void initializeVars(boolean fullInit = true) {
if (settings?.txtEnable) log.info "${device.displayName} InitializeVars()... fullInit = ${fullInit}"
if (fullInit == true ) {
state.clear()
state.driverVersion = driverVersionAndTimeStamp()
}
if (settings?.logEnable == null) device.updateSetting("logEnable", true)
if (settings?.txtEnable == null) device.updateSetting("txtEnable", true)
}

def initialize() {
logDebug "Initializing..."
initializeVars(fullInit = true)
setupChildDevices()
}

def installed() {
logDebug "Parent installed"
}

def updated() {
logDebug "Parent updated"
}

def tuyaBlackMagic() {
return zigbee.readAttribute(0x0000, [0x0004, 0x000, 0x0001, 0x0005, 0x0007, 0xfffe], [:], delay=200)
}

def configure() {
logDebug " configure().."
List cmds = []
cmds += tuyaBlackMagic()
//cmds += refresh()
cmds += zigbee.onOffConfig()
cmds += zigbee.onOffRefresh()
sendZigbeeCommands(cmds)
}

void sendZigbeeCommands(List cmds) {
logDebug "sendZigbeeCommands received : ${cmds}"
sendHubCommand(new hubitat.device.HubMultiAction(cmds, hubitat.device.Protocol.ZIGBEE))
}

def logDebug(msg) {
String sDnMsg = device.displayName + " " + msg
if (settings?.logEnable) log.debug sDnMsg
}

def powerOnState(relayMode) {
List cmds = []
int modeEnum = 99
switch(relayMode) {
case "OFF" :
modeEnum = 0
break
case "ON" :
modeEnum = 1
break
case "Last state" :
modeEnum = 2
break
default :
log.error "${device.displayName} please select a Power On State option"
return
}
logDebug ("setting Power On State option to: ${relayMode}")
cmds += zigbee.writeAttribute(0x0006, 0x8002, DataType.ENUM8, modeEnum)
sendZigbeeCommands(cmds)
}

def processOnOfClusterOtherAttr( descMap ) {
logDebug "cluster OnOff attribute ${descMap.attrId} reported: value=${descMap.value}"
def mode
def attrName
def value = descMap.value as int
switch (descMap.attrId) {
case "8000" :
attrName = "Child Lock"
mode = value == 0 ? "off" : "on"
break
case "8001" :
attrName = "LED mode"
mode = value == 0 ? "Disabled" : value == 1 ? "Lit when On" : value == 2 ? "Lit when Off" : null
break
case "8002" :
attrName = "Power On State"
mode = value == 0 ? "off" : value == 1 ? "on" : value == 2 ? "Last state" : null
break
default :
logDebug "processOnOfClusterOtherAttr: UNPROCESSED On/Off Cluster attrId: ${descMap.attrId} value: ${descMap.value}"
break
}
if (txtEnable) log.info "${device.displayName} ${attrName} is: ${mode}"
}

There is no much info for these switches on the net,,, I didn’t understood which one is not working as expected- the 2 gang or the 3 gang one?

I may need some more detailed information on the problematic switch. You will need to first delete it from the device web page, and then pair it again. When discovered as a new device, a hyperlink ‘Info’ should appear at the time the device was first paired to HE. click on the link and try to copy and paste here all the device details that will be shown in the window.

Thanks mate. 2 gang works fine. When trigger on/off via HE for 3-gang, the codes somehow runs 3 times to turn on all the child. Unable to really pin down which liner causing the issue.

The InClusters list in the driver fingerprint does not match exactly the screenshot. The clusters numbers must be in exactly the same order, as in the screenshot ( 0000 cluster must be the last one, in your fingerprint it is on the first position. Correct it and pair the switch once again, the driver should be selected automatically.

The driver should create 3 child devices. You must use the child devices on/off commands to control a single switch.
If you use on/off commands on the ‘parent’ device, this will affect all the 3 switches.

P.S.
I have added the new fingerprints in the forked driver version 0.2.8

2 Likes

i matched the inclusters and set single threaded = false and VIOLA. it worked!

1 Like

just to add on, when "single threaded = true", initialization does not create the child correctly. when i set it to false, the number of child(ren) created matches.

1 Like

Before you start using the switch in your automations, can you repeat this test again?
Please delete the device first and preferably use the updated driver from the link in this post above.

I am not 100% sure on the actual effect of "singleThreaded= true", I used it first when chasing some weird multiple events coming from TS004F scene switch. My understanding is, that it must prevent the simultaneous execution of one and the same thread created by an device driver - such as do not start executing a new parse() thread from the same driver, if the same one has not finished yet... But I may be wrong.

In this driver, the child devices are created (if they do not exist already) in the driver initialization, so the above key should not have any effect.. Pair the 3 gang switch as a new device with the current setting "ssingleThreaded = true", if it does not work - please pair it for a second time. If it doesn't work again, set the parameter to false (or remove it), to confirm your finding so far.

Also, please make another test - once the switch works successfully when paired to HE with this driver, will the HE inbuilt "Generic Zigbee Multi-Endpoint Switch" also work OK, if you manually switch to it?

Seems to be a similar issue with a 4 channel model zigbee relay module that I got. How to fix?

  • endpointId: 01
  • application: 52
  • firmwareMT: 1141-D3A3-00000052
  • inClusters: 0003,0004,0005,0006,E000,E001,0000
  • isMultiEP: true
  • manufacturer: _TZ3000_wkr3jqmr
  • model: TS0004
  • outClusters: 0019,000A
  • softwareBuild: 00000052

You can try the following :

  1. Install Muxa's driver from the link in the post above.
  2. Manually assign this "Zemismart ZigBee Wall Switch Multi-Gang" driver for use by your device.
  3. Click on the ''Initialize" button, selecting 'Yes' to confirm the deletion of the old child devices and the creation of the new child devices.
  4. Pair the switch again to HE (without removing it).

Pairing with this driver is obligatory, simply changing drivers and clicking Configire/Initialize usually does not work for Tuya devices. They accept some of the configuration commands only immediately after being paired to the hub.


Update May 2024 : this driver is now available for installation and update from HPM.

kkossev - That did the trick, thanks so much!!!! Hadn't realized that driver was handling 7-32v relay modules too. A couple of observations. Clicking Initialize did not prompt to remove old child devices. I didn't need to re-pair. It just worked. This was a module from Aliexpress store Scimagic-RC

1 Like