Shading automation

As this is a recurring question, I’m sharing my ruleset how I’ve setup automated shading using rollershutters and blinds.
Note it’s a full-blown setup that you will likely not need all the parts of. So take this as a starting point and remove and adapt the parts specific to your needs.
Sorry for all the German variable names. Some rainy day, maybe …

The code is definitely not ‘elegant’ and more repetitive and verbose than it would need to be efficient, but I prefer to have it this way as this isn’t on programming but on learning, adapting and to ease spotting errors.
Let me know the places where you need further explanation so I can enhance this documentation over time.

  • There’s a number of rollershutters and some venetian blinds (Raffstore or Jalousien in German) in my house.The latter allow for setting the lamella tilt angle, and all of my rollershutters and blinds are capable of driving to a particular percentage.
    A hint to anyone using Fibaro FGRM actuators to operate their venetian blinds: deploy the 2.4M4 milestone or newer to get lamella tilt control to work.
    The morning (eastern) side is exposed to sunlight in the morning hours from sunrise until about 12:45, and sun starts shining into the [after]noon (mostly southern) side windows at 10:15 until around 18:30 (6:30 PM).
    There’s a multi sensor on each side of the house to track luminance and temperature. The southern one is located inside, the eastern one outside of the house, that why there’s different thresholds they’re compared against.

  • The fundamental idea behind this automation is that while the zombie rate among nerds is said to be high, you usually don’t want darkness but just protection from the heat. So I just shade as little as possible.
    This is why I’ve built two zones (morning and noon/afternoon sides of the house) that are controlled independent from each other as the sun is moving around the house. There’s some overlap, but most of the time, one of the two sides is open. I’m also actually driving my shutters to around 35% only. This will block most direct sunrays but will allow for some light to enter. The ‘high’ protection level is to drive them to 75% which still allows for some lightray pass the spacing between lamellas.
    For venetian blinds, I’ve implemented a two-stage shading algorithm: up to a lower luminance threshold, the blinds will be lowered but lamellas will be set to open. When the upper threshold is reached, they, too, will be closed.

You can and must adapt all of these parameters to your specific housing situation.
Yes, it’s far from being perfect, you need to change the code as not even all of the thresholds are changeable via items …
You should experiment with the core ‘shade?’ evaluation function to include those aspects that you feel to be most important to you. I’ve experimented with using temperature and cloudiness forecasts from the weather binding (unreliable), a UV value my sensor delivers (found that to always be 0, so decided I don’t need to check it, but if you happen to live in the Australian Outback … well :slight_smile: ), various luminance levels and other stuff.

  • Shading is based on luminance (sunlight intensity) and temperature measured by the sensors.
    The core evaluation function actually is this. Easy to understand for Germans I guess.

      var boolean Schutzbedarf_morgens = (Sonnenschutz.state == Modus_Ein) || ((now.millis > Verschattung_Anfang_morgens) && (now.millis < Verschattung_Ende_morgens) && ((hell_genug_morgens && zu_warm_morgens) || zu_hell_morgens))
    

Shading happens based on the moving average of the values the sensors are delivering. To flatten out peaks, configure them to deliver values often enough to fit into the time window or increase the window size. Remember to enable “everyMinute” persistence on these items so there’s at least one up to date value when averageSince() is calculated.
Both of these procedures are to compensate for peaks (and errors) in measurements to avoid constant UPs and DOWNs on cloudy days.

  • Sonnenschutzschaltung is a global switch to enable/disable shading automatism.
    Nachtschaltung is a global switch enabled during nighttime only.
    Tageslicht is a global switch that is ON whenever there’s daylight. This is updated based on astro binding events.
    Aussentemperatur_Hysterese_Sonnenschutz is a variable to compensate for higher temperatures if you expose a sensor to direct sunlight instead of measuring the temperature in the shadows. You can’t have both, correct luminance and (shadow) temperature, in a single sensor.

Code update as of 07/20/19

