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