[SOLVED] UoM and Item.minimumSince().state - how to get back the measurement unit from persisted item states?

There are a couple interesting persistence-related methods one can call on persisted Items, such as minimumSince() and maximumSince(). However, it appears that OpenHAB has no clue about the Units of Measurement when querying persisted data.

For instance, if I persist Wx_OWM_Current_Temperature (member of the gWeatherCurrent group), then I can easily compute the current day’s temperature high and low value as follows:

rule "Daily min/max values for OWM bindings"
when
    Time cron "0/15 * * * * ?" or // FOR DEBUG PURPOSES
    Item Wx_OWM_Current_Observation_Time changed 
then
    val String ruleTitle = "Test daily min/max values for OWM bindings"
    val DateTime startOfDay = now.withTimeAtStartOfDay

    // Weather item:
    val GenericItem wi = Wx_OWM_Current_Temperature
    val String p = "Wx_OWM_Current_Temperature"
    logInfo( ruleTitle, "{}: type of wi.state is '{}'", p, wi.state.class.getCanonicalName() )
    // Current value:
    val QuantityType<Number> cur = wi.state
    // Today's recorded min/max:
    val QuantityType<Number> min = wi.minimumSince(startOfDay).state
    val QuantityType<Number> max = wi.maximumSince(startOfDay).state
    // Now report the class name and item value:
    logInfo( ruleTitle, "{}: cur = '{}' is of type '{}'", p, cur, cur.class.getCanonicalName() )
    logInfo( ruleTitle, "{}: min = '{}' is of type '{}'", p, min, min.class.getCanonicalName() )
    logInfo( ruleTitle, "{}: max = '{}' is of type '{}'", p, max, max.class.getCanonicalName() )
end

This interestingly reveals that wi.minimumSince() and wi.maximumSince() are DecimalType where cur is QuantityType(what I would indeed expect):

2019-03-06 10:01:45.103 [INFO ] [aily min/max values for OWM bindings] - Temperature: type of wi.state is 'org.eclipse.smarthome.core.library.types.QuantityType'
2019-03-06 10:01:45.123 [INFO ] [aily min/max values for OWM bindings] - Temperature: cur = '9.74 °C' is of type 'org.eclipse.smarthome.core.library.types.QuantityType'
2019-03-06 10:01:45.127 [INFO ] [aily min/max values for OWM bindings] - Temperature: min = '8.15' is of type 'org.eclipse.smarthome.core.library.types.DecimalType'
2019-03-06 10:01:45.129 [INFO ] [aily min/max values for OWM bindings] - Temperature: max = '9.74' is of type 'org.eclipse.smarthome.core.library.types.DecimalType'

How can we reassign the UoM from the current item state to the persisted item states? In other words: is there a way to make infer min and max the Units of Measurement from cur?

Perhaps unsurprising, the persistence services are at heart still version 1.x

As there’s no record of units with the stored numbers and no way to guess, I think it’s up to you to assign them.
These seem to work -

val QuantityType<Temperature> max = 22.5 | "°C"
val min = 11.5 | "°C"

I see.

I see. However, while the following works:

  • Items:
Number:Temperature		Wx_OWM_Current_Temperature					"Temperature [%.1f %unit%]"	<temperature>	(gWeatherCurrent) {channel="openweathermap:weather-and-forecast:OWM_ID:local:current#temperature"}
Number:Temperature		Wx_OWM_Current_Temperature_Min				"Temperature Low Today [%.1f %unit%]"	<temperature>	
Number:Temperature		Wx_OWM_Current_Temperature_Max				"Temperature High Today [%.1f %unit%]"	<temperature>	
  • Sitemap:
Default item=Wx_OWM_Current_Temperature		valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]
Default item=Wx_OWM_Current_Temperature_Min	valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]
Default item=Wx_OWM_Current_Temperature_Max	valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]
  • Rules:
val DateTime startOfDay = now.withTimeAtStartOfDay
val min = Wx_OWM_Current_Temperature.minimumSince(startOfDay).state
val max = Wx_OWM_Current_Temperature.maximumSince(startOfDay).state

postUpdate(Wx_OWM_Current_Temperature_Min, min)
postUpdate(Wx_OWM_Current_Temperature_Max, max)

logInfo(ruleTitle, "Today's temperature Min - Max values = '{}' - '{}'", min, max)

This however doesn’t:

  • Items:
Number:Temperature		Wx_OWM_Current_Temperature			"Temperature [%.1f %unit%]"				<temperature>	(gWeatherCurrent)	{channel="openweathermap:weather-and-forecast:OWM_ID:local:current#temperature"}
Number:Temperature		Wx_OWM_Current_Temperature_Min		"Temperature Low Today [%.1f %unit%]"	<temperature>	
Number:Temperature		Wx_OWM_Current_Temperature_Max		"Temperature High Today [%.1f %unit%]"	<temperature>	

Number:Pressure			Wx_OWM_Current_Pressure				"Pressure [%.1f %unit%]"				<pressure>		(gWeatherCurrent)	{channel="openweathermap:weather-and-forecast:OWM_ID:local:current#pressure"}
Number:Pressure			Wx_OWM_Current_Pressure_Min			"Pressure Low Today [%.1f %unit%]"		<pressure>
Number:Pressure			Wx_OWM_Current_Pressure_Max			"Pressure High Today [%.1f %unit%]"		<pressure>

Number:Dimensionless	Wx_OWM_Current_Humidity				"Humidity [%.1f %unit%]"				<humidity>		(gWeatherCurrent)	{channel="openweathermap:weather-and-forecast:OWM_ID:local:current#humidity"}
Number:Dimensionless	Wx_OWM_Current_Humidity_Min			"Humidity Low Today [%.1f %unit%]"		<humidity>
Number:Dimensionless	Wx_OWM_Current_Humidity_Max			"Humidity High Today [%.1f %unit%]"		<humidity>

Number:Speed			Wx_OWM_Current_Wind_Speed			"Wind Speed [%.1f %unit%]"				<wind>			(gWeatherCurrent)	{channel="openweathermap:weather-and-forecast:OWM_ID:local:current#wind-speed"}
Number:Speed			Wx_OWM_Current_Wind_Speed_Min		"Wind Speed Low Today [%.1f %unit%]"	<wind>
Number:Speed			Wx_OWM_Current_Wind_Speed_Max		"Wind Speed High Today [%.1f %unit%]"	<wind>

Number:Dimensionless	Wx_OWM_Current_Cloudiness			"Cloudiness [%.1f %unit%]"				<sun_clouds>	(gWeatherCurrent)	{channel="openweathermap:weather-and-forecast:OWM_ID:local:current#cloudiness"}
Number:Dimensionless	Wx_OWM_Current_Cloudiness_Min		"Cloudiness Low Today [%.1f %unit%]"	<sun_clouds>
Number:Dimensionless	Wx_OWM_Current_Cloudiness_Max		"Cloudiness High Today [%.1f %unit%]"	<sun_clouds>

  • Sitemap:
Default item=Wx_OWM_Current_Temperature			valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]
Default item=Wx_OWM_Current_Temperature_Min		valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]
Default item=Wx_OWM_Current_Temperature_Max		valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]

Default item=Wx_OWM_Current_Pressure
Default item=Wx_OWM_Current_Pressure_Min
Default item=Wx_OWM_Current_Pressure_Max

Default item=Wx_OWM_Current_Humidity
Default item=Wx_OWM_Current_Humidity_Min
Default item=Wx_OWM_Current_Humidity_Max

Default item=Wx_OWM_Current_Pressure
Default item=Wx_OWM_Current_Pressure_Min
Default item=Wx_OWM_Current_Pressure_Max

