HowTo: Get notified on empty batteries

Ok, so the ForEach works

rule "Battery Status Check"
when
    Item gBatteries changed
then
    if (!gBatteries.allMembers.filter( [ battery | (battery.state as Number) <= (lowBatteryThreshold) ] ).empty) {
        val String report = gBatteries.allMembers.filter( [ (state as Number) <= (lowBatteryThreshold) ] ).sortBy( [ state as DecimalType ] ).map[ name + ": " + state.format("%d%%") ].join("\n")
        logInfo("REPORT", report)
        //val message = "Battery levels:\n\n" + report + "\n\nRegards,\n\nOur House"
      sendBroadcastNotification("Battery Report","The following batteries need to be replaced : " + report)
    } else {
        logInfo("REPORT: ", "EMPTY")
    }
end

The logInfos are showing now, as seen below but the Broadcast is empty.

10:03:51.277 [INFO ] [del.core.internal.ModelRepositoryImpl] - Refreshing model 'batteries.rules'
10:03:54.304 [INFO ] [smarthome.event.ItemStateChangedEvent] - LivingRoom_FibButton_Battery changed from 10 to 100
10:03:54.307 [INFO ] [home.event.GroupItemStateChangedEvent] - gBatteries changed from 10 to 100 through LivingRoom_FibButton_Battery
10:03:54.525 [INFO ] [lipse.smarthome.model.script.REPORT: ] - EMPTY
10:03:56.376 [INFO ] [smarthome.event.ItemStateChangedEvent] - AtticTemp changed from 29.4 to 29.8
10:03:56.378 [INFO ] [e.smarthome.model.script.Attic Temp: ] - 29.8
10:03:56.378 [INFO ] [smarthome.event.ItemStateChangedEvent] - AtticHumidity changed from 72.4 to 71.5
10:04:07.261 [INFO ] [smarthome.event.ItemStateChangedEvent] - vSun_Azimuth changed from 84.88678685310002 Ā° to 84.40359334701752 Ā°
10:04:07.263 [INFO ] [smarthome.event.ItemStateChangedEvent] - vSun_Elevation changed from 47.77483630035317 Ā° to 48.39444628125785 Ā°
10:04:11.063 [INFO ] [smarthome.event.ItemStateChangedEvent] - LivingRoom_FibButton_Battery changed from 100 to 10
10:04:11.065 [INFO ] [home.event.GroupItemStateChangedEvent] - gBatteries changed from 100 to 10 through LivingRoom_FibButton_Battery
10:04:11.070 [INFO ] [eclipse.smarthome.model.script.REPORT] - LivingRoom_FibButton_Battery: 10%

Test the broadcast:

rule "Battery Status Check"
when
    Item gBatteries changed
then
    if (!gBatteries.allMembers.filter( [ battery | (battery.state as Number) <= (lowBatteryThreshold) ] ).empty) {
        val String report = gBatteries.allMembers.filter( [ (state as Number) <= (lowBatteryThreshold) ] ).sortBy( [ state as DecimalType ] ).map[ name + ": " + state.format("%d%%") ].join("\n")
        logInfo("REPORT", report)
        //val message = "Battery levels:\n\n" + report + "\n\nRegards,\n\nOur House"
        //sendBroadcastNotification("Battery Report","The following batteries need to be replaced : " + report)
       sendBroadcastNotification("TEST", "TEST")
    } else {
        logInfo("REPORT: ", "EMPTY")
    }
end

My items are numbers and the group is:

/* Group Definitions for Battery Devices*/
Group:Number:MIN gBatteries         "Remaining Battery"

Yes, a broadcast with TEST is indeed received.

10:07:31.607 [INFO ] [del.core.internal.ModelRepositoryImpl] - Refreshing model 'batteries.rules'
10:07:46.821 [INFO ] [smarthome.event.ItemStateChangedEvent] - LivingRoom_FibButton_Battery changed from 10 to 100
10:07:46.824 [INFO ] [home.event.GroupItemStateChangedEvent] - gBatteries changed from 10 to 100 through LivingRoom_FibButton_Battery
10:07:47.041 [INFO ] [lipse.smarthome.model.script.REPORT: ] - EMPTY
10:07:54.045 [INFO ] [smarthome.event.ItemStateChangedEvent] - LivingRoom_FibButton_Battery changed from 100 to 10
10:07:54.048 [INFO ] [home.event.GroupItemStateChangedEvent] - gBatteries changed from 100 to 10 through LivingRoom_FibButton_Battery
10:07:54.053 [INFO ] [eclipse.smarthome.model.script.REPORT] - LivingRoom_FibButton_Battery: 10%

Hereā€™s how my setup looks like:

I have two groups related to battery powered devices: gBatteryLevels (group of items that report their values as number, battery level) and gBatteryWarning (items that report only when the battery is low).

Group gBatteryLevels (gSensors)
Group:Switch:OR(ON,OFF) gBatteryWarning (gSensors)

Most of my devices report both type of items, but some of them report only level of the battery, therefore, the first rule unifies this and fills the missing gBatteryWarning itemsā€¦ Item naming is as follows:

Number MyDeviceBattery "Battery [%.1f %%]" <battery> (gMyRoom) { channel="..." }
Switch MyDeviceBatteryLow "Low battery" <lowbattery> (gMyRoom, gBatteryWarning) { channel="..." }

but in case the item is missing the ā€˜Lowā€™ item, then it will be assigned to a group and itā€™ll be defined like this:

Number MiFloraBattery "Sensor Battery Level [%d %%]" (gMyRoom, gBatteryLevels) { channel="..." }
// this is a virtual item needed for later use
Switch MiFloraBatteryLow "Low battery" <lowbattery> (gMyRoom, gBatteryWarning)

