Rule which iterates through group(s) of similar items

Hi all,

I hope you can help!

Basics out of the way first - currently running OH 2.2 Snapshot 1112 on Ubuntu 17.10 (if you need any more info, just ask!)

I am using the wonderful Heating Boilerplate as a template home heating.

I have the basics set up, but in the guide above, it is assuming that each of the Target Temperature channels will do something - however I am going to be using some Sonoff devices with Thermal Actuators, switching them on/off as necessary to regulate the temperature.

I have decided to give this a try by setting up a Cronā€™d rule, to run every X minutes (for initial testing every 5 minutes) to check temperatures, and turn on/off the Sonoff.

I will be having multiple heating zones, each with their own temperature schedules, and there may be one or more temperature sensors in each zone, and one or more radiators to control.

I have added all of the target temperature items into a group, so that in addition to the cron, it will be fired if any of these items are updated (so you donā€™t have to wait up to 5 minutes for a change to occur! That would have very poor WAF!).

As a starter for 10, I have the basic code below - when the rule fires, it will check a temperature sensor, compare it against the target, and if it falls within a range, set the actuator on or off.

rule "Check heating actuators every 5 minutes"
when
    Time cron "0 0/5 * * * ?" or
	Item gHeatingTargetTemp received update
then
	var Number hylow = 0
	var Number hyhigh = 0
	hylow = (OF_Heating_TargetTemp.state as Number) - hysteresis 
	hyhigh  = (OF_Heating_TargetTemp.state as Number) + hysteresis

	if ((OF_Temp1.state as Number) < hylow) {
		OF_Heating_Actuator.sendCommand(ON)
	} else if (((OF_Temp1.state as Number) > hylow) && ((OF_Temp1.state as Number) < hyhigh)) {
		OF_Heating_Actuator.sendCommand(ON)
	} else if ((OF_Temp1.state as Number) > hyhigh) {
		OF_Heating_Actuator.sendCommand(OFF)    
	}
	
	hylow = (ME_Heating_TargetTemp.state as Number) - hysteresis 
	hyhigh  = (ME_Heating_TargetTemp.state as Number) + hysteresis

	if ((ME_Temp1.state as Number) < hylow) {
		ME_Heating_Actuator.sendCommand(ON)
	} else if (((ME_Temp1.state as Number) > hylow) && ((ME_Temp1.state as Number) < hyhigh)) {
		ME_Heating_Actuator.sendCommand(ON)
	} else if ((ME_Temp1.state as Number) > hyhigh) {
		ME_Heating_Actuator.sendCommand(OFF)    
	}
end

I could then copy/paste and repeat for each sensor/actuator pairings.

This doesnā€™t yet also deal with the situation where I have multiple sensors for one zone (where I would want to average the value) - multiple actuators is again a simple case of copy/paste.

However - this is of course wasted code, and it means if I want to add devices, I will need to copy/paste, and thatā€™s another thing Iā€™ve got to remember to do.

All the Actuators are then part of another Switch group, and if any of them are ON, it should then switch the boiler/pump on to actually generate some heat.

What I think would be the best method, would be to be able to add all temperature sensors and actuators to a group - and have all members of this group iterated through, all temperatures averaged out, and all actuators turned on/off.

This is where Iā€™m struggling to get my head around how Iā€™d start to do thisā€¦

I have had a look at the Using Groups In Rules Design Pattern thread, and canā€™t get my head around it yet - that example appears to be using a single rule to act upon the item that triggered it - I am looking to act upon all items within groups, and use one type of item from one group (temperature sensors) to act upon another type of item in the group (switches), when an item in a group changes (target temps) or on a timerā€¦

I hope that makes some kind of sense - one I get this sorted Iā€™ll post up my findings in the Heating Boilerplate thread as this may be quite a funky way to get multi-zone heating working nicely.

Hi,

i had a similar problem with evaluating some power-measuring devices (all the same style, just 2 for starters), and i didnā€™t want to duplicate the code.

You can write a template function and call it with the actual items like so:

import java.text.DateFormat;
import java.util.Locale

