filter[battery | battery.state as Number < lowBatteryThreshold]
You have to give the lambda an argument and then reference the argument in the body of the lambda passed to filter.
That is one problem.
I’m not sure about the map and join stuff either but I’m pretty sure that is wrong too because your code inside the if statement doesn’t filter anything out and will always produce a report with all the items.
Start with fixing the filters and see where that gets you. Then apply the same filter you use to see if any item is below the threshold to the line that builds the report.
Here is a solution in my usecase that works fine. Only the the notification in sitemap is lack.
Instead of telegram you can simple adapt a other service.
import java.util.HashMap
import java.util.ArrayList
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
var Lock lock = new ReentrantLock()
//
// init values
// -----------------------------------------------------------------------------------------------------------
// customize for you own enviroment
var HashMap<String, Object> batteryNotificationInits = newLinkedHashMap(
"debounceDelay" -> 300, // debounce time state updates in group [ms]
"message" -> "Batterietausch\n", // message subject then batterychange notice
"telegramBot" -> "TelegramBotName", // if the telegram bot available then bot name else ""
"triggerValues" -> newArrayList(7,10,15), // trigger values
"debounceGroupTimer" -> null as Timer // debounce timer - no config
)
//
// data structure
// -----------------------------------------------------------------------------------------------------------
// you will get a telegram message then the batterylevel reached a specified level
// triggerValues specified the level
// the first value (i.e. 7) means, you will get a message for 1%,2%,3%,4%,5%,6%,7%
// all other value means, you will get a message at this point
//
// the battery battery values should be from 1% to 100%
//
// in item file:
// all battery obtained devices must be assigned to the group gBatteryStatus
rule "battery status and send notifications"
when
Item gBatteryStatus received update
then
try {
lock.lock()
var Timer debTimer = batteryNotificationInits.get("debounceGroupTimer")
if (debTimer === null) {
var expTime = now.plusMillis(batteryNotificationInits.get("debounceDelay"))
debTimer = createTimerWithArgument(expTime, "debounce", [ p |
try {
lock.lock()
val String bot = batteryNotificationInits.get("telegramBot")
var boolean telegramBotExist = false
var String notificationMessage = batteryNotificationInits.get("message")
var String notificationMessageItems = ""
var ArrayList<Integer> triggerValues = batteryNotificationInits.get("triggerValues") as ArrayList<Integer>
val Integer minToTrigger = triggerValues.get(triggerValues.size()-1)
if (batteryNotificationInits.get("telegramBot") != "") {
telegramBotExist = true
}
var triggertDevices = gBatteryStatus.members.filter[s|s.state <= minToTrigger]
triggertDevices.forEach [ i |
var Boolean addItem = false
if (i.state <= triggerValues.get(0)) {
addItem = true
}
if (triggerValues.contains((i.state as DecimalType).intValue)) {
addItem = true
}
if (addItem) {
notificationMessageItems = notificationMessageItems + i.name + ': ' + i.state.toString + '%\n'
}
]
if (telegramBotExist && (notificationMessageItems != "")) {
sendTelegram(bot,notificationMessage + notificationMessageItems)
}
// insert message in Sitemap
batteryNotificationInits.put("debounceGroupTimer",null)
lock.unlock()
}
catch(Throwable t) {
logError("RuleBattery","E0201 something is wrong: " + t.toString)
lock.unlock()
}
])
batteryNotificationInits.put("debounceGroupTimer",debTimer)
} else {
debTimer.reschedule(now.plusMillis(batteryNotificationInits.get("debounceDelay")))
}
lock.unlock()
}
catch(Throwable t) {
logError("RuleBattery","E0202 something is wrong: " + t.toString)
smokeAlarmInits.put("debounceGroupTimer",null)
lock.unlock()
}
end
My filter looks right but i think the map component is incorrect in my rule as I get the notification with the correct percentage of the battery but it lacks the device name at the start of the alert.
rule "Battery Status Check"
when
Item gBatteries changed
then
var String msg = ""
var triggertDevices = gBatteries.members.filter[s|s.state <= 20]
triggertDevices.forEach [ i |
msg = msg + (transform("MAP", "batteries.map", i.name) + ': ' + i.state.toString) + '%\n'
logInfo("Battery Check","Low battery at " + i.name + ": " + i.state.toString + "%")
]
if (msg != "") {
sendBroadcastNotification(msg + " of your battery remaining")
}
end
What is the type of the members of gBatteries? Number? Dimmer? Number:Dimensionless? What type it is will dictate what is appropriate for the filter.
Secondly, you are passing an Item name to the transform but your MAP only has numbers in it. Furthermore, the MAP will only work if what you pass to it is exactly those values. You will get an error if the battery happens to be 91 or 90.000001. So unless you have Items named 100, 90, 80, etc. this is almost certainly not what you want. You should have the name of the item on the left and a “human readable” version of the name on the right. See Design Pattern: Human Readable Names in Messages
You might have further errors. You can’t access a var from inside a forEach. That’s why the OP uses a map/join to build the message. Other approaches are to use map/reduce or a StringBuilder to construct the message.
And it is still the case that the number entries in your map file will only work if the numbers happen to be exactly those on the left. If all you are doing is appending a “%” and using “Full” if the value is 100, you are far better off doing this in code rather than using the map.
Here is an rule I’m using for battery note that my threshold is set to 35%
My battery group is called batteryStatus One thing if you have missed a mapping the item name will be used instead until you fixed your mapping
Rule
//////////////////////////// Battery status //////////////////////////////////
rule "Low battery alert"
when
Item batteryStatus changed or
System started
then
var String msg = ""
var triggeredDevices = batteryStatus.members.filter[s|s.state <= 35]
triggeredDevices.forEach [ i |
var name = transform("MAP", "batteries.map", i.name)
if(name == ""){
name = i.name
}
msg = msg + name + ': ' + i.state.toString + '%\n'
logInfo("batteryStatus","Low battery at " + i.name + ": " + i.state.toString + "%")
]
if (msg != "") {
sendBroadcastNotification("Low battery \n"+msg)
}
end
//////////////////////////// End Battery status //////////////////////////////
I don’t actually have a battery alert rule any more. I just have a Group and I periodically look at my sitemap to see what the minimum charge is. My smoke alarms start to beep at around 40% anyway and the door deadbolts have a flashing status light we see every day.
But I do have a Rule to monitor the online/offline status of various servers and services that are relevant to my home automation. And every morning at 8 I generate a report with a list of all those that are offline.
from core.rules import rule
from core.triggers import when
from core.metadata import set_metadata
from personal.util import send_info, get_name
@rule("System status reminder",
description=("Send a message with a list of offline sensors at 08:00 and "
"System start"),
tags=["admin"])
@when("Time cron 0 0 8 * * ?")
@when("System started")
def status_reminder(event):
"""
Called at system start and at 8 AM and generates a report of the known
offline sensors
"""
numNull = len([i for i in ir.getItem("gSensorStatus").members
if isinstance(i.state, UnDefType)])
if numNull > 0:
status_reminder.log.warning("There are {} sensors in an unknown state!"
.format(numNull))
offline = [i for i in ir.getItem("gSensorStatus").members if i.state == OFF]
offline.sort()
if len(offline) == 0:
status_reminder.log.info("All sensors are online")
return
offline_str = ", ".join(["{}".format(get_name(s.name)) for s in offline ])
offline_message = ("The following sensors are known to be offline: {}"
.format(offline_str))
for sensor in offline:
set_metadata(sensor.name, "Alert", { "alerted" : "ON"}, overwrite=False)
send_info(offline_message, status_reminder.log)
send_info and get_name are personal library functions.
from core.actions import NotificationAction
from core.jsr223.scope import actions
from configuration import admin_email, alert_email
from core.metadata import get_value
def send_info(message, logger):
"""
Sends an info level message by sending an email and logging the message
at the info level.
Arguments:
- message: The String to deliver and log out at the info level.
- logger: The logger used to log out the info level alert.
"""
out = str(message)
logger.info("[INFO ALERT] {}".format(message))
NotificationAction.sendNotification(admin_email, out)
(actions.get("mail", "mail:smtp:gmail")
.sendMail(admin_email, "openHAB Info", out))
def get_name(itemName):
"""
Returns the 'name' metadata value or the itemName if there isn't one.
Arguments:
itemName: The name of the Item
Returns:
None if the item doesn't exist.
"""
return get_value(itemName, "name") or itemName