OpenHAB CULfw for Somfy RTS Rollershutters

Hi Christoph,

thank you very much for this much needed tutorial. I got the CUL Switch for programming the motors on my sitemap and also a Switch Item for the rollershutters. I configured the real Item for the Shutters as “Rollershutter item” because I kind of missed the part where you configured the actual rollershutters as actual item. You justed wrote in the rules

when
	** Item AllShutter** received command OFF
    then

but in the tutorial you never defined the item for the rollershutters.

I think that the scripts are working (started from the command line, but I cannot get the rollingCode). However, the items on my sitemap seem to have no connection. I don’t know if it didn’t get the rule or something else…
Is there a way to find out the rollingCode?

Another question: Could you show me the definition of your rollershutter item? Maybe I have the wrong item type or something… I am not quite sure… I tried some out, but they did not work.
For debugging reasons I put the rollingCode on my sitemap, but as string and also as number he only shows an “-”.

Anyway, I will keep you updated! Thank you again for the scripts and configuration.

Hi Floh,

you have not missed anything. I made a mistake. I forgot to implement the second Switch, after programming the new Address. I corrected my tutorial. So there is no Rollershutter item. The reason is, that I would not use it.
There are two points:

  1. My Shutter behave very strange: If I send the command once. They only move a small step.
  2. My shutters are completely opened or close.

This is why I do not need a Rollershutter item. But you can easily adjust my rule. Just replace the Line

rule "Shutter UP"
    when
	Item MyShutterButton received command ON
    then

with

rule "Shutter UP"
    when
	Item <Your Shutter Item> received command UP
    then

and the same for DOWN.

Best regards,
Christoph

EDIT: changes to the description and the script to reflect all feedback until 21.04.2021

As announced this is a rule for sending commands to Somfy RTS devices such as rollershutters, awnings, etc.
create 4 items as described in the rule and follow the instructions.

.rules file

import org.openhab.core.model.script.ScriptServiceUtil
/*
=======================================================================================================================
DESCRIPTION:
  1. Create a group with a generic group name like `gRTS`. Note: this is not a group which represents a physical device/thing, it is a logical group.
     If you choose a different group name, make sure to change the rule accordingly.
  2. Add the following item for each device with the following syntax:
     <itemName>_control			(Type: STRING; item which sends commands to this rule. Valid commands: 0 or UP, STOP or MY, 100 or DOWN, PROG, NUMBER between 1 and 99)
     <itemName>_rollingCode		(Type: NUMBER; persisted item, which stores the last rollingCode)
     <itemName>_calibration		(Type: NUMBER; persisted item, which stores the duration of a half cycle from 0% to 100% in seconds. Required to control the motor with a command like 60[%])
     <itemName>_address			(Type: STRING, 6 digit; persisted item, which stores address of device)
  3. Add all items with suffix `_control` to your group. Do NOT add other items to that group.
  4. - specify the name and location of your python script
     - items for Rolling Code, Calibration and Address may not be NULL (use REST API to set their values) and have to be persisted.
     - If there are any problems with this script, set debug variable in this rule to true
=======================================================================================================================

=======================================================================================================================
CHANGE VALUES ACCORDINGLY: */

var Boolean debug = false
val String fileName = "RTS.rules"
val String scriptCommand = "python3"
val String scriptName = "/etc/openhab/scripts/RTS.py"
rule "RTS"
//======================================================================================================================

when
    Member of gRTS received command
then
    if (debug) logInfo(fileName,"Rule started")
    var String strThing = triggeringItem.name.toString().split("_").get(0)
    var String strItem = triggeringItem.name.toString()
    var String strCommand = receivedCommand.toString()
        if (debug) logInfo(fileName, "Thing: '{}', Item: '{}', Command: '{}'",strThing,strItem,strCommand)
    var String strRTSCode = ""
    var Integer intCommand = 0
    var Float fSeconds = 0.0

