Do something according to several conditions

Hi!
I wanna make my home be a little more smart and “guess my thoughts”.
I think to myself about this conditions (just rough example as start point):

  • If light is off this means that I’m sleeping and it gives 60% to that
  • If XBMC player state Stop - I’m sleeping - 40%
  • If noise level is lower than threshold - I’m sleeping - 80%

Now I wanna try to check all this 3 conditions for example every minute and decide if “house should go into sleeping mode”, I decided that accepted value for me is 95%.

For example:
light is off, xbmc player state is stop and noise level is lower than threshold, means:
1-((1-0.6)*(1-0.4)*(1-0.8)) = 0.952
result is greater than 95 - so house should “think” I’m sleeping and something should happen not really important what.

To let you understand my point clearly - another example:
light is off, player is stop, but noise is NOT lower than threshold, so
1-((1-0.6)*(1-0.4)*(1-0.2)) = 0.8
house “think” that I’m not sleeping - result lower than 95%, but if at that moment I’was really sleeping (so house did not guessed) I can adjust conditions like:
Light was off and I was sleeping - so I can increase level and make 60 -> 61 or left it as is.
Player was stop and I was sleeping - so here the same or 40 -> 41 or left as is.
Noise level was NOT lower than threshold and I was sleeping - so we should decrease this condition level 80 -> 79.
Something like this, maybe you can share your similar projects.

Now the question - how to do that in openhab rules? )) I’m not a coder or programmer, my programming knowledge is almost invisible. I tried to do something like that:

rule "Enter sleeping mode"
when
    Time cron "0 * * * * ?"
then
    var double isSleeping1
    var double isSleeping2
    var double isSleeping3
    switch(Bedroom_OSMC_State.state) {
            case "Stop" : {
                isSleeping1 = 0.40
            }
            case "Play" : {
                isSleeping1 = 0.10
            }
            case "Pause" : {
                isSleeping1 = 0.20
            }
        }
    
    if (Bedroom_Light.state == OFF) {
        isSleeping2 = 0.60
    }
    else {
        isSleeping2 = 0.40
    }
    if (Bedroom_Noise.state < 200) {
        isSleeping3 = 0.80
    }
    else {
        isSleeping3 = 0.20
    }
    var result = 1 - ((1-isSleeping1)*(1-isSleeping2)*(1-isSleeping3))
    postUpdate(Is_Sleeping, String::format("%.3f",result))
end

Could you please help to improve the code and make it more flexible, extendable, reusable…? Maybe to use arrays, or write some function, or there are some libraries already?

I think for what you are trying to do this code is actually pretty tight. I’m not sure I would change it unless you think you will be doing a lot of changes to your formula in the future (e.g. adding more Items to track, variable weights, etc.)

Some things you could do to make it more flexible (I don’t have time right now to provide a full example, hopefully someone else can or you can wait until sometime next week. Code is below, Items and Groups is an exercise left to the student):

  • Put your Items into a group and iterate over the group to calculate the weights
  • Calculate as you go
  • Put your weights into Number Items (you can initialize them in a System started rule). You can even put them on your sitemap for adjustment
  • Set the name of your Number Items and your other Items such that you can easily construct the name of the Number Item from your Switch’s name using gSleepingWeights.members.filter[i|i.name==switch.name+"_Weight_"+switch.state.toString]
  • Have your Is_Sleeping Item be a switch that is ON when you are sleeping and OFF when not.

So your end code would look something like:

val result = 0
gSleepingItems.members.forEach[i | 
    val weight = gSleepingWeights.members.filter[s|s.name=i.name+"_Weight_"+i.state.toString].head.state as DecimalType
    if(result == 0) result = 1-weight
    else result = result * (1-weight)
]
if(result > Sleeping_Threshold.state as DecimalType) Is_Sleeping.sendCommand(ON)
else  Is_Sleeping.sendCommand(OFF)
1 Like

Wow! Really cool! Definitely now I have things to do on this weekend. Thank you! Will write back here with results.

Check what I did after your advice - it’s working pretty well, sure it is rough example and I should use more specific sensors for this task, but as POC it’s ok:

var double sleepYesResult = 0.000
var double sleepNoResult = 0.000
var boolean sleepYesOk = false
var boolean sleepNoOk = false

rule "Is Sleeping?"
when
    Item Bedroom_Noise changed or
    Item Bedroom_Light changed or
    Item Bedroom_OSMC_State changed or
    Item Bedroom_Noise_Weight_Low changed or 
    Item Bedroom_Noise_Weight_Normal changed or
    Item Bedroom_Noise_Weight_High changed or
    Item Bedroom_Light_Weight_ON changed or
    Item Bedroom_Light_Weight_OFF changed or
    Item Bedroom_OSMC_State_Weight_Stop changed or
    Item Bedroom_OSMC_State_Weight_Pause changed or
    Item Bedroom_OSMC_State_Weight_Play changed
    //Time cron "0 * * * * ?"