val Functions$Function3<GenericItem, GenericItem, String, String> calcPow = [counterItem, powerItem, ga|
	var startTime = now().millis
	var result = ""
	var res = 0l
	var history = 0l
	var diff = 0l
	var watt = 0d
	var duration = 0l
	val df = DateFormat.getDateTimeInstance()
	result = executeCommandLine("/etc/openhab2/scripts/knx32bituint.sh@@" + ga, 1500)
	if (result != "") {
		res = Integer::parseInt(result)
		var histOb = counterItem.historicState(now())
		if (histOb != null) {
			var histTS = histOb.timestamp.time
			var nowTS = now().millis
			var timeDiff = Math::round(((nowTS - histTS) / 1000.0)/60.0)
			if (timeDiff < 1) timeDiff = 1
			logDebug("Stromzaehler_calcPow", "Last record: " + df.format(histOb.timestamp) + ", Value: " + (histOb.state as DecimalType).intValue() + ", Now: " + df.format(now().toDate()))
			logDebug("Stromzaehler_calcPow", "TS Hist: " + histTS + ", Now TS: " + nowTS + ", timeDiff: " + timeDiff)
			history = (histOb.state as DecimalType).intValue()
			diff = res - history
			watt = diff * (60.0 / timeDiff) as Double
			postUpdate(powerItem, watt)
		}
		postUpdate(counterItem, res)
		duration = now().millis - startTime
	} else {
		logWarn("Stromzaehler_calcPow", "No result received from KNX!")
	}
	"Result: " + result + ", History: " + history + ", Diff: " + diff + ", Watt: " + watt + ", calculated in " + duration + " millis"
]

rule "Stromzaehler_Berechnung"
when
	Time cron "58 * * * * ?"
then
	var log = ""
	// StromzƤhler Gesamt
	log = calcPow.apply(Stromzaehler_Gesamt, Stromzaehler_Gesamt_Watt, "0/3/1")
	logDebug("Stromzaehler_Berechnung_Gesamt", log)
	postUpdate(Stromzaehler_Gesamt_AVG10, Stromzaehler_Gesamt_Watt.averageSince(now.minusMinutes(10)))

	// StromzƤhler Heizung
	log = calcPow.apply(Stromzaehler_Heizung, Stromzaehler_Heizung_Watt, "0/3/0")
	logDebug("Stromzaehler_Berechnung_Heizung", log)
	postUpdate(Stromzaehler_Heizung_AVG10, Stromzaehler_Heizung_Watt.averageSince(now.minusMinutes(10)))
	
	
end

The Functions$Function3<...> takes 3 input parameters (of type GenericItem, GenericItem and String in my case) and returns a value of the last type (String in my case).
It is called by using functionName.apply(parameters) - hope this helps.

1 Like

That design pattern is actually several examples showing lots of ways to use Groups in Rules and that is just one of them.

Here are a few simple examples which might give you some ideas:

// Do something for every Item that contains Temp in the name
MyGroup.members.filter[i | i.name.contains("Temp")].forEach[ temp |
    // do something with an Item that has Temp in the name
]


// Calculate the average of all the NumberItem members of the Group
val nums = MyGroup.members.filter[i | i instanceOf NumberItem].
val avg = nums.map[state as Number].reduce[result, val | result = (result + val) ] / nums.size

// Send ON to an Associated Switch Item for all Temp sensor Items that are > 20
// We find Associated Items based on the name of the sensor with "_Switch" appended
MyGroup.members.filter[i instanceOf NumberItem].filter[temp.state as Number > 20].forEach[ temp |
    val assocaitedSwitch = MyGroup.members.findFirst[sw | sw.name = temp.name+"_Switch"] as SwitchItem
   assocatedSwitch.sendCommand(ON) 
]

Go back and look at that DP again as all of these methods (except for findFirst which is self explanitory) described with examples.

Iā€™ve been helping someone via PMs on just such a Group based approach.

2 Likes

Thanks @rlkoshak for those examples, they make it a little clearer - Iā€™ll have a proper look tomorrow and have a play and try to apply it to my usage case.

Iā€™m making some progress :slight_smile:

Iā€™ve grouped and renamed some items, and am able to iterate over the various Groups, and retrieving the Temperature sensors that are part of the group.

	logInfo(filename, "Group based update")
	var Number hylow = 0
	var Number hyhigh = 0
	gHeatingTargetTemp.members.forEach[ temp |
		logInfo(filename, temp.name.toString)
		hylow = (temp.state as Number) - hysteresis 
		Number hyhigh  = (temp.state as Number) + hysteresis
		val String GroupName = temp.name.substring(0, 3)
		logInfo(filename, GroupName + "Temp")
		val nums = gTemperature.allMembers.filter[i | i.name.contains(GroupName + "Temp")]
		logInfo(filename, nums.toString)
    ]