items:
Number Lux_Terrasse_morgens "Durchschnitt Ostseite aussen [%.0f lux]"					<sun>			(Sonnenschutz)
Number Lux_Terrasse_mittags "Durchschnitt Südseite aussen [%.0f lux]"					<sun>			(Sonnenschutz)
Number Lux_Wohnen_mittags "Durchschnitt Südseite innen [%.0f lux]"					<sun>			(Sonnenschutz)
Number Lux_Grenzwert_Sonnenschutz_morgens_niedrig "Schwellwert unten morgens [%d lux]"			<sun>			(Sonnenschutz,Einstellungen)
Number Lux_Grenzwert_Sonnenschutz_morgens_hoch "Schwellwert oben morgens [%d lux]"			<sun>			(Sonnenschutz,Einstellungen)
Number Lux_Grenzwert_Sonnenschutz_mittags_niedrig "Schwellwert unten mittags [%d lux]"			<sun>			(Sonnenschutz,Einstellungen)
Number Lux_Grenzwert_Sonnenschutz_mittags_hoch "Schwellwert oben mittags [%d lux]"			<sun>			(Sonnenschutz,Einstellungen)
Number UV_Grenzwert_Sonnenschutz "UV Schwellwert Sonnenschutz [UV index %.0f]"				<sun>			(Sonnenschutz,Einstellungen)
Number EWT_Zuheizung "EWT heizt unterhalb [%.1f °C]"							<heating>		(Lueftung,Einstellungen)
Number EWT_Kuehlung "EWT kühlt oberhalb [%.1f °C]"							<climate>		(Lueftung,Einstellungen)
Number Aussentemperatur_Grenzwert_Sonnenschutz "Schwellwert Sonnenschutz aussen [%.1f °C]"		<temperature>		(Sonnenschutz,Einstellungen)
Number Innentemperatur_Grenzwert_Sonnenschutz "Schwellwert Sonnenschutz innen [%.1f °C]"		<temperature>		(Sonnenschutz,Einstellungen)
Number Aussentemperatur_Hysterese_Sonnenschutz "Hysterese Sonnenschutz aussen [%.1f °C]"		<temperature>		(Sonnenschutz,Einstellungen)
Number Innentemperatur_Hysterese_Sonnenschutz "Hysterese Sonnenschutz innen [%.1f °C]"			<temperature>		(Sonnenschutz,Einstellungen)
Switch Sonnenschutz_Bianca "Sonnenschutz Bianca [MAP(yesno.map): %s]"					<daylight>		(EG_Bianca,Einstellungen)
Switch Sonnenschutzschaltung "Sonnenschutzschaltung [MAP(yesno.map): %s]"				<blinds>		(Einstellungen)
Number Sonnenschutz "Sonnenschutz [MAP(shading.map): %s]"						<blinds>

rules:

val Number Rolladen_vollstaendig_geschlossen = 99
val Number Lamellen_vollstaendig_geschlossen = 99
val Number Rolladen_lichtdurchlaessig = 70 	// war 45
val Number Lamellen_lichtdurchlaessig = 45	 // war 29
val Number Rolladen_lichtundurchlaessig = 86
val Number Lamellen_lichtundurchlaessig = 55 // war 40
val Number Rolladen_offen = 0
val Number Lamellen_offen = 0
val Number Bewoelkung_Grenzwert = 30.0
val Number Modus_Auto = 1
val Number Modus_Ein = 2
val Number Modus_Hoch = 3
val Number Modus_Aus = 0
val Number Lux_Rueckwaertsfenster = 10		// Rueckwaertsfenster in Minuten fuer gleitenden Durchschnitt
val Number Aktionsfenster = 10				// keine mehrfache Aenderung innerhalb dieser Zeitspanne in Minuten

val long Verschattung_Anfang_morgens_statisch = (7 * 3600) * 1000			// Start 7:00
val long Verschattung_Ende_morgens_statisch = (13 * 3600) * 1000			// Ende 13:00
val long Verschattung_Anfang_mittags_statisch = (10 * 3600 + 15 * 60) * 1000		// Start 10:15
val long Verschattung_Ende_mittags_statisch = (18 * 3600 + 30 * 60) * 1000		// Ende 18:30


val Number Lux_Grenzwert_Sonnenschutz_morgens_niedrig_statisch = 7500.0
val Number Lux_Grenzwert_Sonnenschutz_morgens_hoch_statisch = 20000.0
val Number Lux_Grenzwert_Sonnenschutz_mittags_niedrig_statisch = 800.0
val Number Lux_Grenzwert_Sonnenschutz_mittags_hoch_statisch = 2500.0
val Number Aussentemperatur_Grenzwert_Sonnenschutz_statisch = 21.0
val Number Innentemperatur_Grenzwert_Sonnenschutz_statisch = 21.0
val Number Aussentemperatur_Hysterese_Sonnenschutz_statisch = 2.0
val Number Innentemperatur_Hysterese_Sonnenschutz_statisch = 2.0


