RuleDSL: Switch/case statement not executing

  • Platform information:
    • Hardware: Raspberry Pi 3 Model B Rev 1.2_
    • Host: rpi3ohv2 Kernel: 5.4.51-v7+ armv7l bits: 32 Console: tty 2
    • Distro: Raspbian GNU/Linux 10 (busbetweneter)
    • OpenJDK Runtime Environment (Zulu 8.31.1.122-linux_aarch32hf) (build 1.8.0_181-b122)
  • Version: 2.5.11 (Build) (apt-get), text-based config
    • binding = astro, exec, logreader, network, ntp, systeminfo, fritzboxtr0641, expire1, mqtt1, weather1
    • ui = paper, basic, classic, restdocs
    • persistence = rrd4j, mapdb
    • action = mail, mqtt
    • transformation = map, javascript, xslt, scale, jsonpath

I have a rule, where a numeric variable has 1 added to it, based on a CRON schedule.
It has a switch/case construct, which tells me what these numbers mean.
It does not work. :frowning:

Creating a test rule with the same construct, and it works.
The only difference between the rules is: testRules reads and item.state as Number, while the production rule reads a numeric variable.

Why does the production rule it not work?

test.rules

val String logPrefix = "testRules_"

rule "test1"
    when
        Item testValue changed
    then
        var String valveLocation = ""
        var Number testNumber = testValue.state as Number

        switch (testNumber)
        {
            case 1:
            {
                valveLocation = "Driveway west"
            }
            case 2:
            {
                valveLocation = "Trees south-west"
            }
            case 3:
            {
                valveLocation = "Trees south-east"
            }
        }

        logInfo(logPrefix + "1.1", "--> testValue to location.....: {} (= {})", testNumber, valveLocation)
end

Production rule:

var Number currentValveNumber = 0

rule "PumpStation2 force active valve based on current time"
    when
		//         s  m     h D M DoW Y
        Time cron "0 57  9-12 ? * *   *" or
        Time cron "0 27 10-13 ? * *   *"
    then
        var String valveLocation = ""

        currentValveNumber = currentValveNumber + 1

        // display the valve location in the openhab.log
        // for dummies like me, who can't remember which is where :)
        switch (currentValveNumber)
        {
            case 1:
            {
                valveLocation = "Driveway west"
            }
            case 2:
            {
                valveLocation = "Trees south-west"
            }
            case 3:
            {
                valveLocation = "Trees south-east"
            }
        }

        logInfo(logPrefix + "6.1", "--> Advanced valve number to..: {} (= {})", currentValveNumber, valveLocation)
end

Results:

2021-04-08 19:36:17.029 [INFO ] [smarthome.model.script.testRules_1.1] - --> testValue to location.....: 2 (= Trees south-west)
2021-04-08 19:36:30.801 [INFO ] [smarthome.model.script.testRules_1.1] - --> testValue to location.....: 3 (= Trees south-east)
2021-04-08 19:36:33.270 [INFO ] [smarthome.model.script.testRules_1.1] - --> testValue to location.....: 1 (= Driveway west)
2021-04-08 09:57:00.010 [INFO ] [arthome.model.script.Irrigation2_6.1] - --> Advanced valve number to..: 1 (= )
2021-04-08 10:27:00.013 [INFO ] [arthome.model.script.Irrigation2_6.1] - --> Advanced valve number to..: 2 (= )
2021-04-08 10:57:00.019 [INFO ] [arthome.model.script.Irrigation2_6.1] - --> Advanced valve number to..: 3 (= )

Any hints appreciated.

I’m pretty sure that will make an int variable, DSL mostly ignores that kind of typing.
So I can see how currentValveNumber and testValue.state as Number would differ in type.

The funny thing is case 1: will also be int, so I’d expect it to be the state Number that struggled.
I tried all this out in OH2.5 and it seems to work fine.

Using
switch (currentValveNumber as Number)
still works in OH2.5, which proves nothing for me but might be your circumvention.

You might have found a hole in OH3 rules engine here.

Well, my rules are in OH2.5 :slight_smile:

Are you saying that switch (currentValveNumber as Number) is what I need to use, instead of switch (currentValveNumber)?

Just tried it, and get this:

Ah, forget that - I messed up testing.

In fact the opposite approach works …
var currentValveNumber = 0
for the global declaration. I have no idea why. Or rather, I should say that makes perfect sense, but why state as Number works I do not know.

This would be the first global I have w/o a type declaration.
But I’ll give it a try, and see how it runs tomorrow.

have
var int currentValveNumber = 0
or
var Integer currentValveNumber = 0
if you like, same difference.

This is fascinating.

var Number currentValveNumber = 1
switch (currentValveNumber)

works a treat

var Number currentValveNumber = 1
currentValveNumber = currentValveNumber + 0
switch (currentValveNumber)

breaks, gives no matches.
So far as I can make out, type is java.lang.Number with or without add operation, and however I try to examine the contents it is still value “1”,not “1.0” or anything silly.
I have a feeling there is a hidden difference “1” and “01” going in internally in the BigInteger used in a Number.

The real fix for all occasions is -
switch (currentValveNumber.intValue)
to force whatever to type-match the case options.

1 Like

In general, you should avoid giving a type to any variable, global or not, unless you are initializing the variable with something that makes it so the Rules Engine cannot determine the type on it’s own. For example:

var myNumberVar = 0
var Timer myTimer = null

In the former it can figure out the type based on the 0. But the latter null doesn’t tell it what type myTimer will ultimately hold so it will default to the lowest common denominator (Object).

Rules DSL does most of its type checking at runtime, not parse time. When you over-specify the types that limits the engine’s options at runtime. There will be cases where on its own it would have been able to convert both operands to something compatible with each other but if you specify the type of one or both operands the options become more limited and it may fail to find a match.

The other problem that occurs when over-specifying the type is that you’ve moved the type checking from runtime to parse time. At parse time the engine has way less information than at run time so it has to do a brute force check to make sure that all the different types that each operand can be are compatible with each other. This brute force O(N!) algorithm can add minutes to the parse time of your rules. It’s particularly pronounced when you use primitives as types.

I can’t find the post now but there was one equation one could add to a rule, a one liner, that added over a minute to the load time to rules on a well provisioned Intel machine. On RPi it’s even worse. But when removing the types and letting the engine figure that out at runtime, the load time dropped to a couple seconds. In both cases the right answer was calculated.

By default, Rules DSL will use BigDecimal for all numbers and all results of calculations. BigDecimal implements Number so everything should be fine casting them to Number, but clearly there is something going on.

I should add all that I just said above only applies to Rules DSL.

3 Likes

This did it! Works. Thanks!

I am not sure, where I got this from in the context of OH and DSL.
It is a must on C and other language, so good practice for me…

My general issue is: I dig into a problem, know it back to front… ask me a few weeks later… because I don’t use it (over time), I loose it (as the saying goes).

Well, I have got a file named Rich_Nuggets.txt where all this stuff good goes that I can’t find written anywhere else :smiley:

On another note: I deleted all the (number, string) types, and promptly got errors in that rule file, which I have to explore when I get to it. Meaning there seems to be still a point of casting variables.

In any case, thank you both for your support!

There are still cases where the type needs to be typed. One of the big problems with Rules DSL as a language is that it likes to pretend to be typeless like Python or JavaScript et al, but it does a really bad job of it. Sometimes it works and sometimes it doesn’t. When it does work, it will work better than if the type is manually defined. When it doesn’t you have to manually declare the type. So that’s why the rule of thumb is to not define the type unless you have to.

Unfortunately there is no good way to know when you have to until you encounter an error.