Default item=Wx_OWM_Current_Cloudiness
Default item=Wx_OWM_Current_Cloudiness_Min
Default item=Wx_OWM_Current_Cloudiness_Max
  • Rules:
* Compute daily min and max values for OWM bindings up to 'now'
 */
rule "Daily min/max values for OWM bindings"
when
    // Time cron "0/15 * * * * ?" or // DEBUG
    // Time cron "0 1 0 * * ?" or // Run at 00:01 daily (reset the daily min/max values)
    Item Wx_OWM_Current_Observation_Time changed 
then
    val String ruleTitle = "Test daily min/max values for OWM bindings"
    val DateTime startOfDay = now.withTimeAtStartOfDay

    val List<String> weatherParams = newArrayList("Temperature", "Pressure", "Humidity", "Wind_Speed", "Cloudiness")
    weatherParams.forEach[ p |
        val String s = "Wx_OWM_Current_" + p
        // Current Weather item value:
        val GenericItem wi     = gWeatherCurrent.allMembers.findFirst[ i | i.name == s ]
        // Proxy weather items that will hold the daily min/max values up to now:
        val GenericItem wi_min = gWeatherCurrent.allMembers.findFirst[ i | i.name == (s + "_Min") ]
        val GenericItem wi_max = gWeatherCurrent.allMembers.findFirst[ i | i.name == (s + "_Max") ]

        logInfo( ruleTitle, "Fetching Today's '" + p + "' in '"+ s + "' = '" + wi.state + "'" )

        // For Wind_Speed, we get values in m/s but want km/h, so multiply the values by 3.6 in that case:
        val double factor = if (p == "Wind_Speed") { 3.6 } else { 1.0 }
        logInfo( ruleTitle, "{}: factor = {} - type of wi.state is '{}'", p, factor, wi.state.class.getCanonicalName() )

        // wi.state is of type QuantityType:
        val QuantityType<Number> cur = (wi.state)
        // wi.minimumSince(startOfDay).state is of type DecimalType:
        val QuantityType<Number> min = (wi.minimumSince(startOfDay).state) // UoM Help Needed!
        // wi.maximumSince(startOfDay).state is of type DecimalType:
        val QuantityType<Number> max = (wi.maximumSince(startOfDay).state) // UoM Help Needed!

        logInfo( ruleTitle, "{}: factor = {} - cur = '{}' is of type '{}' ", p, factor, cur, cur.class.getCanonicalName() )
        logInfo( ruleTitle, "{}: factor = {} - min = '{}' is of type '{}' ", p, factor, min, min.class.getCanonicalName() )
        logInfo( ruleTitle, "{}: factor = {} - max = '{}' is of type '{}' ", p, factor, max, max.class.getCanonicalName() )

        if (p == "Wind_Speed") {
            logInfo(ruleTitle, "Wind speed in m/s : {}; {} -- {}", cur, min, max)
            /* Sadly the following does not work:
            logInfo(ruleTitle, "Wind speed in km/h: {}; {} -- {}",
                (cur as QuantityType<Number>).toUnit("km/h"),
                (min as QuantityType<Number>).toUnit("km/h"),
                (max as QuantityType<Number>).toUnit("km/h")
            )
            */
        }

        // The min/max values lost their UoM. Is there a way to attach the UoM from the parent Item so it propagates to the persisted item?
        logInfo(ruleTitle, "Today's '" + p + "' Min - Max values = '" + min + "' - '" + max + "'")
        // The following 2 lines won't work as they expect UoM:
        postUpdate(wi_min, min)
        postUpdate(wi_max, max)
    ]
end

I was hoping to find somewhere a method like:

val Number min = newNumberFromDecimaltype(
    wi.minimumSince(startOfDay).state,
    wi.UoM // This obviously / sadly doesn't exist
)

You don’t seem to have changed that line?
val QuantityType<Temperature> min = wi.minimumSince(startOfDay).state | "°C"

