Automatic Shading with Astro, OpenWeather and MqTT (for Shelly 2.5)

Tags: #<Tag:0x00007f3878c169b8> #<Tag:0x00007f3878c16738> #<Tag:0x00007f3878c165f8>

Hello OpenHABers,

here i want to share my Project to have a automatic shading with
OpenHab, Astro, OpenWeather and MqTT.

First of all: The author of the Rule is Florian Schießl. I would also like to thank him for allowing me to publish my project here. His origin project can be found here:
https://www.forwardme.de/2018/03/12/elektrischen-rolladen-abhaengig-von-sonnenstand-und-wetter-steuern-lassen-mit-openhab2/

What is needed?
Software:
OpenHab 2.4
MQTT
Weather binding installed
Astro Binding installed
OpenHab Cloud Connector

Hardware:
RaspBerry 3
Shelly 2.5 shutter actuator

What is the whole thing doing?
Depending on the position of the sun, temperature and clouds, the rule can shade a room via an electric shutter.
In addition, a push message is sent at the beginning and end of the shading.

How it behaves with sun direction and sun altitude, you will find here:
https://www.sunearthtools.com

The whole thing is of course individually configurable.
And works like this:

It should be a circuit depending on sun angle and altitude.
Of course, this should take effect but only from a certain outside temperature, because in winter we are then rather happy about the light and if rain or even clouds are in the sky, a shading is not necessary. If the shutter is already closed when it is to be closed, nothing should happen.
Likewise, nothing should happen, the shutter in trying to open it has now changed state.

Here first the corresponding item file: (Exemplary for the west side of our house)

Group gruppeRolladen_West
// Konfiguration Rollo-Automatik West
Switch Rolloautomatik "Rollo-Automatik aus/an"
Switch Rolloautomatik_oeffnen "Rolladen öffnen aus/an"
Number Rolloautomatik_zielwert "Schließen auf [%d Prozent]"
Number Rolloautomatik_temp_min "Temperatur größer [%d °C]"
Number Rolloautomatik_wolken_max "Bewölkung weniger als [%d Prozent]"
Number Rolloautomatik_azimuth_start "Sonnenrichtung größer gleich [%d °]"
Number Rolloautomatik_elevation_ende "Sonnenhöhe kleiner gleich [%d °]"
DateTime Rolloautomatik_start_last "Letzte Ausführung (Rollo ab)"
DateTime Rolloautomatik_ende_last "Letzte Ausführung (Rollo hoch)"

Here is the rule:

