Zemismart curtain switch

I was able to fix it by using this by the way :slight_smile:


So all good here, thanks everyone for the help

2 Likes

Great news :+1::+1:
I take it these require a neutral wire?

Yeah that is correct.
These require a neutral wire.
Then you have a Live, a L1 and L2 to control the motor for open or close

1 Like

that is for smartthings code, someone need fix it for hubitat users

The fix is quite simple. Just try to add the code as is, it will give you an error about a function, just search and replace that word with "hubitat" and it will be good.

Fixed for HE. Just copy/paste.

/**
 *
 *	Copyright 2019 SmartThings
 *
 *	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.zigbee.zcl.DataType

metadata {
    definition(name: "ZigBee Window Shade", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.blind", mnmn: "SmartThings", vid: "generic-shade") {
        capability "Actuator"
        capability "Configuration"
        capability "Refresh"
        capability "Window Shade"
        capability "Health Check"
        capability "Switch Level"

        command "pause"

        fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0102", outClusters: "0019", model: "E2B0-KR000Z0-HA", deviceJoinName: "SOMFY Blind Controller/eZEX" // SY-IoT201-BD
        fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0102", outClusters: "000A", manufacturer: "Feibit Co.Ltd", model: "FTB56-ZT218AK1.6", deviceJoinName: "Wistar Curtain Motor(CMJ)"
        fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0102", outClusters: "000A", manufacturer: "Feibit Co.Ltd", model: "FTB56-ZT218AK1.8", deviceJoinName: "Wistar Curtain Motor(CMJ)"
        fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0102", outClusters: "0003", manufacturer: "REXENSE", model: "DY0010", deviceJoinName: "Smart Curtain Motor(DT82TV)"
    }


    tiles(scale: 2) {
        multiAttributeTile(name:"windowShade", type: "generic", width: 6, height: 4) {
            tileAttribute("device.windowShade", key: "PRIMARY_CONTROL") {
                attributeState "open", label: 'Open', action: "close", icon: "http://www.ezex.co.kr/img/st/window_open.png", backgroundColor: "#00A0DC", nextState: "closing"
                attributeState "closed", label: 'Closed', action: "open", icon: "http://www.ezex.co.kr/img/st/window_close.png", backgroundColor: "#ffffff", nextState: "opening"
                attributeState "partially open", label: 'Partially open', action: "close", icon: "http://www.ezex.co.kr/img/st/window_open.png", backgroundColor: "#d45614", nextState: "closing"
                attributeState "opening", label: 'Opening', action: "pause", icon: "http://www.ezex.co.kr/img/st/window_open.png", backgroundColor: "#00A0DC", nextState: "partially open"
                attributeState "closing", label: 'Closing', action: "pause", icon: "http://www.ezex.co.kr/img/st/window_close.png", backgroundColor: "#ffffff", nextState: "partially open"
            }
        }
        standardTile("contPause", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
            state "pause", label:"", icon:'st.sonos.pause-btn', action:'pause', backgroundColor:"#cccccc"
        }
        standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 1) {
            state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
        }
        valueTile("shadeLevel", "device.level", width: 4, height: 1) {
            state "level", label: 'Shade is ${currentValue}% up', defaultState: true
        }
        controlTile("levelSliderControl", "device.level", "slider", width:2, height: 1, inactiveLabel: false) {
            state "level", action:"switch level.setLevel"
        }

        main "windowShade"
        details(["windowShade", "contPause", "shadeLevel", "levelSliderControl", "refresh"])
    }
}

private getCLUSTER_WINDOW_COVERING() { 0x0102 }
private getCOMMAND_OPEN() { 0x00 }
private getCOMMAND_CLOSE() { 0x01 }
private getCOMMAND_PAUSE() { 0x02 }
private getCOMMAND_GOTO_LIFT_PERCENTAGE() { 0x05 }
private getATTRIBUTE_POSITION_LIFT() { 0x0008 }
private getATTRIBUTE_CURRENT_LEVEL() { 0x0000 }
private getCOMMAND_MOVE_LEVEL_ONOFF() { 0x04 }

private List<Map> collectAttributes(Map descMap) {
	List<Map> descMaps = new ArrayList<Map>()

	descMaps.add(descMap)

	if (descMap.additionalAttrs) {
		descMaps.addAll(descMap.additionalAttrs)
	}

	return descMaps
}

// Parse incoming device messages to generate events
def parse(String description) {
    log.debug "description:- ${description}"
    if (description?.startsWith("read attr -")) {
        Map descMap = zigbee.parseDescriptionAsMap(description)
        if (supportsLiftPercentage() && descMap?.clusterInt == CLUSTER_WINDOW_COVERING && descMap.value) {
            log.debug "attr: ${descMap?.attrInt}, value: ${descMap?.value}, descValue: ${Integer.parseInt(descMap.value, 16)}, ${device.getDataValue("model")}"
            List<Map> descMaps = collectAttributes(descMap)
            def liftmap = descMaps.find { it.attrInt == ATTRIBUTE_POSITION_LIFT }
            if (liftmap && liftmap.value) {
                def newLevel = zigbee.convertHexToInt(liftmap.value)
                levelEventHandler(newLevel)
            }
        } else if (!supportsLiftPercentage() && descMap?.clusterInt == zigbee.LEVEL_CONTROL_CLUSTER && descMap.value) {
            def valueInt = Math.round((zigbee.convertHexToInt(descMap.value)) / 255 * 100)

            levelEventHandler(valueInt)
        }
    }
}

def levelEventHandler(currentLevel) {
    def lastLevel = device.currentValue("level")
    log.debug "levelEventHandle - currentLevel: ${currentLevel} lastLevel: ${lastLevel}"
    if (lastLevel == "undefined" || currentLevel == lastLevel) { //Ignore invalid reports
        log.debug "Ignore invalid reports"
    } else {
        sendEvent(name: "level", value: currentLevel)
        if (currentLevel == 0 || currentLevel == 100) {
            sendEvent(name: "windowShade", value: currentLevel == 0 ? "closed" : "open")
        } else {
            if (lastLevel < currentLevel) {
                sendEvent([name:"windowShade", value: "opening"])
            } else if (lastLevel > currentLevel) {
                sendEvent([name:"windowShade", value: "closing"])
            }
            runIn(1, "updateFinalState", [overwrite:true])
        }
    }
}

def updateFinalState() {
    def level = device.currentValue("level")
    log.debug "updateFinalState: ${level}"
    if (level > 0 && level < 100) {
        sendEvent(name: "windowShade", value: "partially open")
    }
}

def supportsLiftPercentage() {
    device.getDataValue("manufacturer") != "Feibit Co.Ltd"
}

def close() {
    log.info "close()"
    zigbee.command(CLUSTER_WINDOW_COVERING, COMMAND_CLOSE)
}

def open() {
    log.info "open()"
    zigbee.command(CLUSTER_WINDOW_COVERING, COMMAND_OPEN)
}

def setLevel(data, rate = null) {
    log.info "setLevel()"
    def cmd
    if (supportsLiftPercentage()) {
        cmd = zigbee.command(CLUSTER_WINDOW_COVERING, COMMAND_GOTO_LIFT_PERCENTAGE, zigbee.convertToHexString(data, 2))
    } else {
        cmd = zigbee.command(zigbee.LEVEL_CONTROL_CLUSTER, COMMAND_MOVE_LEVEL_ONOFF, zigbee.convertToHexString(Math.round(data * 255 / 100), 2))
    }

    return cmd
}

def pause() {
    log.info "pause()"
    zigbee.command(CLUSTER_WINDOW_COVERING, COMMAND_PAUSE)
}

/**
 * PING is used by Device-Watch in attempt to reach the Device
 * */