// ACHTUNG Absolutwert muss daher taeglich neu via Cron-getriggerte rule initialisiert werden !
var long Verschattung_Anfang_morgens
var long Verschattung_Ende_morgens
var long Verschattung_Anfang_mittags
var long Verschattung_Ende_mittags

var boolean SB_alt_morgens = false
var boolean SB_alt_hoch_morgens = false
var boolean SB_alt_mittags = false
var boolean SB_alt_hoch_mittags = false
var boolean SB_Aenderung_mittags = true
var boolean SB_Aenderung_morgens = true
var boolean SB_Reset = true
var Number letzte_Aenderung_morgens = Verschattung_Anfang_morgens
var Number letzte_Aenderung_mittags = Verschattung_Anfang_mittags


val Functions$Function3 <RollershutterItem,ContactItem,NumberItem,Boolean> check_close_shutter = [ shutter, door, percentage |
	if (door.state != OPEN) {
		logDebug("rules", shutter.label + "(" + shutter.name + ") wird auf " + percentage + " gefahren, shutter = " + shutter)	
		var Number p = percentage
		shutter.sendCommand(p)
	} else
		logDebug("rules", shutter.label + " wird nicht geschlossen, da " + door.label + " geöffnet ist.")

	logDebug("rules", shutter.label + "(" + shutter.name + ") erledigt")
	true
]


val Functions$Function4 <RollershutterItem,ContactItem,ContactItem,NumberItem,Boolean> check_close_blinds = [ blinds, door1, door2, percentage |
	if (door1.state != OPEN && door2.state != OPEN) {
		logDebug("rules", blinds.label + "(" + blinds.name + ") wird auf " + percentage + " gefahren, blinds = " + blinds)
		var Number p = percentage
		blinds.sendCommand(p)
	} else
		logDebug("rules", blinds.label + " wird nicht geschlossen, da " + door1.label + " oder " + door2.label + " noch geöffnet ist.")

	logDebug("rules", blinds.label + "(" + blinds.name + ") erledigt")
	true
]


rule "Sonnenschutz Init virtual Items"
when
	System started
	or
	Item Nachtschaltung received command OFF
	or
	Item Sonnenschutzschaltung received command 
then
	Lux_Grenzwert_Sonnenschutz_morgens_niedrig.postUpdate(Lux_Grenzwert_Sonnenschutz_morgens_niedrig_statisch)
	Lux_Grenzwert_Sonnenschutz_morgens_hoch.postUpdate(Lux_Grenzwert_Sonnenschutz_morgens_hoch_statisch)
	Lux_Grenzwert_Sonnenschutz_mittags_niedrig.postUpdate(Lux_Grenzwert_Sonnenschutz_mittags_niedrig_statisch)
	Lux_Grenzwert_Sonnenschutz_mittags_hoch.postUpdate(Lux_Grenzwert_Sonnenschutz_mittags_hoch_statisch)
	Aussentemperatur_Grenzwert_Sonnenschutz.postUpdate(Aussentemperatur_Grenzwert_Sonnenschutz_statisch)
	Aussentemperatur_Hysterese_Sonnenschutz.postUpdate(Aussentemperatur_Hysterese_Sonnenschutz_statisch)
	Innentemperatur_Grenzwert_Sonnenschutz.postUpdate(Innentemperatur_Grenzwert_Sonnenschutz_statisch)
	Innentemperatur_Hysterese_Sonnenschutz.postUpdate(Innentemperatur_Hysterese_Sonnenschutz_statisch)

        Verschattung_Anfang_morgens = (now.withTimeAtStartOfDay).millis + Verschattung_Anfang_morgens_statisch
        Verschattung_Ende_morgens   = (now.withTimeAtStartOfDay).millis + Verschattung_Ende_morgens_statisch
        Verschattung_Anfang_mittags = (now.withTimeAtStartOfDay).millis + Verschattung_Anfang_mittags_statisch
        Verschattung_Ende_mittags   = (now.withTimeAtStartOfDay).millis + Verschattung_Ende_mittags_statisch

	SB_Aenderung_mittags = true
	SB_Aenderung_morgens = true
	SB_Reset = true

	letzte_Aenderung_morgens = Verschattung_Anfang_morgens
	letzte_Aenderung_mittags = Verschattung_Anfang_mittags

	logInfo("rules", "Sonnenschutz Initialisierung: SB_Reset = " + SB_Reset)

	Sonnenschutz.postUpdate(Sonnenschutz.state)
