[SOLVED] Making dynamic icons work - 32-point wind rose example

I’m currently running a recent snapshot of OH2.5.

According to the docs the following should work:

windrose.items:

Number:Angle	Wx_OWM_Current_Wind_Direction	"Wind Direction [%d %unit%]"	<wind>	(gWeatherCurrent)	{channel="openweathermap:weather-and-forecast:<api_key>:local:current#wind-direction"}
String				Wx_OWM_Current_Wind_Direction_Simplified	"Wind Direction [%s]"	<wind>	(gWeatherCurrent)
Number:Dimensionless	Wx_OWM_Current_Wind_Direction_Windrose	"Wind Direction [SCALE(windrose-32.map):%s]"	<windrose>	(gWeatherCurrent)

The idea is to map the 32 wind rose points plus the “indeterminate wind direction” point to the [0, 100] range used in Number:Dimensionless items. Here 0 refers to “indeterminate wind direction” and all 32 wind rose points are evenly spaced (in steps of 3).

I also created a set of 32+2 wind rose icons for dynamic icon support. They’re located in /etc/openhab2/icons/classic/ and have the following names:

  • windrose.svg (catch-all for UNDEF state - represents unknown wind direction
  • windrose-N.svg (where N is 0 for variable wind direction, 3 for ‘North’, 6 for ‘North by East’, … 96 for ‘North by West’)

The idea is to transform a given wind direction (range 0°-360°) to a number in the range 0-32, and then to multiply this number by 3 and adding an offset of 3 so state 0 can be used for reporting indeterminate wind direction (different from unknown/undefined wind direction, as those correspond to an error states). In other words:

  1. windDirectionDegrees := (Wx_OWM_Current_Wind_Direction.state as Number).doubleValue
  2. windDirectionForWindrose := windDirectionDegrees / 360.0 * 32.0
  3. windDirectionWindroseDynamicIconState := 3 + (3 * Math.round( windDirectionForWindrose )
  4. postUpdate(Wx_OWM_Current_Wind_Direction_Windrose, windDirectionWindroseDynamicIconState)

(I’m using Math.round() here as .intValue truncates a floating point number (as with Math.floor()) rather than rounding it to the nearest integer.

windrose-32.map:

0=Variable
3=N
6=NbE
9=NNE
12=NEbN
15=NE
18=NEbE
21=ENE
24=EbN
27=E
30=EbS
33=ESE
36=SEbE
39=SEbE
42=SEbS
45=SSE
48=SbE
51=S
54=SbW
57=SSW
60=SWbS
63=SWbS
66=SWbW
69=WSW
72=WbS
75=W
78=WbN
81=WNW
84=NWbW
87=NWbW
90=NWbN
93=NNW
96=NbW

windrose-32.rule:

/* Provide the compass wind direction in a proxy Item (Wx_OWM_Current_Wind_Direction_Simplified) for displaying on the sitemap.
 * This is needed as the SCALE transform only accepts numeric values, whereas the OWM binding sometimes reports non-numeric values
 * in cases where the wind direction is undefined or hard to define.
 */
rule "Update simplified wind direction"
when
	Item Wx_OWM_Current_Wind_Direction changed
then
	val String ruleTitle = "UpdateOWMCurrentWindDirection"

	// Update Wx_OWM_Current_Wind_Direction_Simplified if Wx_OWM_Wind_Speed is numeric
	var String status = ""	// Will hold the textual representation of the wind direction (Wx_OWM_Current_Wind_Direction_Simplified)
	var boolean error = true
	var int windrose = 0 	// Will hold the integer state for using dynamic 32-point windrose icon representing the wind direction (can also be used in a MAP(windrose-32.map) transform)

	if (Wx_OWM_Current_Wind_Direction.state == NULL) {
		status = "(not set)"
	} else if (Wx_OWM_Current_Wind_Direction.state == UNDEF) {
		status = "(undefined)"
	} else if (Wx_OWM_Current_Wind_Direction.state.toString == "-") {
		status = "(-)"
	} else if (Wx_OWM_Current_Wind_Direction.state instanceof Number) {
		logInfo(ruleTitle, "Wx_OWM_Current_Wind_Direction has state '{}' - will be transformed", Wx_OWM_Current_Wind_Direction.state)
		// We can now safely use the SCALE() transform as the input is a Number
		status = transform("SCALE", "wind.scale", Wx_OWM_Current_Wind_Direction.state.toString())

		// Now we will compute the 32-point wind rose offset for use with dynamic icons
		// First convert the wind direction (degrees) to a scale of 0-32:
		val Number x =  (Wx_OWM_Current_Wind_Direction.state as Number).doubleValue / (360.0 / 32.0)
		logInfo(ruleTitle, "Wx_OWM_Current_Wind_Direction has state '{}' - transformed to '{}' (x = {})",
			Wx_OWM_Current_Wind_Direction.state, status, x)
		// Now we round this windrose value to the nearest integer
		val int y = Math.round(x.floatValue)
		logInfo(ruleTitle, "Wx_OWM_Current_Wind_Direction has state '{}' - transformed to '{}' (y = {})",
			Wx_OWM_Current_Wind_Direction.state, status, y)
		// Now we compute the dynamic state offset value (multiply by 3 and add start offset of 3). We also handle negative degrees by adding 32 to the windrose integer value (and use modulo calculus to get back to the range [0..32[)
		windrose = ((y + 32) % 32) * 3 + 3
		logInfo(ruleTitle, "Wx_OWM_Current_Wind_Direction has state '{}' - transformed to '{}' (x = {}) (y = {}) (windrose value {})",
			Wx_OWM_Current_Wind_Direction.state, status, x, y, windrose)
		error = false
	} else { // Unexpected state type
		status = "(invalid: " + Wx_OWM_Current_Wind_Direction.state.toString() + ")"
	}
	if (error) {
		logInfo(ruleTitle, "{} has state '{}', expecting Number", Wx_OWM_Current_Wind_Direction.name, Wx_OWM_Current_Wind_Direction.state.toString())
	} else {
		logDebug(ruleTitle, "{} has Number state '{}'", Wx_OWM_Current_Wind_Direction.name, Wx_OWM_Current_Wind_Direction.state.toString())
	}
	postUpdate(Wx_OWM_Current_Wind_Direction_Simplified, status)
	postUpdate(Wx_OWM_Current_Wind_Direction_Windrose, windrose.intValue.toString)
end

This, in theory, should work.

However, adding the following in the sitemap doesn’t yield the expected result:

// The followng doesn't properly handle 'UNDEF' state, hence replaced with Wx_OWM_Current_Wind_Direction_Simplified:
// Default item=Wx_OWM_Current_Wind_Direction
// Computed by means of a rule whenever Wx_OWM_Current_Wind_Direction changes;
Default item=Wx_OWM_Current_Wind_Direction_Simplified
Default item=Wx_OWM_Current_Wind_Direction_Windrose

Instead, I get an error:

2019-06-14 13:58:21.570 [WARN ] [rm.AbstractFileTransformationService] - Could not transform '51.0' with the file 'windrose.map' : Target value not found in map for '51.0'

This happens, irrespective of the type used in postUpdate(). I tried:

  1. postUpdate(Wx_OWM_Current_Wind_Direction_Windrose, windrose) → doesn’t work (yields float)
  2. postUpdate(Wx_OWM_Current_Wind_Direction_Windrose, windrose.intValue) → doesn’t work (yields float)
  3. postUpdate(Wx_OWM_Current_Wind_Direction_Windrose, windrose.intValue.toString) → doesn’t work (yields float)

I eventually generated a SCALE() transform so I would at least already get the wind directions mapped to the textual representations of the wind direction - here’s windrose-32.scale:

[0..1.5]=(var)
]1.5..4.5]=N
]4.5..7.5]=NbE
]7.5..10.5]=NNE
]10.5..13.5]=NEbN
]13.5..16.5]=NE
]16.5..19.5]=NEbE
]19.5..22.5]=ENE
]22.5..25.5]=EbN
]25.5..28.5]=E
]28.5..31.5]=EbS
]31.5..34.5]=ESE
]34.5..37.5]=SEbE
]37.5..40.5]=SEbE
]40.5..43.5]=SEbS
]43.5..46.5]=SSE
]46.5..49.5]=SbE
]49.5..52.5]=S
]52.5..55.5]=SbW
]55.5..58.5]=SSW
]58.5..61.5]=SWbS
]61.5..64.5]=SWbS
]64.5..67.5]=SWbW
]67.5..70.5]=WSW
]70.5..73.5]=WbS
]73.5..76.5]=W
]76.5..79.5]=WbN
]79.5..82.5]=WNW
]82.5..85.5]=NWbW
]85.5..88.5]=NWbW
]88.5..91.5]=NWbN
]91.5..94.5]=NNW
]94.5..97.5]=NbW