var boolean log = true
rule "Rollos West abfahren"
when Item Azimuth changed
then
    val String logPrefix = 'Rolloautomatik (Rollo West ab) - '
    if (log) logInfo('rules', logPrefix + 'Regel wurde gestartet')
    
    var String timeLast = 'xxxx-xx-xx'
    if (Rolloautomatik_start_last.state == NULL) {
        if (log) logInfo('rules', logPrefix + 'Erstmalige Ausführung am System, Belegung mit Initialwert')
    } else {
        timeLast = Rolloautomatik_start_last.state.toString().substring(0,10)
    }
	var String timeNow = now.toString().substring(0,10)

    if (Rolloautomatik.state == ON) {
        if (timeNow != timeLast) {
            if (Azimuth.state > Integer::parseInt(Rolloautomatik_azimuth_start.state.toString())) {
                if (AussenTemp.state > Integer::parseInt(Rolloautomatik_temp_min.state.toString())) {
                    if (LocalWeatherAndForecast_Current_Cloudiness.state  <= Integer::parseInt(Rolloautomatik_wolken_max.state.toString())) { if (Elevation.state > Integer::parseInt(Rolloautomatik_elevation_ende.state.toString())) {
                            // Rollos runterfahren
                            if (log) logInfo('rules', logPrefix + 'Rollos werden abgefahren')
                            gruppeRolladen_West.members.forEach[i|
                                if (i.state <= Integer::parseInt(Rolloautomatik_zielwert.state.toString())) {
                                    if (log) logInfo('rules', logPrefix + 'Fahre Rolladen auf ' + Rolloautomatik_zielwert.state.toString() + '%: ' + i.name)
                                    i.sendCommand(Integer::parseInt(Rolloautomatik_zielwert.state.toString()))
                                } else {
                                    if (log) logInfo('rules', logPrefix + 'Rolladen ist bereits weiter geschlossen (' + i.state.toString() + '%) als er geschlossen werden sollte und wird daher ignoriert')
                                }
                            ]
                            // Letzte Ausführung mit entsprechendem Zeitstempel belegen
                            sendBroadcastNotification("Verschattung Westseite aktiv") //Pushnachricht
                            Rolloautomatik_start_last.postUpdate(now.toString())
                        } else {
                            if (log) logInfo('rules', logPrefix + 'Elevation für wieder abfahren (' + Rolloautomatik_elevation_ende.state.toString() + ') ist groesser als aktuelle (' + Elevation.state.toString() + ')')
                        }
                    } else {
                        if (log) logInfo('rules', logPrefix + 'Mindestbewoelkung (' + Rolloautomatik_wolken_max.state.toString() + ') wurde unterschritten (' + LocalWeatherAndForecast_Current_Cloudiness.state.toString() + ')')
                    }
                } else {
                    if (log) logInfo('rules', logPrefix + 'Mindest-Temperatur (' + Rolloautomatik_temp_min.state.toString() + ') wurde nicht erreicht durch aktuelle Temperatur (' + AussenTemp.state.toString() + ')')
                }
            } else {
                if (log) logInfo('rules', logPrefix + 'Azimuth (' + Azimuth.state.toString() + ') hat noch nicht Schwellwert (rolloautomatik_azimuth_start.state.toString()) erreicht')
            }
        } else {
            if (log) logInfo('rules', logPrefix + 'Automatik heute bereits einmal gelaufen, wird daher ignoriert')
        }
    } else {
         if (log) logInfo('rules', logPrefix + 'Beende, da Automatik generell nicht aktiv')
    }
end

rule "Rollo West wieder öffnen"
when Item Elevation changed
then
    val String logPrefix = 'Rolloautomatik (Rollo West hoch) - '
    if (log) logInfo('rules', logPrefix + 'Regel wurde gestartet')

    var String timeLastEnde = 'xxxx-xx-xx'
    if (Rolloautomatik_ende_last.state == NULL) {
        if (log) logInfo('rules', logPrefix + 'Erstmalige Ausführung am System, Belegung mit Initialwert')
    } else {
        timeLastEnde = Rolloautomatik_ende_last.state.toString().substring(0,10)
    }

    var String timeLastStart = 'yyyy-yy-yy'
    if (Rolloautomatik_start_last.state == NULL) {
        if (log) logInfo('rules', logPrefix + 'Erstmalige Ausführung am System, Belegung mit Initialwert')
    } else {
        timeLastStart = Rolloautomatik_start_last.state.toString().substring(0,10)
    }

	var String timeNow = now.toString().substring(0,10)


    if (Rolloautomatik_oeffnen.state == ON) {
        if (Elevation.state <= Integer::parseInt(Rolloautomatik_elevation_ende.state.toString())) {
            if (timeLastStart == timeNow) {
                if (timeLastEnde != timeNow) {
                    // Rollos wieder hoch
                    if (log) logInfo('rules', logPrefix + 'Rollos werden hinaufgefahren')
                    gruppeRolladen_West.members.forEach[i|
                        if((Rolloautomatik_zielwert.state as Number).intValue <= (i.state as Number).intValue +5 && (Rolloautomatik_zielwert.state as Number).intValue >= (i.state as Number).intValue- 5) {
                            if (log) logInfo('rules', logPrefix + 'Fahre Rolladen auf 0%: ' + i.name)
                            i.sendCommand(0)
                        } else {
                            if (log) logInfo('rules', logPrefix + 'Fahre Rolladen nicht auf 0%, da dieser zwischenzeitlich manuell verändert wurde: ' + i.name)
                        }
                    ]
                    // Letzte Ausführung mit entsprechendem Zeitstempel belegen
                    sendBroadcastNotification("Verschattung Westseite beendet") //Pushnachricht
                    Rolloautomatik_ende_last.postUpdate(now.toString())
                } else {
                    if (log) logInfo('rules', logPrefix + 'Beende, da heute bereits ein automatisches Wiederhochfahren stattfand')
                }
            } else {
                if (log) logInfo('rules', logPrefix + 'Beende, da heute noch keine Ausführung stattfand. Demzufolge kann auch kein automatisches Öffnen gewollt sein')
            }
        } else {
            if (log) logInfo('rules', logPrefix + 'Beende, da Elevation (' + Elevation.state.toString() + ') nicht  kleiner der eingestellten Elevation (' + Rolloautomatik_elevation_ende.state.toString()+ ') war')
        }
    } else {
        if (log) logInfo('rules', logPrefix + 'Beende, da Automatik generell nicht aktiv')
    }