end


rule "Sonnenschutz Tuer geoeffnet"
when
	Item WohnraumTueren changed
then
	logDebug("rules", "Sonnenschutzcheck: Anzahl offener Türen hat sich auf " + WohnraumTueren.state + " geändert.")

	SB_Reset = true
end


rule "Sonnenschutzcheck"
when
	Time cron "0 1/2 7-18 ? * MON-SUN"
	or
	Item Sonnenschutzschaltung changed to ON
then
	if (Sonnenschutzschaltung.state != ON)
		return;


	var Number a = (Soll_Temp_Wohnen.state as Number) - (Aussentemperatur_Hysterese_Sonnenschutz.state as Number)
	var Number i = (Soll_Temp_Wohnen.state as Number) - (Innentemperatur_Hysterese_Sonnenschutz.state as Number)

	logDebug("rules", "Sonnenschutzcheck: Soll_Temp_Wohnen = " + Soll_Temp_Wohnen.state + "; Hysterese innen/aussen = " + Innentemperatur_Hysterese_Sonnenschutz.state + "/" + Aussentemperatur_Hysterese_Sonnenschutz.state + "; aussen = " + a + "; innen = " + i)

	Aussentemperatur_Grenzwert_Sonnenschutz.postUpdate(a)
	Innentemperatur_Grenzwert_Sonnenschutz.postUpdate(i)

	if ((Sonnenschutzschaltung.state == ON) && (Nachtschaltung.state != ON) && (Szene.state <= 2)) {
		Sonnenschutz.postUpdate(Sonnenschutz.state)
	}
end


rule "Sonnenschutz implementieren"
when
	Item Sonnenschutz received update
then
	var Number Temp_morgens = (TempAussen.state as Number)
	var Number Temp_mittags = (EG_Wohnen_Auge_Temp.state as Number)
	var Number Lux_morgens = Garten_Auge_Lux.averageSince(now.minusMinutes(Lux_Rueckwaertsfenster))
	var Number Lux_mittags = EG_Wohnen_Auge_Lux.averageSince(now.minusMinutes(Lux_Rueckwaertsfenster))
//	Lux_Wohnen_mittags.postUpdate(Lux_mittags_niedrig)

	logDebug("rules", "Sonnenschutz: Sonnenschutz.state = " + Sonnenschutz.state)

	if (Sonnenschutz.state == Modus_Aus || Szene.state == 101)
		return;

	logDebug("rules", "Sonnenschutz: Durchschnittshelligkeit morgens/mittags = " + Lux_morgens + "/" + Lux_mittags)
	var boolean hell_genug_morgens = (Lux_morgens > (Lux_Grenzwert_Sonnenschutz_morgens_niedrig.state as Number) && aktuell_Bewoelkung.state >= Bewoelkung_Grenzwert) || (Lux_morgens > (Lux_Grenzwert_Sonnenschutz_morgens_niedrig.state as Number))
	var boolean zu_hell_morgens = Lux_morgens > (Lux_Grenzwert_Sonnenschutz_morgens_hoch.state as Number)
	var boolean hell_genug_mittags = (Lux_mittags > (Lux_Grenzwert_Sonnenschutz_mittags_niedrig.state as Number) && aktuell_Bewoelkung.state >= Bewoelkung_Grenzwert) || hell_genug_morgens
	// Halb-Verschattung reduziert Innenhelligkeit, darum stark verschatten, wenn trotzdem (SB_alt_mittags) der Grenzwert erreicht
	var boolean zu_hell_mittags = Lux_mittags > (Lux_Grenzwert_Sonnenschutz_mittags_hoch.state as Number) || (hell_genug_mittags && SB_alt_mittags)
	var boolean zu_warm_morgens = (Temp_morgens > (Aussentemperatur_Grenzwert_Sonnenschutz.state as Number) || Temp_max.state > Aussentemperatur_Grenzwert_Sonnenschutz.state)
		&& (Lux_morgens > (Lux_Grenzwert_Sonnenschutz_morgens_niedrig.state as Number))
	var boolean zu_warm_mittags = (Temp_mittags > (Aussentemperatur_Grenzwert_Sonnenschutz.state as Number) || Temp_max.state > Aussentemperatur_Grenzwert_Sonnenschutz.state)
		&& (Lux_mittags > (Lux_Grenzwert_Sonnenschutz_mittags_niedrig.state as Number))

	logDebug("rules", "Sonnenschutz: hell genug morgens/mittags = " + hell_genug_morgens + "/" + hell_genug_mittags + "; zu hell morgens/mittags = " + zu_hell_morgens + "/" + zu_hell_mittags + "; zu warm = " + zu_warm_morgens + "/" + zu_warm_mittags)