Now the 32-point wind rose names are correctly rendered, but the dynamic state icons still won’t work. In fact, none is ever shown.

What am I doing wrong?

It’s an avoidance, but if you have a String Item with values like “SSE” you can have icons windrose-sse.svg

If you get no dynamic icons check the default bitmap/vector setting in your PaperUI for BasicUI/ClassicUI

Ok, I now realize that it works in a regular web browser but not in the iPhone app.

Unsure how I could make it work in the iPhone app though.

I think thats an unsolved issue in the iphone app.

1 Like

It appears that the iOS app only supports PNG icon file format.

If I convert the SVG icons to PNG with ImageMagick (just using convert icon.svg icon.png), then I get 50 by 50 PNG icons, but they initially don’t show up in the app.

I then performed 2 rounds of the following routine before the PNG icons eventually started to appear:

  1. $ sudo openhab-cli clean-cache
  2. $ sudo systemctl restart openhab2
  3. $ sudo systemctl restart openhab2

FWIW, I noticed that openHAB 2.5.0~S1601-1 (Build #1601) which I am currently running requires a double restart after cleaning the cache as not all bundles are properly restored in one run:

2019-06-14 21:46:02.578 [ERROR] [core.karaf.internal.FeatureInstaller] - Failed installing ‘openhab-misc-openhabcloud, openhab-transformation-javascript, openhab-persistence-influxdb, openhab-ui-habpanel, openhab-transformation-regex, openhab-binding-buienradar, openhab-transformation-jsonpath, openhab-binding-zwave, openhab-ui-restdocs, openhab-transformation-xpath, openhab-binding-mqtt, openhab-ui-basic, openhab-transformation-map, openhab-ui-classic, openhab-binding-openweathermap, openhab-binding-tradfri, openhab-binding-astro, openhab-ui-habmin, openhab-transformation-xslt, openhab-binding-gpstracker, openhab-transformation-exec, openhab-ui-paper, openhab-transformation-scale’: Error restarting bundles:
Exception in org.eclipse.smarthome.io.rest.sse.internal.SseActivator.start() of bundle org.openhab.core.io.rest.sse.

hello @shutterfreak I’m looking to make the same visualisation. Could you provide the 32 svg’s of your windrose? thanks in advance

Unfortunately I can’t upload a ZIP archive, hence I had to addthe 32 icons by hand:

  1. windrose-n N

  2. windrose-nbe NbE

  3. windrose-nne NNE

  4. windrose-nebn NEbN

  5. windrose-ne NE

  6. windrose-nebe NEbE

  7. windrose-ene ENE

  8. windrose-ebn EbN

  9. windrose-e E

  10. windrose-ebs EbS

  11. windrose-ese ESE

  12. windrose-sebe SEbE

  13. windrose-se SE

  14. windrose-sebs SEbS

  15. windrose-sse SSE

  16. windrose-sbe SbE

  17. windrose-s S

  18. windrose-sbw SbW

  19. windrose-ssw SSW

  20. windrose-swbs SWbS

  21. windrose-sw SW

  22. windrose-swbw SWbW

  23. windrose-wsw WSW

  24. windrose-wbs WbS

  25. windrose-w W

  26. windrose-wbn WbN

  27. windrose-wnw WNW

  28. windrose-nwbw NWbW

  29. windrose-nw NW

  30. windrose-nwbn NWbN

  31. windrose-nnw NNW

  32. windrose-nbw NbW

As you see, I have 2 different renderings: a more elaborate for the 16 well-known wind directions, and 16 others for the intermediate wind directions.

If you want to use the more elaborate version for all your intermediate wind directions, then you can e.g. start from windrose-se.svg and replace the following rotation code:
transform="rotate(45,200,200)"
with the desired angle (45° = NE). For instance, NEbE = NE (45°) + (360°/32) = 54.25°), yielding:
transform="rotate(54.25,200,200)"

2 Likes

Apparently the SVG files were automagically transformed to PNG by the forum software.

Here’s a new attempt at uploading the SVG icons:

North → East
windrose-n.svg
windrose-nbe.svg
windrose-nne.svg
windrose-nebn.svg
windrose-ne.svg
windrose-nebe.svg

East
windrose-ene.svg
windrose-ebn.svg
windrose-e.svg
windrose-ebs.svg
windrose-ese.svg

South ← East
windrose-sebe.svg
windrose-se.svg
windrose-sebs.svg
windrose-sse.svg
windrose-sbe.svg
windrose-s.svg

South → West
windrose-sbw.svg
windrose-ssw.svg
windrose-swbs.svg
windrose-sw.svg
windrose-swbw.svg

West
windrose-wsw.svg
windrose-wbs.svg
windrose-w.svg
windrose-wbn.svg
windrose-wnw.svg

North ← West
windrose-nwbw.svg
windrose-nw.svg
windrose-nwbn.svg
windrose-nnw.svg
windrose-nbw.svg

Hope this works!