Help me optimize a simple rule (using items as variables, sending commands from an array)

Hi everybody,

for the first time I’m doing a bit more then simple logic on/off rules.

I want to change an item’s color based on the currentUvIndex.

I got it working, but I’m not happy with the code. (Full Code Below)

At the beginning, I’m setting up all the values for the corresponding colors. I’d also love to set up the “target” item of the sendCommand. So when I decide to use another light at some point, I’d just need to change it there. I’ve seen a few examples where people are trying to select the item from a string, but that’s not what I’m trying to here. I’d just like to have a reference to the item.


var item targetItem = realItemname

so I can use
instead of

Is that possible or do I need to use name Strings?


Now I have this huge switch block. Is there a more elegant solution?

Elsewhere I would have used an array, so for testing purposes I used a small example:

var HSBType[] colors = newArrayList(uvIndex10Color, uvIndex6Color)

But this gives me:
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'uvIndex': The argument 'command' must not be null.
I also tried:

which gives me a similar error
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'uvIndex': An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.BusEvent.sendCommand(org.eclipse.smarthome.core.items.Item,org.eclipse.smarthome.core.types.Command) on instance: null

val HSBType uvIndex1Color = HSBType::fromRGB(0, 128, 0)
val HSBType uvIndex2Color = HSBType::fromRGB(0, 255, 0)

val HSBType uvIndex3Color = HSBType::fromRGB(255, 255, 128)
val HSBType uvIndex4Color = HSBType::fromRGB(255, 255, 0)
val HSBType uvIndex5Color = HSBType::fromRGB(255, 201, 15)

val HSBType uvIndex6Color = HSBType::fromRGB(255, 127, 39)
var HSBType uvIndex7Color = HSBType::fromRGB(180, 60, 0)

val HSBType uvIndex8Color = HSBType::fromRGB(243, 103, 35)
val HSBType uvIndex9Color = HSBType::fromRGB(255, 0, 0)
val HSBType uvIndex10Color = HSBType::fromRGB(255, 48, 100)

val HSBType uvIndex11Color = HSBType::fromRGB(255, 0, 255)

var HSBType[] colors = newArrayList(uvIndex10Color, uvIndex6Color)

rule "uvIndex"
        Item switchNanoWeather changed to ON
        var int currentUvIndex = (UVIndex_Current_UVIndex.state as Number + 0.5).intValue
        logInfo("uv", "index: {}", currentUvIndex)
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                case 8:
                case 9:
                case 10:
                case 11:


It’s not reliable. You need to use the Item name. The problem is the Item Object changes from one run to the next causing targetItem to become stale and stop working.

You would use sendCommand(targetItem, cmd).

HSBType[] is not supported syntax in Rules DSL. I’m surprised it didn’t throw an error on that.

You’ve not specified the types of the stuff you’ve put into the ArrayList so when you call get you end up with an Object and Object is not supported by sendCommand. You either need to tell it the type:

var List<HSBType> colors = newArrayList(uvIndex10Color, uvIndex6Color)

or cast the Object returned by get.

colors.get(1) as HSBType

Design Pattern: How to Structure a Rule. You would still use the switch but move the actual call to the Item to the end of the Rule so there is only one line that actually does stuff. Once you do that, the need for your targetLight variable from 1 probably goes away too since the light Item itself will only be referenced in the one place.

Personally, I prefer to use a Map for something like this. Key the map on the index value and then you don’t even need the switch statement at all.

import java.util.Map