//	logDebug("rules", "Sonnenschutz debug 1b: Zeitraum morgens von-bis[nach Beginn-vor Ende] = " + now.millis + " / " + Verschattung_Anfang_morgens + "-" + Verschattung_Ende_morgens + "[" + (now.millis > Verschattung_Anfang_morgens) + "-" + (now.millis < Verschattung_Ende_morgens) + "]")
//	logDebug("rules", "Sonnenschutz debug 1c: Zeitraum mittags von-bis[nach Beginn-vor Ende] = " + now.millis + " / " + Verschattung_Anfang_mittags + "-" + Verschattung_Ende_mittags + "[" + (now.millis > Verschattung_Anfang_mittags) + "-" + (now.millis < Verschattung_Ende_mittags) + "]")

	logDebug("rules", "Sonnenschutz: SB_Reset = " + SB_Reset + "; (Sonnenschutz == Modus_Ein) = " + (Sonnenschutz.state == Modus_Ein) + "; (Sonnenschutz == Modus_Hoch) = " + (Sonnenschutz.state == Modus_Hoch))
	var boolean Schutzbedarf_morgens = (Sonnenschutz.state == Modus_Ein) || ((now.millis > Verschattung_Anfang_morgens) && (now.millis < Verschattung_Ende_morgens) && ((hell_genug_morgens && zu_warm_morgens) || zu_hell_morgens))
	var boolean Schutzbedarf_mittags = (Sonnenschutz.state == Modus_Ein) || ((now.millis > Verschattung_Anfang_mittags) && (now.millis < Verschattung_Ende_mittags) && ((hell_genug_mittags && zu_warm_mittags) || zu_hell_mittags))

	var boolean Schutzbedarf_hoch_morgens = (Sonnenschutz.state == Modus_Hoch) || (Schutzbedarf_morgens && zu_hell_morgens && zu_warm_morgens)
	var boolean Schutzbedarf_hoch_mittags = (Sonnenschutz.state == Modus_Hoch) || (Schutzbedarf_mittags && zu_hell_mittags && zu_warm_mittags)

	logDebug("rules", "Sonnenschutz: Schutzbedarf morgens[hoch]/mittags[hoch] = " + Schutzbedarf_morgens + "[" + Schutzbedarf_hoch_morgens + "]/" + Schutzbedarf_mittags + "[" + Schutzbedarf_hoch_mittags + "]")

	SB_Aenderung_morgens = (SB_Reset || SB_alt_hoch_morgens != Schutzbedarf_hoch_morgens || SB_alt_morgens != Schutzbedarf_morgens)
	SB_Aenderung_mittags = (SB_Reset || SB_alt_hoch_mittags != Schutzbedarf_hoch_mittags || SB_alt_mittags != Schutzbedarf_mittags)

	logDebug("rules", "Sonnenschutz: Schutzbedarf Alt-Speicher morgens[hoch][Aenderung]/mittags[hoch][Aenderung] = " + SB_alt_morgens + "[" + SB_alt_hoch_morgens + "][" + SB_Aenderung_morgens + "]/" + SB_alt_mittags + "[" + SB_alt_hoch_mittags + "][" + SB_Aenderung_mittags + "]")

	logDebug("rules", "Sonnenschutz Morgen-Check: Schaltung = " + Sonnenschutzschaltung.state + "; Lichtstärke (" + Lux_Durchschnitt_Nachlauf + "-min-Durchschnitt/Grenzwert[hoch]) = " + Lux_morgens + "/" + Lux_Grenzwert_Sonnenschutz_morgens_niedrig.state + "[" + Lux_Grenzwert_Sonnenschutz_morgens_hoch.state + "] lux; Bewölkung (aktuell/Grenzwert) = " + aktuell_Bewoelkung.state + "/" + Bewoelkung_Grenzwert + " %; Temperatur(Sensor/max. heute/Grenzwert) = " + Temp_morgens + "/" + Temp_max.state + "/" + Aussentemperatur_Grenzwert_Sonnenschutz.state + " °C; Schutzbedarf morgens (aktuell[hoch]/alt[hoch]/Änderung) = " + Schutzbedarf_morgens + "[" + Schutzbedarf_hoch_morgens + "]/" + SB_alt_morgens + "[" + SB_alt_hoch_morgens + "]/" + SB_Aenderung_morgens)

	var Number c = now.millis - Aktionsfenster * 60 * 1000
	var Number closure = if (Schutzbedarf_hoch_morgens) Rolladen_lichtundurchlaessig else Rolladen_lichtdurchlaessig
	var Number lamellas = if (Schutzbedarf_hoch_morgens) Lamellen_lichtundurchlaessig else Lamellen_lichtdurchlaessig

