Doing it smarter - help with rule code rollershutter automation

Hello,

I wonder if I could ask you to have a look on my rule code for my automatic shadowing with the rollershutters. It is working so far but I have a feeling, it is very C64 style.

Thanks for your help

var boolean development = false
var Number schliesswertGr = 80
var Number schliesswertKl = 75
var Number aussentempschwelle = 15

rule "Temperatur Differenz Aussen"
when 
Item TemperatureAussenHinten received update
or
Item TemperatureAussenVorne received update
or
Item HeatPump_Temperature_1 received update
or
Item RolloAutomatik changed to ON
then 

//Temperaturdifferenz berechnen und senden 
var Number TempAussenVorne = (TemperatureAussenVorne.state)
var Number TempAussenHinten = (TemperatureAussenHinten.state)
var Number TempAussDiff = (TempAussenVorne - TempAussenHinten)
var boolean sonne = (Sun.state)
if (sonne == ON) sonne = true
else sonne = false

//Azimuth und Elivation auslesen
var Number AzimuthNow = (Azimuth.state)
var Number ElevationNow = (Elevation.state)

var Number Diff = (TempAussenHinten - TempAussDiff)
var Number tau = 0.05 // Glaettungsfaktor, 1 waere keine Glaettung
TempAussDiff = TempAussDiff + (tau * Diff)

TemperatureAussenDifferenz.postUpdate(TempAussDiff)

if (TempAussDiff >= 6 && RolloAutomatik.state == ON && TemperatureAussenHinten.state > aussentempschwelle && sonne == false || sonne && TempAussDiff >= 3 && RolloAutomatik.state == ON && TemperatureAussenHinten.state > aussentempschwelle || development ) {
    sonne = true
    Sun.postUpdate(ON)
    if (ElevationNow < 45 && ElevationNow > 0) {
        if (AzimuthNow > 105 && AzimuthNow < 196) {
            AziOGSued.sendCommand(ON)
            AziEGSued.sendCommand(ON)
            AziOGWest.sendCommand(OFF)
            AziEGWest.sendCommand(OFF)
            AziEGSuedMitte.sendCommand(ON)
        }
        if (AzimuthNow > 196 && AzimuthNow < 270) {
            AziOGSued.sendCommand(ON)
            AziEGSued.sendCommand(ON)
            AziOGWest.sendCommand(ON)
            AziEGWest.sendCommand(ON)
            AziEGSuedMitte.sendCommand(ON)
        }
        if (AzimuthNow > 270 && AzimuthNow < 350) {
            AziOGSued.sendCommand(OFF)
            AziEGSued.sendCommand(OFF)
            AziOGWest.sendCommand(ON)
            AziEGWest.sendCommand(ON)
            AziEGSuedMitte.sendCommand(OFF)
        }
    }
    else if (ElevationNow > 45 && ElevationNow < 80) {
        if (AzimuthNow > 100 && AzimuthNow < 196) {
            AziOGSued.sendCommand(OFF)
            AziEGSued.sendCommand(ON)
            AziOGWest.sendCommand(OFF)
            AziEGWest.sendCommand(OFF)
            AziEGSuedMitte.sendCommand(ON)
        }
        if (AzimuthNow > 196 && AzimuthNow < 270) {
            AziOGSued.sendCommand(OFF)
            AziEGSued.sendCommand(ON)
            AziOGWest.sendCommand(ON)
            AziEGWest.sendCommand(ON)
            AziEGSuedMitte.sendCommand(ON)
        }
        if (AzimuthNow > 270 && AzimuthNow < 350) {
            AziOGSued.sendCommand(OFF)
            AziEGSued.sendCommand(OFF)
            AziOGWest.sendCommand(ON)
            AziEGWest.sendCommand(ON)
            AziEGSuedMitte.sendCommand(OFF)
        }
    }
}
else if (TempAussDiff < 3 && sonne) {
    Thread::sleep(300000) // schlafe 5 minuten
    if (TempAussDiff < 3) {
        sonne = false
        Sun.postUpdate(OFF)
        AziOGSued.sendCommand(OFF)
        AziEGSued.sendCommand(OFF)
        AziEGSuedMitte.sendCommand(OFF)
        AziOGWest.sendCommand(OFF)
        AziEGWest.sendCommand(OFF)
    }
}
else {
    AziOGSued.postUpdate(OFF)
    AziEGSued.postUpdate(OFF)
    AziEGSuedMitte.postUpdate(OFF)
    AziOGWest.postUpdate(OFF)
    AziEGWest.postUpdate(OFF)
}
end



rule "Rolladen AziOGSued"
when
Item AziOGSued received command ON
then
SchlafzimmerRollo_S.sendCommand(schliesswertKl)
KindMaxRollo_S.sendCommand(schliesswertKl)
end

