HowTo: Get notified on empty batteries

In addition to all the SmartHome functionality that makes life easier, the SmartHone unfortunately also requires frequent maintenance. Anyone who takes care of the SmartHome topic will quickly notice that the number of battery-powered devices is increasing by leaps and bounds. From the window contact to the thermostat to the plant sensors, everything has a battery that eventually runs out and has to be replaced.
In order to keep the effort to a minimum and not have to check all devices by hand regularly, the SmartHome has to support us in this task. What is the task?

Dear SmartHome, please inform me in time about a low battery level of one or more devices, so that I can still buy batteries and then change them.

The following collection of items, groups and rules does just that.

The items:
In my environment there are two kinds of battery status items. One is a switch that goes ON when the battery is low and the second is a percentage indicator of how much charge is left. Furthermore, Additonally i have structured my things/items so that all items of a thing are grouped together. Below you will find one thing of each type.

//  Thing: Window Contact / Right Windows
Group GF_KI_WindowContactR "Kitchen: Window (right)" <fts_window_2w_open_r> (gGF_Kitchen)
    Contact GF_KI_WindowContactR_State "Kitchen: Window (right) [MAP(cnt2ger.map):%s]" <fts_window_2w_open_r> (GF_KI_WindowContactR, gWindowContacts) 
    Switch GF_KI_WindowContactR_LowBattery "Kitchen: Window (right)[MAP(swt2battery.map):%s]" <measure_battery_50> (GF_KI_WindowContactR, gBatteryStatus)

//  Thing: ZWave Multisensor Node 3
Group GF_GT_Multisensor "Guest WC: Multi sensor" <message_presence> (gGF_GuestToilet)
    Switch GF_GT_Multisensor_Motion "Guest WC: Motion [MAP(swt2motion.map):%s]" <message_presence_active> (GF_GT_Multisensor, gPIRMotionTrigger) 
    Number  GF_GT_Multisensor_BatteryLevel  "Guest WC: Multi sensor [%d %%]" <measure_battery_50> (GF_GT_Multisensor, gBatteryStatus) 

The group:
Only one group is needed to identify the battery status channels.

    Group gBatteryStatus "Battery state" <measure_battery_75>

And last but not least - the rule:
Every day at 6 o’clock in the evening the rule is triggered. First the rule chekcs if there are any empty batteries. If not thats it. If there are any empty batteries create a loop and add the label of the battery channel to the message. So make sure that the battery state channel label has the right value. At the end i use Pushover to send the message

//  Rule: Publish Battery Status
//
//  Process all items of group GRP_BatteryStatus. Check if battery is empty and create message that is published once a day.
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("-> Battery report <-\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("-> End <-")

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

    }

end

That’s it. Any suggestion to improve the rule are welcome. To have a life view on empty batteries with a second rule a group may be dynamically filled with empty batteries.

10 Likes

Nice set of rules and thank you for the tutorial!
However I noticed with a lot of my battery powered devices that the reported battery status in % is not always decreasing in a logical linear way over time. A lot of devices suddenly drop from 30% or 40% to empty without ever showing 10%. I am working with an “on every update” persistence strategy so I can see every reported battery percentage in the MySQL tables. So even if your rules are absolutely logical some of my devices would not work with them. I meanwhile came up to replace batteries now as soon as the percentage drops below 50%. Very often the period of time when 100% is reported is very long, while as soon as it goes < 50% time becomes short. But of course this depends on the devices you use.

1 Like

Obviously you can change the threshold to 50% or if you had this on a special category extend the check.

Good job! :ok_hand:

Definetly. I just wanted to contribute my experiences as you asked for suggestions:

And for me it had to learn that battery level reporting is sometimes tricky.

1 Like

Nice Rule. I used something similar but I only trigger the Rule when the batteries change instead of every day. Though I have to say that the Rule I though I had in my setup has gone missing in action. I should probably do something about that. :wink: Git to the rescue!

The big difference is I defined the Group as

Group:Number:MIN gBatteries

and the Rule is triggered by Item gBatteries changed so the Rule only triggers when there is a new low battery value that is lower than the last lowest. It doesn’t really matter much, but I like my Rules to only have to run when necessary. But by doing then then I can quickly short circuit the Rule.

rule "Low battery alert"
when
    Item gBatteries changed
then
    if(gBatteries.state > 20) return;

    val lowBatteriesList = gBatteries.members.filter[ b | b.state < 20 ].map[ name ].reduce[ list, bat | list = list + ", " + transform("MAP", "batteries.map", bat) ]
    aAlert.sendCommand("The following batteries need to be replaced: " + lowBatteryList.subString(0, lowBatteryList.size-2) + ".")
end

I let it keep alerting me until I change the batteries. The squeaky wheel gets the grease. :wink:

6 Likes

@rlkoshak Very nice, especially the map/reduce approach. I have to think about that for my rule as well. The main difference i see is that you only use items with a battery level value whereas in my rule switches and levels are used.

If the wheel is too squeaky i will break it :triumph::angry:

@rlkoshak and @Dibbler42
both rules are really nice. I am using a smiliar method like @rlkoshak, but it works also.

my only things using batteries are homematic sensors. CCU returns a low bat warning <1.1V for AAA batteries and set a switch to ON. So:

Group:Switch:OR(ON, OFF)   gBatterie     <battery>
rule "Batteriewarnung"
when    Member of gBatterie changed to ON
then    val batterie = gBatterie.members.filter[ b | b.state == ON ].map[ label ].reduce[ s, label | s + ", " + label ]
        sendTelegram("marco" , "%s muss erneuert werden", batterie.toString)
end
3 Likes

Hi @rlkoshak
And what is the content of the batteries.map file, please?

See Design Pattern: Human Readable Names in Messages.

It is a mapping between Item names and something a little more human friendly. I wanted to use names for the message that differed from the label but I also wanted plain English names instead of Item names in the message.

2 Likes

Coolio. Thanks

I se the label in my rule and this is a bit anoying. Amap file is may be the solution. i hvae to think about it

I also opted for a daily reminder using Cron, instead of triggering the notification once when the battery goes low. This way I can’t miss the notification so easily.

I am thinking about a double opt in approach. First sens a daily reminder using cron and in addition create da dynamich group with empty batteries that is displayed in the ui

I do two things here.
monitor if the device is active by saving last battery update time and check if thsi is older then x hours. And with a cron job check every day if battery level is lower then x.

I need both, because sometimes the battery level stays at 100% but the device do not work anymore because battery is empty. This device will not report low battery or l0%.

Oh, checking on the last update time is actually a nice touch!

i run almost this exact same rule, but i have it also run at night and send me an email with a list of low batteries

Hi Rich

Im trying this rule but it doesnt like it with the following error:

06:34:08.094 [INFO ] [del.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'batteries.rules', using it anyway:
The value of the local variable lowBatteriesList is not used
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.
Assignment to final parameter

All of those are warnings and do not cause the Rule to not work.

But the warnings do point at other potential problems. Please post the Rule as YOU have implemented it. It could be as simple as a wrong capitalization or the like.

Hi Rich

Sure

rule "Low battery alert"
when
    Item gBatteries changed
then
    if(gBatteries.state > 20) return;

    val lowBatteriesList = gBatteries.members.filter[ b | b.state < 20 ].map[ name ].reduce[ list, bat | list = list + ", " + transform("MAP", "batteries.map", bat) ]
    sendBroadcastNotification("The following batteries need to be replaced: " + lowBatteryList.subString(0, lowBatteryList.size-2) + ".")
end