/** zemismart
*  Tuya Window Shade (v.0.1.0) Hubitat v1
 *	Copyright 2020 iquix
 *
 *	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.
 This DTH is coded based on iquix's tuya-window-shade DTH.
 https://github.com/iquix/Smartthings/blob/master/devicetypes/iquix/tuya-window-shade.src/tuya-window-shade.groovy


https://raw.githubusercontent.com/shin4299/XiaomiSJ/master/devicetypes/shinjjang/zemismart-zigbee-blind.src/zemismart-zigbee-blind.groovy

 */

import groovy.json.JsonOutput
//mc // import physicalgraph.zigbee.zcl.DataType
import hubitat.zigbee.zcl.DataType
import hubitat.helper.HexUtils

metadata {
	definition(name: "ZemiSmart Zigbee Blind", namespace: "ShinJjang", author: "ShinJjang-iquix", ocfDeviceType: "oic.d.blind", vid: "generic-shade") {
		capability "Actuator"
		capability "Configuration"
		capability "Window Shade"
// mc not supported in HE		capability "Window Shade Preset"
		capability "Switch Level"
        capability "Switch"
        capability "Light" //GH

		command "pause"
        command "presetPosition"
        
        attribute "Direction", "enum", ["Reverse","Forward"]

		fingerprint endpointId: "01", profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006", outClusters: "0019", manufacturer: "_TYST11_wmcdj3aq", model: "mcdj3aq", deviceJoinName: "Zemismart Zigbee Blind"
//mc changeed endpointId from 0x01 to 01	
    }

	preferences {
        input "preset", "number", title: "Preset position", description: "Set the window shade preset position", defaultValue: 50, range: "0..100", required: false, displayDuringSetup: false
        input name: "Direction", type: "enum", title: "Direction Set", defaultValue: "00", options:["01": "Reverse", "00": "Forward"], displayDuringSetup: true
	    input "logEnable", "bool", title: "Enable logging", required: true, defaultValue: true
    }
// removed tiles section as not used in Hubitat
}

private getCLUSTER_TUYA() { 0xEF00 }
private getSETDATA() { 0x00 }

// 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(logEnable == true) log.debug "Pasred Map $descMap"
		
        if (descMap?.clusterInt==CLUSTER_TUYA) {
			if ( descMap?.command == "01" || descMap?.command == "02" ) {
				int dp = zigbee.convertHexToInt(descMap?.data[3]+descMap?.data[2])
                if(logEnable == true) log.debug "dp = " + dp
				String data6 = descMap.data[6]
                
                switch (dp) {
					case 1025: // 0x04 0x01: Confirm opening/closing/stopping (triggered from Zigbee)
                    	if (data6 == "00") {
                        	if(logEnable == true) log.debug "parsed opening"
                            //levelEventMoving(100)
                            sendEvent([name:"windowShade", value: "opening", descriptionText: "Zigbee Opnening"])
                        } 
                        else if (data6 == "02") {
                        	if(logEnable == true) log.debug "parsed closing"
                            //levelEventMoving(0)
                            sendEvent([name:"windowShade", value: "closing" , descriptionText: "Zigbee closing" ])
                        }
                    else {log.debug "parsed else case $dp open/close/stop zigbee" + descMap.data[6]}
                    break;
                    
					case 1031: // 0x04 0x07: Confirm opening/closing/stopping (triggered from remote)
                    	if (data6 == "01") {
                            if(logEnable == true) log.trace "remote closing"
                           // levelEventMoving(0)
                            sendEvent([name:"windowShade", value: "closing" , descriptionText: "Remote closing" ])
                        } 
                        else if (data6 == "00") {
                        	if(logEnable == true) log.trace "remote opening"
                            //levelEventMoving(100)
                            sendEvent([name:"windowShade", value: "opening", descriptionText: "Remote Opnening"])
                        }
                    else {log.debug "parsed else case $dp open/close/stop remote ${descMap.data[6]}"}
                    break;
                    
					case 514: // 0x02 0x02: Started moving to position (triggered from Zigbee)
                        int pos = zigbee.convertHexToInt(descMap.data[9])
						if(logEnable == true) log.debug "moving to position :"+pos
                        levelEventMoving(pos)
                        break;
                    
					case 515: // 0x03: Arrived at position
                    	int pos = zigbee.convertHexToInt(descMap.data[9])
                        if(logEnable == true) log.debug "${device.displayName} arrived at position:$pos , desc:$description"
                    	levelEventArrived(pos)
                        break;
                    
                    log.warn "UN-handled CLUSTER_TUYA case " + dp " " descMap
				}
                data6 = null
			}
		}
        else {
            if(logEnable == true) log.warn "UN-Pasred Map $descMap"
        }
    descMap = null
	}
}