//	logDebug("rules", "Sonnenschutz debug 3a: SB_Reset = " + SB_Reset +"; SB_Aenderung_morgens = " + SB_Aenderung_morgens)
//	logDebug("rules", "Sonnenschutz debug 3b: now > Anfang_morgens = " + if (now.millis > Verschattung_Anfang_morgens) "true" else "false")
//	logDebug("rules", "Sonnenschutz debug 3c: now < Ende_morgens = " + if (now.millis < Verschattung_Ende_morgens) "true" else "false")
//	logDebug("rules", "Sonnenschutz debug 3d: c = " + c + " > letzte_Aenderung_morgens = " + letzte_Aenderung_morgens + " = " + if (c > letzte_Aenderung_morgens) "true" else "false")

	if ((SB_Reset || SB_Aenderung_morgens) && now.millis < Verschattung_Ende_morgens && c > letzte_Aenderung_morgens) {
		if (Schutzbedarf_morgens) {
			logInfo("rules", "Sonnenschutz: Verschatte vormittags beschienene Fensterflächen ...")

			check_close_shutter.apply(EG_Kueche_Rolladen_Tuer,EG_Kueche_Tuer_Kontakt,closure)
			check_close_shutter.apply(EG_Kueche_Rolladen_Fenster,EG_Kueche_Fenster_Kontakt,closure)
			check_close_shutter.apply(EG_Essen_Rolladen,EG_Essen_Fenster_Kontakt,closure)
	
			check_close_blinds.apply(EG_Wohnen_Jalousie_links,EG_Wohnen_Tuer_li1_Kontakt,EG_Wohnen_Tuer_li2_Kontakt,Rolladen_vollstaendig_geschlossen)
			check_close_blinds.apply(EG_Wohnen_Jalousie_Mitte,EG_Wohnen_Tuer_Mitte_li_Kontakt,EG_Wohnen_Tuer_Mitte_re_Kontakt,Rolladen_vollstaendig_geschlossen)
			check_close_blinds.apply(EG_Wohnen_Jalousie_links_Lamellen,EG_Wohnen_Tuer_li1_Kontakt,EG_Wohnen_Tuer_li2_Kontakt,lamellas)
			check_close_blinds.apply(EG_Wohnen_Jalousie_Mitte_Lamellen,EG_Wohnen_Tuer_Mitte_li_Kontakt,EG_Wohnen_Tuer_Mitte_re_Kontakt,lamellas)

		} else {
			logInfo("rules", "Sonnenschutz: keine Verschattung nötig, öffne vormittags beschienene Fensterflächen ...")

			EG_Kueche_Rolladen_Tuer.sendCommand(Rolladen_offen)
			EG_Kueche_Rolladen_Fenster.sendCommand(Rolladen_offen)
			EG_Essen_Rolladen.sendCommand(Rolladen_offen)
			EG_Wohnen_Jalousie_links.sendCommand(Rolladen_offen)
			EG_Wohnen_Jalousie_Mitte.sendCommand(Rolladen_offen)

			logDebug("rules", "Sonnenschutz: Rolläden/Jalousien vormittags fertig heraufgefahren.") 
		}

	} else
		logDebug("rules", "Sonnenschutz: keine Anpassung der Verschattung auf den vormittags beschienenen Fensterflächen nötig.")


	logDebug("rules", "Sonnenschutz Mittags-Check : Schaltung = " + Sonnenschutzschaltung.state + "; Lichtstärke (" + Lux_Durchschnitt_Nachlauf + "-min-Durchschnitt/Grenzwert[hoch]) = " + Lux_mittags + "/" + Lux_Grenzwert_Sonnenschutz_mittags_niedrig.state + "[" + Lux_Grenzwert_Sonnenschutz_mittags_hoch.state + "] lux; Bewölkung (aktuell/Grenzwert) = " + aktuell_Bewoelkung.state + "/" + Bewoelkung_Grenzwert + " %; Temperatur(Sensor/max. heute/Grenzwert) = " + Temp_mittags + "/" + Temp_max.state + "/" + Aussentemperatur_Grenzwert_Sonnenschutz.state + " °C; Schutzbedarf mittags (aktuell[hoch]/alt[hoch]/Änderung) = " + Schutzbedarf_mittags + "[" + Schutzbedarf_hoch_mittags + "]/" + SB_alt_mittags + "[" + SB_alt_hoch_mittags + "]/" + SB_Aenderung_mittags)

	closure = if (Schutzbedarf_hoch_mittags) Rolladen_lichtundurchlaessig else Rolladen_lichtdurchlaessig
	lamellas = if (Schutzbedarf_hoch_mittags) Lamellen_lichtundurchlaessig else Lamellen_lichtdurchlaessig