def ping() {
    return refresh()
}

def refresh() {
    log.info "refresh()"
    def cmds
    if (supportsLiftPercentage()) {
        cmds = zigbee.readAttribute(CLUSTER_WINDOW_COVERING, ATTRIBUTE_POSITION_LIFT)
    } else {
        cmds = zigbee.readAttribute(zigbee.LEVEL_CONTROL_CLUSTER, ATTRIBUTE_CURRENT_LEVEL)
    }
    return cmds
}

def configure() {
    // Device-Watch allows 2 check-in misses from device + ping (plus 2 min lag time)
    log.info "configure()"
    sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
    log.debug "Configuring Reporting and Bindings."

    def cmds
    if (supportsLiftPercentage()) {
        cmds = zigbee.configureReporting(CLUSTER_WINDOW_COVERING, ATTRIBUTE_POSITION_LIFT, DataType.UINT8, 0, 600, null)
    } else {
        cmds = zigbee.levelConfig()
    }
    return refresh() + cmds
}
3 Likes

Hi all, I also have a similar situation my device is a Xiaomi aquaria curtain motor theres a Smartthings code however i can't seem to get it to work on hubitat, sadly the generic Zigbee shade does not support all the functions, eg when manually opened, curtains state won't reflect in hubitat, in ST however this is perfectly reflected, code below, I get this error ---

