Routines for Window Shades Fibaro Roller Shutter 3

Routines for opening and closing window shades, simply don’t work!

First of all, in the documentation, the structure for Window Shades states a presetPosition command, but routines trigger a setLevel(String value) command. Stange! and it took me ages to figure this out.

Second:
If you set the action to close window shades, all you get in the logs is:

java.lang.NullPointerException: Cannot invoke method size() on null object @line 959 (takeAction)

Meaning it calls setLevel with a value null, so I cannot use close I have to use open to 10%.

Third:
This is what I have in my device handler and it works flawlessly if I trigger it manually using the slider:

def setPosition(Integer value) {
    def cmds = []
    def TDelayGetPos = 0
    logging("${device.displayName} - Executing setLevel( $value )","info")
	if (device.currentValue("position") > value) {
    	TDelayGetPos = (timeDownMov.intValue()+100)*10
        sendEvent(name: "windowShade", value: "closing")
    } else if (device.currentValue("position") < value) {
	    TDelayGetPos = (timeUpMov.intValue()+100)*10
        sendEvent(name: "windowShade", value: "opening")
    }
    logging("${device.displayName} - Time Delay Value: ${TDelayGetPos} ms","info")
    cmds << zwave.basicV1.basicSet(value: (value > 0) ? value-1 : 0)
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,TDelayGetPos)
}

This is what I have in the Device Handler for the Routines:

def setLevel(String value) {	//This Function is only used for routines
    def cmds = []
    def TDelayGetPos = 0
    def val1 = 0
    val1 = value.toInteger()
    logging("${device.displayName} - Executing setLevel( $val1 )","info")
	if (device.currentValue("position") > val1) {
    	TDelayGetPos = (timeDownMov.intValue()+100)*10
    } else if (device.currentValue("position") <= val1) {
	    TDelayGetPos = (timeUpMov.intValue()+100)*10
    }    
    logging("${device.displayName} - Time Delay Value: ${TDelayGetPos} ms","info")
	cmds << zwave.basicV1.basicSet(value: (val1 > 0) ? val1-1 : 0)
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,TDelayGetPos)
}

I get the exact same thing in the Logs for both commands, but only setPosition works, the other one does nothing.

11:38:54: info Estores Escritório - encapsulating command using Secure Encapsulation, command: SwitchMultilevelGet()
11:38:54: info Estores Escritório - encapsulating command using Secure Encapsulation, command: BasicSet(value: 0)
11:38:54: info Estores Escritório - Time Delay Value: 14980 ms
11:38:54: info Estores Escritório - Executing setLevel( 0 )

All I want to do Is to Open the Shades at a certain time, and close them at Sunset. As simple as this! And it doesn’t work… Maybe I’m just too dumb and don’t understand the whole concept of Smartthings, or maybe they named it wrong…

BTW: I have a semi working DH for the Fibaro Roller Shutter 3 that I can share.

