Need help with rule (Velux windows controlled from outside temp, alarm and Lumiance), now optimizing

Another rule is making my hair grey. I get no errors, but nothing seems to happen, not even logging.
What am I doing wrong? (I grap´d this rule from another thread and build something I thought was working).
This is mostly self explained, I hope.

rule "Automatic controle of all skylight windows"

when

  Item NetamoUdendoersTemperature changed

then

	if(NetamoUdendoersTemperature.state >= 25 && alarm_totalalarm.state == OFF && Node13_SensorLuminance.state >= 100) {
		logInfo("Skylight windows controle: ","Alarm OFF, outdoor temperature >=25grader, windows 100% open")
			VeluxAlleAaben100.sendCommand(ON)
    } 

	else if (NetamoUdendoersTemperature.state >= 23 && alarm_totalalarm.state == OFF && Node13_SensorLuminance.state >= 100) {
		logInfo("Skylight windows controle: ","Alarm OFF, outdoor temperature >=23 degrees, windows 75% open")
			VeluxAlleAaben75.sendCommand(ON)
    } 

 	else if (NetamoUdendoersTemperature.state >= 20 && alarm_totalalarm.state == OFF && Node13_SensorLuminance.state >= 100) {
		logInfo("Skylight windows controle: ","Alarm OFF, outdoor temperature >=20 degrees, windows 50% open")
			VeluxAlleAaben50.sendCommand(ON)
    } 
	else if (NetamoUdendoersTemperature.state < 19 && alarm_totalalarm.state == OFF && Node13_SensorLuminance.state >= 100) {
		logInfo("Skylight windows controle: ","Alarm OFF, outdoor temperature < 19 degres, windows on ventilation") 	
			VeluxAlleVent.sendCommand(ON)
    }

	else if (NetamoUdendoersTemperature.state < 18 && alarm_totalalarm.state == OFF && Node13_SensorLuminance.state >= 100) {
		logInfo("Skylight windows controle: ","Alarm OFF, outdoor temperature <18 degrees , close windows") 	
			VeluxAlleLuk.sendCommand(ON)
    }

end

// Logic.
// All skylight windows automatic controlled from these criterias: 
// 
//	- Alarm is OFF (someone is at home).
//	- Outdoor temperature as set.
//	- Lumiance has to be bigger than 100 Lux.

I would suggest a slightly rebuilt rule:

rule "Automatic control of all skylight windows"
when
    Item NetamoUdendoersTemperature changed
then
    logInfo ("skyWin","rule triggered")
    if(alarm_totalalarm.state != OFF ) {
        logInfo ("skyWin","alarmstate: {} Stop rule here.",alarm_totalalarm.state)
        return;
    }
    if(!(Node13_SensorLuminance.state instanceof Number)) {
        logInfo ("skyWin","Luminance has no Numbervalue. Stop rule here.")
        return;
    }
    if((Node13_SensorLuminance.state as Number) < 100 ) {
        logInfo ("skyWin","Luminance: {} Stop rule here.",Node13_SensorLuminance.state)
        return;
    }
    if(!(NetamoUdendoersTemperature.state instanceof Number)) {
        logInfo ("skyWin","Temperature has no Numbervalue. Stop rule here.")
        return;
    }
    val Number fTemp = NetamoUdendoersTemperature.state as Number
    logInfo ("skyWin","Temperature is {}",fTemp)
    switch fTemp {
        case fTemp >= 25 : {
            logInfo ("skyWin","Temperature >= 25 degrees, windows 100% open")
            VeluxAlleAaben100.sendCommand(ON)
        }
        case fTemp >= 23 : {
            logInfo ("skyWin","Temperature >= 23 degrees, windows 75% open")
            VeluxAlleAaben75.sendCommand(ON)
        }
        case fTemp >= 20 : {
            logInfo ("skyWin","Temperature >= 20 degrees, windows 50% open")
            VeluxAlleAaben50.sendCommand(ON)
        }
        case fTemp > 18 : {
            logInfo ("skyWin","Temperature > 18 degrees, windows on ventilation")
            VeluxAlleVent.sendCommand(ON)
        }
        default : {
            logInfo ("skyWin","Temperature < 18 degrees, close windows")
            VeluxAlleLuk.sendCommand(ON)
        }
    }
end

As a first step, the rule checks if alarm is off. If not, there is nothing to do, so just stop the rule.
As a second step, the rule checks if luminance is above 99 (but only after checking, if the item has a number in it…) if not, there is nothing to do…
As the third step (after checking, if the temperature is a valid number, too), the rule does it’s decision.