//find regular commands
    switch strCommand.toUpperCase {
        case "" : {
            logInfo(fileName,"Item: '{}' received empty command",strItem)
            return
        }
        case "MY", case "STOP" : strRTSCode = "1"
        case "UP" : strRTSCode = "2"
        case "DOWN" : strRTSCode = "4"
        case "PROG" : strRTSCode = "8"
//find number values in command
        default: {
	        if (debug) logInfo(fileName,"No standard commands submitted")
            var strCommand2 = transform("REGEX", "\\D*(\\d+)\\D*", strCommand)
                if (debug) logInfo(fileName,"strCommand2: '{}'",strCommand2)
            if (strCommand2 !== null) {
                intCommand = Integer.parseInt(strCommand2)
                    if (debug) logInfo(fileName,"intCommand: '{}'",intCommand.toString())
                switch intCommand {
                    case 0 : strRTSCode = "2"
                    case 100 : strRTSCode = "4"
                    case intCommand > 100 : logInfo(fileName,"Item: '{}' received command '{}': not in range [0..100]",strItem,intCommand.toString())
//calculate time when motor has to stop
                    default: {
                        strRTSCode = "4"
                        var Float fCalibration = (ScriptServiceUtil.getItemRegistry.getItem(strThing+"_calibration").state as DecimalType).floatValue
		            if (debug) logInfo(fileName, "fCalibration: '{}'",fCalibration.toString())
                        fSeconds = intCommand.floatValue * fCalibration / 100
                            if (debug) logInfo(fileName, "fSeconds: '{}'",fSeconds.toString())
                    }
                }
            } else {
                logInfo(fileName,"Item: '{}' received unknown command '{}'",strItem,strCommand)
                return
            }
        }
    }

//prepare serial data payload for python script
//Syntax: Ys + EncryptionKey=A1 + Command + 0 + RollingCode + Address
    var Integer intRollingCode = (ScriptServiceUtil.getItemRegistry.getItem(strThing + "_rollingCode").state as DecimalType).intValue
        if (debug) logInfo(fileName, "intRollingCode: '{}'",intRollingCode.toString())
    var String strRollingCodeHex = String::format("%04X", intRollingCode)
    var String strAddress = ScriptServiceUtil.getItemRegistry.getItem(strThing + "_address").state.toString()
        if (debug) logInfo(fileName, "PAYLOAD: strRTSCode: '{}', strRollingCodeHex: '{}', strAddress: '{}'",strRTSCode,strRollingCodeHex,strAddress)
    var String strPayLoad = "YsA1" + strRTSCode + "0" + strRollingCodeHex + strAddress + "\n"
        if (debug) logInfo(fileName, "PAYLOAD STRING: '{}'",strPayLoad)
    var result = executeCommandLine(Duration.ofSeconds(2),scriptCommand, scriptName, strPayLoad)
        logInfo(fileName, "Python script result: {}",result)
    ScriptServiceUtil.getItemRegistry.getItem(strThing + "_rollingCode").postUpdate(intRollingCode + 1)

//send STOP after fSeconds if command is number from 1 to 99
    if (fSeconds > 0.0) {
        strRTSCode = "1"
        strRollingCodeHex = String::format("%04X", intRollingCode +1)
        strPayLoad = "YsA1" + strRTSCode + "0" + strRollingCodeHex + strAddress + "\n"
            if (debug) logInfo(fileName, "PAYLOAD STRING timer triggered: '{}'",strPayLoad)
        createTimer(now.plusSeconds(fSeconds.longValue), [ |
            var result = executeCommandLine(Duration.ofSeconds(2),scriptCommand, scriptName, strPayLoad)
                logInfo(fileName, "Python script result timer triggered: {}",result)
            ScriptServiceUtil.getItemRegistry.getItem(strThing + "_rollingCode").postUpdate(intRollingCode + 2)
        ])
    }
end

Python script (copy to /etc/openhab/scripts/)

#!/usr/bin/python3
# cudos go to chacha
# Install pyserial from https://pythonhosted.org/pyserial/pyserial.html#installation
# add 'stty -F /dev/ttyUSB0 -hupcl' to file /etc/rc.local
import serial
import sys

payLoad = sys.argv[1]
print("Python script received payload " + payLoad)
ser = serial.Serial('/dev/ttyUSB0', 38400, timeout=.1)
	#or: ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
ser.write(payLoad.encode)
print ser.readline()
	#todo: throw error if not ok
ser.close()

Reason for editing: adding automatic stop to rule