private levelEventMoving(int currentLevel) {
	int lastLevel = device.currentValue("level")
	if(logEnable == true) log.debug "${device.displayName} levelEventMoving - targert level: ${currentLevel} lastLevel: ${lastLevel}"
	if (lastLevel == "undefined" || currentLevel == lastLevel) { //Ignore invalid reports
		log.debug "${device.displayName} Ignore invalid reports" + lastLevel
	} 
    else {
		if (lastLevel < currentLevel) {
			sendEvent([name:"windowShade", value: "opening"])
		} 
        else if (lastLevel > currentLevel) {
			sendEvent([name:"windowShade", value: "closing"])
		}
        else {} //last and current the same
    }
}

private levelEventArrived(int level) {
    if(logEnable == true) log.info "arrived at " + level
    
//debunce start when in revers mode time in mill's
    def nowtime = now()
    if (nowtime-50 <= state.lastmsg){
        def diff = nowtime-state.lastmsg
        state.lastmsg = nowtime
        log.warn "${device.displayName} debouced level = $level, two message recived in $diff millisecondes filter is set to 50"  //nowtime = ${nowtimeform.format('HH:mm: ss: SSS')}, lastmsg = ${stateform.format('HH:mm: ss: SSS')} differance ${diffform.format('SSS')} millisecondes"
    return
    }
    state.lastmsg = nowtime
//debunce end

    if (level == 0) { sendEvent(name: "windowShade", value: "closed")} 
    else if (level == 100) { sendEvent(name: "windowShade", value: "open")} 
    else if (level > 0 && level < 100) { sendEvent(name: "windowShade", value: "partially open")} 
    else { sendEvent(name: "windowShade", value: "unknown")}
    
    sendEvent(name: "position", value: (level))
//To enable in GoggleHome
    sendEvent(name: "level", value: (level)) //dimmer
    if (level < 100){ sendEvent(name: "switch", value: "on")}
    else {sendEvent(name: "switch", value: "off")}
//google home end
}

def close() {
	if(logEnable == true) log.info "close()"
	//int currentLevel = device.currentValue("level")
    if (device.currentValue("level") == 0) {
    	sendEvent(name: "windowShade", value: "closed")
        return
    }
	sendTuyaCommand("0104", "00", "0102")
}

def open() {
	if(logEnable == true) log.info "open()"
    //int currentLevel = device.currentValue("level")
    if (device.currentValue("level") == 100) {
    	sendEvent(name: "windowShade", value: "open")
        return
    }
	sendTuyaCommand("0104", "00", "0100")
}

def pause() {
	if(logEnable == true) log.info "pause()"
	sendTuyaCommand("0104", "00", "0101")
}

def stopPositionChange(){
    pause()
}

def startPositionChange(pos){
    if (pos == "open") open()
    else close()
}

def setLevel(data, rate = null) {
	if(logEnable == true) log.info "setLevel("+data+")"
    int currentLevel = device.currentValue("level")
    if (currentLevel == data) {
    	sendEvent(name: "level", value: currentLevel)
        sendEvent(name: "position", value: currentLevel) //HE capability attribute
    }
    else {
        sendTuyaCommand("0202", "00", "04000000"+HexUtils.integerToHexString(data.intValue(), 1))
    }
}

def setPosition(position){ //mc new for HE Commands
    if(logEnable == true) log.info "setPos to $position"
    setLevel(position)
}

def presetPosition() { //custom command preset 
    setLevel(preset ?: 50)
}

def installed() {
	sendEvent(name: "supportedWindowShadeCommands", value: JsonOutput.toJson(["open", "close", "pause"]), displayed: false)
}

def updated() {
	def val = Direction
    sendEvent([name:"Direction", value: (val == "00" ? "Forward" : "Reverse")])    
	DirectionSet(val)
}
def initialize() {
	// Runs on reboot, or can be triggered manually.
    byte randomSixty = Math.abs(new Random().nextInt() % 60)
	log.info "Initialize running updated in $randomSixty seconds"
    state.lastmsg = now()
    runIn(randomSixty,updated)
}

private DirectionSet(Dval) {
	if(logEnable == true) log.info "Direction set ${Dval} "
    sendTuyaCommand("05040001", Dval, "") //not tested
}

def configure() {
	log.info "configure() running updated"
    updated()
}

private sendTuyaCommand(dp, fn, data) {
	if(logEnable == true) log.trace "send tuya command ${dp},${fn},${data}"
	zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + dp + fn + data)
}

private rand(n) {
	return (new Random().nextInt(n))
}
def on (){
    if(logEnable == true) log.warn "$device - some thing thinks im a switch  and is turning me on"
    close()
}
def off () {
    if(logEnable == true) log.warn "$device - some thing thinks im a switch and is turning me off"
    open()
}