[SOLVED] Reminder to change batterys with three types of measurements (State, Percent, Voltage)

Hi there,

i´m currently starting to migrate and optimize a rule that i used in oH 1.8 to send me an reminder when one of the batterys in my smarthome devices is empty or almost empty.
This rule is 96 lines long and every single battery item is hardcoded…
I want to make this rule simplier/smarter then before and most of all scalable

What do i have?
5 devices with State (OPEN = full/CLOSED = empty)
3 devices with Voltage (under 2.20V is low)
2 devices with Percent (under 20% is low)

So currently i need 10 hardcoded ifs to check one by one.
Two things will stay in my rule:

var BatterieText = "Die folgenden Batterien sind leer: "
BatterieText = BatterieText.removeEnd(", ")

I already made groups for each type:

Group gBatteryState
Group gBatteryLevel
Group gBatteryPercent

Then i thought how to trigger this?
A cron jon is nice but would cause that i´m not able to use triggeringItem and make it easier.
So i´m just checking if one of the items changed

Member of gBatteryLevel changed
or
Member of gBatteryState changed
or
Member of gBatteryPercent changed

What brings me to the next check, what type of item triggered the rule?

var TriggerLevel = gBatteryLevel.members.filter[ i|i.state <= 2.20 ].map[ name ].reduce[ result, name | result = name + ", " ]
var TriggerState = gBatteryState.members.filter[ i|i.state == CLOSED ].map[ name ].reduce[ result, name | result = name + ", " ]
var TriggerPercent = gBatteryPercent.members.filter[ i|i.state <= 20 ].map[ name ].reduce[ result, name | result = name + ", " ]

Now i´m down to three checks instead of ten (or more with more devices)

Let´s move on and make a check if the triggering item is low or in other words, passed the three checks before.

val EmptyBattery = (TriggerLevel !== null || TriggerState !== null || TriggerPercent !== null)

Add the triggering item to the BatterieText and send a message if the triggering item is low.

if(TriggerLevel !== null)
{
	BatterieText = BatterieText + triggeringItem.label
	logInfo("BatteryCheck", TriggerLevel)
}

if(TriggerState !== null)
{
	BatterieText = BatterieText + triggeringItem.label
	logInfo("BatteryCheck", TriggerState)
}

if(TriggerPercent !== null)
{
	BatterieText = BatterieText + triggeringItem.label
	logInfo("BatteryCheck", TriggerPercent)
}

if(EmptyBattery)
{
	sendTelegram("bot1", BatterieText)
}
else
{
	logInfo("BatteryCheck", "Alle Batterien sind voll.")
}

And i think here´s my problem.
This rule would only work with one device having a low battery and run through this rule.
But what about the unlikely event of two batterys being low and triggering this rule one after another?
I was thinking about an persisted dummy item that will be filled with all triggering items and then being used for a time based reminder.

Any ideas how to make this battery check able to work with multiple triggering items?

kind regards
Michael

Is it neccessary to get the information immediatly? Just create a cron TRigger and check the battery states once a day.

No it‘s not neccessary, but i wouldn‘t be able to use triggeringItem to determine what battery is empty.
A cron job still has the problem how to get more then one battery into the string.

The rule was cron job based before but had every battery hardcoded…

I solved it like this. the rule needs to be adapted, but maybe thats a hint for you.

rule "Publish Battery Status"
when

    Time cron "0 0 18 ? * * *"

then

    val String ruleIdentifier = "Publish Battery Status"

    val Integer batteryThreshold = 10 // %. This should be enough to change the battery within a few days
    val StringBuilder aMessage = new StringBuilder

    var Integer emptyBatteries = 0

    emptyBatteries = gBatteryStatus.members.filter[ i | ((i.state instanceof DecimalType) && (i.state < batteryThreshold)) || ((i.state instanceof OnOffType) && (i.state == ON)) ].size

    logInfo(ruleIdentifier, "Daily battery check found {} empty batteries to report!", emptyBatteries)

    if (emptyBatteries != 0) {

        aMessage.append("-> Batteriestatusreport <-\n")
        gBatteryStatus.members.filter[ i | ((i.state instanceof DecimalType) && (i.state < batteryThreshold)) || ((i.state instanceof OnOffType) && (i.state == ON)) ].forEach[ aBattery | 
            aMessage.append(aBattery.label+"\n")
        ]
        aMessage.append("-> Ende <-")

        sendPushoverMessage(pushoverBuilder(aMessage.toString).withDevice("Disorganiser"))
        logInfo(ruleIdentifier, "Information about {} empty batteries has been published!", emptyBatteries)

    }

end

In your solution with the emptyBatteries number could be filled from the three groups and the same could be dome to create the string

That´s a good hint :wink: Thanks!
I didn´t had the .forEach and append in mind.

I´ll have a look when i´m back from work and try to adapt it.

1 Like

You could also make use of group states instead of cron triggers. My Battery rule looks like that:

Group:Number:MIN gBatteriealarm_Level
Group:Switch:OR(ON,OFF) gBatteriealarm_State
rule "Batteriealarm Level"
when
    Item gBatteriealarm_Level changed
then
    if(gBatteriealarm_Level.state < 25){
        val StringBuilder sb = new StringBuilder
			sb.append("Niedriges Batterielevel bei:\n")
			gBatteriealarm_Level.members.filter[ i | i.state < 25].forEach[i | sb.append(i.label +" :" + i.state + " %" + "\n") ]
			sendTelegram("bot1", sb.toString)
    }
end

rule "Batteriealarm State"
when
    Item gBatteriealarm_State changed
then
    if(gBatteriealarm_State.state == ON){
        val StringBuilder sb = new StringBuilder
			sb.append("Leere Batterie bei:\n")
			gBatteriealarm_State.members.filter[ i | i.state == ON].forEach[i | sb.append(i.label + "\n") ]
			sendTelegram("bot1", sb.toString)
    }
end

I think i got a working rule now!

rule "Batterien prüfen"

when

	Time cron "0 0 18 * * ?"

then

	val String ruleIdentifier = "BatteryCheck"
	val StringBuilder BatterieText = new StringBuilder
	BatterieText.append("Folgende Batterien sind leer:\n")

	var Integer emptyBatteriesL = 0
	var Integer emptyBatteriesS = 0
	var Integer emptyBatteriesP = 0

	emptyBatteriesL = gBatteryLevel.members.filter[ i | i.state <= 2.20].size
	emptyBatteriesS = gBatteryState.members.filter[ i | i.state == ON].size
	emptyBatteriesP = gBatteryPercent.members.filter[ i | i.state <= 20].size
	val emptyBatteries = emptyBatteriesL + emptyBatteriesS + emptyBatteriesP

	logInfo(ruleIdentifier, "Es sind {} Batterien leer.", emptyBatteries)

	if(emptyBatteries != 0)
	{
		gBatteryLevel.members.filter[ i | i.state <= 2.20].forEach[ i | BatterieText.append(i.label+"\n")]
		gBatteryState.members.filter[ i | i.state == ON].forEach[ i | BatterieText.append(i.label+"\n")]
		gBatteryPercent.members.filter[ i | i.state <= 20].forEach[ i | BatterieText.append(i.label+"\n")]
		Thread::sleep(100)
		logInfo(ruleIdentifier, BatterieText.toString)
		sendTelegram("bot1", BatterieText.toString)
		sendTelegram("bot2", BatterieText.toString)
	}

end

I made some tests with dummy items and it seems to work pretty good :slight_smile:
Thanks for your help Thomas!
And thanks to you Christoph for another idea how to solve this.