rule "Rolladen AziOGWest"
when
Item AziOGWest received command ON
then
KindEmmiRollo_W.sendCommand(schliesswertGr)
end

rule "Rolladen AziEGSued"
when
Item AziEGSued received command ON
then
WohnzimmerRollo_SW.sendCommand(schliesswertGr)
WohnzimmerRollo_SO.sendCommand(schliesswertGr)
end

rule "Rolladen AziEGWest"
when
Item AziEGWest received command ON
then
WohnzimmerRollo_W.sendCommand(schliesswertGr)
BueroRollo_W.sendCommand(schliesswertGr)
end


rule "Rolladen AziEGSuedMitte"
when
Item AziEGSuedMitte received command ON
then
var aussperrschutz = WohnzimmerFenster_S.state
if (aussperrschutz == NULL){
    aussperrschutz = 'OPEN'
}
if (aussperrschutz == 'CLOSED'){
    WohnzimmerRollo_S.sendCommand(schliesswertGr)
}
end

// *********************************************
// OFF RULES
// *********************************************

rule "Rolladen AziOGSued"
when
Item AziOGSued received command OFF
then
if ()
SchlafzimmerRollo_S.sendCommand(0)
KindMaxRollo_S.sendCommand(0)
end

rule "Rolladen AziOGWest"
when
Item AziOGWest received command OFF
then
KindEmmiRollo_W.sendCommand(0)
//KindMaxRollo_W.sendCommand(0)
end

rule "Rolladen AziEGSued"
when
Item AziEGSued received command OFF
then
WohnzimmerRollo_SW.sendCommand(0)
WohnzimmerRollo_SO.sendCommand(0)
end

rule "Rolladen AziEGWest"
when
Item AziEGWest received command OFF
then
WohnzimmerRollo_W.sendCommand(0)
BueroRollo_W.sendCommand(0)
end


rule "Rolladen AziEGSuedMitte"
when
Item AziEGSuedMitte received command OFF
then
WohnzimmerRollo_S.sendCommand(0)
end

Personally, putting parens around things that don’t need parens is asking for trouble and it makes the code harder to read. So, for example,

var Number TempAussenVorne = (TemperatureAussenVorne.state)

would be

var Number TempAussenVorne = TemperatureAussenVorne.state

Review Design Pattern: How to Structure a Rule. I think this Rule might benefit from this. It could at least make it shorter a bit. But beyond that there isn’t a whole lot you can do to simplify this using normal straight forward means.

There might be something clever we can do here though to split up the complexity. For example, separate the calculation of the Azimuth/Elevation into it’s own Rules as described in Design Pattern: Separation of Behaviors and Design Pattern: Time Of Day. Create a name for each of the six states represented by the elevation and azimuth (perhaps seven states for when the elevation/azimuth doesn’t fit one of the states you care about). This goes into it’s own Rule triggered by changes to the Elevation and Azimuth.

In your “Temperatur Differenz Aussen” Rule, you only need to test the temp and use a switch statement for the state calculated above.

Next you can use clever naming and Groups to further simplify the Rule. Create two Groups for each state calculated above, one for ON and one for OFF. Then you can use Design Pattern: Associated Items to send the ON and OFF commands based on the calculated state. Put the appropriate Items into each Group.

It will look something like this.

Since I don’t know appropriate names that make sense I’ll just use “ONE”, “TWO” etc for the elevation/azimuth states.

Items:

String EleAzm

Group:Switch EleAzm_ONE_ON
Group:Switch EleAzm_ONE_OFF
Group:Switch EleAzm_TWO_ON
Group:Switch EleAzm_TWO_OFF
// and so on
Group:Switch All_RS // all Azi Items are members of this Group

Switch AziOGSued ... (All_RS, EleAzm_ONE_ON, EleAzm_TWO_ON, EleAzm_THREE_OFF, EleAzm_FOUR_OFF, EleAzm_Five_OFF, EleAzm_SIX_OFF) ...
Switch AziEGSued ... (All_RS, EleAzm_ONE_ON, EleAzm_TWO_ON, EleAzm_THREE_OFF, EleAzm_FOUR_ON, EleAzm_FIVE_ON, EleAzm_SIX_ON) ...
// and so on
rule "Calculate EleAzm state"
when
    Item Elevatio changed or
    Item Aziuth changed