This gives the following in the log:

2017-12-06 21:38:06.704 [INFO ] [home.model.script.heating_mode.rules] - Group based update
2017-12-06 21:38:06.706 [INFO ] [home.model.script.heating_mode.rules] - LR_Heating_TargetTemp
2017-12-06 21:38:06.708 [INFO ] [home.model.script.heating_mode.rules] - LR_Temp
2017-12-06 21:38:06.710 [INFO ] [home.model.script.heating_mode.rules] - [LR_Temp1 (Type=NumberItem, State=22.0, Label=Living Room Temperature, Category=temperature, Groups=[gTemperature, gGroundFloor, GroundFloorTempAvg])]
2017-12-06 21:38:06.711 [INFO ] [home.model.script.heating_mode.rules] - OF_Heating_TargetTemp
2017-12-06 21:38:06.712 [INFO ] [home.model.script.heating_mode.rules] - OF_Temp
2017-12-06 21:38:06.714 [INFO ] [home.model.script.heating_mode.rules] - [OF_Temp1 (Type=NumberItem, State=21.5, Label=Office Temperature, Category=temperature, Groups=[gTemperature, gOffice, gFirstFloor, FirstFloorTempAvg])]
2017-12-06 21:38:06.715 [INFO ] [home.model.script.heating_mode.rules] - ME_Heating_TargetTemp
2017-12-06 21:38:06.716 [INFO ] [home.model.script.heating_mode.rules] - ME_Temp
2017-12-06 21:38:06.722 [INFO ] [home.model.script.heating_mode.rules] - [ME_Temp1 (Type=NumberItem, State=18.1, Label=Megan Bedroom Temperature, Category=temperature, Groups=[gTemperature, gMegan, gFirstFloor, FirstFloorTempAvg])]
2017-12-06 21:38:06.729 [INFO ] [home.model.script.heating_mode.rules] - MB_Heating_TargetTemp
2017-12-06 21:38:06.730 [INFO ] [home.model.script.heating_mode.rules] - MB_Temp
2017-12-06 21:38:06.732 [INFO ] [home.model.script.heating_mode.rules] - [MB_Temp1 (Type=NumberItem, State=21.5, Label=Master Bedroom Bedroom Temperature, Category=temperature, Groups=[gTemperature, gMasterBedroom, gFirstFloor, FirstFloorTempAvg])]

However, Iā€™m now stuck with the next part - calculating the average from all the Temp sensors in the group.

val avg = nums.map[state as Number].reduce[result, val | result = (result + val) ] / nums.size

This is giving the following errors:

2017-12-06 21:51:28.474 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'heating_mode.rules' has errors, therefore ignoring it: [20,52]: mismatched input ',' expecting ']'
[20,58]: no viable alternative at input '|'
[20,79]: no viable alternative at input 'val'
[24,5]: extraneous input ']' expecting 'end'

Edit: I have gone more verbose, and now have the following as the full rule.

    logInfo(filename, "Group based update")
	var Number hylow = 0
	var Number hyhigh = 0
	gHeatingTargetTemp.members.forEach[ temp |
		logInfo(filename, temp.name.toString)
		hylow = (temp.state as Number) - hysteresis 
		hyhigh  = (temp.state as Number) + hysteresis
		val String GroupName = temp.name.substring(0, 3)
		logInfo(filename, GroupName + "Temp")
		val nums = gTemperature.allMembers.filter[i | i.name.contains(GroupName + "Temp")]
		//val nums = gTemperature.allMembers.filter[i | i instanceOf NumberItem]
		logInfo(filename, nums.toString)
		var Number sum = 0
		gTemperature.allMembers.filter[i | i.name.contains(GroupName + "Temp")].forEach [ i |
			sum = sum + (i.state as Number)
		]
		logInfo(filename, sum.toString)
		val Number avg = sum / nums.size
		logInfo(filename, "Avg: " + avg.toString + ", hylow: " + hylow.toString + ", hyhigh: " + hyhigh.toString)
		if (avg < hylow) {
			gHeatingActuators.members.filter[i | i.name.contains(GroupName + "Heating_Actuator")].forEach [ i |
				logInfo(filename, i.name + " ON")
				i.sendCommand(ON)
			]
		} else if ((avg > hylow) && (avg < hyhigh)) {
			gHeatingActuators.members.filter[i | i.name.contains(GroupName + "Heating_Actuator")].forEach [ i |
				logInfo(filename, i.name + " ON")
				i.sendCommand(ON)
			]
		} else if (avg > hyhigh) {
			gHeatingActuators.members.filter[i | i.name.contains(GroupName + "Heating_Actuator")].forEach [ i |
				logInfo(filename, i.name + " OFF")
				i.sendCommand(OFF)
			] 
		}
    ]

