Java cast error in rule, string to number

Whilst testing this rule to convert an item state value (String) to a float (Number) , I get the error below; why can the rules system not convert a string to number? The string value is “3.1324”.

(using this guide Type Conversions) from the guru @rlkoshak

Rule:

rule "Scale voltages"

when
    Time cron "0 0/1 * 1/1 * ? *"
then
    i_TRV_Lounge_Scaled_Voltage.sendCommand(new DecimalType((i_TRV_Lounge_Get_Voltage.state as DecimalType) * 28.8) )
end

Error

2020-01-10 21:32:00.417 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Scale voltages': Could not cast 3.19141 to java.lang.Number; line 6, column 62, length 40

Item:

	String i_TRV_Lounge_Get_Voltage     		"Current voltage [%s]V" 		<battery>		(g_HVAC, g_TRV) {channel="mqtt:topic:b_MQTT_Broker:t_TRV_Dad:c_Voltage"}
	Number i_TRV_Lounge_Scaled_Voltage			"Voltage"						<battery>		(g_HVAC, g_TRV, g_Diagnostics) 

What’s wrong?
I’ve tried DecimalType, PercentType
I’m no good with Java so please just a quick line - not a link to pages of Java - but just a quick few words on why it has to be a certain way.

Please use https://www.openhab.org/docs/configuration/rules-dsl.html#working-with-item-states-conversions instead of that post. The docs are kept up, that post not so much.

And this can’t be answered in just a few words. Your mistake is a lack of understanding of a key concept which you need to understand going forward. So even if you don’t want to, you gotta read it or you will keep having this problem. And I link to other sources because they explain something better than I can in a forum posting.

In programming there is a concept called polymorphism. In this case an Object can be a State, DecimalType, and a Number all at the same time. But you can only use it as one of those at a time. This is true of pretty much all programming languages that support Objects.

Most of the time the Rules DSL is good at guessing this correctly for you. Sometimes it fails miserably. When working with DecimalTypes it is particularly bad.

A cast is when you explicitly tell it what type you want to use. So when you see MyItem.state as Number you are seeing code that is telling the engine “I want to use the state in it’s Number aspect.” But here’s the thing, the Object already has to be a Number. You can’t cast something that isn’t a Number to a Number.

i_TRV_Lounge_Get_Voltage.state is a String Item. Thus it’s state is StringType, not DecimalType and thus it’s not a Number. You can’t convert it to a Number just by casting it. It’s not a Number. In this case you would have to parse the String into an Object that is a Number.

But to avoid the XY Problem, why is i_TRV_Lounge_Get_Voltage a String in the first place? If it’s always a numerical value it should be a Number Item. Then you can just cast it to Number (never use DecimalType, go straight to Number) and use it (assuming you check that it isn’t NULL or UNDEF first or otherwise take actions to avoid that possibility, any Item’s state can be either of those and neither NULL nor UNDEF are Numbers so trying to cast them will fail with an error too).

If for some reason you can’t convert it to a Number Item, which is unlikely, like I said, you need to convert the String to a Number. The easiest way to do this is to create a BigDecimal. Just trust me on that, it’s not something that anyone is expected to just know. But by default the Rules Engine treats all numbers and calculation results as BigDecimal. But there are other ways to do this as well. Using BigDecimal:

val voltage = new BigDecimal(i_TRV_Lounge_Get_Voltage.state.toString)
i_TRV_Lounge_Scaled_Voltage.sendCommand(voltage * 28.8)

The first line converts the StringType to a BigDecimal which is also a Number. We can do math with Numbers.

Hi Rich.
I’m a programmer but my age gives it away, I use MC68xxx, Z80A, Occam, Ada, Fortran, COBOL, … you get the idea. Military applications do not allow unsafe type program languages. And don’t get me on ‘C’ !!
Java et,al are awful but they seem to garner favour amongst lazy programmers :slight_smile:
Well, I’m still getting an error:

val voltage 

rule "Scale voltages"
when
    Time cron "0 0/1 * 1/1 * ? *"
then
    if ( i_TRV_Lounge_Get_Voltage.state != NULL && i_TRV_Lounge_Get_Voltage.state != UNDEF ) { 
        voltage = new BigDecimal(i_TRV_Lounge_Get_Voltage.state.toString) 
        i_TRV_Lounge_Scaled_Voltage.sendCommand(voltage * 28.8)
    }

    /*
    i_TRV_Lounge_Scaled_Voltage.sendCommand((new DecimalType(DecimalType.valueOf(i_TRV_Lounge_Get_Voltage.state))) as Number)
    */
    logInfo("rule name","Lounge TRV Voltage=" + i_TRV_Lounge_Get_Voltage.state)
end
2020-01-10 23:05:00.449 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Scale voltages': An error occurred during the script execution: null

Thank you for the detailed synopsis, I am aware of what you allude to and I understand. It’s just this Java, I’ve steered clear of it since it first raised it’s head back in early 90s. So, can I ask you again to debug why I am getting this error, relating to null please.

You might be surprised at how many military programs are written in Java. Not on embedded systems but pretty much everywhere else it’s written in Java. Slowly that’s changing and Python is becoming more common. Whether or not a language is type safe or not does not automatically disqualify it and it is possible and often easier to code more reliable and code that runs more safely in those languages.

Just be clear though. You are writing and looking at a Domain Specific Language written specifically for openHAB which is based on Xtext and bears a lot of similarities to Xtend. It is not Java. But it runs on Java so a lot of Java stuff is available. But a Java program will bear almost no resemblance to a Rules DSL Rule.

That null error us usually caused when the language is unable to cast something you have to a usable type in the context. But I don’t think that’s happening here. You’ve defined voltage as a val instead of a var. val is a constant, kind of like CONST in C IIRC. You can’t assign a new value to it once it’s declared. I was able to use val in my version of the code because voltage never gets reassigned to, it is scoped to the Rule. When the Rule exits, so does voltage. You’ve declared it as a global which means it need to be written to every time the Rule runs. So you need to use var.

In general, it is better to declare variables as tightly scoped as possible. If you don’t need to access voltage across more than one Rule, it should not be a global.

I work in UK MoD and with the US DoD. I can assure you 101% mate, that NO JAVA whatsoever gets into a jet fighter ! It is not qualified and neither is granted a MiL-SIL level , nor (UK) DS Satefy Class. NO way. NEVER. And the same will hold true for Pyhton or any other unsafe language - to use uK vernacular.
:slight_smile:
So , back to the problem,…

Changed val to var, still get the same error

Jeez my heads hurts. Such simple tasks take eons to get working. I could crack this in any language I have above, in seconds.
Give me Assembler any time of day.
There’s no substitute for a “proper” “language” - oh yea

EDIT: Just seen your bit about reassignment, so hang on…

Rich, I’'m still getting the same error. What gives here mate, please?

error


2020-01-10 23:35:15.995 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'Scale_voltages.rules'
2020-01-10 23:36:00.361 [INFO ] [pse.smarthome.model.script.rule name] - Lounge TRV Voltage=3.19141
2020-01-10 23:36:00.370 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Scale voltages': An error occurred during the script execution: null

rule

rule "Scale voltages"

when
    Time cron "0 0/1 * 1/1 * ? *"
then
    logInfo("rule name","Lounge TRV Voltage=" + i_TRV_Lounge_Get_Voltage.state)

    if ( i_TRV_Lounge_Get_Voltage.state != NULL && i_TRV_Lounge_Get_Voltage.state != UNDEF && i_TRV_Lounge_Get_Voltage.state  != null) { 
        var voltage = new BigDecimal(i_TRV_Lounge_Get_Voltage.state.toString) 
        i_TRV_Lounge_Scaled_Voltage.sendCommand(voltage * 28.8)
    }
end   

in openhab.log,
I get confirmation to an INFO log that the value is 3.1413
but the next line is the error
in fact, whatever I do with the voltage scaling, , I get the same error.
So, I tried this, but it also failed with the same error

rule "Scale_voltages"

when
    Time cron "0 0/1 * 1/1 * ? *"
then

    if ( i_TRV_Lounge_Get_Voltage.state != NULL && i_TRV_Lounge_Get_Voltage.state != UNDEF && i_TRV_Lounge_Get_Voltage.state  != null) { 
        
     	logInfo("rule name","Lounge TRV Voltage=" + i_TRV_Lounge_Get_Voltage.state)
        var voltage = new BigDecimal::parseBigDecimal(i_TRV_Lounge_Get_Voltage.state.toString) 
        i_TRV_Lounge_Scaled_Voltage.sendCommand( new BigDecimal(voltage * 28.8))
        
    }
end 

gives me:

2020-01-10 23:55:00.990 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'Scale_voltages.rules'
2020-01-10 23:56:00.449 [INFO ] [pse.smarthome.model.script.rule name] - Lounge TRV Voltage=3.19141
2020-01-10 23:56:00.452 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Scale_voltages': An error occurred during the script execution: null
[23:56:17] openhabian@openhab:/etc/openhab2/rules$

What IS going on here, where’s this “null” from ?

Right, cracked it AT LAST , dear me.

Rule


rule "Scale_voltages"

when
    Time cron "0 0/1 * 1/1 * ? *"
then

    if ( i_TRV_Lounge_Get_Voltage.state != NULL && i_TRV_Lounge_Get_Voltage.state != UNDEF && i_TRV_Lounge_Get_Voltage.state  != null) { 
     	logInfo("rule name","Lounge TRV Voltage=" + i_TRV_Lounge_Get_Voltage.state)
        i_TRV_Lounge_Scaled_Voltage.postUpdate(Float::parseFloat(i_TRV_Lounge_Get_Voltage.state.toString) * 28.0 )
    }
end

Had to make the Sitemap item a String instead of Number

Now it works, and gives me a % battery charge based on a range of 0…3.5V

I have NO IDEA why Rich’s didn’t work or the various other code snippets above, but I’d love to know because Rich knows this system well , and I’m confused at the sheer number of possible ways and methods one can try before eventually getting it working. Bizarre.

Now I have to go away and work out what this rule is doing !! :slight_smile: but I note use of postUpdate not sendCommand and parseFloat:: and etc. I know what they do, but I do not know why this worked , and none of the above did - they all appear different ways to cut the same cake !

Ohhhhh my head

1 Like

I did say not embedded. There are millions of lines of Java code running everything from satellite ground stations, command and control systems, analysis systems, cyber security, big data, logistics, personnel management, etc. I’ve worked with the US DOD and IC my entire career and have written some of that Java code. I even write a nice little bit of Java code for the MOD during an exercise once. The ITAR export controls made that one pretty interesting. I’m currently working some python coffee for them but USAF. you have any

Code running on jets, missiles, and radars is not the only code the military uses. Based on my experience, it doesn’t make up a quarter of all the code the military uses.

You mean there is no substitute for a massage you already know. Of course it’s ready if you already know how.

Just to avoid the XY Problem, why is that item a String in the first place? Why can’t it be a Number?

The can never happen so this condition is superfluous.

Add a log statement inside the if to see the error is coming from the if itself or coming from one of the lines inside the if clause.

Unfortunately one if the big limitations of the Roles DSL, and one if the reasons it’s being dropped as the default and why so many of us are pushing scripted automation is the wire reporting. Roles DSL wow reporting sucks. Just about anything that isn’t completely obvious gets reported as null. Python tells you exactly what line has the error and exactly what the error is and it almost always makes sense.

Like I said before, that usually means a type caring problem but until we narrow the speech of the wire down it could be almost anything. You got it to work by chasing the Item type, I think.

I still don’t know why but items can’t be Numbers. In fact, the proxy Item pregnant should be a Number:Dimensionless since it’s a percent.

There are many ways to do the same thing for some of this kind of stuff. I usually try to lead people down a path of least complexity. Often the best solution is some other approach completely but that requires information I don’t have, like why both items are not number items to begin with.

1 Like

This was also what worked for me after, literally, trying close to 10 other combinations and losing close to 5 hours with this.

Sometimes is very frustrating how hard is to do very simple things in OH like a basic “if” comparation. :frowning:

Well, it’s working now finally! Wanted to move blinds a certain way whenever my temperature sensor reaches above 28º but wasn’t being able until now.

Thank you for posting your solution.