Rounding a Number in a rule?

Gday,

I have this rule to let me know the percentage of my rainwater tank.

rule "Water tank percent"
	when
		Item mysDistance received update
	then
		val DecimalType distance = mysDistance.state as DecimalType
		val level = 100.0 - (((distance.intValue - 20) / 85.0) * 100 )
		WaterLevel.postUpdate(new DecimalType(level))
		if ((WaterLevel.state as DecimalType) < 0) {
		WaterLevel.postUpdate(new DecimalType(0))
		}
		if ((WaterLevel.state as DecimalType) > 100) {
		WaterLevel.postUpdate(new DecimalType(100))
		//WaterLevel.postUpdate(new PercentType(100))
		}
	end

Both ‘mysDistance’ & ‘WaterLevel’ are Number items.

Generally it works fine for displaying the value on the sitemap however the dynamic icons are not working.
I suspect that it is a problem with the long value that the rule calculates,

2016-02-20 10:51:19.132 [INFO ] [runtime.busevents ] - WaterLevel state updated to 98.980952380952381

When the rule hits the limits, (0% or 100%) the dynamic icons do work correctly.

Could someone help me round the calculated ‘level’ value in the rule to an integer before doing the post update?

Regards,
George

try to change it to →

int level = 100 - (((distance.intValue - 20) / 85) * 100 )

and i would write the next like that →

if(level < 0) {
    level=0;
}
if(level > 100) {
    level=100;
}
WaterLevel.postUpdate(new DecimalType(level))

1 Like

Thanks, I have changed the rule to this,

rule "Water tank percent"
	when
		Item mysDistance received update 
	then
		val DecimalType distance = mysDistance.state as DecimalType
		int level = 100.0 - (((distance.intValue - 20) / 85.0) * 100 )
		if(level < 0) {
    	        level=0;
		}
		if(level > 100) {
    	        level=100;
		}
		WaterLevel.postUpdate(new DecimalType(level))
	end

But when it runs I get this error,

2016-02-22 15:56:40.770 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Water tank percent': The name 'int' cannot be resolved to an item or type.

Can you see where I may be going wrong?

Thanks again

Cheers,

I think you may need to put ‘var’ in front of the ‘int’, like this:

var int level = 100.0 - (((distance.intValue - 20) / 85.0) * 100 )

1 Like

Thanks for that.

It seems to give me this error

2016-02-22 18:05:32.694 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Water tank percent': Could not invoke constructor: org.openhab.core.library.types.DecimalType.DecimalType(long)

Thanks again. Do you have any other thoughts on this one?

Regards,
George

Calculate val using:

val int level = Math::round(100.0 - (((distance.longValue - 20.0) / 85.0) * 100.0).intValue

The other suggested solutions will truncate rather than round and the problems you are seeing stem from the Rules DSL trying its hardest to cast your numbers to BigDecimal. The above should force it to be a primitive int.

NOTE: I do not know if the .intValue is required above.

2 Likes

Rick,

Thanks for that however I am getting an error when it tries to run,

2016-02-23 05:12:42.314 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Water tank percent': Could not invoke method: java.lang.Math.round(float) on instance: null

Thanks again, this one seems to be a bit tricky, do you have any other ideas for rounding?

Regards,
George

Are you certain that distance is not null? If mysDistance is undefined it will return null as the state.

Rick,

I think so, here is a grab from the log,

2016-02-23 05:12:42.309 [INFO ] [runtime.busevents             ] - mysDistance state updated to 20
2016-02-23 05:12:42.313 [INFO ] [runtime.busevents             ] - mysDistanceupdate state updated to 2016-02-23T05:12:42
2016-02-23 05:12:42.314 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Water tank percent': Could not invoke method: java.lang.Math.round(float) on instance: null

The rule tries to run on an update to mysDistance.

Regards,
George

1 Like

Maybe try distance.floatValue in the calculation. Unfortunately the Rules DSL tends to treat pretty much every error as a null pointer error.

Rick,

Thanks for that.

I am getting this error when I changed it,

2016-02-24 17:39:57.377 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'Water tank percent': Could not invoke method: java.lang.Math.round(float) on instance: null

Do I need to import anything particular to make this work?

Regards
George

Not sure what the problem here is. Try breaking the calculation up into multiple steps and add logging statements between each step so we can see which part of the calculation it doesn’t like.

Hey there,

I am experiencing the same problems while trying to get the average:

items:

if( Keller_Waschmaschine_Energie.historicState(now.minusMinutes(5)) != null) {
    //Mittelwerte
    var rmin1 = Keller_Waschmaschine_Energie.averageSince(now.minusMinutes(1))
    var rmin5 = Keller_Waschmaschine_Energie.averageSince(now.minusMinutes(5))
    logDebug(    "Keller", "RMin1: " + rmin1 + " RMin5: " + rmin5)
    
    //Mittelwerte
    var min1 = Keller_Waschmaschine_Energie.averageSince(now.minusMinutes(1)) as DecimalType
    var min5 = Keller_Waschmaschine_Energie.averageSince(now.minusMinutes(5)) as DecimalType
    logDebug(    "Keller", "Min1: " + min1 + " Min5: " + min5)
    
    logDebug(    "Keller", "Update1")
    Keller_Waschmaschine_Energie_1min.postUpdate( rmin1)
    Keller_Waschmaschine_Energie_5min.postUpdate( rmin5)
    
    logDebug(    "Keller", "Update2")
    Keller_Waschmaschine_Energie_1min.postUpdate( String::format( "%.1f", min1.floatValue()))
    Keller_Waschmaschine_Energie_5min.postUpdate( String::format( "%.1f", min5.floatValue()))
}

Output:

2016-02-25 19:04:08.443 [DEBUG] [rg.openhab.model.script.Keller] - RMin1: 56.02333333333333342807236476801335811614990234375 RMin5: 58.578571428571422075037844479084014892578125
2016-02-25 19:04:08.464 [DEBUG] [rg.openhab.model.script.Keller] - Min1: 56.02333333333333342807236476801335811614990234375 Min5: 58.578571428571422075037844479084014892578125
2016-02-25 19:04:08.467 [DEBUG] [rg.openhab.model.script.Keller] - Update1
2016-02-25 19:04:08.475 [DEBUG] [rg.openhab.model.script.Keller] - Update2
2016-02-25 19:04:08.482 [WARN ] [.c.i.events.EventPublisherImpl] - given new state is NULL, couldn't post update for 'Keller_Waschmaschine_Energie_1min'
2016-02-25 19:04:08.488 [WARN ] [.c.i.events.EventPublisherImpl] - given new state is NULL, couldn't post update for 'Keller_Waschmaschine_Energie_5min'

It seems that posting a string-update to a Number doesn’t work? :open_mouth:

If you have a String you can try the postUpdate action. Otherwise I think you need to parse the string yourself (e.g. Float::parseFloat(min1)).

How does the postUpdate differ from item.postUpdate?
I can successfully cast it to a float, but not post an item update! :frowning:

This is going to get down into the weeds a little bit, so bear with me. I put the key info in bold.

As you see, there are two ways to update an Item, four if you include sendCommand. Lets just focus on postUpdate for now. This all also applies for the difference between sendCommand and item.sendCommand.

The code in openHAB is Object Oriented (OO). In an OO language you have these things called Classes. A Class is a definition for collection of data and methods that operate on that data. When your program is running, an instance of a Class (i.e. a bundle with actual values for the data and functions to operate on that data) it is called an Object.

So the definition of the Class Item lists all the data and methods you can call on an Item Object. In your code, MyItem is an Object, an instance of the Item Class.

There is one powerful feature of OO languages and that is inheritance. With Inheritance you can create a parent Class (e.g. Item) which defines a bunch of generic things. Then you can create a sub-Class (e.g. Switch) which gets all the stuff from the Item class plus the opportunity to create some new data, members, or the ability to change the behavior of some of the methods the Item Class defined.

With Inheritance you can then in your code treat a Switch as if it were an Item which then lets you write code that doesn’t have to care about the difference between a Switch, a Contact, a Dimmer, etc. This is what the Rules Domain Specific Language (DSL) does, it treats all your Items as if they are Item objects.

All of that is background. The key piece of information is the line I bolded above. When the coders created the Switch, Contact, Dimmer, etc Classes, they wrote their own version of the postUpdate method customized for that particular Class. So when you call MySwitchItem.postUpdate(newState) on it runs different code than when you call MyDimmerItem.postUpdate(newState). And this different code can be smart and flexible in converting newState from various different Classes (e.g Strings, ints, OnOffType, etc.) into the specific type of Class the Item needs (e.g. Switch needs an OnOffType).

The postUpdate Action on the other hand is completely generic and has to work for all Item Classes. Consequently it is not able to be as smart about handling converting between random types that you might pass to it and the State Class the Item needs.** So the developers created only one postUpdate action that accepts two Strings, one that is the Item name and the second which is a String version of the new State.** The Rules DSL will sometimes hide this fact from you by automatically converting things to a String for you, but it isn’t always able to do that (e.g. when you try to pass a primitive int or float as the new State).

Therefore, because the Item.postUpdate method can be smarter and more flexible about converting stuff passed to it to the needed State I always recommend using Item.postUpdate wherever possible.

3 Likes

Thank you for your detailed explanation!
But as you can see in my code-snippet I am using the item.postUpdate already.
And it does not seem to be able to convert a value from a string, since it returns null.

This is most likely a bug. See the following Thread:

There are various threads about rounding.
But I still didn’t found a working universal solution.

I have numbervalues in rules. From calculations or from Item-states.

Now I want to display them in the same format - in this case with 1 decimal-place.

What is the shortest and easiest way to create a string of a number with exactly 1 decimal-digit?

In what context? Are you creating the string in a rule or are you just putting it on your sitemap?

If putting it on your sitemap just use the [%.1f] formatting option. E.g.

Text item=Temperature label="Temperature is [%.1f]"

In a rule you can use String::format method:

val myStr = String::format("Temperature is %.1f", Temperature.state as DecimalType)
1 Like