However, the ON/OFF command isnā€™t sent to the Item. It logs the ON/OFF correctly, so gets into the forEach correctly.

Cheers!

My bad. ā€œvalā€ is a key word. You canā€™t use it as like that in the reduce.

.reduce[result, value | result = (result + value) ]

You might need to specify the type as Number as well:

.reduce[Number result, Number value | result = (result + value)]

I probably should have mentioned this earlier, but for your average, it would probably be easiest to put the Items you want averaged into a Group with AVG and then you can replace all that map.reduce stuff with just looking at the Groupā€™s state.

Group:Number:AVG BedroomAvgTemp

I think you mentioned you wanted everything in only one Group though so I didnā€™t recommend it earlier, unless Iā€™m remembering another thread.

Iā€™m going to need a little help pointing me to which foreEach loop seems to be failing. There are a lot of them.

Thanks - the use of ā€˜valā€™ makes sense itā€™s picking up as a protected keyword now - didnā€™t click earlier :slight_smile:

Iā€™m quite a verbose programmer anyway, but Iā€™ll still look at reducing it using the map.reduce stuff :slight_smile: Iā€™m not adverse to using groups, it will allow me to persist it and graph it at a later date too - but some people may not wish to have the overhead of persisting the additional groups, so I like the idea of calculating it on the fly.

The bit itā€™s not appearing to do correctly is i.sendCommand(ON) or i.sendCommand(OFF) - the item states arenā€™t updated.

Edit: the following works great to calculate the average:

val avg = nums.map[state as Number].reduce[result, value | result = (result + value) ] / nums.size

So this wonā€™t work and Iā€™m surprised you are not getting an exception or a validation error when you run.

You cannot access variables defined outside the scope of the forEach lambda. In other words, sum doesnā€™t exist inside the forEach, which is the whole reason we had to use the map reduce to calculate the average before. You can access val but you canā€™t change a val so that wouldnā€™t work for this use case.

Show your relevant log statements.

Add an else that just logs that something went wrong. Either that part of the code is never executing or none of your if elses are evaluating to true.

And because Iā€™m the opposite of a verbose programmer:

  • Only do the filter on gTemperature once and save the result in a variable.

Hereā€™s my log from when I had the sum within the forEach, including showing that the correct ON/OFF log entry is generated:

2017-12-06 23:06:41.953 [INFO ] [home.model.script.heating_mode.rules] - LR_Heating_TargetTemp
2017-12-06 23:06:41.956 [INFO ] [home.model.script.heating_mode.rules] - LR_Temp
2017-12-06 23:06:41.959 [INFO ] [home.model.script.heating_mode.rules] - [LR_Temp1 (Type=NumberItem, State=22.0, Label=Living Room Temperature, Category=temperature, Groups=[gTemperature, gGroundFloor, GroundFloorTempAvg])]
2017-12-06 23:06:41.963 [INFO ] [home.model.script.heating_mode.rules] - Avg: 22.00000000, hylow: 21.5, hyhigh: 22.5
2017-12-06 23:06:41.965 [INFO ] [home.model.script.heating_mode.rules] - LR_Heating_Actuator ON
2017-12-06 23:06:41.968 [INFO ] [home.model.script.heating_mode.rules] - OF_Heating_TargetTemp
2017-12-06 23:06:41.969 [INFO ] [home.model.script.heating_mode.rules] - OF_Temp
2017-12-06 23:06:41.971 [INFO ] [home.model.script.heating_mode.rules] - [OF_Temp1 (Type=NumberItem, State=21.4, Label=Office Temperature, Category=temperature, Groups=[gTemperature, gOffice, gFirstFloor, FirstFloorTempAvg])]
2017-12-06 23:06:41.974 [INFO ] [home.model.script.heating_mode.rules] - Avg: 21.40000000, hylow: 16.5, hyhigh: 17.5
2017-12-06 23:06:41.976 [INFO ] [home.model.script.heating_mode.rules] - OF_Heating_Actuator OFF
2017-12-06 23:06:41.982 [INFO ] [home.model.script.heating_mode.rules] - ME_Heating_TargetTemp
2017-12-06 23:06:41.983 [INFO ] [home.model.script.heating_mode.rules] - ME_Temp
2017-12-06 23:06:41.985 [INFO ] [home.model.script.heating_mode.rules] - [ME_Temp1 (Type=NumberItem, State=18.1, Label=Megan Bedroom Temperature, Category=temperature, Groups=[gTemperature, gMegan, gFirstFloor, FirstFloorTempAvg])]
2017-12-06 23:06:41.989 [INFO ] [home.model.script.heating_mode.rules] - Avg: 18.10000000, hylow: 18.5, hyhigh: 19.5
2017-12-06 23:06:41.991 [INFO ] [home.model.script.heating_mode.rules] - ME_Heating_Actuator ON
2017-12-06 23:06:41.994 [INFO ] [home.model.script.heating_mode.rules] - MB_Heating_TargetTemp
2017-12-06 23:06:41.995 [INFO ] [home.model.script.heating_mode.rules] - MB_Temp
2017-12-06 23:06:41.999 [INFO ] [home.model.script.heating_mode.rules] - [MB_Temp1 (Type=NumberItem, State=21.4, Label=Master Bedroom Bedroom Temperature, Category=temperature, Groups=[gTemperature, gMasterBedroom, gFirstFloor, FirstFloorTempAvg])]
2017-12-06 23:06:42.002 [INFO ] [home.model.script.heating_mode.rules] - Avg: 21.40000000, hylow: 17.0, hyhigh: 18.0
2017-12-06 23:06:42.005 [INFO ] [home.model.script.heating_mode.rules] - MB_Heating_Actuator OFF

