Strange type problem with rule

Hi all. I am having the following odd problem in a DSL-based rule in OH4:

I have a number item “Temperatur” defined. In a rule I wish to compare to its value.

I have tried two things, first:
if(Temperatur.state<1) {
This seems to cause the problem that this branch condition does not become true even if the value is below 1.

Then I tried:
if((Temperatur.state as Number)<1) {
This gives me the same problem.

When debugging this, I tried to log the value, as in:
logInfo("FROSTNACHT-TEMP", Temperatur.state);

Stragely, this gives me a nullpointer exception:
Script execution of rule with UID 'knx-8' failed: An error occurred during the script execution: Could not invoke method: org.openhab.core.model.script.actions.Log.logInfo(java.lang.String,java.lang.String,java.lang.Object[]) on instance: null in knx

How can this be? The value is definitely not null. When I add +“” , as in:
logInfo("FROSTNACHT-TEMP", Temperatur.state+"");
… then I can observe the right temperature value.

Best wishes, Eric

That’s just one of quirks of Rules DSL unfortunately. Sometimes it’s great at detecting and converting types to what it needs to and other times it completely fails.

Note: when you see “on instance: null” in a Rules DSL error, it’s not that the variable is actually null, it’s almost always that it failed to convert the Object to a type that it can work with in the context.

In that log statement’s case, it can’t convert Temperatur.state, a State Object, into a String automatically. When you append an empty string to it you’ve given it enough context to realize that it needs to call toString on the Object.

Your if condition probably has to do with the Item being of type Number:Temerature meaning you either need to strip the unit (in which case why bother with the units in the first place) or compare to a value with units.

One other trick with Rules DSL and type is that the type of the first operand is used as the type for the operations. So either of these should work:

if(Temperature.state as QuantityType<?> < 1|°C)
if(1|°C > Temperature.state)

but the following will throw that null error.

if(Temperatur.state < 1|°C)

I tend to discourage new development in Rules DSL. If you are a programmer, give one of the other languages a look. JS Scripting and jRuby are great! If you are not a programmer, give Blockly a try. All of these too have their own quirks but in general provide a more complete and consistent development environment.

Thanks a lot for the tips! And I though JavaScript was the weirdest language I could ever encounter :joy:

From what I can see, Temperatur is in my case just a bare number, though. I defined it as:
Number Temperatur "Außentemperatur [%.1f °C]" { channel="knx:device:bridge:sensors:Temperatur" }

Thus the casting to QuantityType does not work:
logInfo("FROSTNACHT-TEMP", Temperatur.state as QuantityType<?>);
gives me:
Script execution of rule with UID 'knx-8' failed: Could not cast 15.700000000000001 to org.openhab.core.library.types.QuantityType; line 140, column 32, length 35 in knx

Can I somehow print out the type of the expression Temperatur.state to see what type it is, such that I can infer which operations it should support?

And thanks for the tip with the other scripting languages. I tried JS a bit but some of the rules did not look as nice - plus it’s also an often idiotic language. But it might still be the better choice for me, maybe.

try

if ((Temperatur.state as Number) < 1.0)

That seems to do the trick, thank you very much! I still do not see how it could make a difference but I have learned to to reason with logic here. :wink:

Assuming you have a little knowledge of inheritance and polymorphism…

All Items get injected into Rules DSL as type Item.

The method Item.getState() returns an Object of type State.

Therefore, the call to Temperatur.state returns an Object of type State (org.openhab.core.types.State to be specific).

A State doesn’t support math or comparisons to other types on it’s own.

However, if your Item is a Number Item, we know that the State is a DecimalType. DecimalType inherits from State and it also inherits from java.lang.Number. Therefore we can do math with the state from a Number Item (assuming it’s not UnDefType, i.e. NULL or UNDEF).

Remember above where I said:

One other trick with Rules DSL and type is that the type of the first operand is used as the type for the operations

In the expression if(Temperatur.state < 1.0). The first operand is a State and the second one is a Number. So the type fixed for the operation is State and there is no method implemented anywhere that allows for a State < Number comparison.

However, you know that the Item is a Number Item and therefore that State is actually a DecimalType which is also a Number. Therefore you can tell Rules DSL “no, I want this operation to work with Numbers” by making the first operand be the type you want to work with. There are two ways you can do that as demonstrated above: swap the operation so the constant is the first operand, or cast the State to a version of itself that is usable.

if(1 > Temperatur.state)
if(Temperatur.state as Number < 1)

Rules DSL often punishes you when you try to force the types of things except when it can’t figure out types on it’s own in which case you as the programmer need to know the types of everything to work around it. Are you sure JS is more idiotic than Rules DSL? In comparison I find it way more consistent than Rules DSL and it supports all the standard programming concepts like functions, libraries, etc.

Which version of JS? If JS Scripting, did you try rule builder?

I only create rules in the UI so all I ever really see is the actual JS code anyway. But I find the rule builder to create pretty reasonable rules code.

rules 
  .when()
    .item('HallLight').changed().to("300,100,100")
  .then((event) => {
    // code goes here
  }).build("Description of rule);

Thanks a lot for all the explanations. I actually know a fair bit about programming language theory and practice, which is why the DSL is right now so confusing to me. Your explanations help understand what’s going on, so thank you for that, but they also confirm that it’s a pretty unorthodox language design.

The JS rule builder I had not yet seen. That looks interesting! I will have a look, thanks!

Check out JRuby too while you’re at it

rule "Rule Name" do
  description "Rule description" # optional
  changed ColorItem1, to: "300,100,100"
  run do |event|
    logger.info "#{event.item} changed to #{event.state}"
  end
end

I have a question about the JS rule builder. Is there a way to send two different messages on an event, something like the following?

send("80").toItem("X").and().send("60").toItem("Y")

I could not find it in the documentation.

Not that way I think. You’ll have to use a then(funct) and either define an anonymous function or pass it a function that sends the commands to the two Items.

Ah I see, thanks. Well, maybe an idea for a future extension then.