Rules/Xtend comparing types & strange behavior

I’m virtualizing my lights, as has been done before (Make any light controllable by HSBType to simplify automation), but I’ve encountered strange behavior in the processing of rules that I’d like help explaining!

My end goal is to modify incoming light command messages; I am attempting to write my code in a generic manner so that it can identify the type of light as well as the type of command, and then perform math/mapping to a new command (such that all lights could be treated as color, dimmer, or switch, and have reasonable behavior on all hardware endpoints even if they do not support that type of command - eventually extending this beyond lights). An intermediate goal is to learn how to write rules (I am inexperienced with OH2 & Java, but I’m not new to programming).

My immediate goal, however, is to understand why a code fragment (two failing versions are included below) would be failing; there is a way for me to write it (shown below) that functions and meets my requirements, but of course I am immediately suspicious of this behavior / my code.

For the same set of inputs (constant realItem, constant receivedCommand), this will work (“nested IF case”), correctly extracting the Brightness value from HSB:

            if ( realItem.getType() == "Dimmer" ) {
                if ( receivedCommand instanceof HSBType ) {
                    realCommand = receivedCommand.getBrightness().toString()
                }
            }

This will not work (“boolean logic case”):

            if ( ( realItem.getType() == "Dimmer" ) && ( receivedCommand instanceof HSBType ) ) {
                realCommand = receivedCommand.getBrightness().toString()
            }

12:07:19.668 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘virtual light’: ‘getBrightness’ is not a member of ‘org.eclipse.smarthome.core.types.Command’; line 42, column 31, length 31

Now it starts to feel really weird (“getType only case”) (remember, the receivedCommand is still HSBType; this fragment is just for testing):

            if ( realItem.getType() == "Dimmer" ) {
                realCommand = receivedCommand.getBrightness().toString()
            }

12:03:31.658 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘virtual light’: ‘getBrightness’ is not a member of ‘org.eclipse.smarthome.core.types.Command’; line 42, column 31, length 31

I’ve noted that if I explicitly cast the command to HSBType, it seems to work in all cases. Should this be necessary? Is this “more correct”? (It’s just super weird because the {} should not execute unless the command is ALREADY HSBType.)

realCommand = (receivedCommand as HSBType).brightness.toString()