then
    sleepYesResult = 0.000
    sleepNoResult = 0.000
    sleepYesOk = false
    sleepNoOk = false
    SLEEPING_ITEMS.members.forEach[i | 
        var weight = (SLEEPING_WEIGHTS.members.filter([s|s.name==i.name+"_Weight_"+i.state.toString]).head.state as DecimalType).floatValue()
        if (weight > 0) {
            sleepYesOk = true
            if (sleepYesResult == 0) sleepYesResult = 1-weight*weight/10000
            else sleepYesResult =  (1-weight*weight/10000)*sleepYesResult
        }
        else {
            sleepNoOk = true
            if (sleepNoResult == 0) sleepNoResult = 1-weight*weight/10000
            else sleepNoResult =  (1-weight*weight/10000)*sleepNoResult
        }
    ]
    if (sleepYesOk) {sleepYesResult = 1 - sleepYesResult}
    if (sleepNoOk) {sleepNoResult = 1 - sleepNoResult}
    // debug
    Calculated_Yes_Sleeping.postUpdate(String::format("%.3f",sleepYesResult))
    Calculated_No_Sleeping.postUpdate(String::format("%.3f",sleepNoResult))
    
    Is_Sleeping.sendCommand(if(sleepYesResult > sleepNoResult && sleepYesResult*100 > Sleeping_Threshold.state as DecimalType) {ON} else {OFF})
end

weight is from -100 to 100. Positive value votes for Sleep mode ON, negative votes for OFF. As well I used weight power^2 to let higher weight be more “powerful”.

Example weights:

    Bedroom_Noise_Weight_Low.postUpdate(90)
    Bedroom_Noise_Weight_Normal.postUpdate(0)
    Bedroom_Noise_Weight_High.postUpdate(-50)
    Bedroom_Light_Weight_ON.postUpdate(-90)
    Bedroom_Light_Weight_OFF.postUpdate(60)
    Bedroom_OSMC_State_Weight_Stop.postUpdate(60)
    Bedroom_OSMC_State_Weight_Pause.postUpdate(-20)
    Bedroom_OSMC_State_Weight_Play.postUpdate(-60)

As you can see when Bedroom Light is ON it votes for NOT sleeping mode (negative) because I personally almost never sleep with light, but the same weight but vote for sleeping mode has Low Noise - sure when we sleep noise should be low or we could not sleep well… etc etc, now I can easily add different sensors and weights doing little changes in rule (thanks @rlkoshak ) and after all - my home will be guessing my wishes better and better ))

BTW, I’ve tried:

rule "Is Sleeping?"
when Item  changed
then ...

but it does not work - rule is not triggering at all

Next TODO: Work with several modes (for example security mode can be: white, yellow, orange and red not only two)

PS: I use this sitemap to debug and adjust values:

    Frame label="Sleeping mode debug"
    {
        Text item=Is_Sleeping
        Text item=Calculated_Yes_Sleeping
        Text item=Calculated_No_Sleeping
        Switch item=Bedroom_Light
        Selection item=Bedroom_Noise  label="Bedroom Noise" mappings=["Low"="Low", "Normal"="Normal", "High"="High"]
        Slider item=Sleeping_Threshold label="Sleeping Threshold"
        Setpoint item=Bedroom_Noise_Weight_Low label="Noise LOW sleeping weight" minValue=-99.0 maxValue=99 step=1
        Setpoint item=Bedroom_Noise_Weight_Normal label="Noise NORMAL sleeping weight" minValue=-99.0 maxValue=100 step=1
        Setpoint item=Bedroom_Noise_Weight_High label="Noise HIGH sleeping weight" minValue=-99.0 maxValue=99 step=1
        Setpoint item=Bedroom_Light_Weight_ON label="Bedroom Light ON sleeping weight" minValue=-99.0 maxValue=99 step=1
        Setpoint item=Bedroom_Light_Weight_OFF label="Bedroom Light OFF sleeping weight" minValue=-99.0 maxValue=99 step=1
        Setpoint item=Bedroom_OSMC_State_Weight_Stop label="Player State STOP sleeping weight" minValue=-99.0 maxValue=99 step=1
        Setpoint item=Bedroom_OSMC_State_Weight_Pause label="Player State PAUSE sleeping weight" minValue=-99.0 maxValue=99 step=1
        Setpoint item=Bedroom_OSMC_State_Weight_Play label="Player State PLAY sleeping weight" minValue=-99.0 maxValue=99 step=1
    }

A group’s state is an aggregation of the states of all its members. Consequently it is almost never the case that a change to a group’s members results in a change to the group when using the default aggregation function of OR.

However, if you trigger on a group using “received update” the rule will trigger multiple times for every change to any of its members, which would be ok in this instance.

1 Like

It might still be better to list all of the individual items’ changed triggers instead of “group received update”, simply because the “group received update” will probably fire much more frequently than listening to the individual items, and this could have a performance impact. But it’s a tradeoff between performance and code brevity.

Very much so. There are also cases where the rule executing multiple times for one change can change the rule’s behavior. When making the tradoff between performance and brevity i almost always choose brevity until performance actually becomes a problem. Thus far I’ve never needed to change a rule to improve performance. However, in this case i can see a potential as the number of items in the group grows there will be a lot going on.

1 Like