[SOLVED] OH2: JSON Wind direction scale to text not transforming

  • Platform information:
    • Hardware: Raspberry Pi 3 Model B Rev 1.2_
  • OS: Raspbian GNU/Linux 8 (stretch)
  • OpenJDK Runtime Environment (Zulu 8.31.1.122-linux_aarch32hf) (build 1.8.0_181-b122)
  • openHAB Version: 2.4.0 (Build)
    • binding = expire1,fritzboxtr0641,mqtt1,weather1,astro,exec,network,ntp,systeminfo,logreader
    • ui = paper,basic,classic
    • persistence = rrd4j,mapdb
    • action = mail,mqtt
    • transformation = map,javascript,xslt,scale,jsonpath
    • misc = restdocs

My weather station connected via WeeWX > mqtt > OH died, and I decided to quiz my other weather station (AccuRite 5in1) via 433MHz.
Installed rtl-sdr on a rPi, Realtek USB dongle; all working well.
Sending the weather data straight to a mosquitto-client to the mqtt-broker on the OHv2 rPi.
All working well.

However…
The JSON path rule does not seem to catch every message, or does not extract the field:value pairs I want… some are simply dropped (yet sent properly by the receiver)… like so:

08/11/19 14:11:40.486 ArgyleCourt/Property/AcuRite5in1 {"time" : "2019-08-11 14:11:39", "model" : "Acurite 5n1 sensor", "sensor_id" : 430, "channel" : "A", "sequence_num" : 0, "battery" : "OK", "message_type" : 49, "wind_speed_kph" : 11.761, "wind_dir_deg" : 225.000, "rain_mm" : 573.787}
08/11/19 14:11:40.498 ArgyleCourt/Property/AcuRite5in1 {"time" : "2019-08-11 14:11:39", "model" : "Acurite 5n1 sensor", "sensor_id" : 430, "channel" : "A", "sequence_num" : 1, "battery" : "OK", "message_type" : 49, "wind_speed_kph" : 11.761, "wind_dir_deg" : 225.000, "rain_mm" : 573.787}
08/11/19 14:11:40.512 ArgyleCourt/Property/AcuRite5in1 {"time" : "2019-08-11 14:11:39", "model" : "Acurite 5n1 sensor", "sensor_id" : 430, "channel" : "A", "sequence_num" : 2, "battery" : "OK", "message_type" : 49, "wind_speed_kph" : 11.761, "wind_dir_deg" : 225.000, "rain_mm" : 573.787}
08/11/19 14:11:59.359 ArgyleCourt/Property/AcuRite5in1 {"time" : "2019-08-11 14:11:58", "model" : "Acurite 5n1 sensor", "sensor_id" : 430, "channel" : "A", "sequence_num" : 0, "battery" : "OK", "message_type" : 56, "wind_speed_kph" : 10.934, "temperature_C" : 20.389, "humidity" : 20}
08/11/19 14:11:59.371 ArgyleCourt/Property/AcuRite5in1 {"time" : "2019-08-11 14:11:58", "model" : "Acurite 5n1 sensor", "sensor_id" : 430, "channel" : "A", "sequence_num" : 1, "battery" : "OK", "message_type" : 56, "wind_speed_kph" : 10.934, "temperature_C" : 20.389, "humidity" : 20}
08/11/19 14:11:59.415 ArgyleCourt/Property/AcuRite5in1 {"time" : "2019-08-11 14:11:58", "model" : "Acurite 5n1 sensor", "sensor_id" : 430, "channel" : "A", "sequence_num" : 2, "battery" : "OK", "message_type" : 56, "wind_speed_kph" : 10.934, "temperature_C" : 20.389, "humidity" : 20}

The following items have been defined:

// ----- 190810 MaxG: created
// ----- AccuRite 5in1 outdoor weather station
Group    gAccuRite																		(gAll, gPersist_rrd4j)

// AccuRite Weather Charts
Group    gAccuRiteTemp_Chart
Number   gAccuRiteTemp_Chart_Period

Group    gAccuRiteHumi_Chart
Number   gAccuRiteHumi_Chart_Period

Group    gAccuRiteWind_Chart
Number   gAccuRiteWind_Chart_Period

Group    gAccuRiteRain_Chart
Number   gAccuRiteRain_Chart_Period

// data received
String   ar_DataReceived		"AccuRite data"																		{mqtt="<[mymosquitto:ArgyleCourt/Property/AcuRite5in1:state:default]"}
DateTime ar_LastUpdated			"AccuRite: Last update [%1$td/%1$tm %1$tH:%1$tM]"	<calendar>	(gAccuRite, gLUP)