I’m sure I’m probably doing something horrible, something wrong, and probably also something horribly wrong. What is it? (;

It might be more worth your time to use Scripted Automation and write Rules in Python, JavaScript, or Groovy. It’s more powerful and far more forgiving for those who already know how to program than Rules DSL.

Since you are a programmer I’ll assume you know the basics of OO. receivedCommand is of type Command. getBrightness (note in Rules DSL the parens are optional when the method doesn’t require arguments) only exists on HSBType. So you need to do a similar check to see if receivedCommand is of type HSBType (assuming this Rule can be triggered with other Command types), cast it to HSBType and then you can call getBrightness: (receivedCommand as HSBType).getBrightness.

Yes, because receivedCommand could be OnOffType or PercentType or IncreaseDecreaseType and still be reasonable and in a Rule like this useful. So receivedCommand defaults to a type higher up in the hierarchy. It’s up to you to cast it down.

Right, you’ve checked whether it’s HSBType but that doesn’t actually change the type. You have to cast to use it as receivedCommand.

1 Like

Why does it work to use this without explicit casting in some instances? That is what is mystifying to me. (Given ALL receivedCommands are HSBType - I indicated that the command was held constant for this testing.)

But when the Rules Engine loads the file it doesn’t know that you will only ever send HSBType commands to that Rule. Even after it’s running, it has no way of knowing that you will only ever send HSBType commands to that Rule. It can’t even guess that is the case if the Rule is only triggered by a Color Item receiving a command because you can send a Color Item HSBType, OnOffType, PercentType, and I think IncreaseDecreaseTypes as commands. So it sets the type of receivedCommand to the parent of all commands Command.

You as the programmer can know that you will only ever send HSBType commands to trigger the Rule so you can tell it “hey, it’s an HSBType” by casting receivedCommand to HSBType.

When it works and when it doesn’t depends on the context and how much the parser is able to deduce from the context. In the receivedCommand case, it can’t deduce anything more specific than Command as the type.

When it works, sometimes it doesn’t matter because the thing you are doing with the Object is declared at the higher levels (e.g. all Objects have toString). Sometimes it is able to figure it out based on the context (e.g. "some string " + MyItem.state works because it knows you are appending to a String as the first argument is a String so there is no other possibility but to call toString on MyItem.state for you, but MyItem.state + " some string" won’t work because there could be other operations beyond appending to a String that is intended so it needs you to tell it what you want by casting or converting the state to the right type). Sometimes it really needs some other type entirely so you need to parse or convert from one Object to another (e.g. createTimer((MyNumberItem.state as Number).intValue, ..., createTimer requires a primitive int as the first argument so you need to cast the State to a Number and then call a method to get the primitive).

As a general Rule, you should not specify type until you encounter an error in which case your first step is to define the type. Ultimately this is what you get when you try to mix a strongly typed language (Java) with a weakly typed language (XTend).

So, as I suggested before, you time might be better spend on Scripted Automation.

1 Like

Thanks for taking the time to respond! It’s funny that a nested IF could provide enough context to the parser, but a && statement would not - I’m used to compilers essentially optimizing those two implementations into the same executed code. It feels like a parsing bug, and I’d say that it’s still unfriendly that it works in some cases but not others, but so long as there is a workaround or a more correct way to express what you’re doing in code, it’s not a big deal to me. I’ll stick to code that explicitly casts after a type test.

(For anyone else monitoring…)

            if ( ( receivedCommand instanceof HSBType ) && 
                 ( realItem.getType() == "Dimmer" ) ) {
                realCommand = (receivedCommand as HSBType).brightness.toString()
            }

I’m using the native language to start with a better-supported platform and to be able to utilize and contribute back code fragments, but I’d probably much prefer Python in the end. Next steps. Thanks for your help!

Python is a native language. I believe the plan is for Python to become the default language in OH 3. At the very least Rules DSL will not be the default and to some extent it will be deprecated.

Programming rules in Python has been around for years and it is very mature and much more capable than Rules DSL in many ways. And for years I have recommended those who already know how to program to use Scripted Automation (formerly called JSR223) over Rules DSL. Now I recommend all new users use it if they are just getting started.

If you prefer Python I cannot encourage you strongly enough to just go straight to Python. Don’t mess with Rules DSL. It’s legacy and it will be around for a long time, but there are enough examples (there are Python version for almost all of them), docs, and users who are using Python (including me) to help you along the way. And with the Helper Libraries, Python Rules look enough like Rules DSL Rules that it is not super hard to translate between the two. For some comprehensive examples see Journey to JSR223 Python 1 of 9 showing step by step how I converted all my old Rules DSL to Python. And see [beta testers wanted!] Jython addon w/ helper libraries (requires OH 2.5.x) for the easiest way to get started.

These problems with casting are not the only problems with Rules DSL. You also have:

  • You can’t have more than 5 Rules running at the same time
  • There is the concept of lambdas but they are not thread safe and only accessible from the file they are defined in
  • You can’t make your own data structures or classes or anything like that, at best you can fake it using Java collections (ArrayList, HashMap, etc.)
  • There are no globals outside a given .rules file (except for Items)
  • You can’t access Item metadata from Rules DSL Rules
  • They are very very slow to parse and load on RPis, this is exacerbated when you over specify type and it’s really bad when you use primitives

Compared to Scripted Automation, Rules DSL is no longer any easier to learn for non-programmers yet they come with so many problems. Just get started with the good stuff.

1 Like

:+1: :+1: :+1: :+1:

I was aware of many of the limitations, but I’m glad I tried it. I feel like I’ve learned enough of Rules DSL. See you in Python land! (:

I don’t want to jump into anyone else’s post, and if I should start a new thread feel free to tell me and I will move this.

I’m doing something very similar but I’m trying to accomplish it in Python. I’m actually liking writing rules in Python as opposed to the rules DSL so I’m moving most of my code in that direction. I just had a question I am having trouble finding the answer to. Above @rlkoshak stated that it’s possible to get the type for the received command in Python. Is there any way you can point me either to documentation on how to accomplish that or give me a hint. I have experience with programming and am familiar with OOP styles so I can decipher examples and documentation if you have something that might point me in the right direction.

Essentially I’m attempting to do exactly what @gws mentioned as his final goal, just in Python. So if either of you have any tips I’d really appreciate it.

Thanks!

I have not read through this topic, but I believe you are looking for something like this…

if isinstance(event.itemCommand, HSBType) and realItem.type == "Dimmer":
    realCommand = event.itemCommand.brightness.toString()

Small hint here…

https://openhab-scripters.github.io/openhab-helper-libraries/Guides/But%20How%20Do%20I.html#stop-a-rule-if-the-triggering-item-s-state-is-null-or-undef

Thanks so much for the info! I’ll make use of that.