RTS allows to have a gap of 100 between the counter of transmitter and receiver.
You probably will have that problem in the near future

do you know if both packages are required?

I’d guess you need transport-serial for openhab bindings

Hey Oliver,

many thanks for the script. I tried everything out and got the following error:
If the state of the rollingCode is e.g. ‘0x0001’:

2021-03-31 10:53:26.612 [INFO ] [.model.script.Shutter_BueroFlo.rules] - Rule started
2021-03-31 10:53:26.618 [INFO ] [.model.script.Shutter_BueroFlo.rules] - Thing: 'ShutterBueroFlo', Item: 'ShutterBueroFlo', Command: 'DOWN'
2021-03-31 10:53:26.622 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'Shutter_BueroFlo-1' failed: Could not cast 0x0001 to org.openhab.core.library.types.DecimalType; line 84, column 35, length 89 in Shutter_BueroFlo

If the state of the rollingCode is e.g. ‘0001’ (i thought maybe he doesn’t like the leading ‘0x’):

2021-03-31 10:56:09.423 [INFO ] [.model.script.Shutter_BueroFlo.rules] - Rule started
2021-03-31 10:56:09.430 [INFO ] [.model.script.Shutter_BueroFlo.rules] - Thing: 'ShutterBueroFlo', Item: 'ShutterBueroFlo', Command: 'DOWN'
2021-03-31 10:56:09.440 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'Shutter_BueroFlo-1' failed: Could not cast 0001 to org.openhab.core.library.types.DecimalType; line 84, column 35, length 89 in Shutter_BueroFlo

The referenced line in your rule is:

var String strPayLoad = "YsA1" + strRTSCode + "0" + strRollingCodeHex + strAddress + "\n"

My question is: How do I need to save the rollingCode state? I tried it in decimal values (1 and 101) as well and got the same error.

As short background:
I added a fifth item: The shutter itself, so that I could interact with it on my sitemap.

Rollershutter ShutterBueroFlo "Rolladen Büro Flo" <rollershutter> (gShutter_BueroFlo)

Of course I made sure, that all items are in the same group (gShutter_BueroFlo). And obviously the rule is triggered, as I can see in the logs.

Maybe it has something to do with the type of variable you store the rolling code in and after that parse it to the strPayLoad variable.
I found this link and that link that might help with the format of the rollingCode. Unfortanetly I am not expierienced enough in coding.

I would appreciate your help!

Hi Flo,
your Item hast to be of type number dimensionless.
what OH version are on?

maybe a misunderstandig. Please put all _control items into one group. not alle items belonging to buroflo.

All Shutter related items are in the group “gShutter_BueroFlo”. And the group seems to work.

I am on OH3 with openHabian 3.0.1 on a Rasperry Pi 4.
Which item do you mean? The rollershutter item (the actual rollershutter), or one of the others (calibration or rolling code)?

No, if you have 3 shutters then you need to assign 3 items to your group - only the ones that receive commands, in my example all Items with suffix control
Otherwise the script will be executed every time you change calibration or rolling code

Hi Flo,
what do you mean by this?

Hey Oliver,
Sorry for the late response. I meant, that I created a fith item (a rollershutter item) where I can send the commands UP and DOWN via the PaperUI.

Anyway, I deleted them after your response and set up everything like you said. all _control-items in one group, item type number:dimensionless…

He still seems to have a problem with the script:

2021-04-06 13:57:49.372 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'Shutter_BueroFlo-1' failed: Could not cast 0x0200 to org.openhab.core.library.types.DecimalType; line 84, column 35, length 89 in Shutter_BueroFlo

in the same referenced line I mentioned earlier… I tested the rollingCode out as string, number, number:dimensionless and everything failed.
What would help me is, if you could let me see the actual content of your shutter related items, so e.g. I have my four items _control, _rollingcode, _calibration and _address, and they look like this:

_control:

Number:Dimensionless ShutterBueroFlo_control "Command [%d]" (gShutter_BueroFlo)

(gShutter_BueroFlo) is the groupname where the _control-item is a member of (and noone else).

My _rollingCode file looks like this:

String ShutterBueroFlo_rollingCode "RollingCode [%s]"

My _calibration file looks like this:

Number ShutterBueroFlo_calibration "Calibration: [%d]%%"

