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.

like

var item targetItem = realItemname

so I can use
targetItem.sendCommand
instead of
realItemname.sendCommand

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

Second:

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)
LightPanel0901_Color.sendCommand(colors.get(1))

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

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"
when
        Item switchNanoWeather changed to ON
then
        var int currentUvIndex = (UVIndex_Current_UVIndex.state as Number + 0.5).intValue
        logInfo("uv", "index: {}", currentUvIndex)
        switch(currentUvIndex)
        {
                case 1:
                         LightPanel0901_Color.sendCommand(uvIndex1Color)
                case 2:
                         LightPanel0901_Color.sendCommand(uvIndex2Color)
                case 3:
                         LightPanel0901_Color.sendCommand(uvIndex3Color)
                case 4:
                         LightPanel0901_Color.sendCommand(uvIndex4Color)
                case 5:
                         LightPanel0901_Color.sendCommand(uvIndex5Color)
                case 6:
                         LightPanel0901_Color.sendCommand(uvIndex6Color)
                case 7:
                         LightPanel0901_Color.sendCommand(uvIndex7Color)
                case 8:
                         LightPanel0901_Color.sendCommand(uvIndex8Color)
                case 9:
                         LightPanel0901_Color.sendCommand(uvIndex9Color)
                case 10:
                         LightPanel0901_Color.sendCommand(uvIndex10Color)
                case 11:
                         LightPanel0901_Color.sendCommand(uvIndex11Color)

        }
        LightPanel0901_Color.sendCommand(colors[])
end

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"
when
    Item switchNanoWeather changed to ON
then
    var currentUvIndex = (UVIndex_Current_UVIndex.state as Number + 0.5) as Number
    LightPanel0901_Color.sendCommand(colors.get(currentUvIndex)
end
2 Likes

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"
when
        Item switchNanoWeather changed to ON
then
        var int currentUvIndex = (UVIndex_Current_UVIndex.state as Number + 0.5).intValue
        logInfo("uv", "index: {}", currentUvIndex)

        LightPanel0901_Color.sendCommand(mapUvIndexColors.get(currentUvIndex))
end

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"
when
	Item switchNanoWeather changed to ON
then
	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
	while((i+=1)<alTemperatures.size)
	{
		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
        while((i+=1)<alCloudiness.size)
        {
		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
        while((i+=1)<alWind.size)
        {
		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)))
                }else{
			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
        while((i+=1)<alRain.size)
        {
		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
	LightPanel0101_Color.sendCommand(mapUvIndexColors.get(currentUvIndex))

	//Current temp and what it feels like below
	LightPanel0202_Color.sendCommand(alTempColors.get(0))
	LightPanel0201_Color.sendCommand(alTempColors.get(1))

	//Temperature forecasts
	LightPanel0301_Color.sendCommand(alTempColors.get(2))
	LightPanel0401_Color.sendCommand(alTempColors.get(3))
	LightPanel0502_Color.sendCommand(alTempColors.get(4))
	LightPanel0501_Color.sendCommand(alTempColors.get(5))
	LightPanel0601_Color.sendCommand(alTempColors.get(6))
	LightPanel0701_Color.sendCommand(alTempColors.get(7))
	LightPanel0802_Color.sendCommand(alTempColors.get(8))
	LightPanel0903_Color.sendCommand(alTempColors.get(9))


	//Current Cloudiness
	LightPanel0102_Color.sendCommand(alCloudinessColors.get(0))
	
	//Cloudiness Forecast
	LightPanel1001_Color.sendCommand(alCloudinessColors.get(1))
	LightPanel1101_Color.sendCommand(alCloudinessColors.get(2))
	LightPanel1201_Color.sendCommand(alCloudinessColors.get(3))

	//Current Wind Speed
	LightPanel0103_Color.sendCommand(alWindColors.get(0))

	//Wind Speed Forecast
	LightPanel0801_Color.sendCommand(alWindColors.get(1))
	LightPanel0902_Color.sendCommand(alWindColors.get(2))
	LightPanel0901_Color.sendCommand(alWindColors.get(3))


	//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)))
	}else{
		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)))
        }else{
                LightPanel0904_Color.sendCommand(new HSBType(new DecimalType(0), new PercentType(100), new PercentType(0)))
        }



	

end


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.