Value too long for column "ENDPOINT_ID VARCHAR(2)": "'0x01' (4)"; SQL statement: INSERT INTO FINGERPRINT(DEVICE_TYPE_ID, PROFILE_ID, ENDPOINT_ID, IN_CLUSTERS, OUT_CLUSTERS, MANUFACTURER, MODEL, DEVICE_JOIN_NAME, DEVICE_ID, MFR, PROD, CONTROLLER_TYPE) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [22001-197] HELP ?

/**
*

  • 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.
  • 2017
    */

metadata {
definition(name: "Xiaomi Curtain SJ", namespace: "ShinJjang", author: "ShinJjang", ocfDeviceType: "oic.d.blind") {
capability "Window Shade"
capability "Switch Level"
capability "Actuator"
capability "Health Check"
capability "Switch"
capability "Sensor"
capability "Refresh"

// attribute "Window Shade", "enum", ["open", "close", "stop"]

    fingerprint endpointId: "0x01", profileId: "0104", deviceId: "0202", inClusters: "0000, 0004, 0003, 0005, 000A, 0102, 000D, 0013, 0006, 0001, 0406", outClusters: "0019, 000A, 000D, 0102, 0013, 0006, 0001, 0406"

}

command "levelOpenClose"
command "shadeAction"

command "stop"

preferences {
      input name: "mode", type: "bool", title: "Xiaomi Curtain Direction Set", description: "Reverse Mode ON", required: true,
         displayDuringSetup: true

}

tiles(scale: 2) {
    multiAttributeTile(name: "windowShade", type: "generic", width: 6, height: 4) {
        tileAttribute("device.windowShade", key: "PRIMARY_CONTROL") {
            attributeState("closed", label: 'closed', action: "windowShade.open", icon: "st.doors.garage.garage-closed", backgroundColor: "#A8A8C6", nextState: "opening")
            attributeState("open", label: 'open', action: "windowShade.close", icon: "st.doors.garage.garage-open", backgroundColor: "#F7D73E", nextState: "closing")
            attributeState("closing", label: '${name}', action: "windowShade.open", icon: "st.contact.contact.closed", backgroundColor: "#B9C6A8")
            attributeState("opening", label: '${name}', action: "windowShade.close", icon: "st.contact.contact.open", backgroundColor: "#D4CF14")
            attributeState("partially open", label: 'partially\nopen', action: "windowShade.close", icon: "st.doors.garage.garage-closing", backgroundColor: "#D4ACEE", nextState: "closing")
        }
        tileAttribute("device.level", key: "SLIDER_CONTROL") {
            attributeState("level", action: "setLevel")
        }
    }
    standardTile("open", "open", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("open", label: 'open', action: "windowShade.open", icon: "st.contact.contact.open")
    }
    standardTile("close", "close", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("close", label: 'close', action: "windowShade.close", icon: "st.contact.contact.closed")
    }
    standardTile("stop", "stop", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state("stop", label: 'stop', action: "windowShade.stop", icon: "st.illuminance.illuminance.dark")
    }
    standardTile("refresh", "command.refresh", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
        state "default", label: " ", action: "refresh.refresh", icon: "https://www.shareicon.net/data/128x128/2016/06/27/623885_home_256x256.png"
    }
    standardTile("home", "device.level", width: 2, height: 2, decoration: "flat") {
        state "default", label: "home", action:"presetPosition", icon:"st.Home.home2"
    }
    main(["windowShade"])
    details(["windowShade", "open", "stop", "close", "refresh"])
}

}
// Parse incoming device messages to generate events
def parse(String description) {
def parseMap = zigbee.parseDescriptionAsMap(description)
log.debug "parseMap11:${parseMap}"
def event = zigbee.getEvent(description)

try {
    if (parseMap.raw.startsWith("0104")) {
        log.debug "Xiaomi Curtain"
        log.debug "Unhandled Event - description:${description}, parseMap:${parseMap}, event:${event}"
    } else if (parseMap.raw.endsWith("0007")) {
        log.debug "Unhandled Event - description:${description}, parseMap:${parseMap}, event:${event}"
        log.debug "running…"
    } else if (parseMap.endpoint.endsWith("01")) {
        log.debug "Unhandled Event - description:${description}, parseMap:${parseMap}, event:${event}"
        if (parseMap["cluster"] == "000D" && parseMap["attrId"] == "0055") {
            long theValue = Long.parseLong(parseMap["value"], 16)
            float floatValue = Float.intBitsToFloat(theValue.intValue());
            def windowShadeStatus = ""
        int curtainLevel = floatValue.intValue()
            if(mode == true) {
                if (theValue > 0x42c70000) {
                    log.debug "Just Closed"
                    windowShadeStatus = "closed"
                    curtainLevel = 0
                } else if (theValue > 0) {
                    log.debug curtainLevel + '% Partially Open'
                    windowShadeStatus = "partially open"
                    curtainLevel = 100 - floatValue.intValue()
                } else {
                    log.debug "Just Fully Open"
                    windowShadeStatus = "open"
                    curtainLevel = 100
                }
            } else {
                if (theValue > 0x42c70000) {
                    log.debug "Just Fully Open"
                    windowShadeStatus = "open"
                    curtainLevel = 100
                } else if (theValue > 0) {
                    log.debug curtainLevel + '% Partially Open'
                    windowShadeStatus = "partially open"
                    curtainLevel = floatValue.intValue()
                } else {
                    log.debug "Just Closed"
                    windowShadeStatus = "closed"
                    curtainLevel = 0
                }
            }

            def eventStack = []
            eventStack.push(createEvent(name:"windowShade", value: windowShadeStatus as String))
            eventStack.push(createEvent(name:"level", value: curtainLevel))
            eventStack.push(createEvent(name:"switch", value: (windowShadeStatus == "closed" ? "off" : "on")))

            return eventStack
        }
    } else {
        log.debug "Unhandled Event - description:${description}, parseMap:${parseMap}, event:${event}"
    }

} catch (Exception e) {
    log.warn e
}

}