And my _address file looks like this:

String ShutterBueroFlo_address "Address: [%s]"

Now somehow the script seems to have a problem with the rollingCode. As I said earlier: I tried several itemtypes (number, number:dimensionless, string) and the two options “0x0200” and “0200”. But nothing worked. The script always says, that it could not cast “0x0200” or “0200” to the decimaltype-library from openHAB.
I really appreciate the help! thanks again!

excuse my. My docs are wrong. rollingCode has to be NUMBER.
don’t forget to set the state via REST API to a proper value

the overall idea behind this script is, that you can add as many rollershutters as you want without the need to have multiple copies of this script.
In case you have two more rollershutters, just add each _control item to your existing group gShutter_BueroFlo. Of course then it would make sense to rename the group to a more general name.

Hey Oliver,

thank you very much!! I got it working!! I can post my config next week, when I have more time. I needed to make little adjustments, because pyserial had a problem with the command. But this was a minor change. If anyone is interested in how to integrate it to Alexa I can post this as well.

thank you again! I am more than happy!

Hi Flo,
could you post the changes to the py script?

One more question to the nanoCul. Is this device working out of the box? Or do you have to change the Firmware or add specific drivers?

Hi Oliver,

mine worked out of the box.

Hey Oliver,

sorry for the wait. I had a lot to do… But, as promised, here my configuration including the connection to Amazon Alexa.

I mentioned my hardware (nanoCUL-Stick and openhabian 3 on a Rasperry Pi 4) earlier in this thread.

Let’s begin:
under /etc/openhab/rules the rule for the shutters:

import org.openhab.core.model.script.ScriptServiceUtil
/*
=======================================================================================================================
DESCRIPTION:
    Change the following data in this script: Group name, scriptName
    All devices have to be a member of the group.
    Syntax of item names:
     <itemName>_control                 (Type: STRING; valid commands: 0 or UP, STOP or MY, 100 or DOWN, PROG, NUMBER:dimensionless [1..99])
     <itemName>_rollingCode             (Type: STRING; persisted item, which stores the last rollingCode)
     <itemName>_calibration             (Type: NUMBER; persisted item, which stores the duration of a half cycle from 0% to 100% in seconds. Required to control the motor with a command like 60[%])
     <itemName>_address                 (Type: STRING; persisted item, which stores address of device)
    Make sure that Items for rollingcode, Calibration and Address are not NULL
    If there are problems, set debug variable in this script to true
    still to be done:
      more error checking, esp. if item is null
      check return string of python script
      or use serial binding
=======================================================================================================================

=======================================================================================================================
CHANGE VALUES ACCORDINGLY: */

var Boolean debug = true
val String fileName = "Shutter_BueroFlo.rules"
val String scriptCommand = "python3"
val String scriptName = "/etc/openhab/scripts/RTS.py"
var Timer tScript = null
rule "RTS BueroFlo"
//======================================================================================================================

when
    Member of gShutter_BueroFlo received command
then
    if (debug) logInfo(fileName,"Rule started")
    tScript?.cancel
    var String strThing = triggeringItem.name.toString().split("_").get(0)
    var String strItem = triggeringItem.name.toString()
    var String strCommand = receivedCommand.toString()
        if (debug) logInfo(fileName, "Thing: '{}', Item: '{}', Command: '{}'",strThing,strItem,strCommand)
    var String strRTSCode = ""
    var Integer intCommand = 0
    var Float fSeconds = 0.0