In fact, if omitting the log lines, the rule would be much shorter:

rule "Automatic control of all skylight windows"
when
    Item NetamoUdendoersTemperature changed
then
    if(alarm_totalalarm.state != OFF ) return;
    if(!(Node13_SensorLuminance.state instanceof Number)) return;
    if((Node13_SensorLuminance.state as Number) < 100 ) return;
    if(!(NetamoUdendoersTemperature.state instanceof Number)) return;
    val Number fTemp = NetamoUdendoersTemperature.state as Number
    switch fTemp {
        case fTemp >= 25 : VeluxAlleAaben100.sendCommand(ON)
        case fTemp >= 23 : VeluxAlleAaben75.sendCommand(ON)
        case fTemp >= 20 : VeluxAlleAaben50.sendCommand(ON)
        case fTemp > 18 : VeluxAlleVent.sendCommand(ON)
        default : VeluxAlleLuk.sendCommand(ON)
    }
end
3 Likes

Thanks alot Udo. I´ll give it a try.
I do however use the log lines, just to make sure everything is the way it´s suppose to. When rules are working, I can get rid of the log lines :slight_smile:

Just a side note - Any specific reason why my first rule didnt work? I´m still trying to learn creating rules, and the best way to learn, is to know why it doesn´t work :slight_smile:

Hello Udo

Just to improve my programming knowledge…

does the switch case only fits one time?
Because 20 is higher than 18 and 25 is higher than 23, 20 and 18?

I did similar solutions with many if’s and thats much easier :slight_smile:

Thanks, Michael

switch case will execute the first case, which does fit, so the sequence is important.
When there are fixed values, the case is even easier:

switch myValue {
    case 0: logInfo("test","Value is zero")
    case 1: logInfo("test","Value is one")
    case 2: logInfo("test","Value is two")
    case 3: logInfo("test","Value is three")
    case 4: logInfo("test","Value is four")
    case myValue < 4: logInfo("test","Value is {}",myValue)
    default: logInfo("test","Value is A Suffusion of Yellow")
}

So, you only need to reuse the value in the case line when doing a comparison.

I can only guess why your rule does not work (or better: maybe only does not work reliable), and the first guess would be, that whenever the rule triggered, at least one of the requirements was not fullfilled…
With the logging, it will be easy to see, which one is “guilty” :wink:

First thing to do is check whether the Rule is actually firing. So look at events.log to make sure the events are occurring that trigger the Rule. Then add as the first line of the Rule a log statement to confirm that the Rule is firing.

Once you’ve proven that the events are occurring and Rule is triggering then you know that the problem is with the if/else statements. So log out the relevant values you are comparing against and work through the code line by line and figure out what the code it doing by “executing” the code on paper or in your mind.

That is one approach to diagnose this sort of problem. But often, especially for programmers with a little more experience, it is faster to restructure the Rule in a way that makes it simpler/easier to understand like Udo did.

As a case in point, I’d further refine Udo’s Rule as follows:

rule "Automatic control of all skylight windows"
when
    Item NetamoUdendoersTemperature changed
then
    // Exit the rule when there is nothing to do
    if(alarm_totalalarm.state != OFF ) return;
    if(!(Node13_SensorLuminance.state instanceof Number)) return;
    if((Node13_SensorLuminance.state as Number) < 100 ) return;
    if(!(NetamoUdendoersTemperature.state instanceof Number)) return;

    // Calculate which velux to send the ON command to
    val Number fTemp = NetamoUdendoersTemperature.state as Number
    var velux = VeluxAlleLuk
    switch fTemp {
        case fTemp >= 25 : velux = VeluxAlleAaben100
        case fTemp >= 23 : velux = VeluxAlleAaben75
        case fTemp >= 20 : velux = VeluxAlleAaben50
        case fTemp > 18  : velux = VeluxAlleVent
    }

    // Send the command
    velux.sendCommand(ON)
end

I call this the 1-2-3 Rule structure and I wrote it up here.

The advantages are admittedly minor. The switch statement looks a little less complex. All of the side effects are centralized at the bottom of the Rule so if at a later date you want to do some additional checks (e.g. is it the right time of day?) before sending the Item the ON command.

2 Likes

And this will actually do a VeluxAlleLuk.sendCommand(ON) ? Wow…

Yes because VeluxAlleLuk is just an Item Object. All I’ve done is save the address in memory to VeluxAlleLuk to the variable velux so anything you can do with VeluxAlleLuk you can also do with velux.

It comes in handy in situations like this. This is also how we are able to pass Items to lambdas and such. The same principle is happening.