Please put your code in a code block by putting ``` on a separate line above and below your code. That’ll make it easier to read, and more likely that you’ll get help.

I have a working DH for this device at the thread below. It should work with routines, but I’m not sure if that’s been tested (and I actually don’t own one of these). If you’d like to test, I’ll appreciate the feedback.

@philh30

I have not tested your DH but I suspect it will have the same problem!

It’s a routine implementation problem. I have my DH working with routines after adding the switch and switch level capability and adding the commands “on” and “off” as an exact copy of the “close” and “open” commands.

In the routines I have defined the windows shades as dimmers and voilá everything works.

If you try to create a routine to automate the window shades, instead of using as dimmers, it will not work. It must be something to do with the code behind the routines.

Here’s a copy of my DH

/**
 *  FIBARO Roller Shutter 3
 *
 *  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.
 *
 */
metadata {
    definition (name: "Fibaro FGR-223 V0.1", namespace: "Fibaro", author: "JBranquinho") {
        capability "Window Shade"
        capability "Energy Meter"
        capability "Power Meter"
        capability "Configuration"
        capability "Switch"
        capability "Switch Level"
        
        command "reset"
        command "refresh"
        command "calibrate"
        command "stop"
        command "setPosition"
        command "setSlats"

        attribute "position", "number"
        attribute "slats", "number"
        attribute "power", "number"
        attribute "timeUpMov", "number"
        attribute "timeDownMov", "number"
        // attribute "TDelay_GetPos", "number" //Time Delay to get Current Position

        fingerprint mfr: "010F", prod: "0303"
        fingerprint deviceId: "0x1106", inClusters:"0x5E,0x55,0x98,0x9F,0x56,0x6C,0x22,0x26,0x85,0x8E,0x59,0x86,0x72,0x5A,0x73,0x32,0x70,0x71,0x75,0x60,0x5B,0x7A"
    }

    tiles (scale: 2) {
        multiAttributeTile(name:"windowShade", type: "generic", width: 3, height: 4, canChangeIcon: false){
            tileAttribute ("device.windowShade", key: "PRIMARY_CONTROL") {
                attributeState "unknown", label: 'Unknown', action: "calibrate", icon: "https://s3-eu-west-1.amazonaws.com/fibaro-smartthings/rollerShuter/Roleta50.png", backgroundColor: "#ffffff"
                attributeState "closed", label: 'Closed', action: "open", icon: "https://s3-eu-west-1.amazonaws.com/fibaro-smartthings/rollerShuter/Roleta100.png", backgroundColor: "#ffffff", nextState:"opening"
                attributeState "open", label: 'Open', action: "close", icon: "https://s3-eu-west-1.amazonaws.com/fibaro-smartthings/rollerShuter/Roleta0.png", backgroundColor: "#00a0dc", nextState:"closing"
                attributeState "opening", label: 'Opening', action: "stop", icon: "https://s3-eu-west-1.amazonaws.com/fibaro-smartthings/rollerShuter/Roleta50.png", backgroundColor: "#99d9f1", nextState:"partially open"
                attributeState "closing", label: 'Closing', action: "stop", icon: "https://s3-eu-west-1.amazonaws.com/fibaro-smartthings/rollerShuter/Roleta50.png", backgroundColor: "#99d9f1", nextState:"partially open"
                attributeState "partially open", label: 'Partially Open', action: "open", icon: "https://s3-eu-west-1.amazonaws.com/fibaro-smartthings/rollerShuter/Roleta50.png", backgroundColor: "#00a0dc", nextState:"opening"            
            }
            tileAttribute("device.multiStatus", key:"SECONDARY_CONTROL") {
                attributeState("combinedMeter", label:'${currentValue}')
            }
            tileAttribute ("device.position", key: "SLIDER_CONTROL") {
                attributeState "position", action:"setPosition"
            }
        }
        valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
            state "power", label:'${currentValue}\nW', action:"refresh"
        }
        valueTile("energy", "device.energy", decoration: "flat", width: 2, height: 2) {
            state "energy", label:'${currentValue}\nkWh', action:"refresh"
        }
        valueTile("reset", "device.energy", decoration: "flat", width: 2, height: 2) {
            state "reset", label:'reset\nkWh', action:"reset"
        }
        valueTile("calibrate", "device.calibrate", decoration: "flat", width: 2, height: 1) {
            state "calibrate", label:'Calibrate', action:"calibrate"
        }
        valueTile("slatsText", "device.slats", decoration: "flat", width: 2, height: 1) {
            state "slats", label:'Slats:'
        }
        controlTile("slats", "device.slats", "slider", range:"(0..100)", height: 1, width: 2) {
            state "slats", label:'slats', action:"setSlats"
        }
    }

    preferences {
        input (
                title: "Fibaro Roller Shutter 3 ZW5 manual",
                description: "Tap to view the manual.",
                image: "http://manuals.fibaro.com/wp-content/uploads/2017/02/d2_icon.png",
                url: "https://manuals.fibaro.com/content/manuals/en/FGR-223/FGR-223-EN-T-v1.0.pdf",
                type: "href",
                element: "href"
        )

        parameterMap().each {
            input (
                    title: "${it.num}. ${it.title}",
                    description: it.descr,
                    type: "paragraph",
                    element: "paragraph"
            )

            input (
                    name: it.key,
                    title: null,
                    type: it.type,
                    options: it.options,
                    range: (it.min != null && it.max != null) ? "${it.min}..${it.max}" : null,
                    defaultValue: it.def,
                    required: false
            )
        }

        input ( name: "logging", title: "Logging", type: "boolean", required: false )
    }
}

def open() {
    def cmds = []
    def TDelayGetPos = 0
    TDelayGetPos = (timeUpMov.intValue()+100)*10
    logging("${device.displayName} - Time Delay Value: ${TDelayGetPos} ms","info")
    cmds << zwave.basicV1.basicSet(value: 99)
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,TDelayGetPos)
}

def close() {
    def cmds = []
    def TDelayGetPos = 0
    TDelayGetPos = (timeDownMov.intValue()+100)*10
    logging("${device.displayName} - Time Delay Value: ${TDelayGetPos} ms","info")
    cmds << zwave.basicV1.basicSet(value: 0)
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,27000)
}

def stop() {
    def cmds = []
    cmds << zwave.switchMultilevelV3.switchMultilevelStopLevelChange()
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,2000)
}

def calibrate() {
	encap(zwave.configurationV2.configurationSet(configurationValue: intToParam(2, 1), parameterNumber: 150, size: 1))
}

def setPosition(Integer value) {
    def cmds = []
    def TDelayGetPos = 0
    logging("${device.displayName} - Executing setLevel( $value )","info")
	if (device.currentValue("position") > value) {
    	TDelayGetPos = (timeDownMov.intValue()+100)*10
        sendEvent(name: "windowShade", value: "closing")
    } else if (device.currentValue("position") < value) {
	    TDelayGetPos = (timeUpMov.intValue()+100)*10
        sendEvent(name: "windowShade", value: "opening")
    }
    logging("${device.displayName} - Time Delay Value: ${TDelayGetPos} ms","info")
    cmds << zwave.basicV1.basicSet(value: (value > 0) ? value-1 : 0)
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,TDelayGetPos)
}

def setSlats(Integer value) {
    refresh()
}

def reset() {
    logging("${device.displayName} - Executing reset()","info")
    def cmds = []
    cmds << zwave.meterV3.meterReset()
    cmds << zwave.meterV3.meterGet(scale: 0)
    cmds << zwave.meterV3.meterGet(scale: 2)
    encapSequence(cmds,1500)
}

def refresh() {
    logging("${device.displayName} - Executing refresh()","info")
    def cmds = []
    cmds << zwave.basicV1.basicGet()
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
	cmds << zwave.meterV3.meterGet(scale: 0)
    cmds << zwave.meterV3.meterGet(scale: 2)
    encapSequence(cmds,1500)
}

def setLevel(Integer value) {	//This Function is only used for routines
    def cmds = []
    def TDelayGetPos = 0
    def val1 = value.toInteger()
    logging("${device.displayName} - Executing setLevel( $val1 )","info")
	if (device.currentValue("position") > val1) {
    	TDelayGetPos = (timeDownMov.intValue()+100)*10
    } else if (device.currentValue("position") <= val1) {
	    TDelayGetPos = (timeUpMov.intValue()+100)*10
    }    
    logging("${device.displayName} - Time Delay Value: ${TDelayGetPos} ms","info")
	//cmds << zwave.basicV1.basicSet(value: (val1 > 0) ? val1-1 : 0)
    cmds << zwave.switchMultilevelV3.switchMultilevelSet(value: val1, dimmingDuration: 0x00)
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,TDelayGetPos)
}

def on() {
    def cmds = []
    def TDelayGetPos = 0
    TDelayGetPos = (timeDownMov.intValue()+100)*10
    logging("${device.displayName} - Time Delay Value: ${TDelayGetPos} ms","info")
    cmds << zwave.basicV1.basicSet(value: 99)
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,27000)
}

def off() {
    def cmds = []
    def TDelayGetPos = 0
    TDelayGetPos = (timeDownMov.intValue()+100)*10
    logging("${device.displayName} - Time Delay Value: ${TDelayGetPos} ms","info")
    cmds << zwave.basicV1.basicSet(value: 0)
    cmds << zwave.switchMultilevelV3.switchMultilevelGet()
    encapSequence(cmds,27000)
}

def updated() {
    if ( state.lastUpdated && (now() - state.lastUpdated) < 500 ) return
    def cmds = []
    logging("${device.displayName} - Executing updated()","info")
    runIn(3,"syncStart")
    state.lastUpdated = now()
}

private syncStart() {
    boolean syncNeeded = false
    parameterMap().each {
        if(settings."$it.key" != null) {
            if (state."$it.key" == null) { state."$it.key" = [value: null, state: "synced"] }
            if (state."$it.key".value != settings."$it.key" as Integer || state."$it.key".state in ["notSynced","inProgress"]) {
                state."$it.key".value = settings."$it.key" as Integer
                state."$it.key".state = "notSynced"
                syncNeeded = true
            }
        }
    }
    if ( syncNeeded ) {
        logging("${device.displayName} - starting sync.", "info")
        multiStatusEvent("Sync in progress.", true, true)
        syncNext()
    }
}

private syncNext() {
    logging("${device.displayName} - Executing syncNext()","info")
    def cmds = []
    for ( param in parameterMap() ) {
        if ( state."$param.key"?.value != null && state."$param.key"?.state in ["notSynced","inProgress"] ) {
            multiStatusEvent("Sync in progress. (param: ${param.num})", true)
            state."$param.key"?.state = "inProgress"
            cmds << response(encap(zwave.configurationV2.configurationSet(configurationValue: intToParam(state."$param.key".value, param.size), parameterNumber: param.num, size: param.size)))
            cmds << response(encap(zwave.configurationV2.configurationGet(parameterNumber: param.num)))
            break
        }
    }
    if (cmds) {
        runIn(10, "syncCheck")
        sendHubCommand(cmds,1000)
    } else {
        runIn(1, "syncCheck")
    }
}

private syncCheck() {
    logging("${device.displayName} - Executing syncCheck()","info")
    def failed = []
    def incorrect = []
    def notSynced = []
    parameterMap().each {
        if (state."$it.key"?.state == "incorrect" ) {
            incorrect << it
        } else if ( state."$it.key"?.state == "failed" ) {
            failed << it
        } else if ( state."$it.key"?.state in ["inProgress","notSynced"] ) {
            notSynced << it
        }
    }
    if (failed) {
        logging("${device.displayName} - Sync failed! Check parameter: ${failed[0].num}","info")
        sendEvent(name: "syncStatus", value: "failed")
        multiStatusEvent("Sync failed! Check parameter: ${failed[0].num}", true, true)
    } else if (incorrect) {
        logging("${device.displayName} - Sync mismatch! Check parameter: ${incorrect[0].num}","info")
        sendEvent(name: "syncStatus", value: "incomplete")
        multiStatusEvent("Sync mismatch! Check parameter: ${incorrect[0].num}", true, true)
    } else if (notSynced) {
        logging("${device.displayName} - Sync incomplete!","info")
        sendEvent(name: "syncStatus", value: "incomplete")
        multiStatusEvent("Sync incomplete! Open settings and tap Done to try again.", true, true)
    } else {
        logging("${device.displayName} - Sync Complete","info")
        sendEvent(name: "syncStatus", value: "synced")
        multiStatusEvent("Sync OK.", true, true)
    }
}

private multiStatusEvent(String statusValue, boolean force = false, boolean display = false) {
    if (!device.currentValue("multiStatus")?.contains("Sync") || device.currentValue("multiStatus") == "Sync OK." || force) {
        sendEvent(name: "multiStatus", value: statusValue, descriptionText: statusValue, displayed: display)
    }
}

def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
    def paramKey = parameterMap().find( {it.num == cmd.parameterNumber } ).key
    switch (cmd.parameterNumber) {
        case 156:
            sendEvent([name: "timeUpMov", value: cmd.scaledConfigurationValue])
            break
        case 157:
            sendEvent([name: "timeDownMov", value: cmd.scaledConfigurationValue])
            break
    }
    logging("${device.displayName} - Parameter ${paramKey} value is ${cmd.scaledConfigurationValue} expected " + state."$paramKey".value, "info")
    state."$paramKey".state = (state."$paramKey".value == cmd.scaledConfigurationValue) ? "synced" : "incorrect"
    syncNext()
}

def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) {
    logging("${device.displayName} - rejected request!","warn")
    for ( param in parameterMap() ) {
        if ( state."$param.key"?.state == "inProgress" ) {
            state."$param.key"?.state = "failed"
            break
        }
    }
}

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
    logging("${device.displayName} - BasicReport received, ignored, value: ${cmd.value}","info")
}

def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelStopLevelChange cmd) {
    logging("${device.displayName} - SwitchMultilevelStopLevelChange received, ignoring {$cmd} ","info")
    //ignore
}

def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd) {
    logging("${device.displayName} - SwitchMultilevelReport received, value: ${cmd.value}","info")
	switch (cmd.value) {
        case 0:
            sendEvent(name: "windowShade", value: "closed")
            break
        case 99:
            sendEvent(name: "windowShade", value: "open")
            break
        default:
            sendEvent(name: "windowShade", value: "partially open")
            break
    }    
    sendEvent(name: "position", value: (cmd.value > 0) ? cmd.value+1 : 0)
}

def zwaveEvent(physicalgraph.zwave.commands.meterv3.MeterReport cmd) {
    logging("${device.displayName} - MeterReport received, value: ${cmd.scaledMeterValue} scale: ${cmd.scale} ep: $ep","info")
    switch (cmd.scale) {
        case 0:
            sendEvent([name: "energy", value: cmd.scaledMeterValue, unit: "kWh"])
            break
        case 2:
            sendEvent([name: "power", value: cmd.scaledMeterValue, unit: "W"])
            break
    }
    multiStatusEvent("${(device.currentValue("power") ?: "0.0")} W | ${(device.currentValue("energy") ?: "0.00")} kWh")
}

def zwaveEvent(physicalgraph.zwave.commands.centralscenev1.CentralSceneNotification cmd) {
    logging("${device.displayName} - CentralSceneNotification received, sceneNumber: ${cmd.sceneNumber} keyAttributes: ${cmd.keyAttributes}","info")
    log.info cmd
    def String action
    def Integer button
    switch (cmd.sceneNumber as Integer) {
		//For later use
    }
    log.info "button $button $action"
    sendEvent(name: "button", value: action, data: [buttonNumber: button], isStateChange: true)
}

def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
    logging("${device.displayName} - NotificationReport received for ${cmd.event}, parameter value: ${cmd.eventParameter[0]}", "info")
	//For later use
}

def parse(String description) {
    def result = []
    logging("${device.displayName} - Parsing: ${description}")
    if (description.startsWith("Err 106")) {
        result = createEvent(
                descriptionText: "Failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
                eventType: "ALERT",
                name: "secureInclusion",
                value: "failed",
                displayed: true,
        )
    } else if (description == "updated") {
        return null
    } else {
        def cmd = zwave.parse(description, cmdVersions())
        if (cmd) {
            logging("${device.displayName} - Parsed: ${cmd}")
            zwaveEvent(cmd)
        }
    }
}

def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
    def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
    if (encapsulatedCommand) {
        logging("${device.displayName} - Parsed SecurityMessageEncapsulation into: ${encapsulatedCommand}")
        zwaveEvent(encapsulatedCommand)
    } else {
        log.warn "Unable to extract Secure command from $cmd"
    }
}

def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
    def version = cmdVersions()[cmd.commandClass as Integer]
    def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
    def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
    if (encapsulatedCommand) {
        logging("${device.displayName} - Parsed Crc16Encap into: ${encapsulatedCommand}")
        zwaveEvent(encapsulatedCommand)
    } else {
        log.warn "Unable to extract CRC16 command from $cmd"
    }
}

def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
    def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
    if (encapsulatedCommand) {
        logging("${device.displayName} - Parsed MultiChannelCmdEncap ${encapsulatedCommand}")
        zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
    } else {
        log.warn "Unable to extract MultiChannel command from $cmd"
    }
}

private logging(text, type = "debug") {
    if (settings.logging == "true") {
        log."$type" text
    }
}

private secEncap(physicalgraph.zwave.Command cmd) {
    logging("${device.displayName} - encapsulating command using Secure Encapsulation, command: $cmd","info")
    zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}

private crcEncap(physicalgraph.zwave.Command cmd) {
    logging("${device.displayName} - encapsulating command using CRC16 Encapsulation, command: $cmd","info")
    zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
}

private multiEncap(physicalgraph.zwave.Command cmd, Integer ep) {
    logging("${device.displayName} - encapsulating command using MultiChannel Encapsulation, ep: $ep command: $cmd","info")
    zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:ep).encapsulate(cmd)
}

private encap(physicalgraph.zwave.Command cmd, Integer ep) {
    encap(multiEncap(cmd, ep))
}

private encap(List encapList) {
    encap(encapList[0], encapList[1])
}

private encap(Map encapMap) {
    encap(encapMap.cmd, encapMap.ep)
}

private encap(physicalgraph.zwave.Command cmd) {
    if (zwaveInfo.zw.contains("s")) {
        secEncap(cmd)
    } else if (zwaveInfo.cc.contains("56")){
        crcEncap(cmd)
    } else {
        logging("${device.displayName} - no encapsulation supported for command: $cmd","info")
        cmd.format()
    }
}

private encapSequence(cmds, Integer delay=250) {
    delayBetween(cmds.collect{ encap(it) }, delay)
}

private encapSequence(cmds, Integer delay, Integer ep) {
    delayBetween(cmds.collect{ encap(it, ep) }, delay)
}

private List intToParam(Long value, Integer size = 1) {
    def result = []
    size.times {
        result = result.plus(0, (value & 0xFF) as Short)
        value = (value >> 8)
    }
    return result
}

private Map cmdVersions() {
    //[0x5E: 1, 0x86: 1, 0x72: 2, 0x59: 1, 0x73: 1, 0x22: 1, 0x31: 5, 0x32: 3, 0x71: 3, 0x56: 1, 0x98: 1, 0x7A: 2, 0x20: 1, 0x5A: 1, 0x85: 2, 0x26: 3, 0x8E: 2, 0x60: 3, 0x70: 2, 0x75: 2, 0x27: 1]
    [0x5E: 1, 0x26: 3, 0x85: 2, 0x8E: 2, 0x59: 1, 0x55: 1, 0x86: 1, 0x72: 2, 0x5A: 1, 0x73: 1, 0x98: 1, 0x32: 3, 0x70: 2, 0x56: 1, 0x71: 3, 0x75: 2, 0x60: 3, 0x5B: 1, 0x7A: 2, 0x22: 1, 0x20: 1]
}

private parameterMap() {[
        [key: "switchType", num: 20, size: 1, type: "enum", options: [
                0: "0 - Momentary Switches",
                1: "1 - Toggle Switches",
                2: "2 - Single Momentary Switch"
        ], def: "2", title: "Switch Types",
         descr: ""],
        [key: "inputsOrientation", num: 24, size: 1, type: "enum", options: [
                0: "0 - S1 - Channel 1; S2 - Channel 2",
                1: "1 - S1 - Channel 2; S2 - Channel 1"
        ], def: "0", title: "Inputs Orientation",
         descr: ""],
        [key: "outputsOrientation", num: 25, size: 1, type: "enum", options: [
                0: "0 - Q1 - Channel 1; Q2 - Channel 2",
                1: "1 - Q1 - Channel 2; Q2 - Channel 1"
        ], def: "0", title: "Outputs Orientation",
         descr: ""],
        [key: "powerMeasure", num: 60, size: 1, type: "enum", options: [
                0: "0 - Inactive",
                1: "1 - Active"
        ], def: "0", title: "Measure Power Consumed by Device",
         descr: ""],
        [key: "powerReportsonChange", num: 61, size: 2, type: "number", def: 15, min: 0, max: 500, title: "Power Reports on Change",
         descr: "0 - Reports Disabled; 1-500 (1-500%) Change in power"],
        [key: "powerReportsPeriodic", num: 62, size: 2, type: "number", def: 3600, min: 0, max: 32400, title: "Periodic Power Reports",
         descr: "0 - Reports Disabled; 30-32400 (30-32400s) Report Interval"],
        [key: "energyReportsonChange", num: 65, size: 2, type: "number", def: 10, min: 0, max: 500, title: "Energy Reports on Change",
         descr: "0 - Reports Disabled; 1-500 (0.01-5kWh) Change in Energy"],
        [key: "energyReportsPeriodic", num: 66, size: 2, type: "number", def: 3600, min: 0, max: 32400, title: "Periodic Energy Reports",
         descr: "0 - Reports Disabled; 30-32400 (30-32400s) Report Interval"],
        [key: "forceCalibration", num: 150, size: 1, type: "enum", options: [
                0: "0 - Device is not calibrated",
                1: "1 - Device is calibrated",
                2: "2 - Force Device calibration"
        ], def: "0", title: "Force Calibration",
         descr: ""],
        [key: "operatingMode", num: 151, size: 1, type: "enum", options: [
                1: "1 - Roller Blind Mode, with positioning",
                2: "2 - Venetian Blind Mode, with positioning",
                3: "3 - Gate Mode, without positioning",
                4: "4 - Gate Mode, with positioning",
				5: "5 - Roller Blind Mode, with buil-in driver",
				6: "6 - Roller Blind Mode, with buil-in driver (impulse)"
        ], def: "1", title: "Roller Shutter operating modes",
         descr: ""],
        [key: "vBlindTimeFullSlats", num: 152, size: 4, type: "number", def: 150, min: 0, max: 90000, title: "Venetian blind - time of full turn of the slats",
         descr: "0-90000 -> 0 - 900s Time of turn"],
        [key: "setSlatsPrevPos", num: 153, size: 1, type: "enum", options: [
                0: "0 - Slats return to previously set position only in case of the main controller operation",
				1: "1 - Slats return to previously set position in case of the main controller operation, momentary switch operation, or when the limit switch is reached",
				2: "2 - Slats return to previously set position in case of the main controller operation, momentary switch operation, when the limit switch is reached or after receiving the Switch Multilevel Stop control frame"
        ], def: "1", title: "Set slats back to previous position",
         descr: ""],		 
        [key: "TDelayafterEndSwitch", num: 154, size: 2, type: "number", def: 10, min: 0, max: 600, title: "Delay motor stop after reaching end switch",
         descr: "0-600 -> 0 - 60s Time"],		 
        [key: "motorOpDetect", num: 155, size: 2, type: "number", def: 10, min: 0, max: 255, title: "Motor operation detection",
         descr: "0-Reaching a limit switch will not be detected; 1-255W Report Interval"],	
        [key: "timeUpMov", num: 156, size: 4, type: "number", def: 6000, min: 1, max: 90000, title: "Time of up movement",
         descr: "1-90000 (0.01 - 900.00s) Movement Time"],
        [key: "timeDownMov", num: 157, size: 4, type: "number", def: 6000, min: 1, max: 90000, title: "Time of down movement",
         descr: "1-90000 (0.01 - 900.00s) Movement Time"]
]}

@philh30 I’ll test your DH as soon as I can. And thank you so much for all the help.

I’m aware of the issue with routines and shades, and have been able to get it to work with the Qubino shutter. I don’t think anything should be different with this device. Routines call the setLevel() function passing a string as the desired level. Close passes a string value of 0.

You shouldn’t need to masquerade as a dimmer to get it to work. You shouldn’t even need to create a function of a different name since you can overload the setLevel function to handle strings.

I think you’re misinterpreting that error. I don’t see any use of the method size() in the code that you posted for your setLevel function.

Probably I am…
I don’t know much about coding, I’m a PLC programmer for a living, I’m used to low level coding like assembler. Higher level languages for me the stop at VB Script, and SQL.

I have tried lots of different approaches and none of them worked, apart from using the shades as a dimmer. And apart from that, in the documentation:

# reviewed 2018-02-15
name: Window Shade
status: proposed
attributes:
  windowShade:
    schema:
      type: object
      properties:
        value:
          $ref: OpenableState
        constraints:
          type: object
          properties:
            values:
              type: array
              items:
                $ref: OpenableState
      required:
        - value
    type: ENUM
    values:
      - closed
      - closing
      - open
      - opening
      - partially open
      - unknown
    enumCommands:
      - command: close
        value: closed
      - command: open
        value: open
    actedOnBy:
      - presetPosition
commands:
  close:
    arguments: [
      ]
  open:
    arguments: [
      ]
  presetPosition:
    arguments: [
      ]
public: true
id: windowShade
version: 1

No setLevel anywhere…

Tomorrow night I will be testing your DH, and leave you feedback. Probably it’s way better than mine as this is my first DH ever. BTW my hub is V3 Europe Version.

I think they’ve stopped maintaining the capability reference. It still lists Window Shade as “proposed” when it’s obviously moved past that.

Hi ,
I’m using @philh30 DH for fgr-223 (I have Roller shutter ) and it’s working great !
I also tried with test routine and it worked .

Hope that helps

@zvika77 Great! Thanks for testing!