val Map<Number, HSBType> colors = newHasMap( "1" -> HSBType::fromRGB(0, 128, 0),
                                             "2" -> HSBType::fromRGB(0, 255, 0), 

rule "uvIndex"
    Item switchNanoWeather changed to ON
    var currentUvIndex = (UVIndex_Current_UVIndex.state as Number + 0.5) as Number

Thank you very much for the input!

Hm it doesn’t seem to be 100% correct.

[INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'nanoleafWeather.rules', using it anyway: The field Tmp_nanoleafWeatherRules.colors refers to the missing type Object

Do I need to add some import for the ArrayList Type? And why is the log about a “tmp_” rule? I guess it’s just that the engine copies it somewhere for validation?

And when I run it I get:

 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'uvIndex': 'get' is not a member of 'Object'

Yes, perfect!
Just for reference in case anybody who might read this at a later date:
It should be hashMap (not hasMap) and when assigning the Values the numbers shouldn’t be in quotes or you will get
[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'uvIndex': The argument 'command' must not be null.

So for reference, full working rule:

import java.util.Map

val Map<Number, HSBType> mapUvIndexColors =  newHashMap(1 -> HSBType::fromRGB(0, 128, 0),
                                                        2 -> HSBType::fromRGB(0, 255, 0),
                                                        3 -> HSBType::fromRGB(255, 255, 128),
                                                        4 -> HSBType::fromRGB(255, 255, 0),
                                                        5 -> HSBType::fromRGB(255, 201, 15),
                                                        6 -> HSBType::fromRGB(255, 127, 39),
                                                        7 -> HSBType::fromRGB(180, 60, 0),
                                                        8 -> HSBType::fromRGB(243, 103, 35),
                                                        9 -> HSBType::fromRGB(255, 0, 0),
                                                        10-> HSBType::fromRGB(255, 48, 100),
                                                        11-> HSBType::fromRGB(255, 0, 255)

rule "uvIndex"
        Item switchNanoWeather changed to ON
        var int currentUvIndex = (UVIndex_Current_UVIndex.state as Number + 0.5).intValue
        logInfo("uv", "index: {}", currentUvIndex)


Much nicer structured than my array approach! I’m still trying to figure out why it’s still throwing the errors! (Small Typo … it should be “hashMap”)

Yes, you would need to add import java.util.List to the top of the file.

I’ve never seen that before but the line it’s complaining about isn’t actually in a Rule so this is probably the name of the global context.

Given the first error that error is not unexpected.

Yes, that was a typo, it should have been HashMap.

Slowly getting the hang of it. Since it’s my first “bigger” rule, is it OK to ask for any kind of improvements/problems that it might have? It’s running fine now, it seems just sometimes a bit slow (especially for the first time after it was edited). But that might be due to the limitations of the raspberry pi as well.

For reference a picture of what it does - color-coding weather and forecast on my living room nanoleaf installation.

import java.util.Map
import java.util.ArrayList

val tempRange_min = -2
val tempRange_max = 35

val tempHue_min = 600
val tempHue_max = 300

val funcMap= [ Number x, Number in_min, Number in_max, Number out_min, Number out_max |
    ((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)

val Map<Number, HSBType> mapUvIndexColors =  newHashMap(1 -> HSBType::fromRGB(00, 100, 00),
                                                        2 -> HSBType::fromRGB(0, 140, 0),
                                                        3 -> HSBType::fromRGB(0, 255, 0),
                                                        4 -> HSBType::fromRGB(140, 245, 0),
                                                        5 -> HSBType::fromRGB(255, 255, 0),
                                                        6 -> HSBType::fromRGB(255, 127, 0),
                                                        7 -> HSBType::fromRGB(255, 90, 0),
                                                        8 -> HSBType::fromRGB(255, 0, 0),
                                                        9 -> HSBType::fromRGB(255, 0, 70),
                                                        10-> HSBType::fromRGB(230, 0, 108),
                                                        11-> HSBType::fromRGB(230, 0, 255)

rule "uvIndex"
	Item switchNanoWeather changed to ON
	var int currentUvIndex = (UVIndex_Current_UVIndex.state as Number + 0.5).intValue

	var ArrayList<Number> alTemperatures = newArrayList(	Wetterbericht_Current_ApparentTemperatureNumber.state as Number,
								Wetterbericht_Current_OutdoorTemperatureNumber.state as Number,
								Wetterbericht_ForecastHours03_ForecastedTemperatureNumber.state as Number,
								Wetterbericht_ForecastHours06_ForecastedTemperatureNumber.state as Number,
								Wetterbericht_ForecastHours09_ForecastedTemperatureNumber.state as Number,
								Wetterbericht_ForecastHours12_ForecastedTemperatureNumber.state as Number,
								Wetterbericht_ForecastHours15_ForecastedTemperatureNumber.state as Number,
								Wetterbericht_ForecastHours18_ForecastedTemperatureNumber.state as Number,
								Wetterbericht_ForecastHours21_ForecastedTemperatureNumber.state as Number,
								Wetterbericht_ForecastHours24_ForecastedTemperatureNumber.state as Number

	var ArrayList<HSBType> alTempColors = newArrayList

	var ArrayList<Number> alCloudiness = newArrayList(	Wetterbericht_Current_Cloudiness.state as Number,
								Wetterbericht_ForecastHours03_ForecastedCloudiness.state as Number,
								Wetterbericht_ForecastHours06_ForecastedCloudiness.state as Number,
								Wetterbericht_ForecastHours24_ForecastedCloudiness.state as Number
	var ArrayList<HSBType> alCloudinessColors = newArrayList

	var ArrayList<Number> alWind = newArrayList(		Wetterbericht_Current_WindSpeed.state as Number	,
								Wetterbericht_ForecastHours03_ForecastedWindSpeed.state as Number,
								Wetterbericht_ForecastHours06_ForecastedWindSpeed.state as Number,
								Wetterbericht_ForecastHours24_ForecastedWindSpeed.state as Number
	var ArrayList<HSBType> alWindColors = newArrayList

	var ArrayList<Number> alRain = newArrayList(		Wetterbericht_ForecastHours03_ForecastedRain.state as Number,    
								Wetterbericht_ForecastHours06_ForecastedRain.state as Number,    
								Wetterbericht_ForecastHours09_ForecastedRain.state as Number,    
								Wetterbericht_ForecastHours12_ForecastedRain.state as Number,    
								Wetterbericht_ForecastHours15_ForecastedRain.state as Number,    
								Wetterbericht_ForecastHours18_ForecastedRain.state as Number,    
								Wetterbericht_ForecastHours21_ForecastedRain.state as Number,    
								Wetterbericht_ForecastHours24_ForecastedRain.state as Number    
	 var ArrayList<Number> alSnow = newArrayList(           Wetterbericht_ForecastHours03_ForecastedSnow.state as Number,
                                                                Wetterbericht_ForecastHours06_ForecastedSnow.state as Number,
                                                                Wetterbericht_ForecastHours09_ForecastedSnow.state as Number,
                                                                Wetterbericht_ForecastHours12_ForecastedSnow.state as Number,
                                                                Wetterbericht_ForecastHours15_ForecastedSnow.state as Number,
                                                                Wetterbericht_ForecastHours18_ForecastedSnow.state as Number,
                                                                Wetterbericht_ForecastHours21_ForecastedSnow.state as Number,
                                                                Wetterbericht_ForecastHours24_ForecastedSnow.state as Number

	if(currentUvIndex > 11) currentUvIndex=11	

	var int i =-1
		if(alTemperatures.get(i) < tempRange_min) alTemperatures.set(i, tempRange_min)
		if(alTemperatures.get(i) > tempRange_max) alTemperatures.set(i, tempRange_max)
		alTempColors.add(new HSBType(new DecimalType(((funcMap.apply(alTemperatures.get(i), tempRange_min, tempRange_max, tempHue_min, tempHue_max) as Number).intValue) % 360), new PercentType(100), new PercentType(100)))

        i =-1
		alCloudinessColors.add(new HSBType(new DecimalType(funcMap.apply(alCloudiness.get(i).intValue, 0, 100, 210, 190)), new PercentType(funcMap.apply(alCloudiness.get(i).intValue, 0, 100, 100, 0)), new PercentType(funcMap.apply(alCloudiness.get(i).intValue, 0, 100, 100, 70))))
        	logInfo("cloud", "i {} -> {}", i, alCloudiness.get(i).intValue)

        i =-1
		if(alWind.get(i) < 33)
			alWindColors.add(new HSBType(new DecimalType(funcMap.apply(alWind.get(i).intValue, 0, 33, 140, 0)), new PercentType(100), new PercentType(100)))
			alWindColors.add(new HSBType(new DecimalType(330), new PercentType(100), new PercentType(100)))
		logInfo("wind", "i {} -> {}", i, alWind.get(i).intValue)

	var totalRain6 = 0
	var totalRain24 = 0
	var Boolean willItSnow = false
		if(i<2) totalRain6 += alRain.get(i).intValue 
		totalRain24 += alRain.get(i).intValue 
		if(alSnow.get(1).intValue > 0) willItSnow = true
	logInfo("rainNsnow", "rain6 -> {}, rain24 -> {}, WillItSnow? {}", totalRain6, totalRain24, willItSnow)

	//Current uvIndex

	//Current temp and what it feels like below

	//Temperature forecasts

	//Current Cloudiness
	//Cloudiness Forecast

	//Current Wind Speed

	//Wind Speed Forecast

	//Rain Forecast
	//240 / 1-10
	//300 - 330 / 10-45
	// 0 -> snow

	if(totalRain6 < 1 && !willItSnow )
		LightPanel0104_Color.sendCommand(new HSBType(new DecimalType(240), new PercentType(100), new PercentType(0)))
	}else if(totalRain6 < 10 && !willItSnow){
	}else if(totalRain6 < 45 && !willItSnow){
		LightPanel0104_Color.sendCommand(new HSBType(new DecimalType(funcMap.apply(totalRain6, 10, 45, 300, 330)), new PercentType(100), new PercentType(100)))
		LightPanel0104_Color.sendCommand(new HSBType(new DecimalType(0), new PercentType(100), new PercentType(0)))

        if(totalRain24 < 1 && !willItSnow )
                LightPanel0904_Color.sendCommand(new HSBType(new DecimalType(240), new PercentType(100), new PercentType(0)))
        }else if(totalRain24 < 10 && !willItSnow){

        }else if(totalRain24 < 45 && !willItSnow){
                LightPanel0904_Color.sendCommand(new HSBType(new DecimalType(funcMap.apply(totalRain24, 10, 45, 300, 330)), new PercentType(100), new PercentType(100)))
                LightPanel0904_Color.sendCommand(new HSBType(new DecimalType(0), new PercentType(100), new PercentType(0)))



It does take the RPi some time to parse and load .rules files. You can add some logging to the top and bottom of the Rule to see if the delay is in triggering the Rule (pointing to parsing/loading which is largely outside your control or inefficiencies in the Rule itself).

I’m not convinced that your ArrayLists are necessary at all. If you use Design Pattern: Associated Items you could eliminate almost half of the lines of code. More lines means longer to parse and load the files, not to mention more code often leads to more brittle code and harder to maintain code.

Avoid calling intValue and otherwise using primitives. This is known to greatly exaggerate the amount of time it takes to load and parse .rules files.