Add UoM to Rules calculated variable [Solved]

  • Platform information:
    • Hardware: Pi 4B 4GB
    • OS: OpenHABian
    • Java Runtime Environment: 11
    • openHAB version: 3.1.0-1
  • Issue of the topic: I am trying to use Units of Measure (UoM) in Rules. I am able to apply the UOM to the variables I create with just a number, but have been unable to apply the UoM to a calculated variable.

The rule works fine w/o UoM, but it is awkward because I have to remember that the calculated results is in meters, and I would like to be able to leave the UOM conversion to the computer.

Here is the Rules code w/o UoM:

val Number radius=75 //
val Number FarFromHomeRadius= 482803 //300 mi in meters
val Number EmilyFarFromMidwayRadius=32187 // 20 mi in meters
// val PointType home_location = new PointType(new DecimalType(30.093643), new DecimalType(-95.337313)) //Spring Home 30.095020701729204, -95.3372609550871
val PointType home_location = new PointType(new DecimalType(42.535647135832804), new DecimalType(-115.47038000151313)) 
val String Home = "Midway"

rule "George iPhone Home"
when Item GeorgeiPhoneIPhone8_Location changed
then
val PointType Gphone_location =  GeorgeiPhoneIPhone8_Location.state as PointType
val Number Gdistance = Gphone_location.distanceFrom(home_location).intValue()
GeorgeDistanceFromHome.postUpdate(Gdistance)   //Temporary while learning
//logInfo("George Distance", GeorgeDistanceFromHome.state)
if ( Gdistance <= radius && George_iPhone_Home.state != ON ) {
George_iPhone_Home.postUpdate(ON)
logInfo("iPhone Home", "George's iPhone is at " + Home + " home.")
} 
else {
    if (Gdistance > radius && George_iPhone_Home.state != OFF ) {
        George_iPhone_Home.postUpdate(OFF)
    logInfo("iPhone Home", "George's iPhone is away from " + Home + "home.")
    }
    if ((Gdistance > FarFromHomeRadius) && (George_Recently_Present_But_Now_Absent.state != ON)) {George_Recently_Present_But_Now_Absent.postUpdate(OFF)}
}
end

Now I try to add UoM:

val Number radius=75 | m
val Number FarFromHomeRadius= 300 | mi 
val Number EmilyFarFromMidwayRadius= 20 | mi 

so far so good. But I have been unable to figure out how to add UoM to the calculate distance:

val Number Gdistance = Gphone_location.distanceFrom(home_location).intValue()

Just adding | m at the end yields “no viable alternative” in VSCode. I have tried various conversions and castings, but I don’t really know what I’m doing, and nothing worked.

I searched through the community:

I would appreciate assistance in figuring out how to make this work.

I would for a start, stop casting things as Number. While a Number can hold a Quantity (value & units) it is not a fully featured Quantity type with auto conversions etc.
So just
var fred = 22.5 | mm
and let the interpreter sort it out.

Doesn’t distanceFrom() return a Quantity anyway?

Thanks for the suggestions. I tried removing the casting.

val radius=75 | m  //reduced from 100 to 75.  See if we get any false flags, and if it goes off at Stirlinds
val FarFromHomeRadius= 300 | mi  //300 mi in meters
val Number EmilyFarFromMidwayRadius= 20 | mi // 20 mi in meters

val PointType home_location = new PointType(new DecimalType(40.535647135832804), new DecimalType(-114.47038000151313)) //Midway Home 45.535647135832804, -111.47038000151313 
val String Home = "Midway"

rule "George iPhone Home"
when Item GeorgeiPhoneIPhone8_Location changed
then
val PointType Gphone_location =  GeorgeiPhoneIPhone8_Location.state as PointType
val Gdistance = Gphone_location.distanceFrom(home_location)
//GeorgeDistanceFromHome.postUpdate(Gdistance)   //Temporary while learning
//logInfo("George Distance", GeorgeDistanceFromHome.state)
if ( Gdistance <= radius && George_iPhone_Home.state != ON ) {
George_iPhone_Home.postUpdate(ON)
logInfo("iPhone Home", "George's iPhone is at " + Home + " home.")
} 
else {
    if (Gdistance > radius && George_iPhone_Home.state != OFF ) {
        George_iPhone_Home.postUpdate(OFF)
    logInfo("iPhone Home", "George's iPhone is away from " + Home + "home.")
    }
    if ((Gdistance > FarFromHomeRadius) && (George_Recently_Present_But_Now_Absent.state != ON)) {George_Recently_Present_But_Now_Absent.postUpdate(OFF)}
}
end

I get the following message in VSCode

Ambiguous binary operation.
The operator declarations
	operator_greaterThan(Type, Number) in NumberExtensions and
	operator_greaterThan(Number, Number) in NumberExtensions
both match.

This is in reference to a comparison:

    if (Gdistance > radius && George_iPhone_Home.state != OFF ) {

Seems like I’m getting close!

No, it returns a DecimalType in meters but not a QuantityType.

PointType (openHAB Core 3.2.0-SNAPSHOT API).

So either the radius needs to be returned to be just a number with an assumed units of m or the distanceFrom needs to be converted to a QuantityType.

To do the latter it’s as simple as

new QuantityType(Gphone_location.distanceFrom(home_location).toString + " m")

It would be nice though if distanceFrom did return a QuantityType, though that would probably break a bunch of people’s rules. Maybe an alternative function could be added…

1 Like

Feels like a job for a new method distanceTo() :wink:

1 Like

Thanks for the help.

I tried this in my rule:

val Gdistance = new QuantityType(Gphone_location.distanceFrom(home_location).toString + " m")

I get this message in VSCode. I’m sure the problem is that I don’t know what I’m doing. If only it were FORTRAN, I’m sure I would know what to do. In other words I’m probably your grandfather’s age, but still trying to learn new things.

Bounds mismatch: The type argument <Object> is not a valid substitute for the bounded type parameter <T extends Quantity<T>> of the constructor QuantityType<T>(String)

I suspect that rossko57 and I are both significantly older than you would guess.

That error is surprising. The JavaDocs for QuantityType implies that supplying a type is not required.

But this is a relatively unused part of OH and maybe the Language Service Processor (the part of OH that checks the syntax of Rules DSL) is simply confused. Did you try to run it?

We could supply the type but the JavaDocs really indicate that’s not necessary.

new QuantityType<javax.measure.quantity.Length>(Gphone_location.distanceFrom(...

As you suspected, the rule appears to be working fine notwithstanding the LSP message. It there somewhere I should report that, or should I just be happy that y’all helped me solve my problem and move on. I went ahead and added the <javax.measure.quantity.Length> which stopped the LSP message. I don’t want to get into the habit of ignoring messages because I don’t know enough to know which ones matter, and I might miss something that is significant.

I really appreciate the helpful explanations.

This is about the VSC complaints

Experimenting -

var banana = new QuantityType("23.0 mm")
logInfo("test", "banana {}", banana)  // log banana 23.0 mm
// VSCode opaquely complains about Bounds Mismatch

// that was a dirty generic Quantity anyway
// this would be more 'correct', properly typed
var grape = new QuantityType<Length>("2.0 km")
logInfo("test", "grape {}", grape)  // log grape 2.0 km
// VSCode complains 'Length cannot be resolved ...'
// which is at least more easily understood (and ignored)

  // by extension then we can do this of course
var apple = 3.1417
var orange = new QuantityType<Length>(apple.toString + "mm" )
logInfo("test", "orange {}", orange) // log orange 3.1417 mm
// and tolerate the VSC complaint

There should also be a non-string-parsing variation like
new QuantityType<Length>( Number, Unit )
but I can’t crack the secret to that in DSL, going to be SIUnit.Millimtre or something like.

This should all be commonplace, but as Rich says it doesn’t crop up very often in practice, I suppose constants are more often used than tacking units on a numeric variable.

2 Likes

Thanks. I posted a link on the GitHub site you referenced. Not a big deal now that I understand, but if they are updating things, something to keep in mind.

Thanks again for all of your help.