def close() {
setLevel(0)
}

def open() {
setLevel(100)
}

def off() {
log.debug "off()"
close()
}

def on() {
log.debug "on()"
open()
}

def stop() {
log.debug "stop()"
delayBetween([
zigbee.command(0x0102, 0x02),
zigbee.readAttribute(0x000d, 0x0055)
], 500)
// runIn(1, refresh)
}

def setLevel(level) {
if (level == null) {level = 0}
level = level as int

if(mode == true){
if(level == 100) {
log.debug "Set Close"
zigbee.command(0x0006, 0x00)
} else if(level < 1) {
log.debug "Set Open"
zigbee.command(0x0006, 0x01)
} else {
log.debug "Set Level: ${level}%"
def f = 100 - level
String hex = Integer.toHexString(Float.floatToIntBits(f)).toUpperCase()
zigbee.writeAttribute(0x000d, 0x0055, 0x39, hex)
}
} else{
if (level == 100){
log.debug "Set Open"
zigbee.command(0x0006, 0x01)
} else if(level > 0) {
log.debug "Set Level: ${level}%"
String hex = Integer.toHexString(Float.floatToIntBits(level)).toUpperCase()
zigbee.writeAttribute(0x000d, 0x0055, 0x39, hex)
} else {
log.debug "Set Close"
zigbee.command(0x0006, 0x00)
}
}
}