Hmm. So I should have added a loginfo after the triggering item?

Thats what I thought I did with the loginfo´s in each if´s.

Yeah, but I havn´t got the knowledge. I thought this rule was as simple as it gets, (though it could be made easyer, like Udo´s), but it looked bullet proof to me :slight_smile:
Thats why I dont understand, why it wasn´t working.

From your 1-2-3. I´m a naive coder :slight_smile:

Actually, I do have some situations I totally forgot about, (due to very high temperature here in Denmark atm. ).
What I have now is a rule to controle the windows automaticly. But there are two situations I forgot about.

What if…

  1. Windows are opened - Last person leave home, switching the alarm ON.
    Windows should change, either close (VeluxAllLuk) or set to ventilation (VeluxAllVent) depending on the outside temperature, ie above 20 degrees celcius set to ventilation, below 20 degrees celsius close windows.

  2. Lumiance goes below 100 but outside temperatur is still above above 19 degrees. Due to incoming mosquitoes etc, windows should be set to (VeluxAllVent) (Node13_SensorLuminance < 100)

I cant really figure out how to code this from the rule.

  1. I think maybe a new rule for switching the alarm ON is the best idea?
  2. I´m lost on this one as it´s a situation which somehow should belongs in the “main” rule, or?

The first line after then should be a log statement. That log statement will execute EVERY time the Rule gets triggered. If you see that log statement you know that the Rule is triggering but there is something wrong with the Rule. If you don’t see the log statement you know that there is something wrong with triggering the Rule.

But if there is a logic error in your if statements then you will never see any log statements and you won’t know whether or not the Rule is actually firing. The solution to the Rule not firing versus the Rule firing but not doing anything is completely different.

The goal of debugging is to systematically eliminate potential sources for the error until you figure narrow it down to exactly what is wrong.

If you put the log statements ONLY inside the if statements and you don’t see anything then all you know is that there is something wrong with your if statements. But if you log out the values of NetamoUdendoersTemperature, alarm_totalalarm, and Node13_SensorLuminance as the first line of your Rule, you can immediately if your problem is that one of these Items is NULL. And if there isn’t a NULL Item, you can walk through your code to figure out why your if statements are not executing.

The log statements inside the if clauses only give you useful information when the code is working.

  1. This belongs in a different Rule triggered when the alarm changes to ON. In this Rule check the current state of the windows and temp and adjust as necessary.

  2. You can create a separate Rule for this also triggered on the Luminance, or add the Luminance as a trigger to this same rule.

I would probably add it to this Rule.

rule "Automatic control of all skylight windows"
when
    Item NetamoUdendoersTemperature changed or
    Item Node13_SensorLuminance changed
then
    // Exit the rule when there is nothing to do
    if(alarm_totalalarm.state != OFF ) return;
    if(!(Node13_SensorLuminance.state instanceof Number)) return;
    if(!(NetamoUdendoersTemperature.state instanceof Number)) return;

    // Calculate which velux to send the ON command to
    val Number fTemp = NetamoUdendoersTemperature.state as Number
    val Number lux = Node13_SensorLuminance.state as Number
    var velux = VeluxAlleLuk
    
    switch fTemp {
        case fTemp >= 25 && lux >= 100: velux = VeluxAlleAaben100
        case fTemp >= 23 && lux >= 100: velux = VeluxAlleAaben75
        case fTemp >= 20 && lux >= 100: velux = VeluxAlleAaben50
        case fTemp > 19 && lux < 100, 
        case fTemp > 18 && lux >= 100 : velux = VeluxAlleVent
    }

    // Send the command
    velux.sendCommand(ON)
end

I removed the check to see if the lux was under 100 and return because we now need the Rule to run even when the value is under 100.

Then I added tests for lux to the switch statements to make sure they are above 100 for all the cases that were there before.

Then I added a case for the temp being over 19 and lux under 100. You can have more than one case for a given body by separating them with a common. So velux = VellusAlleVent will run if case fTemp > 19 && lux < 100 or case fTemp > 18 && lux >= 100 are true.

Thats why I fail to see, why my original rule didnt fire. I know 100% possitive that the netamo item changed. It´s a Netamo outside base unit measuring the outside temperature. It has been running rock steady for several months. And I would have notice any probelms long time ago if there were a problem. So triggering worked. Since I never received any loginfo in my original rule, it has be caused from the next line in the rule. I just dont see any problems with it.

Anyway, back to your modified rule…I think I get it now (I hope).

I just tried to add alarm criteria as well. But I have a problem since this is not a Number, but an switch (ON/OFF). I´m not quite sure how to define a switch. Is it just a switch then, like this:

rule "Automatic control of all skylight windows"
when
    Item NetamoUdendoersTemperature changed or
    Item Node13_SensorLuminance changed or
    Item alarm_totalalarm changed
then
    // Exit the rule when there is nothing to do
    if(!(alarm_totalalarm.state != OFF )) return;
    if(!(Node13_SensorLuminance.state instanceof Number)) return;
    if(!(NetamoUdendoersTemperature.state instanceof Number)) return;

    // Calculate which velux to send the ON command to
    val Number fTemp = NetamoUdendoersTemperature.state as Number
    val Number lux = Node13_SensorLuminance.state as Number
    val Switch alarm = alarm_totalalarm.state as Switch
    var velux = VeluxAlleLuk
    
    switch fTemp {
        case fTemp >= 25 && lux >= 100 && alarm = OFF: velux = VeluxAlleAaben100
        case fTemp >= 23 && lux >= 100 && alarm = OFF: velux = VeluxAlleAaben75
        case fTemp >= 20 && lux >= 100 && alarm = OFF: velux = VeluxAlleAaben50
        case fTemp > 19 && lux < 100, 
        case fTemp > 18 && lux >= 100  && alarm = OFF: velux = VeluxAlleVent
	case alarm = ON : velux = VeluxAlleVent
    }

    // Send the command
    velux.sendCommand(ON)
end

Just as simple as this? :slight_smile:

You saw the event in events.log? You can’t say triggering worked if you have seen no evidence that the Item changed state. You can assume. And you may very well be right. But when debugging a problem you MUST eliminate that as a potential source of problems and you can only do that with evidence. That evidence can be from events.log or it can be through the first line in your Rule after then being a logInfo statement.

Not it doesn’t. It could be lots of places. Your original Rule will generate absolutely no output if none of your if else statements evaluates to true. And since there is no output you have no evidence to tell you whether your mistake is your if else conditions, the state of your Items are not as expected, or that the Rule never ran in the first place.

Debugging is a skill and many people find it hard. You have to approach it like a scientist. You come up with a hypothesis: “My Rule isn’t triggering”. You then come up with an experiment: “I’ll add a logInfo as the first line of my Rule and watch both events.log and openhab.log. When NetamoUdendoersTemperature changes in events.log I should see my log statement in openhab.log.” Then you have a conclusion: “I didn’t see my log statement in openhab.log so my Rule isn’t triggering. I need to look at the Rule trigger for errors.” or “I didn’t see the event in events.log to trigger the Rule. So I need to look at the binding for errors.” or “I saw my initial log statement but nothing after that, I need to look at my Rule’s logic for errors.” Based on the conclusion you need to come up with more hypothesis’s and experiments until you diagnose what the problem actually is.

val alarm = alarm_totalalarm.state

should be sufficient. If not then

val alarm = alarm_totalalarm.state as OnOfftype

But yes, it is as simple as this. Note that if the temp is over 19 and the lux is less than 100 VeluxAlleVent will get the ON command no matter what the alarm state is.

And since alarm = ON does the same as the last two cases I’d just add it as another case

        case fTemp > 19 && lux < 100, 
        case fTemp > 18 && lux >= 100  && alarm = OFF,
        case alarm == ON: velux = VeluxAlleVent

Note the use of == for comparisons. = is for assignment as in var velux = VeluxAlleLuk.

There are several events changes according to the logfile.

2018-08-07 06:50:07.057 [vent.ItemStateChangedEvent] - NetamoUdendoersTemperature changed from 16.299999237060546875 ℃ to 16.3999996185302734375 ℃
2018-08-07 07:00:07.981 [vent.ItemStateChangedEvent] - NetamoUdendoersTemperature changed from 16.3999996185302734375 ℃ to 16.6000003814697265625 ℃
2018-08-07 07:10:14.973 [vent.ItemStateChangedEvent] - NetamoUdendoersTemperature changed from 16.6000003814697265625 ℃ to 17 ℃
2018-08-07 07:20:21.681 [vent.ItemStateChangedEvent] - NetamoUdendoersTemperature changed from 17 ℃ to 17.6000003814697265625 ℃
.
. (continues)

2018-08-08 00:30:42.555 [vent.ItemStateChangedEvent] - NetamoUdendoersTemperature changed from 23.200000762939453125 ℃ to 23.1000003814697265625 ℃
2018-08-08 00:40:48.188 [vent.ItemStateChangedEvent] - NetamoUdendoersTemperature changed from 23.1000003814697265625 ℃ to 23 ℃