Now, the .rules file handling all of thisā€¦

rule "Convert gBatteryLevels (number) to gBatteryWarning (switch)"
when
    Member of gBatteryLevels changed
then
    val device = triggeringItem
    val Number batteryValue = device.state as DecimalType
    val String batteryLowName = device.name+"Low"

    if(batteryValue <= 15) sendCommand(batteryLowName, 'ON')
    else postUpdate(batteryLowName, 'OFF')
end

And finally the main rule that does the alertingā€¦

rule "Low battery alert"
when
    Member of gBatteryWarning changed to ON
then
    val StringBuilder devices = new StringBuilder
    val triggeredDevices = gBatteryWarning.members.filter[ b | b.state == ON ]

    triggeredDevices.forEach[ device, index | 
        var deviceMappedName = transform("MAP", "batteryDevices.map", device.name)

        if (triggeredDevices.size > 1) {
            if (index == triggeredDevices.size-1) devices.append(deviceMappedName + ".")
            else devices.append(deviceMappedName + ", ")
        } else {
            devices.append(deviceMappedName)
        }
    ]

    sendBroadcastNotification("Low battery alert for: " + devices.toString)
end

I borrowed @rlkoshakā€™s idea of mapping some human-readable device names so iā€™m using a .map fileā€¦

I hope someone will find this useful.

1 Like

Thanks @suntribe , may you share your map file?

batteryDevices.map holds my items representing lowbattery switches and whatever i want to display in a messageā€¦

smth like thisā€¦

MyDeviceBatteryLow=My device in gMyRoom
MiFloraBatteryLow=The plant sensor in wherever
....
<itemname>=<whatever you want to display it>
1 Like

Thanks!

The alert just needs now to print the battery state (level)

Try removing the .join("\n")

rule "Battery Status Check"
when
    Item gBatteries changed
then
    if (!gBatteries.allMembers.filter( [ battery | (battery.state as Number) <= (lowBatteryThreshold) ] ).empty) {
        val String report = gBatteries.allMembers.filter( [ (state as Number) <= (lowBatteryThreshold) ] ).sortBy( [ state as DecimalType ] ).map[ name + ": " + state.format("%d%%") ]
        //logInfo("REPORT", report)
        //val message = "Battery levels:\n\n" + report + "\n\nRegards,\n\nOur House"
        sendBroadcastNotification("Battery Report","The following batteries need to be replaced : " + report)
        //sendBroadcastNotification("TEST", "TEST")
    } else {
        logInfo("REPORT: ", "EMPTY")
    }
end

Still empty im afraid, the notification says ā€™ Battery reportā€™ but after setting the item to 10, it doesnt display it.

This is very very odd.
Have you tried another notification, email for example?

I havent, I dont use email notifications.

All other notifications work just fine :frowning:

One more try:

rule "Battery Status Check"
when
    Item gBatteries changed
then
    if (!gBatteries.allMembers.filter( [ battery | (battery.state as Number) <= (lowBatteryThreshold) ] ).empty) {
        val String report = gBatteries.allMembers.filter( [ (state as Number) <= (lowBatteryThreshold) ] ).sortBy( [ state as DecimalType ] ).map[ name + ": " + state.format("%d%%") ]
        //logInfo("REPORT", report)
        val String message = "The following batteries need to be replaced : " + report
        sendBroadcastNotification("Battery Report", message)
        //sendBroadcastNotification("TEST", "TEST")
    } else {
        logInfo("REPORT: ", "EMPTY")
    }
end

Worse, now I dont even get ANY notification haha.


openhab> smarthome:status gBatteries
10
openhab>

The updates to members are getting to the groupā€¦

Uncomment the logInfo

And add another one:

rule "Battery Status Check"
when
    Item gBatteries changed
then
    if (!gBatteries.allMembers.filter( [ battery | (battery.state as Number) <= (lowBatteryThreshold) ] ).empty) {
        val String report = gBatteries.allMembers.filter( [ (state as Number) <= (lowBatteryThreshold) ] ).sortBy( [ state as DecimalType ] ).map[ name + ": " + state.format("%d%%") ]
        logInfo("REPORT", report)
        val String message = "The following batteries need to be replaced : " + report
        logInfo("MESSAGE: ", message)
        sendBroadcastNotification("Battery Report", message)
        sendBroadcastNotification("TEST", "TEST")
    } else {
        logInfo("REPORT: ", "EMPTY")
    }
end

Now I see this:


21:10:50.026 [INFO ] [home.event.GroupItemStateChangedEvent] - gBatteries changed from 100 to 5 through LivingRoom_FibButton                                                    _Battery
21:10:50.031 [ERROR] [untime.internal.engine.RuleEngineImpl] - Rule 'Battery Status Check': An error occurred during the scr                                                    ipt execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.LogAction.logInfo(java.lang.String,java.l                                                    ang.String,java.lang.Object[]) on instance: null

Okā€¦
Put the .join("\n") back in:

rule "Battery Status Check"
when
    Item gBatteries changed
then
    if (!gBatteries.allMembers.filter( [ battery | (battery.state as Number) <= (lowBatteryThreshold) ] ).empty) {
        val String report = gBatteries.allMembers.filter( [ (state as Number) <= (lowBatteryThreshold) ] ).sortBy( [ state as DecimalType ] ).map[ name + ": " + state.format("%d%%") ].join("\n")
        logInfo("REPORT", report)
        val String message = "The following batteries need to be replaced : " + report
        logInfo("MESSAGE: ", message)
        sendBroadcastNotification("Battery Report", message)
        sendBroadcastNotification("TEST", "TEST")
    } else {
        logInfo("REPORT: ", "EMPTY")
    }
end

The problem is NOT the filter or the rule. It works. The problem is with the formatting of the notification.