//	logDebug("rules", "Sonnenschutz debug 3a: SB_Reset = " + SB_Reset +"; SB_Aenderung_mittags = " + SB_Aenderung_mittags)
//	logDebug("rules", "Sonnenschutz debug 3b: now > Anfang_mittags = " + if (now.millis > Verschattung_Anfang_mittags) "true" else "false")
//	logDebug("rules", "Sonnenschutz debug 3c: now < Ende_mittags = " + if (now.millis < Verschattung_Ende_mittags) "true" else "false")
//	logDebug("rules", "Sonnenschutz debug 3d: c = " + c + " > letzte_Aenderung_mittags = " + letzte_Aenderung_mittags + " = " + if (c > letzte_Aenderung_mittags) "true" else "false")

	if ((SB_Reset || SB_Aenderung_mittags) && now.millis > Verschattung_Anfang_mittags && now.millis < Verschattung_Ende_mittags && c > letzte_Aenderung_mittags) {
		if (Schutzbedarf_mittags) {
			logDebug("rules", "Sonnenschutz: Verschatte (nach)mittags beschienene Fensterflächen ...")

			check_close_blinds.apply(EG_Wohnen_Jalousie_rechts,EG_Wohnen_Tuer_re1_Kontakt,EG_Wohnen_Tuer_re2_Kontakt,Rolladen_vollstaendig_geschlossen)
			check_close_blinds.apply(EG_Wohnen_Jalousie_rechts_Lamellen,EG_Wohnen_Tuer_re1_Kontakt,EG_Wohnen_Tuer_re2_Kontakt,lamellas)

			if (Sonnenschutz_Bianca.state == ON)
				check_close_shutter.apply(EG_Bianca_Rolladen,EG_Bianca_Tuer_Kontakt,closure)
			
			check_close_shutter.apply(EG_Wohnen_Rolladen,EG_Wohnen_Tuer_alt_Kontakt,closure)
		} else {
			logDebug("rules", "Sonnenschutz: keine Verschattung nötig, öffne (nach)mittags beschienene Fensterflächen ...")

			EG_Wohnen_Jalousie_rechts.sendCommand(Rolladen_offen)
			if (Sonnenschutz_Bianca.state == ON)
				EG_Bianca_Rolladen.sendCommand(Rolladen_offen)
			EG_Wohnen_Rolladen.sendCommand(Rolladen_offen)

			logDebug("rules", "Sonnenschutz: nachmittags fertig heraufgefahren.") 
		}
	} else
		logDebug("rules", "Sonnenschutz: keine Anpassung der Verschattung auf den nachmittags beschienenen Fensterflächen nötig.")

	if (SB_Aenderung_morgens) {
		SB_Aenderung_morgens = false
		letzte_Aenderung_morgens = now.millis
	}
	if (SB_Aenderung_mittags) {
		SB_Aenderung_mittags = false
		letzte_Aenderung_mittags = now.millis
	}

	SB_alt_morgens = Schutzbedarf_morgens
	SB_alt_hoch_morgens = Schutzbedarf_hoch_morgens
	SB_alt_mittags = Schutzbedarf_mittags
	SB_alt_hoch_mittags = Schutzbedarf_hoch_mittags
	SB_Reset = false

	if (now.isAfter(Verschattung_Ende_mittags) && now.minusMinutes(Aktionsfenster).isBefore(Verschattung_Ende_mittags)) {
		EG_Wohnen_Jalousie_rechts.sendCommand(Rolladen_offen)
		EG_Wohnen_Rolladen.sendCommand(Rolladen_offen)
		if (Sonnenschutz_Bianca.state == ON)
			EG_Bianca_Rolladen.sendCommand(Rolladen_offen)

		SB_Aenderung_mittags = true
		logDebug("rules", "Sonnenschutz Mittagseite beendet, Jalousien werden wieder hochgefahren.")
	}
	if (now.isAfter(Verschattung_Ende_morgens) && now.minusMinutes(Aktionsfenster).isBefore(Verschattung_Ende_morgens)) {
		EG_Kueche_Rolladen_Tuer.sendCommand(Rolladen_offen)
		EG_Kueche_Rolladen_Fenster.sendCommand(Rolladen_offen)
		EG_Essen_Rolladen.sendCommand(Rolladen_offen)
		EG_Wohnen_Jalousie_links.sendCommand(Rolladen_offen)
		EG_Wohnen_Jalousie_Mitte.sendCommand(Rolladen_offen)

		SB_Aenderung_morgens = true
		logDebug("rules", "Sonnenschutz Morgenseite beendet, Jalousien werden wieder hochgefahren.")
	}

	logDebug("rules", "Sonnenschutz: Verschattungs-Check ausgeführt.")