Infact every ten minutes the item updates.
Ofcouse if there are no changes in the temperature, I don´t think there will be any update. But this is an outdoor sensor, where changes will happen almost every second, specially due to the many decimals.

So, yes I´m pretty sure the triggering worked. Thats why I´m almost possitive, that the reason has to be in the first if line. I just cant see it.

Agree. But this time, I´m very sure, that at least the trigger worked. And since I never got any logged info, the problem has to be somewhere in the first if line.

Ahh I see. Ofcouse thats obvoius.

Again, no it doesn’t.

You have shown that the event is taking place so the possibilities are:

  • the Rule isn’t actually triggering
  • there is a logic error in the Rule that is keeping all the if/else clauses to evaluate to false

We can eliminate that the problem is that the events are not occurring as you’ve seen evidence that they are occurring in events.log. But we cannot eliminate the possibility that the Rule itself isn’t triggering. You’ve seen no evidence that it actually is triggering.

Hypothesis: My Rule is actually triggering when the events occur
Experiment: Add a logInfo as the first line of the Rule
Conclusion: ???

Trust me, if you just assume that something must be happening without evidence that it is happening you will end up wasting a ton of time.

You must be deliberate. You must be methodological. You must trust no assumptions and prove to yourself every step of the way.

And the problem can’t be with just the first line of your Rule. There are tons of cases not covered by your if/else statements. If any one of those cases is true, then your Rule will generate no output. This is why:

Hypothesis: The states of the three Items are such that none of my if/else clauses evaluate to true
Experiment: Log out the states of all three Items as the first line of my Rule and add an else clause to run when none of the it/else clauses evaluate to true. Manually check each if/else to see which one should evaluate to true given the inputs. Since it isn’t evaluating to true, check the clause for potential reasons why (need to cast to Number, need to use || instead of &&, etc).

Hmm, the rule does´nt seem to work.
Here is the rule:

rule "Automatic control of all skylight windows"
when
    Item NetamoUdendoersTemperature changed or
    Item Node13_SensorLuminance changed or
    Item alarm_totalalarm changed
then
    // Exit the rule when there is nothing to do
    if(alarm_totalalarm.state instanceof Switch ) return;
    if(!(Node13_SensorLuminance.state instanceof Number)) return;
    if(!(NetamoUdendoersTemperature.state instanceof Number)) return;

    // Calculate which velux to send the ON command to
    val Number fTemp = NetamoUdendoersTemperature.state as Number
    val Number lux = Node13_SensorLuminance.state as Number
    val alarm = alarm_totalalarm.state as OnOfftype
    var velux = VeluxAlleLuk
    
    switch fTemp {
        case fTemp >= 25 && lux >= 100: velux = VeluxAlleAaben100
        case fTemp >= 23 && lux >= 100: velux = VeluxAlleAaben75
        case fTemp >= 20 && lux >= 100: velux = VeluxAlleAaben50
        case fTemp > 19 && lux < 100, 
        case fTemp > 18 && lux >= 100  && alarm = OFF,
        case alarm == ON: velux = VeluxAlleVent
    }

    // Send the command
    velux.sendCommand(ON)
end

And this is the error I get:

2018-08-08 22:11:28.825 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'automaticwindows.rules' has errors, therefore ignoring it: [23,49]: no viable alternative at input '='

[23,51]: no viable alternative at input 'OFF'

Remember, = is assignment. == is comparison. You are trying to assign OFF to the variable alarm but you can’t do that because:

  • alarm is a final variable, i.e. a val. Once set a val cannot be changed.

  • The case is expecting the result of a comparison, i.e. true or false. alarm = OFF doesn’t return anything so it makes no sense in this context

    case fTemp > 18 && lux >= 100 && alarm == OFF,

Found the problem, I think… At least I get no error now:

        case fTemp > 18 && lux >= 100  && alarm = OFF,

Should be:

        case fTemp > 18 && lux >= 100  && alarm == OFF,

Yeah, you told me, and I forgot :slight_smile:

Openhab loaded the rule fine. But windows are open, even though lux is 0. And outside temperature is 24.1. I would suspect the windows to close till ventilation by now.

And what is alarm_totalalarm?

If alarm_totalalarm is not ON then, based on the logic of the Rules, VeluxAlleLuk should have received an ON command. If alarm_totalalarm is ON then VeluxAlleVent should have received an ON command.

If that is correct then look at events.log to verify that events have occurred to cause the Rule to run. The Rules only trigger when there is a change to one of those three Items. If there has been no change the Rule won’t run even if the Items are updating.