def shadeAction(level) {
if(mode == true){
if(level == 100) {
log.debug "Set Close"
zigbee.command(0x0006, 0x00)
} else if(level < 1) {
log.debug "Set Open"
zigbee.command(0x0006, 0x01)
} else {
log.debug "Set Level: ${level}%"
def f = 100 - level
String hex = Integer.toHexString(Float.floatToIntBits(f)).toUpperCase()
zigbee.writeAttribute(0x000d, 0x0055, 0x39, hex)
}
} else{
if (level == 100){
log.debug "Set Open"
zigbee.command(0x0006, 0x01)
} else if(level > 0) {
log.debug "Set Level: ${level}%"
String hex = Integer.toHexString(Float.floatToIntBits(level)).toUpperCase()
zigbee.writeAttribute(0x000d, 0x0055, 0x39, hex)
} else {
log.debug "Set Close"
zigbee.command(0x0006, 0x00)
}
}
}
def refresh() {
log.debug "refresh()"
// "st rattr 0x${device.deviceNetworkId} ${1} 0x000d 0x0055"
zigbee.readAttribute(0x000d, 0x0055)
}

If you use an aqara curtain its best if you check directlt on the xiaomi/aqara drivers post here in the community

HI,
I'm new here and need help with this topic.
do any one has driver for the zemismart zigbee curtain module ?
I make it work, but i can only full up or down, can't stop it in between.
any suggestion ?

You can cheat your way into it by just usin button to lower it for x amount of time and stop and so on,
If your motor doesn't make much noise and is fast you can just make it fully open first and then close for x amount of time.
There's various ways you can go to cheat it to stop more or less where you want but doin it properly there's no way if you are refering to controlling the switch.

if you are talkin about controlling the motor directly instead, then we need to know what motor it is as it may already even have an oficial driver

Hello Cexinho! It seems you've found a perfect solution for the curtain switch i've bought. I have to thank you for that ! :slight_smile:

I face only one problem left and can't really find a solution to this one :
I couldn't set the highest and lowest point of my blind. Moreover, the closing command works a bit longer than the opening command. Meaning than every time i close then open the blind, it's getting a bit lower and lower.

So, could you tell me how you did configure the highest and lowest point of your blind with HE ?
Btw, i'm a new HE user and i know barely nothing about code etc..

Thanks a lot for your time :slight_smile:

The highest and lowest point of the blind is set directly on the blind itself. It should have some knob kinda thingies or something where you can put a flat head screwdriver to configure that. It should be one knob/screw thinigie for the top limit and one for the lower limit.

The switch itself only tells the curtain motor to open or close, the motor itself is the one who knows where the limits are. Let me know if this helps.
You can see them here as an example btw

Woaw, what a quick answer Cexinho. Thanks a lot !

Unfortunately, that's what i first did before installing the switch. Once the switch was installed i found out that the parameters had been reset and the switch kinda did his own thing.

So i guess you don't have any "code" solution nor had to modify parameters in HE to set everything UP.

Idk if anyone else faced that kind of issue ?
Thanks again for your time!

If you bought the exact same switch I mentioned there is no straightforward way.
There may be some ways to kinda achieve it by using timers but its a complex solution that I'm not even sure that is possible within HE.
The switch by itself has no notion of where the limits are. The switch also has no ability to override the limits that are set physical within the curtain.
Try adjust the knobs again, They may have not even been properly set the first time.
(all assuming that you bought that switch from zemismart or similar generic zigbee switch)

I'll try to do that one more time.

At first i thought it was the exact same switch as yours, as the description is quite similar. But i found out it's not :
Zemismart Zigbee EU Curtain Switch SmartThing Hub Control For Electric Blind Motorized Roller Shade

I also found that the "Lora Tap" one works with a really cheap tuya hub, and that there's a calibration function with the hub or the app, i don't know yet.

The switch :

The "hub" :

So I bought those to try and make my own opinion.
Just waiting for the delivery now.

Good day to everyone reading. If you're interested in any kind of feedback, let me know.

It didn't work for me.
I noticed by the other replies, that i might be not understood correctly.
I'm using a zemismart ,module behind regular switch, not zemismart switch
This is the product (any idea to make it work with that ?)

i have this problem too, any suggestion?

Check this page here to see if any of them work.
https://www.zemismart.com/pages/zigbee-handler-download-38
If it doesn't work try provide the details that hubitat gives for this device and I can try find the original site I found the driver on.
I found it on the website of the factory itself I believe so would need to take a look again.
So please give those a try.
You may need to fix them to work for hubitat which you can find a guide on here on the community somewhere (can't relaly remember the title) but most of the fixes are just changing whatever is giving error to "hubitat"

1 Like

The drive that you post its ok, all function work (When I check command)! But my problem is when I go to choice a template, I can't find anyone that work with this drive