//find regular commands
    switch strCommand.toUpperCase {
        case "" : {
            logInfo(fileName,"Item: '{}' received empty command",strItem)
            return
        }
        case "MY", case "STOP" : strRTSCode = "1"
        case "UP" : strRTSCode = "2"
        case "DOWN" : strRTSCode = "4"
        case "PROG" : strRTSCode = "8"
//find number values in command
        default: {
                if (debug) logInfo(fileName,"No standard commands submitted")
            var strCommand2 = transform("REGEX", "\\D*(\\d+)\\D*", strCommand)
                if (debug) logInfo(fileName,"strCommand2: '{}'",strCommand2)
            if (strCommand2 !== null) {
                intCommand = Integer.parseInt(strCommand2)
                    if (debug) logInfo(fileName,"intCommand: '{}'",intCommand.toString())
                switch intCommand {
                    case 0 : strRTSCode = "2"
                    case 100 : strRTSCode = "4"
                    case intCommand > 100 : logInfo(fileName,"Item: '{}' received command '{}': not in range [0..100]",strItem,intCommand.toString())
//calculate time when motor has to stop
                    default: {
                        strRTSCode = "4"
                        var Float fCalibration = (ScriptServiceUtil.getItemRegistry.getItem(strThing+"_calibration").state as DecimalType).floatValue
                            if (debug) logInfo(fileName, "fCalibration: '{}'",fCalibration.toString())
                        fSeconds = intCommand.floatValue * fCalibration / 100
                            if (debug) logInfo(fileName, "fSeconds: '{}'",fSeconds.toString())
                    }
                }
            } else {
                logInfo(fileName,"Item: '{}' received unknown command '{}'",strItem,strCommand)
                return
            }
        }
    }

//prepare serial data payload for python script
//Syntax: Ys + EncryptionKey=A1 + Command + 0 + RollingCode + Address
    var Integer intRollingCode = (ScriptServiceUtil.getItemRegistry.getItem(strThing + "_rollingCode").state as DecimalType).intValue
        if (debug) logInfo(fileName, "intRollingCode: '{}'",intRollingCode.toString())
    var String strRollingCodeHex = String::format("%04X", intRollingCode)
    var String strAddress = ScriptServiceUtil.getItemRegistry.getItem(strThing + "_address").state.toString()
        if (debug) logInfo(fileName, "PAYLOAD: strRTSCode: '{}', strRollingCodeHex: '{}', strAddress: '{}'",strRTSCode,strRollingCodeHex,strAddress)
    var String strPayLoad = "YsA1" + strRTSCode + "0" + strRollingCodeHex + strAddress + "\n"
        if (debug) logInfo(fileName, "PAYLOAD STRING: '{}'",strPayLoad)
    var result = executeCommandLine(Duration.ofSeconds(2),scriptCommand, scriptName, strPayLoad)
        logInfo(fileName, "Python script result: {}",result)
    ScriptServiceUtil.getItemRegistry.getItem(strThing + "_rollingCode").postUpdate(intRollingCode + 1)

//send STOP after fSeconds if command is number from 1 to 99
    if (fSeconds > 0.0) {
        strRTSCode = "1"
        strRollingCodeHex = String::format("%04X", intRollingCode +1)
        strPayLoad = "YsA1" + strRTSCode + "0" + strRollingCodeHex + strAddress + "\n"
            if (debug) logInfo(fileName, "PAYLOAD STRING timer triggered: '{}'",strPayLoad)
        tScript = createTimer(now.plusSeconds(fSeconds.longValue), [ |
            var result = executeCommandLine(Duration.ofSeconds(2),scriptCommand, scriptName, strPayLoad)
                logInfo(fileName, "Python script result timer triggered: {}",result)
            ScriptServiceUtil.getItemRegistry.getItem(strThing + "_rollingCode").postUpdate(intRollingCode + 2)
        ])
    }
end

under /etc/openhab/scripts the RTS-Python script, where I needed to change the coding of the Payload (notice the line with ser.write):

#!/usr/bin/python3
# cudos go to chacha
# Install pyserial from https://pythonhosted.org/pyserial/pyserial.html#installation
# add 'stty -F /dev/ttyUSB0 -hupcl' to file /etc/rc.local
import serial
import sys

payLoad = sys.argv[1]
print("Python script received payload " + payLoad)
ser = serial.Serial('/dev/ttyUSB0', 38400, timeout=.1)
        #or: ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
ser.write(payLoad.encode())
ser.close()

under /etc/openhab/items the next following items:
the _address.items-Item:

String ShutterBueroFlo_address "Address: [%s]"

the _calibration.items-Item:

Number ShutterBueroFlo_calibration "Calibration: [%d]%%"

the _control.items-Item:

Number:Dimensionless ShutterBueroFlo_control "Command [%d]"

the _rollingCode.items-Item:

Number ShutterBueroFlo_rollingCode "RollingCode [%s]"

