/** 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 Jorge 2", namespace: "ShinJjang", author: "ShinJjang-iquix", ocfDeviceType: "oic.d.blind", vid: "generic-shade") { capability "Actuator" capability "Configuration" capability "Window Shade" capability "Refresh" capability "HealthCheck" 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) log.debug "Pasred Map $descMap" if (descMap?.clusterInt==CLUSTER_TUYA) { if ( descMap?.command == "01" || descMap?.command == "02" ) { def dp = zigbee.convertHexToInt(descMap?.data[3]+descMap?.data[2]) if(logEnable) log.debug "dp = " + dp switch (dp) { case 1025: // 0x04 0x01: Confirm opening/closing/stopping (triggered from Zigbee) def data = descMap.data[6] if (descMap.data[6] == "00") { if(logEnable) log.debug "parsed opening" levelEventMoving(100) } else if (descMap.data[6] == "02") { if(logEnable) log.debug "parsed closing" levelEventMoving(0) } else {log.debug "parsed else case $dp open/close/stop zigbee $data"} break; case 1031: // 0x04 0x07: Confirm opening/closing/stopping (triggered from remote) def data = descMap.data[6] if (descMap.data[6] == "01") { log.trace "remote closing" levelEventMoving(0) } else if (descMap.data[6] == "00") { log.trace "remote opening" levelEventMoving(100) } else {log.debug "parsed else case $dp open/close/stop remote $data"} break; case 514: // 0x02 0x02: Started moving to position (triggered from Zigbee) def pos = zigbee.convertHexToInt(descMap.data[9]) if(logEnable) log.debug "moving to position :"+pos levelEventMoving(pos) break; case 515: // 0x03: Arrived at position def pos = zigbee.convertHexToInt(descMap.data[9]) if(logEnable) log.debug description log.info "arrived at position :"+pos levelEventArrived(pos) break; log.warn "UN-handled CLUSTER_TUYA case $dp $descMap" } } } else { //log.warn "UN-Pasred Map $descMap" } } } private levelEventMoving(currentLevel) { def lastLevel = device.currentValue("level") if(logEnable) log.debug "levelEventMoving - currentLevel: ${currentLevel} lastLevel: ${lastLevel}" if (lastLevel == "undefined" || currentLevel == lastLevel) { //Ignore invalid reports log.debug "Ignore invalid reports" } else { if (lastLevel < currentLevel) { sendEvent([name:"windowShade", value: "opening"]) } else if (lastLevel > currentLevel) { sendEvent([name:"windowShade", value: "closing"]) } } } private levelEventArrived(level) { 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") //return } sendEvent(name: "level", value: (level)) sendEvent(name: "position", value: (level)) //To enable in GoggleHome if (level < 100){ sendEvent(name: "switch", value: "on")} else {sendEvent(name: "switch", value: "off")} } def close() { if(logEnable) log.info "close()" def currentLevel = device.currentValue("level") if (currentLevel == 0) { sendEvent(name: "windowShade", value: "closed") return } sendTuyaCommand("0104", "00", "0102") } def open() { if(logEnable) log.info "open()" def currentLevel = device.currentValue("level") if (currentLevel == 100) { sendEvent(name: "windowShade", value: "open") return } sendTuyaCommand("0104", "00", "0100") } def pause() { if(logEnable) log.info "pause()" sendTuyaCommand("0104", "00", "0101") } def setLevel(data, rate = null) { if(logEnable) log.info "setLevel("+data+")" def currentLevel = device.currentValue("level") if (currentLevel == data) { sendEvent(name: "level", value: currentLevel) sendEvent(name: "position", value: currentLevel) //HE capability attribute return } sendTuyaCommand("0202", "00", "04000000"+HexUtils.integerToHexString(data.intValue(), 1)) sendEvent(name: "level", value: level) } def setPosition(position){ //mc new for HE Commands if(logEnable) log.info "setPos to $position" setLevel(position, null) } 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 poll() { null } def DirectionSet(Dval) { if(logEnable) log.info "Direction set ${Dval} " //zigbee.command(CLUSTER_TUYA, SETDATA, "00" + zigbee.convertToHexString(rand(256), 2) + "05040001" + Dval) sendTuyaCommand("05040001", Dval, "") //not tested } def configure() { log.info "configure()" } private sendTuyaCommand(dp, fn, data) { if(logEnable) 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)) }