No validation errors, and Iā€™m getting the result I expect (I wonder if itā€™s because I only have a single Temp item that matches, currently?

Anyway, as per my edit above, Iā€™ve reduced that down to a single line to get the avg :slight_smile:

And Iā€™m an idiot - Iā€™ve not been looking at the events.log file - where the _Heating_Actuators have actually been receiving the commandā€¦

So - itā€™s all working quite nicely, actually! Once I strip out the excess, and sort out my temp comparisons, as Iā€™m sure thereā€™s a better way of doing it, it might be quite a nice solution?

I might need to rethink my naming though - limiting it to 2 characters as a prefix isnā€™t very expandable - I might need to instead put the variable part at the end of the item name :slight_smile:

Cheers for your help - Iā€™m starting to understand a little better now.

Excellent! That is what I like to hear. Glad you have it working!

found this useful thread, an initial experiment

val String filename = "rivers.rules"

rule "river monitor"

when
 System started

then


// Do something for every Item that contains Temp in the name

MyGroup.members.filter[i | i.name.contains("RiverLevel")].forEach[ temp | logInfo(filename, temp.name.toSt$

// initally log to prove the rule

// do something with an Item that has Temp > RiverLevel in the name

// compare the level with the typ max then flag
// an array ? or ?

]


end

There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
2018-03-22 21:47:54.052 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'rivers.rules'
   

comments , suggestions for code to re examine appreciated

The error means the ruleā€™s engine can not determine what type either i or temp is. So try telling it what they are:

MyGroup.members.filter[ GenericItem i | i.name.contains(ā€œRiverLevelā€)].forEach[ GenericItem temp | logInfo(filename, temp.name.toSt$

Hi Rich,

I have seen this error message as well and user Grouped handling of items a lot nowadays (thanks to your help in another thread) :slight_smile:
However, my rules work properly except the error in the log (during startup).
So do I need to user ā€œGenericItemā€ or can I alternatively just ignore the error ā€œThere is no contextā€¦ā€?
Thanks in advance.

Itā€™s a warning so you can ignore it.

Or you can try giving it a type and see if the warning goes away. You donā€™t have to use GenericItem. For example, if you know all the members are Switches, you can use SwitchItem.

Hi Rich,

Thanks. I will consider checking it out after I fixed my major problems :wink:

Should be easy because I just group items of the same type.

Hi @rlkoshak,

i had the same errors in my logs and i added genericitem and contactitem.

But now i get a new error message:

Here is my rule:

rule "Fensterkontakte State Changed"
	when
		Item gFensterkontakte received update // NOTE: the rule will trigger multiple times per event
	then
		if(SystemStarting.state == OFF) {
		    Thread::sleep(500) // give persistence time to catch up
		    val haveHistory = gFensterkontakte.members.filter[ ContactItem c|c.lastUpdate("mapdb") !== null ]
		    val mostRecent = haveHistory.sortBy[ lastUpdate("mapdb") ].last
		    if(mostRecent === null) logError("Test contacts", "Failed to find most recent")
		    else if(mostRecent.state == OPEN){
				val dt = gKontakteZeit.members.filter[ GenericItem dt|dt.name == mostRecent.name+"_last_opened" ].head
				if(dt === null) logError("Test contacts", "Failed to find " + mostRecent.name+"_last_opened")
				else dt.postUpdate(new DateTimeType)
		    }
		}
end

And here is the error-log:

{
	"resource": "/w:/rules/fensterkontakte.rules",
	"owner": "_generated_diagnostic_collection_name_#0",
	"code": "org.eclipse.xtext.xbase.validation.IssueCodes.incompatible_types",
	"severity": 8,
	"message": "Type mismatch: cannot convert from (ContactItem)=>boolean to Function1<? super Item, Boolean>",
	"startLineNumber": 110,
	"startColumn": 52,
	"endLineNumber": 110,
	"endColumn": 100
}


{
	"resource": "/w:/rules/fensterkontakte.rules",
	"owner": "_generated_diagnostic_collection_name_#0",
	"code": "org.eclipse.xtext.xbase.validation.IssueCodes.incompatible_types",
	"severity": 8,
	"message": "Type mismatch: cannot convert from (GenericItem)=>boolean to Function1<? super Item, Boolean>",
	"startLineNumber": 114,
	"startColumn": 42,
	"endLineNumber": 114,
	"endColumn": 102
}

I made copy&paste from vscode, so the error message is in long format?

I was going to ask where you got JSON formatted error logs. :smiley:

Which lines do the two errors refer to?

The errors donā€™t make much sense given the code so Iā€™d like to know which parts of which lines are underlined in red.

All I can say with what I know is you donā€™t need to cast c or dt to ContactItem or GenericItem respectively in the calls to filter. They will both be GenericItems by default.

However, from what I can see you donā€™t need most of this code anyway. If you use Member of to trigger the Rule then triggeringItem will be mostRecent.

rule "Fensterkontakte State Changed"
when
    Member of gFensterkontakte received update
then
    if(SystemStarting.state != OFF) return;
    if(triggeringItem.state == OPEN) {
        val dt = gKontakteZeit.members.filter[ dt|dt.name == triggeringItem.name+"_last_opened" ].head
        if(dt === null) logError("Test contacts", "Failed to find " + triggeringItem.name+"_last_opened")
        else dt.postUpdate(new DateTimeType)
    }
end

hmm, I think if you trigger from
Item myGroup received update
the triggeringItem is , the group?
While with trigger
Member of myGroup received update
you will get the originating Item in triggeringItem, i.e. the member?

Indeed, I messed up and forgot to add the Member of to the trigger even after saying thatā€™s what I was going to do.

Iā€™ll correct the original.

1 Like

The two errors are from the lines where i added contactitem and genericitem.

val haveHistory = gFensterkontakte.members.filter[ ContactItem c|c.lastUpdate("mapdb") !== null ]

and

val dt = gKontakteZeit.members.filter[ GenericItem dt|dt.name == mostRecent.name+"_last_opened" ].head

Before i had the same rule but with no contactitem and genericitem in it, i got the same errors/warnings like the thread opener.

I will change my rule into your suggestion.

I have the old rule from this forum, i think @rlkoshak has posted this rule long time ago in the tutorial/example section. I only changed my item-names to my needs.

I can not find mapdb in your rule. My standard persistence is rrd4j and so i added mapdb into this rule. Do i need this somewhere?