end

The rule asks whether the automatic system has generally been running, possibly already running, whether the temperature, cloudiness and current status of the blinds are suitable and if so they will be driven down.

Important: All blinds must be assigned to group groupRolladen_West.

If a shutter is in a different state than it should have been when it was shut down at the calculated time of going back up, it will not automatically open.

And here the relevant part from the sitemap:

Text label="Konfiguration Verschattung West" icon="rollershutter" {
        Frame label="Aktivieren der Automatik" {
        Switch item=Rolloautomatik
        Setpoint item=Rolloautomatik_zielwert minValue=0 maxValue=100 step=1
        }
        Frame label="Rollo ab, wenn..." {
        Setpoint item=Rolloautomatik_temp_min minValue=0 maxValue=35 step=1
        Text item=AussenTemp label="Temperatur Aussen [%.1f °C]" icon="temperature" valuecolor=[>27="red",>20="orange",>10="purple",>5="green",<=5="blue"]
        Setpoint item=Rolloautomatik_wolken_max minValue=0 maxValue=100 step=1
        Text item=LocalWeatherAndForecast_Current_Cloudiness icon="sun_clouds" label="Bewölkung [%.0f %%]"
        Setpoint item=Rolloautomatik_azimuth_start minValue=0 maxValue=360 step=1
        Default item=Azimuth icon="niveau" label="Azimuth"
        }
        Frame label="Rollo hoch, wenn..." {
        Switch item=Rolloautomatik_oeffnen
        Setpoint item=Rolloautomatik_elevation_ende minValue=-20 maxValue=60 step=1
        Default item=Elevation label="Elevation"

It look like this in the sitemap:

Have Fun!

9 Likes

Hi, great script.

How do you manage for the other directions (i.e. south & east)? Do you run this script for each direction?

Hi Dominik,

yes at the moment I use it for two directions.
Two rules for each.

Nice work! Currently planning something similar, so I’ll be taking this one as a good example =)
Additionally I’ll close the shutters in case the window is open and it starts raining.

Hi,

I love the script, but I have to issue with it.

Without knowing why, it happend to me already twice, that the rules start to be active at 0:00. As they then already run at 0:00 they do not start again. What I don’t get, the Azimut at this time should be far away from excecution value.

Does someone else have the same issue?

Further I am interested, how to reset the behaviour, that the rule is blocked for further execution? As I understood, the rule runs only if timeNow is not timeLast. But, how to reset timeLast?

Any helpful guess would be appreciated.

Hi Dominik,

Maybe you can reset the timeLast with the RestApi or restart OpenHab.

Br Peter

Thanks, but restart OpenHab is not an option.
My idea would be to have a switch, which would reset the value via postUpdate command.

After analysing the logs, it is clear to me, why the rule run after midnight.

TimeNow !=TimeLast and the azimuth is still above the given value: 2019-06-20 00:00:30.491 [vent.ItemStateChangedEvent] - Azimuth changed from 337.4839890387086 ° to 337.6765460913182 °

I added if (now.isBefore((Sunset_Time.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli) ||now.isAfter((Sunrise_Time.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli)) which should help to execute the script only during daytime.

Maybe interesting for someone else as well.