Hi,
today I was thinking of creating a report (maybe persistence) with all of my batterie driven devices. I want to send this report in a email once a month to see which batteries might be changed soon. I am not quite sure of the howto. I mean it`s quite easy to send an attachment with the email plugin, but I don’t really know how to fill the file.
Maybe someone knows a good way to do this.
You don’t really need to create a file to attach.
You could setup a rule that checks (e.g. once a day/week/month/etc) the battery levels (Item states) and sends an email notification with the ones below a defined threshold.
Check the DPs for rule examples. Start with: Design Pattern: Working with Groups in Rules
I studied the DPs a lot and I am already working with groups in rules. I only thought there is a nicer solution for storing values in a file instead of building a very long string.
Here’s what I do. It depends on all battery level items being part of group gBattery
and the battery level update time items being part of the group gBatteryLastUpdate
.
val int NUMBER_HOURS = 8
rule "Battery Health Monitor"
when
Time cron "0 0 19 * * ?"
then
logInfo("battery-health-check", "BATTERY: Checking health of battery devices")
var SimpleDateFormat df = new SimpleDateFormat( "MM/dd" )
var String timestamp = df.format( new Date() )
val String mailTo = AlertEmailAddress.state.toString
var String subject = "Battery Health Report for devices on " + timestamp
val StringBuilder body = new StringBuilder("Battery health report:\n\n")
// List battery levels from lowest to highest
gBattery.members.sortBy[(state as Number).intValue].forEach [ NumberItem myItem | {
body.append(String::format("%-30s%8.4s\n", myItem.name, myItem.state.toString))
}]
body.append("\n\n")
// List battery levels that haven't been updated in NUMBER_HOURS hours
gBatteryLastUpdate.members.forEach [ GenericItem myItem | {
if (myItem.state !== NULL) {
var DateTime dateTime = new DateTime((myItem.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli)
if (dateTime.isBefore(now.minusHours(NUMBER_HOURS))) {
var SimpleDateFormat lastUpdateTime = new SimpleDateFormat( "MM/dd/YYYY HH:mm:ss" )
var long t1 = (myItem.state as DateTimeType).zonedDateTime.toInstant().toEpochMilli
logWarn("battery-health-check", "BATTERY: No battery update from " + myItem.name + " since " + lastUpdateTime.format(new Date(t1)))
body.append("No battery update from '")
.append(myItem.name)
.append("' in at least ")
.append(NUMBER_HOURS)
.append(" hours:\t")
.append(lastUpdateTime.format(new Date(t1)))
.append("\n")
}
}
}]
logInfo("battery-health-check", "BATTERY: Sending battery health check email")
sendMail(mailTo, subject, body.toString())
end
Note: I wrote this rule a long time ago (in fact it’s one of the first rules I wrote), so there may be some things that could be done more efficiently.
I guess I also should include how I’ve defined the items, as well as the rule that maintains the update time.
Number Office_BatteryLevel "Office Battery Level [%.0f %%]" <battery> (gBattery) { channel="zwave:device:zstick:node42:battery-level" }
DateTime Office_BatteryLevelTime "Last Updated [%1$tm/%1$td/%1$tY %1$tT]" <clock> (gBatteryLastUpdate)
import java.util.concurrent.locks.ReentrantLock
// Determine the time when a battery level was updated
// Post the time to an item called item.name + Time
// Define item like this:
// DateTime XXXXX_BatteryLastUpdate "Last Updated [%1$tm/%1$td/%1$tY %1$tT]" <clock> (gBatteryLastUpdate)
// where XXXXX is the name of the battery level item
val Procedure$Procedure1<NumberItem> handleBatteryUpdate = [
NumberItem myItem |
if (myItem !== null) {
val itemName = myItem.name + "Time"
var i = gBatteryLastUpdate.members.findFirst[ name.equals(itemName) ]
if (i !== null) {
//logInfo("handleBatteryUpdate", "BATTERY: Updating time of battery level change: " + itemName)
i.postUpdate(new DateTimeType());
}
}
]
val ReentrantLock latch = new ReentrantLock
rule "Handle Battery Update"
when
Member of gBattery received update
then
try {
latch.lock
val NumberItem item = triggeringItem as NumberItem
//logInfo("handle-battery-update", "BATTERY: Update received from " + item.name + ": " + item.state)
handleBatteryUpdate.apply(item)
}
catch (Exception e) {
logError("battery-last-update", "BATTERY: Exception occurred in latched rule! " + e.toString)
}
finally {
latch.unlock
}
end
My rule is a bit simpler:
All my batteries are part of the group Batteries
This will send an email with all the batteries with a level less than 15%
I do it at 10:55am because it’s convenient for me…
val Number lowBatteryThreshold = 15
rule "Battery Monitor"
when
Time cron "0 55 10 * * ?"
then
if (!Batteries.allMembers.filter( [ battery | (battery.state as Number) <= (lowBatteryThreshold) ] ).empty) {
val String report = Batteries.allMembers.filter( [ (state as Number) <= (lowBatteryThreshold) ] ).sortBy( [ state as DecimalType ] ).map[ name + ": " + state.format("%d%%") ].join("\n")
val message = "Battery levels:\n\n" + report + "\n\nRegards,\n\nOur House"
sendMail("vzorglub@gmail.com", "Low battery alert !", message)
}
end
This is very close to mine. I also trigger this when a device in group battery changes. So I get that immediate update. I also have a lookup table to remind me what kind of battery.
import org.openhab.core.library.types.DecimalType
import java.util.HashMap
import util.LinkedHashMap
val int lowBatteryThreshold = 10
var HashMap<String, String> batteries =
newLinkedHashMap(
"LivingRoomLightsSwitchBatteryLevel" -> "CR2032 Coin Cell",
"HVAC_Battery" -> "4 AA",
"PoolSensorTempBattery" -> "1 18650 li-ion"
)
rule "Battery Monitor"
when
Time cron "0 0 0 * * ?" or
Item gBattery received update
then
if (! gBattery.allMembers.filter([state < lowBatteryThreshold]).empty) {
val report = gBattery.allMembers.filter([state instanceof DecimalType]).sortBy([state]).map[
name + ": " + state.format("%d%%") + ":Type=" + batteries.get(name)
].join("\n")
val message = "Battery levels:\n\n" + report + "\n\nRegards,\n\nopenHab"
sendMail("emailgoeshere", "[HA] Battery Report", report)
}
end
It`s a nice idea to even tell what kind of batterie is needed. But I don’t understand where ‘name’ in .get(name) is defined as the second string of the hashmap. How is that working?
Hi
I’m bring life to this threwad again. I run OH 2.5 and would like to implement Marks solution. But get one error I couldn’t really sort out.
I made two diffrent files for rules one for survailence and one for report. Report rules semms to load correctly but survailence doesn’t. I paste log entrys below.
08:12:56.721 [INFO ] [del.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model ‘battery.rules’, using it anyway:
The field Tmp_batteryRules.handleBatteryUpdate refers to the missing type Object
08:12:56.749 [INFO ] [del.core.internal.ModelRepositoryImpl] - Refreshing model ‘battery.rules’
Just to share my approach here as well and I hope it’s useful for one or the other…
In general I work also with Groups, means all my Batteries are in a group.
Whenever one of these changes, I check the level and activate a Battery Alarm switch for the specific (_Sw) item.
I then also trigger to send the type of Battery which is needed on a daily report in the morning:
items:
Number:Dimensionless Z_Bath_W_Batt "Bad Fenster Batterie [%.0f %unit%]" <batterylevel> (G_Num,G_Batt,gBathBattery) ["Measurement","Energy"] {channel="zwave:device:zwaveUSBCtrl:node18:battery-level", widgetOrder="04"}
Switch Z_Bath_W_Batt_Sw "Bad Fenster Batterie Alarm" <siren> (G_jdbc,G_Batt_Sw,gBathBattery) ["LowBattery"] // {widgetOrder="05"}
Group:Number G_Batt "Batterien [%s]"
Group:Switch:OR(ON,OFF) G_Batt_Sw "Batterie-Alarm"
String Act_Batt_Alarm "Aktive Batterie-Alarme [%s]" (G_jdbc) // from G_Batt_Sw
String Req_Batt "Benötigte Batterien [%s]" (G_jdbc) // from G_Batt_Sw based on batteries.map
Rule for checking the level and set battery alarm accordingly
// ***************** Battery driven Device monitoring ***************//
rule "Battery Check"
when
Member of G_Batt changed
then
val action = getActions("telegram","telegram:telegramBot:MyBot")
// get triggering item and its state
val itemName = triggeringItem.name.toString
val Number itemState = triggeringItem.state as Number
// get the matching _Sw to triggering item and its state
val String itemNameSw = itemName + "_Sw"
val itemSwState = ScriptServiceUtil.getItemRegistry.getItem(itemNameSw).state
// skip accumulator driven devices
if(itemName == "BerlingBatt" || itemName == "Xido_Batt") return; // failing fast
// special handling for Z_Lock
if(itemName == "Z_Lock") {
if(itemState < 40) {
postUpdate(itemNameSw, "ON")
logInfo("+++ SYSTEM", "Battery Alarm activated for " + itemName)
action.sendTelegram("Die Batterien von " + itemName + " sind bald leer (" + itemState + " %%)")
}
else {
if(itemSwState != OFF) {
postUpdate(itemNameSw, "OFF")
logInfo("+++ SYSTEM", "Battery Alarm DE-activated for " + itemName)
}
}
return; // failing fast
}
if(itemState < 12) {
if(itemSwState != ON) {
postUpdate(itemNameSw, "ON")
logInfo("+++ SYSTEM", "Battery Alarm activated for " + itemName)
}
}
else {
if(itemSwState != OFF) {
postUpdate(itemNameSw, "OFF")
logInfo("+++ SYSTEM", "Battery Alarm DE-activated for " + itemName)
}
}
end
Rule for checking the active alarms and set the type of batteries to be replaced
rule "Battery Alarm State"
when
Member of G_Batt_Sw changed
then
if(G_Batt_Sw.state == ON) {
// screening of active battery alarms
val String tmp = G_Batt_Sw.members.filter [ i | i.state == ON ].map[ label ].reduce[ s, label | s + ", " + label ]
Act_Batt_Alarm.postUpdate(tmp)
// checking associated / required batteries
val String batt = G_Batt_Sw.members.filter [ i | i.state == ON ].map[ name String::format("%s",transform("MAP", "batteries.map", name)) ].reduce[ s, name | s + ", " + name ]
Req_Batt.postUpdate(batt) // Required batteries for replacement
logInfo("+++ SYSTEM", "Battery alarm active on: " + Act_Batt_Alarm.state.toString + " -> Required Batteries: " + Req_Batt.state.toString)
}
else {
Act_Batt_Alarm.postUpdate("Keine")
Req_Batt.postUpdate("Keine") // No batteries for replacement required
}
end
map.file:
BerlingBatt_Sw=Interner Akku
Xido_Batt_Sw=Interner Akku
Outd_Batt_Sw=2 x AAA
Rain_Batt_Sw=2 x AAA
Gard_Wat_Batt_Sw=3 x AA
Gard_Sens_Batt_Sw=2 x AA
Z_Bath_W_Batt_Sw=1 x ER 14250
Z_Shock_Batt_Sw=1 x CR123A
Z_Flood_Batt_Sw=1 x CR123A
Z_Motion_Batt_Sw=1 x CR123A
After getting up in the morning I get a Telegram message with the battery types to be replaced.
I do it this way:
I just have a rule that triggers when a battery drops below a certain percentage, which sends a notification to the openHAB app.