// data being extracted
Number   ar_Temperature			"Outdoor temperature [%.1f °C]"						<temperature>	(gAccuRite, gAccuRiteTemp_Chart, gSensors_Temperature)
Number   ar_Humidity			"Outdoor Humidity [%.0f %%]"						<temperature>	(gAccuRite, gAccuRiteHumi_Chart)
Number   ar_WindSpeed			"Wind speed [%.0f km/h]"			    			<wind>			(gAccuRite, gAccuRiteWind_Chart)
Number   ar_WindDir		    	"Wind direction [%.0f°]"			    			<wind>			(gAccuRite)
String   ar_WindDirText	    	"Wind direction [SCALE(wind_direction.scale):%s]" 	<wind>			(gAccuRite)
String   ar_Battery		    	"AccuRite Battery [%s]"		    					<battery4>		(gAccuRite)
Number   ar_RainTotal			"Rain Total [%.0f mm]"								<clouds_rain>	(gAccuRite)

// data being derived / calculated
Number   ar_TemperatureMin		"Outdoor temperature minimum [%.1f °C]"				<temperature>	(gAccuRite, gAccuRiteTemp_Chart)
Number   ar_TemperatureMax		"Outdoor temperature maximum [%.1f °C]"				<temperature>	(gAccuRite, gAccuRiteTemp_Chart)
Number   ar_HumidityMin			"Outdoor humidity minimum [%.0f %%]"				<temperature>	(gAccuRite, gAccuRiteHumi_Chart)
Number   ar_HumidityMax			"Outdoor humidity maximum [%.0f %%]"				<temperature>	(gAccuRite, gAccuRiteHumi_Chart)
Number   ar_Humidex				"Outdoor Humidex [SCALE(humidex.scale):%s]"							(gAccuRite)
Number   ar_WindSpeedMin	   	"Wind speed minimum [%.0f km/h]"    				<wind>			(gAccuRite, gAccuRiteWind_Chart)
Number   ar_WindSpeedMax		"Wind speed maximum [%.0f km/h]"    				<wind>			(gAccuRite, gAccuRiteWind_Chart)
Number   ar_RainLast24H			"Rain last 24 hours [%.0f mm]"						<clouds_rain>	(gAccuRite)
Number   ar_RainToday			"Rain Today [%.0f mm]"								<clouds_rain>	(gAccuRite, gAccuRiteRain_Chart)
Number   ar_RainYesterday		"Rain Yesterday [%.0f mm]"							<clouds_rain>	(gAccuRite)
Number   ar_RainWeek			"Rain Week [%.0f mm]"								<clouds_rain>	(gAccuRite)
Number   ar_RainMonth			"Rain Month [%.0f mm]"								<clouds_rain>	(gAccuRite)

With the following rule doing the work:

rule "AccuRite 5in1 data received"
  when
    Item ar_DataReceived changed
  then
    ar_LastUpdated.postUpdate(new DateTimeType())
    //logInfo("AR.1.0", "ar_DataReceived.........: {}", "changed")

    val String ar_json      = (ar_DataReceived.state as StringType).toString
    val String model        = transform("JSONPATH", "$.model", ar_json)
    val String sensor_id    = transform("JSONPATH", "$.sensor_id", ar_json)
    val String sequence     = transform("JSONPATH", "$.sequence_num", ar_json)
    val String message_type = transform("JSONPATH", "$.message_type", ar_json)

    // make sure we are listening to the correct sender
    if (model == "Acurite 5n1 sensor") {
      // get sensor ID; in case have another one in the future :)
      if (sensor_id == "430") {
        // the weather stations sends the same msg 3 times with sequence_num [0..2]
        if (sequence == "1") {
          //logInfo("AR.1.02", "sequence./ message_type.: {} / {}", sequence, message_type)

          // wind speed is in both messages (49, 56)
          val String wind_speed = transform("JSONPATH", "$.wind_speed_kph", ar_json)
          //logInfo("AR.1.03", "wind_speed..............: {}", wind_speed)
          ar_WindSpeed.postUpdate(wind_speed)

          // wind direction and rain
          if (message_type == "49") {
            val Number wind_dir   = transform("JSONPATH", "$.wind_dir_deg", ar_json)
            val String rain_total = transform("JSONPATH", "$.rain_mm", ar_json)
            logInfo("AR.1.04", "wind_dir................: {}", wind_dir)
            //logInfo("AR.1.05", "wind_speed..............: {}", wind_speed)
            ar_WindDir.postUpdate(wind_dir)
            ar_RainTotal.postUpdate(rain_total)
          }

          // temperature and humidity
          if (message_type == "56") {
            val String temp = transform("JSONPATH", "$.temperature_C", ar_json)
            val String humi = transform("JSONPATH", "$.humidity", ar_json)
            val String batt = transform("JSONPATH", "$.battery", ar_json)
            ar_Temperature.postUpdate(temp)
            ar_Humidity.postUpdate(humi)
            ar_Battery.postUpdate(batt)
          }
        }
      }
    }