then
    val newState = "NONE"
    if(Elevation.state < 45 && Elevation.state > 0) {
        if(Azimuth.state > 105 && Azimuth.state < 196) newState = "ONE"
        if(Azimuth.state >196 && Azimuth.state < 270) newState = "TWO"
        if(Azimuth.state >270 && Azimuth.state < 350) newState = "THREE"
    }
    else if(Elevation.state > 45 && Elevation.state > 80) {
        if(Azimuth.state > 105 && Azimuth.state < 196) newState = "FOUR"
        if(Azimuth.state >196 && Azimuth.state < 270) newState = "FIVE"
        if(Azimuth.state >270 && Azimuth.state < 350) newState = "SIX"
    }

    // NOTE: We could be clever here to avoid the duplicated code but I don't 
    // see the need for it here.

    if(EleAzm.state != newState) EleAzm.postUpdate(newState)
end

rule "Temperatur Differenz Aussen"
when
    Item TemperatureAussenHinten received update or
    Item TemperatureAussenVorne received update or
    Item HeatPump_Temperature_1 received update or
    Item RolloAutomatik changed to ON
then
    // all the calculations at the top remain the same only you don't need 
    // AzimuthNow and ElevationNow any longer

    if (TempAussDiff >= 6 && 
        RolloAutomatik.state == ON && 
        TemperatureAussenHinten.state > aussentempschwelle && 
        sonne == false || 
        sonne && 
        TempAussDiff >= 3 && 
        RolloAutomatik.state == ON && 
        TemperatureAussenHinten.state > aussentempschwelle || 
        development ) {
        sonne = true 
        Sun.postUpdate(ON) 

        // Here is the clever part that takes advantage of the Groups and the state we set up above
        if(EleAzm.state.toString != "NONE") {
            sendCommand("EleAzm_"+EleAzm.state.toString+"_ON", "ON")
            sendCommand("EleAzm_"+EleAzm.state.toString+"_OFF", "OFF")
        }
    }

    else if (TempAussDiff < 3 && sonne) {
        createTimer(now.plusMinutes(5), [ | // Never sleep for more than 500 msec. If you need to delay the execution of some commands, create a Timer instead.

            if (TempAussDiff < 3) { // woudn't you want to recalculate the temp diff now that it's five minutes later?
                sonne = false
                Sun.postUpdate(OFF)
                All_RS.sendCommand(OFF) // sends OFF to all the Azi Items
            }
        ])
    }

    else All_RS.sendCommand(OFF) // sends OFF to all Azi Items
end

You can consolidate your ON and OFF Rules but I’m not sure it buys you much.

NOTE: I just typed in the above without even using VSCode. It’s not complete (see the … and comments) and likely has errors.

2 Likes

Great , any many thanks! I will try to follow your good to understand hints!

This part is a kind of limiting the number of shutter drives.

If temperature trigger is fired, wait for some minutes to see if it is a stable decrease of temperature or just a short one … therefor I wanted to wait for 5min …

But you don’t ever check it again. You need to recalculate TempAussDiff after the five minutes to see if the diff is still < 3. Otherwise all you are doing is waiting five minutes and using the TempAussDiff calculated five minutes ago.

And like the comment says, you should never sleep for more than 500 msec. Use Timers if you need a longer amount of time. This is what is shown above. See Why have my Rules stopped running? Why Thread::sleep is a bad idea for details.

1 Like

Hi

I wanted to give a feedback what my current code looks like. With your help I made it shorter and I reduced complexity a little. The previous precision was too much and I ended up with this.

Most benefit is the dramtically reduced use of homematic Unit work load. I did not liek to use groups because If something is changing or I have to adjust it is easier to do it with the single items.

Thanks again for your help

 var boolean development = false
var Number schliesswertGr = 80
var Number schliesswertKl = 75
var Number aussentempschwelle = 15

rule "Temperatur Differenz Aussen"
when 
Item TemperatureAussenHinten received update
or 
Item TemperatureAussenVorne received update
or
Time cron "0 */5 * * * ?" 
or
Item RolloAutomatik changed

then 
//Azimuth und Elevation auslesen
var Number AzimuthNow = Azimuth.state
var Number ElevationNow = Elevation.state

