One of the things openHAB lacks is some kind of configurable scheduler… and by that I don’t mean, of course, the cron
trigger for rules.
I wanted to send a Telegram reminder, for example to read the electricity and gas meters - this is simple enough - but I wanted to re-schedule the alert. Why? Well, some kind of snooze mechanism… I often forget to read the meters because, when I receive the reminder, I’m not at home…
So, here is what I came up with…
I will post all of my scripts and rules below, but I wanted first to explain a bit WHY I chose to directly modify another rule…
To send a reminder, one could use a cron rule, a Google calendar/CalDAV event - evidently, no problems here. One could process the response from Telegram (there are some threads on how to control OH with Telegram and even a binding, project started by @Belgadon) so, also no problems here…
What I wanted to share is how I managed to re-scheduled another rule based on the response… As I wanted the changes to survive a reboot and even a reinstall of OH, I chose to directly modify the when
clause of the alerting rule.
I’m sure that advanced users, @rlkoshak - master of design patters comes to mind , could find a more elegant approach like, dunno, HashMaps
- never managed to wrap my head around this one, encoding values in StringItems
and saving/restoring them after reboot… but my programming abilities are limited (and also my English, sorry ).
So, let’s start:
telegram_response.items:
String TelegramResponse "Telegram Response [%s]" {mqtt="<[mqtt:telegram/response:state:default"}
String TelegramResponse_Reminder (gPersist_MapDB_Change, gPersist_MapDB_Restore)
TelegramResponse
is populated by a python script telegram2mqtt.py
- inspiration comes from @Dixon’s post which is run in the background (started by /etc/rc.local
).
I won’t post the scripts I use to send a Telegram message with a keyboard and how I read Telegram responses mainly because it will make this post too long.
One far better approach is to use @Belgadon Telegram binding and modified Telegram action (post). Maybe his work will be included in official openHAB addons.
This python script will publish any callback (from Inline keyboard
) or any texts from any chats is the format <chat_id>@<message>
.
The telegram_reminder_meters.rules
is the one that send the initial alert and gets modified by the response:
rule "telegram reminder"
when
Time cron "0 0 18 26-28 12 ? 2018"
then
val keyboard_postpone2H = '{\"text\":\"Snooze 2 hours\"}'
val keyboard_postpone4H = '{\"text\":\"Snooze 4 hours\"}'
val keyboard_postpone1D = '{\"text\":\"Snooze 1 day\"}'
val keyboard_done = '{\"text\":\"Reading done\"}'
val keyboard = '{\"keyboard\":[[' + keyboard_postpone2H + ',' + keyboard_postpone4H + ',' + keyboard_postpone1D + '],[' + keyboard_done + ']],"resize_keyboard":true,"one_time_keyboard":true,"selective":true}\"'
val chatId = "<chat_id>"
var String message = "*REMINDER!*\n_You should read the electricity and gas meters...\nElectricty meter should be read between_ *26-05 every month* _and the gas meter between_ *24-28 every month*_..._"
logWarn("DEBUG",executeCommandLine("/etc/openhab2/scripts/my_scripts/sendTelegram_withKeyboard.sh " + chatId + " '" + message + "' '" + keyboard + "'", 30000))
TelegramResponse_Reminder.postUpdate("telegram_reminder_meters")
end
As you can see, it is initially programmed to run on 18:00 every day between 26 and 28.12.2018.
It uses another external script to send a Telegram message with Markdown
syntax and a reply_markup
keyboard.
Whenever the TelegramResponse
changes the following rule with read the response and change telegram_reminder_meters.rule
cron trigger:
telegram_response.rules:
import org.quartz.CronExpression
rule "telegram response"
when
Item TelegramResponse changed
then
Thread::sleep(150)
if(previousState == "") {
if (TelegramResponse_Reminder.state != "") {
// split Telegram response by "@"
// <sender> is transformed from a Telegram chat_id in a bot name to be used with sendTelegram action
val sender = transform("MAP","telegram_chatId.map",TelegramResponse.state.toString.split("@").get(0))
// <response> will be the actual response received by Telegram, whether by pressing the keyboard buttons or simple text
val response = TelegramResponse.state.toString.split("@").get(1)
switch(TelegramResponse_Reminder.state){
// the rule that sent the reminder gets modified. Value is kept in TelegramResponse_Reminder
case "telegram_reminder_meters": {
var String new_cron
switch(response) {
// depending on the response, we will create the new cron trigger
// TO-DO: use some REGEX to extact the numbers from string and ditch the switch-case
case "Snooze 2 hours": {
new_cron = now.plusHours(2).toString("0 m H d M ? yyyy")
}
case "Snooze 4 hours": {
new_cron = now.plusHours(4).toString("0 m H d M ? yyyy")
}
case "Snooze 1 day": {
new_cron = now.plusHours(24).toString("0 m H d M ? yyyy")
}
case "Reading done": {
new_cron = now.withDayOfMonth(26).withTime(18,0,0,0).toString("0 m H d-28 M ? yyyy")
}
}
}
// validate the cron trigger
if(CronExpression::isValidExpression(new_cron)){
sendTelegram(sender,"Scheduling " +TelegramResponse_Reminder.state + ".rules to run on [" + (new DateTime(new CronExpression(new_cron).getNextValidTimeAfter(now.toDate))).toString("dd.MM.yyyy HH:mm") + "]")
executeCommandLine("/etc/openhab2/scripts/my_scripts/replace_cron.sh /etc/openhab2/rules/" + TelegramResponse_Reminder.state + ".rules '" + new_cron + "'",30000)
} else {
sendTelegram(sender,"[telegram_response.rules] The resulting CRON expression is not valid: [" + new_cron + "]")
}
TelegramResponse_Reminder.postUpdate("")
}
}
TelegramResponse.postUpdate("")
}
}
end
replace_cron.sh:
#!/bin/sh
# CALL: replace_cron.sh <quartz_cron_expression>
/bin/sed -i "s/Time cron.*/Time cron $2/g" $1
As I said earlier, I sure there are other, more elegant solutions to re-schedule a rule, but this is what I came up with.
I’m interested to hear form advanced users/programmers what are the downsides of this approach…
Comments welcome!
Edit: found some typos…