Additionally I added a Rollershutter item, which is in the group of gShutter_BueroFlo. This triggeres the rule, mentioned above. More importantly I use this item for the connection to Alexa, but I will come to that later in the post. So here the Rollershutter item:

Rollershutter ShutterBueroFlo "Rolladen Flo Office" <rollershutter> (gShutter_BueroFlo)

As Oliver explained, we need a persistence for the rollingCode-item, so we need to change/update/create the file /etc/openhab/persistence/rrd4j.persist and add this line/lines:

Items {
  gShutter_BueroFlo* : strategy =  restoreOnStartup
}

To be able to control the shutters, I need to put them in my openhab-sitemap unter /etc/openhab/sitemaps:

Group item=BueroFlo label="Büro Flo" icon=office
                {
                        Switch item=ShutterBueroFlo icon=rollershutter label="Rolladen Büro Flo"
                        Slider item=ShutterBueroFlo_calibration icon=rollershutter label="Stand Rolladen Büro Flo"
                        Group item=BueroFlo_rolladeninfo label="Rolladeninfo"
                        {
                                Text item=ShutterBueroFlo_control icon=network label="Command Code [%s]"
                                Text item=ShutterBueroFlo_address icon=rollershutter label="Adresse [%s]"
                                Text item=ShutterBueroFlo_calibration icon=rollershutter label="Stand [%d]%%"
                                Text item=ShutterBueroFlo_rollingCode icon=rollershutter label="RollingCode [%s]"
                        }
                }

For troubleshooting reasons I also wanted to see the control-, address-, calibration- and rollingCode-items. So you can ignore the part after “Rolladeninfo”. The main items here are the switch and slider item for the control of the rollershutters.

Now, to implement Alexa in this whole setup (thanks again to all of you):
The first requirement to use Alexa is the openHAB Cloud Connector, see here.
The second requirement is the integration from openHAB Cloud to your Amazon Account. I found a very good step-by-step-guide here.

For Alexa to work, we need to make a change in the rollershutter item and add a few things:

Rollershutter ShutterBueroFlo "Rolladen Flo Office" <rollershutter> (gShutter_BueroFlo) {alexa="RangeController.rangeValue" [category="EXTERIOR_BLIND", friendlyNames="@Setting.Opening", supportedRange="0:100:10", unitOfMeasure="Percent", actionMappings="Close=0,Open=100,Lower=(-10),Raise=(+10)", stateMappings="Closed=0,Open=1:100"]}

After that it should pop up as a device in your Alexa App, when you scan for new devices in your Alexa App. Mappings and assignments in the Alexa App are up to you.

After that you can control the Rollershutters via your voice. So far - depending on the language - these commands work after saying “Alexa %Name of Rollershutter device%” in my configuration “Alexa Rolladen Büro Flo”:

"UP", "100", "OPEN", "AUF" -> for opening the shutter
"CLOSE", "0", "SHUT", "DOWN", "RUNTER" -> for closing the shutter

Of course you can configure your “own” commands by adding a scene(?) for Alexa, where you put in the actual command and map it to a saying like “Good night” (for closing the rollershutters). But I did not implement it until now. AND this is another topic and has nothing to do with this thread.

Oliver, sorry for the long post, you only wanted to know about the “.encode”-command in the python script, but I thought, that maybe someome could benefit from this post.

best regards to all and thank you again for this amazing script! :slight_smile:
Flo

Hi Flo,
thanks for the feedback that the python script needs to be changed. I still had no time to test as the nanocul is not working.

there is still some misunderstanding and you can make it easier as follows:

Number:Dimensionless ShutterBueroFlo_control "Command [%d]"

this is the item which triggers the rule and sends the command code to the rule. there is no need to have an additional switch item to trigger the rule.
not sure about the sitemap syntax, but in OH2 you could use the following:

Switch	item=ShutterBueroFlo_control	label="Rolladen Büro Flo"		icon="blinds"		mappings=[UP="Hoch", STOP="Stop", DOWN="Runter", 50="Halb runterfahren"]

with a tap on one of these four buttons you send the command to the rule.

Please also change the _control item to type STRING
If you do these changes, remove ALL items from your group EXCEPT _control item. the group item is not intended to group various items related to one physical thing. it is a container whose members are ALL “_control” items across several rollershutters