end

I have a wind direction item to convert the degrees to a text representation, with the following scale file:

[0.00..11.24]=N
[11.25..33.74]=NNE
[33.75..56.24]=NE
[56.25..78.74]=ENE
[78.75..101.24]=E
[101.25..123.74]=ESE
[123.75..146.24]=SE
[146.25..168.74]=SSE
[168.75..191.24]=S
[191.25..213.74]=SSW
[213.75..236.24]=SW
[236.25..258.74]=WSW
[258.75..281.24]=W
[281.25..303.74]=WNW
[303.75..326.24]=NW
[326.25..348.74]=NNW
[348.75..360.00]=N
NaN=Non-numeric state

The last line is from https://www.openhab.org/addons/transformations/scale/, and will result in:

2019-08-11 14:02:18.192 [WARN ] [.internal.ScaleTransformationService] - Scale transform file '/etc/openhab2/transform/wind_direction.scale' does not comply with syntax for entry : 'NaN', 'Non-numeric state'

… assuming this NaN entry is not really allowed.
However, with or without that line I am getting this error:

2019-08-11 14:02:18.196 [WARN ] [rm.AbstractFileTransformationService] - Could not transform '-' with the file 'wind_direction.scale' : Scale can only be used with numeric inputs or valid quantity types

… and the text translation never works, despite feeding a number (or at least I assume that to be the case).

Here the relevant lines:

items:
Number   ar_WindDir		    	"Wind direction [%.0f°]"			    			<wind>			(gAccuRite)
String   ar_WindDirText	    	"Wind direction [SCALE(wind_direction.scale):%s]" 	<wind>			(gAccuRite)

rule part:
val Number wind_dir   = transform("JSONPATH", "$.wind_dir_deg", ar_json)

I had a look at this post SCALE transform and UNDEF - Could not transform 'UNDEF' but could not think of a reason why this would apply to my problem.

The sequence of wind directions recognised by OH

2019-08-11 14:27:18.030 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 247.5
2019-08-11 14:27:18.042 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 247.5
2019-08-11 14:27:55.721 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 270.0
2019-08-11 14:28:32.921 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 247.5
2019-08-11 14:29:10.665 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 270.0
2019-08-11 14:29:10.681 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 270.0
2019-08-11 14:32:18.363 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 225.0
2019-08-11 14:33:33.375 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 270.0
2019-08-11 14:34:10.567 [INFO ] [lipse.smarthome.model.script.AR.1.04] - wind_dir................: 270.0

Hmm, figured it out…

            ar_WindDirText.postUpdate(wind_dir)

I never fed the wind direction into the text item to translate.

Also, an error I noticed later:

2019-08-11 15:09:11.395 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'AccuRite 5in1 data received': An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.BusEvent.postUpdate(org.eclipse.smarthome.core.items.Item,java.lang.Number) on instance: null

… was related to:

val Number wind_dir   = transform("JSONPATH", "$.wind_dir_deg", ar_json)

which should have been:

val Number wind_dir   = Float::parseFloat(transform("JSONPATH", "$.wind_dir_deg", ar_json))

… in order to really cast this into a number.

You’re mixing up things: NaN is a Number representing “Not a number”, while UNDEF means there is no state defined.

The main problem is that the SCALE transform does not have any support for non-numeric values (NaN being a numeric value).

Wind direction is a very good example where having the SCALE transform support UNDEF state would make a lot of sense: if the wind speed is very low, then there is no defined wind direction…

Unless the SCALE transform is extended to support the edge case of UNDEF state, you’ll have to resort on using either a rule or using the visibility attribute in a sitemap to show/hide an item (or an unlinked text item) in case its value is (or not) UNDEF…

SCALE transform was only enhanced to include NaN in May, so it won’t be in OH 2.4

It’s not clear if that works with UNDEF or NULL states. Transforms can also be used with non Item state variables.

The reason for seeing “anything but a number” was that I did not update the WindDirText with WindDir :face_with_raised_eyebrow: ; hence, scale did not get a number to work with. BTW ‘0’ works as North quite well.