If I try the following, OH bails out with an error on the 2 lines where i add | "°C":

val QuantityType<Temperature> min = Wx_OWM_Current_Temperature.minimumSince(startOfDay).state | "°C"
val QuantityType<Temperature> max = Wx_OWM_Current_Temperature.maximumSince(startOfDay).state | "°C"

So that doesn’t work.

In my example, I’m iterating over 5 weather-related items (Temperature, Wind Speed, Humidity, Cloudiness and Pressure), most of which have different units of measurement.

I tried looking up the UoM from an ArrayList of String items, but that doesn’t work:

    val List<String> weatherParamUnits = newArrayList("°C", "hPa", "%", "m/s","%")
    int i = 0
    weatherParams.forEach[ p |
        val String s = "Wx_OWM_Current_" + p
        // same code as above

        val QuantityType<Number> min = (wi.minimumSince(startOfDay).state) | weatherParamUnits.get(i).toString
        val QuantityType<Number> max = (wi.maximumSince(startOfDay).state) | weatherParamUnits.get(i).toString
        // (does not work)

I get the following error:

Configuration model 'openweathermap.rules' has errors, therefore ignoring it: [346,76]: no viable alternative at input '|'
[347,76]: no viable alternative at input '|'

Okay, odd, I only tried with constants.

This might offer clues, xxx = new QuantityType( ) looks promising but needs figuring out

If I try this:

val QuantityType<Speed> min = new QuantityType(
    Wx_OWM_Current_Wind_Speed.minimumSince(startOfDay).state,
    "m/s"
)

then I get the following error message:

Error during the execution of rule 'Daily min/max values for OWM bindings': An error occurred during the script execution: Could not invoke constructor: org.eclipse.smarthome.core.library.types.QuantityType.QuantityType(java.lang.Number,javax.measure.Unit)

If I replace "m/s" with METRES_PER_SECOND then I get:

2019-03-06 15:40:30.112 [ERROR] [ntime.internal.engine.ExecuteRuleJob] - Error during the execution of rule 'Daily min/max values for OWM bindings': The name 'METRES_PER_SECOND' cannot be resolved to an item or type; line 387, column 13, length 17

So now it boils down to know how I can refer to a Unit<T> in rules DSL (see line 107 of eclipse/smarthome/core/library/types/QuantityType.java

I managed to find my way out, thanks to eclipse/smarthome/core/library/unit/SIUnits.java:

    if (Wx_OWM_Current_Wind_Speed !== null) {
        // SIUnits only defines speed as km/h (so 'SIUnits.METRES_PER_SECOND' is not defined)
        // However the persisted OWM data is expressed in m/s - hence we multiply the state as Number value by 3.6:
        val QuantityType<Speed> min = new QuantityType(
            (Wx_OWM_Current_Wind_Speed.minimumSince(startOfDay).state as Number)* 3.6,
            SIUnits.KILOMETRE_PER_HOUR // .METRES_PER_SECOND // "m/s"
        )
        val QuantityType<Speed> max = new QuantityType(
            (Wx_OWM_Current_Wind_Speed.maximumSince(startOfDay).state as Number)* 3.6,
            SIUnits.KILOMETRE_PER_HOUR // .METRES_PER_SECOND // "m/s"
        )
        postUpdate(Wx_OWM_Current_Wind_Speed_Min, min)
        postUpdate(Wx_OWM_Current_Wind_Speed_Max, max)
        logInfo(ruleTitle, "Today's Wind_Speed Min - Max values = '{}' - '{}'", min, max)
    }

Now OH2 will complain with the following known error message (see your link):

The field SIUnits.KILOMETRE_PER_HOUR refers to the missing type Object

But the log shows that it works:

2019-03-06 16:14:30.158 [INFO ] [aily min/max values for OWM bindings] - Today's Wind_Speed Min - Max values = '11.16 km/h' - '24.12 km/h'

I filed an issue on GitHub regarding this UoM import: