Ikea Blinds

Here Is a link to the ported code.

Although the set level/position does not work

Hmm, not much there. Was that all that was shown after clicking on more... ?

you have setLevel (data,rate=null){

Hubitat is going to send duration, not rate. So, you want to change that line to

setLevel(data,duration=null){

That MIGHT fix the set level command. But this is a very odd driver. Not structure like one I've seen before. Also, you can remove the "tiles" section, hubitat doesn't use them.

2 Likes

Although this is not a 'HE' native response I have paired my blind and the included repeater and the switch directly with deConz. HomeAssistant now controls the blind well for me using the deConz driver. I show both a direct routing and via repeater.

HA statestream then 'advertises' the blind under 'covers' and I have just added support for this in the alpha MQTT application. It will be included in alpha4. So I do have control of the blinds from HE.

It would be nice to get a direct integration with HE though.

Surprised by this... I would expect the blind should pair directly.. but not be fully supported and controllable.

Not so - It will for me - I do think the blind has a poor signal, caused by the metal casing so benefits from the near repeater though. My routing shows both direct and via repeater paths.

It seems following the included remote button pairing instructions might reset the blind. I didn't do that , instead recognising the remote separately in deConz and grouping it that way.

Using the ported driver from St. I posted up the thread a bit, when i try to use set level or set position, I get the following errors:-

Is this an easy code fix?

You have both setLevel and setPosition available because the driver exposes both the Window Shade Capability and the Switch Level capability. However, you don't have any method implemented for your setPosition funciton within the driver.

Also, when you command setLevel to the driver, you aren't going to be able to pass the second parameter. I can't see in the driver where you are able to pass the speed to travel to the blinds so it is going to take as long as it is going to take. Leave the second parameter blank.

Try out this driver and see what happens. I don't have the device so this is a stab in the dark. I added the setPosition function and also added a check to make the number passed to setPosition or setLevel into an integer rather than a decimal.

/**
 *
 *
 *	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.
 *
 *  
 *  first release for IKEA smart window blinds for hubitat adapted from the driver for ST by Wayne Man
 */
import hubitat.zigbee.zcl.DataType

metadata {
    definition(name: "IKEA Window Blinds", namespace: "ryan780", author: "ryan780", 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"
        
       	attribute "lastCheckin", "String"
		attribute "lastOpened", "String"

		fingerprint inClusters: "0000,0001,0003,0004", manufacturer: "IKEA of Sweden", model: "FYRTUR block-out roller blind"
    }


    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: "https://raw.githubusercontent.com/a4refillpad/media/master/blind-open.png", backgroundColor: "#00A0DC", nextState: "closing"
                attributeState "closed", label: 'Closed', action: "open", icon: "https://raw.githubusercontent.com/a4refillpad/media/master/blind-closed.png", backgroundColor: "#ffffff", nextState: "opening"
                attributeState "partially open", label: 'Partially open', action: "close", icon: "https://raw.githubusercontent.com/a4refillpad/media/master/blind-part-open.png", backgroundColor: "#d45614", nextState: "closing"
                attributeState "opening", label: 'Opening', action: "pause", icon: "st.thermostat.thermostat-up", backgroundColor: "#00A0DC", nextState: "partially open"
                attributeState "closing", label: 'Closing', action: "pause", icon: "st.thermostat.thermostat-down", backgroundColor: "#ffffff", nextState: "partially open"
            }
            tileAttribute("device.lastCheckin", key: "SECONDARY_CONTROL") {
    			attributeState("default", label:'Last Update: ${currentValue}',icon: "st.Health & Wellness.health9")
            }
        }
        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: 'Blind is ${currentValue}% closed', defaultState: true
        }
        controlTile("levelSliderControl", "device.level", "slider", width:2, height: 1, inactiveLabel: false) {
            state "level", action:"switch level.setLevel"
        }
        standardTile("resetClosed", "device.windowShade", inactiveLabel: false, decoration: "flat", width: 3, height: 1) {
			state "default", action:"close", label: "Override Close", icon:"https://raw.githubusercontent.com/a4refillpad/media/master/blind-closed.png"
	  	}
		standardTile("resetOpen", "device.windowShade", inactiveLabel: false, decoration: "flat", width: 3, height: 1) {
			state "default", action:"open", label: "Override Open", icon:"https://raw.githubusercontent.com/a4refillpad/media/master/blind-open.png"
	  	}
 

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

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}"
    def now = new Date().format("yyyy MMM dd EEE h:mm:ss a", location.timeZone)
    //  send event for heartbeat    
    sendEvent(name: "lastCheckin", value: now)
    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 ? "open" : "closed")
        } else {
            if (lastLevel < currentLevel) {
                sendEvent([name:"windowShade", value: "closing"])
            } else if (lastLevel > currentLevel) {
                sendEvent([name:"windowShade", value: "opening"])
            }
            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
	data = data.toInteger()
    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
}

def setPosition(value){
	setLevel(value)
}
10 Likes

You are a genius, that works well with no errors in the logs :+1::+1:

The set level and set position do the same things

2 Likes

Sweet! Glad you got them working. :slight_smile:

3 Likes

Nice work guys. I hope to start beta testing these the second they're available in the US. Looks like you guys already have a head start on making these work with HE straight out of the box! Much appreciated.

3 Likes

I appreciate that but all the real credit should go to whoever Wayne Man is over on the ST forum. He did all the heavy lifting. I just tinkered. :stuck_out_tongue_winking_eye:

3 Likes

These look neat. I’m just not sure I care for the material they come with.

1 Like

I’m just looking for automated blackout shades. As long as I’m able to get the width I need, these will do just fine :smiley:

3 Likes

In Europe they are in 5 widths.
This is the fabric width. The whole blind is about 45mm wider.
60cm 80cm 100cm 120cm 140cm
They can with a bit of effort be trimmed down.

2 Likes

Good work guys. I've been thinking about getting HE for a while and just happened to find this link talking about the ikea blinds. Just joined the forum and see a lot of familiar names from years past from the ST forum on here.

2 Likes

This is really good to hear as I'm waiting for them to hit Aus in October.
I am preparing myself for a delayed release date though. he he he

1 Like

Hopefully wider size will come to North America. I need a minimum of 165cm for the window I want this on. If not, then 120cm and 60cm shades could be used together, since it's a three panel casement style, with very wide frames between each panel. All depends on the wife acceptance and price, in that order.

Welcome to the Hubitat Community Wayne!

Thank you for creating this driver, and thanks to Ryan for the additional work in porting it to HE.

3 Likes

@SmartHomePrimer

Here is what I found about the sizes for the US. This was from a YT video from Jun 25, 2019.

2 Likes

Thanks. A colleague sent me that a few weeks ago too. Going to mix sizes. It’ll work since I’m planning to install only the blackout shades on a three panel casement. The frames are wide and will cover the gap well enough for my use case.

1 Like

UPDATE for US Hubitatters: Ikea is now listing Fyrtur blinds on their website. You can see all sizes and prices here: FYRTUR Blackout roller blind - wireless, battery operated gray - IKEA

They are not yet available to purchase, but I imagine they will be soon.

8 Sizes, priced as follows.

  • 23" x 76 3/4" @ $129
  • 27" x 76 3/4" @ $139
  • 30" x 76 3/4" @ $149
  • 32" x 76 3/4" @ $154
  • 34" x 76 3/4" @ $159
  • 36" x 76 3/4" @ $164
  • 38" x 76 3/4" @ $169
  • 48" x 76 3/4" @ $179
4 Likes