if (ElevationNow <= 0 && ElevationNow >= 80 || RolloAutomatik.state == OFF || TemperatureAussenHinten.state <= aussentempschwelle || AzimuthNow < 100 && AzimuthNow > 285 ) {
    AziOGSued.postUpdate(OFF)
    AziEGSued.postUpdate(OFF)
    AziEGSuedMitte.postUpdate(OFF)
    AziWest.postUpdate(OFF)
    return;
}
else {
    //Temperaturdifferenz berechnen und senden 
    var Number TempAussDiff = (TemperatureAussenVorne.state as Number) - (TemperatureAussenHinten.state as Number)
    var Number Diff = (TemperatureAussenHinten.state as Number) - (TemperatureAussenDifferenz.state as Number)
    var Number tau = 0.1 // Glaettungsfaktor, 1 waere keine Glaettung
    TempAussDiff = TempAussDiff + (tau * Diff)
    TemperatureAussenDifferenz.postUpdate(TempAussDiff)

    var Boolean sonne = Sun.state
    if (sonne == ON) sonne = true
    else sonne = false

    if (TempAussDiff >= 6 && sonne == false || sonne && TempAussDiff > 3 || development ) {
        sonne = true
        Sun.postUpdate(ON)
        if (AzimuthNow > 105 && AzimuthNow < 196) {
            if (ElevationNow > 45) AziOGSued.sendCommand(OFF)
            else (AziOGSued.sendCommand(ON))
        AziEGSued.sendCommand(ON)
        AziWest.sendCommand(OFF)
        AziEGSuedMitte.sendCommand(ON)
        }
        if (AzimuthNow > 196 && AzimuthNow < 270) {
            if (ElevationNow > 45) AziOGSued.sendCommand(OFF)
            else (AziOGSued.sendCommand(ON))
        AziEGSued.sendCommand(ON)
        AziWest.sendCommand(ON)
        AziEGSuedMitte.sendCommand(ON)
        }
        if (AzimuthNow > 270 && AzimuthNow < 285) {
        AziOGSued.sendCommand(OFF)
        AziEGSued.sendCommand(OFF)
        AziWest.sendCommand(ON)
        AziEGSuedMitte.sendCommand(OFF)
        }
    }
    
    else if (TempAussDiff <= 3 && sonne) {
        createTimer(now.plusMinutes(5), [ | // schlafe 5 minuten
        if ((TemperatureAussenVorne.state as Number) - (TemperatureAussenHinten.state as Number) + (tau * ((TemperatureAussenHinten.state as Number) - (TemperatureAussenDifferenz.state as Number))) < 3) {
            sonne = false
            Sun.postUpdate(OFF)
            AziOGSued.sendCommand(OFF)
            AziEGSued.sendCommand(OFF)
            AziEGSuedMitte.sendCommand(OFF)
            AziWest.sendCommand(OFF)
        }
        ])
    }   
}		
end

// *********************************************
// ON RULES
// *********************************************

rule "Rolladen AziOGSued"
when
Item AziOGSued received command ON
then
if ( SchlafzimmerRollo_S_OUT.state == 1) SchlafzimmerRollo_S_IN.sendCommand(schliesswertKl)
if ( KindMaxRollo_S_OUT.state == 1) KindMaxRollo_S_IN.sendCommand(schliesswertKl)
end

rule "Rolladen AziWest"
when
Item AziWest received command ON
then
if (log) logInfo("****RolloSteuerung****", "AziWest Kommandos gesendet" + KindEmmiRollo_W_OUT.state)
if ( KindEmmiRollo_W_OUT.state == 1) KindEmmiRollo_W_IN.sendCommand(schliesswertGr)
if ( WohnzimmerRollo_W_OUT.state == 1) {
    WohnzimmerRollo_W_IN.sendCommand(schliesswertGr)
}
if ( BueroRollo_W_OUT.state == 1) BueroRollo_W_IN.sendCommand(schliesswertGr)
end

rule "Rolladen AziEGSued"
when
Item AziEGSued received command ON
then
if ( WohnzimmerRollo_SW_OUT.state == 1) WohnzimmerRollo_SW_IN.sendCommand(schliesswertGr)
if ( WohnzimmerRollo_SO_OUT.state == 1) WohnzimmerRollo_SO_IN.sendCommand(schliesswertGr)

end

rule "Rolladen AziEGSuedMitte"
when
Item AziEGSuedMitte received command ON
then
var aussperrschutz = WohnzimmerFenster_S.state
if (aussperrschutz == NULL) aussperrschutz = 'OPEN'
if (aussperrschutz == 'CLOSED' && WohnzimmerRollo_S_OUT.state == OFF ) WohnzimmerRollo_S_IN.sendCommand(schliesswertGr)
end

// *********************************************
// OFF RULES
// *********************************************

rule "Rolladen AziOGSued"
when
Item AziOGSued received command OFF
then
SchlafzimmerRollo_S_IN.sendCommand(1)
KindMaxRollo_S_IN.sendCommand(1)
end

rule "Rolladen AziWest"
when
Item AziWest received command OFF
then
KindEmmiRollo_W_IN.sendCommand(1)
//KindMaxRollo_W_IN.sendCommand(1)
WohnzimmerRollo_W_IN.sendCommand(1)
BueroRollo_W_IN.sendCommand(1)
end

rule "Rolladen AziEGSued"
when
Item AziEGSued received command OFF
then
WohnzimmerRollo_SW_IN.sendCommand(1)
WohnzimmerRollo_SO_IN.sendCommand(1)
end

rule "Rolladen AziEGSuedMitte"
when
Item AziEGSuedMitte received command OFF
then
WohnzimmerRollo_S_IN.sendCommand(1)
end