end


17 Likes

Hi Markus,

Nice code, might use it. which persistence are you using to calculate average?

Thanks!

rrd4j, but any except mapdb should do.

I happen to use mapdb for my items so no good, Now I went ahead and also installed the mysql add-on as I did install mysql earlier. It is working like a treat!

Another question if I may:
You use var boolean SB_alt_morgens = false and update that at the end to be the “new state”. Does that get saved in the rule for next time? Ie if that boolean is now true, next time the rule runs it is actually remembered as true? Or do I need to create an item for that too?

Thanks in advance!

The variables defined at the beginning of the rule file are initialized at OH startup.
They remain in memory as long as OH runs so the next time the rule runs it will be remembered.
These variable can be seen by all rules in that file. They are “global” to that file.
You can kind of think of them as items created when OH starts. BUT you can not persist these.

I do have a similar rule set in place for my shading automation - but I lack some best practise for overriding them via manual control.

Use case:
If I decide for some reason to position the blinds differently than the rules, I just change them - but if the cron (or whatever trigger) calls, they’re set to the rule preset again.

My workaround(?) solution:
for now I have a variable for each of my six blinds, which is triggered, if I press the button manually and stays until it gets reset (on midnight or by some action).

I don’t find this very smart, how do you cope with manual overrides?

I struggled with my blinds on OH so I turned to node-red
I use node-red-contrib-blindcontroller - lots of options, fully automatic if you want, seasonal settings… IT’S BLINDING!!
You can set a manual value and an expiry time for the manual override

Perfect, thanks!

You want it cheap and like to solder? :grinning:
ESP8266 with ESPEasy firmware and BH1750 sensor (under 10 Euro)

1 Like

You can obviously use any OH-connected sensor. As I’ve got ZWave in place anyway, I’m using Fibaro FGMS and Aeotec Multisensors.

PS: All, please open a new thread on questions that are not directly on the code so all of these “examples” threads stay clean. You can also retract posts.

Could you please post your setup for the blinds with node-red-contrib-blindcontroller… I am totalling failing…

Automation works fine… but if the automation sets the value for OH it produces a feedback in the Input trigger and is treated as manual override… any suggestions?

In another thread please.