import hubitat.device.HubAction import hubitat.device.Protocol /* * revision 1.01 - 2022-02-22 - added Moes 4-Gang Switch / ZTS-EU4 * revision 1.02 - 2022-02-27 - added more Tuya fingerprints for 1,2,3 and 4 gangs TS0601 wall switches * revision 1.03 - 2022-09-26 - added Zemismart 6 Gangs Wall Light Switch * revision 1.04 - 2022-10-12 - _TZE200_tz32mtza bug fix; code cleanup * revision 1.05 - 2023-03-16 - added OZ Smart 1-2-3-4 gang switches _TZE200_gbagoilo _TZE200_nh9m9emk _TZE200_go3tvswy _TZE200_mexisfik * revision 1.06 - 2023-04-24 - added importUrl; _TZE200_aqnazj70 _TZE200_wunufsil _TZE200_oisqyl4o _TZE200_atpwqgml * */ metadata { definition (name: "Moes ZigBee Wall Switch 1/2/3-Gang", namespace: "Moes 1.31", 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" } 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() 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() 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" 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"] ) { def switchFunc = (descMap?.data[2]) def switchAttr = (descMap?.data[3]) def switchState = (descMap?.data[6]) == "01" ? "on" : "off" if (switchFunc in ["01", "02", "03", "04", "05", "06"] && switchAttr =="01") { def cd = getChildDevice("${device.id}-${switchFunc}") 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) } 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' : 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 '_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) } def 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 } 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}"]) } } } } 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 } }