The Missing Link to simple UI-configurable Scheduling

Tags: #<Tag:0x00007fc1fa566ee0> #<Tag:0x00007fc1fa566dc8> #<Tag:0x00007fc1fa566c60>

…and how to solve it.

Yes, this is another thread about the widespread topic on “how can i implement a simple UI configurable scheduler, e.g. for an alarm clock?”.
However my intent is a bit different, essentially all the discussions i saw on this topic tried to solve the problem more or less elegant, but none of those discussions focused on the root cause, or why it actually is so difficult to achieve something which should be very easy?

So let’s take a look at the underlying root cause and how to fix that:

User Requirement:
A simple functionality to Configure Alarms etc. on the UI Level, must work without dependecies to other services (offline) and if possible be solvable with core Openhab functionality.

Problem: The simplest approach as stated in plenty of other discussions would be to utilize Cron, but Cron doesnt work with variables (due to the nature of how it works) which is required if we want to be able to configure it on a UI level, there are solutions to this, but none of them are simple or straightforward. So what is missing here?

Root Cause: For this we need to take a look at the environment we are actually working in, for this scheduling task we want to use the Rule Engine within Openhab, on this “abstraction layer” of the application we are working purely event based, we listen for updates on Items and if one is triggered we run our logic. Cron however is purely timing based, this is a component of the OS layer which is exposed in Openhab, but it doesn’t really fit into this event based environment. So we need a way somehow to bridge these two worlds, the timing based “Time” capabilities of the OS and our event based rules in Openhab.

The 90% Solution (Working):
So to make this work we essentially need a Time Item that contains the current Timestamp and that triggers a event whenever this is updated. There is actually a binding that provides just what we are looking for. A real genius developed the NTP Binding , somehow however i only found two instances in Blogs were this slightly gets touched that it can be utilized for rules, it seems it is completely overlooked how genius this actually is.

Prerequisites:

  • Openhab
  • Item Persistence
  • NTP-Binding

How to configure:

  • Install the NTP Binding, leave the config at default values (triggers an update event once a minute)
  • Create an Item Alarm_Hour with Type number (done in PaperUi), rest of the fields can be left empty
  • Create an Item Alarm_Minutes with Type number (done in PaperUi), rest of the fields can be left empty
  • Create a UI in Habpanel to fill the Items, what i used for testing:
  • Widget:Knob Name:Hours, linked to Alarm_Hour, Min 0, Max 23
  • Widget:Knob Name:Minutes, linked to Alarm_Minutes, Min 0, Max 59
    Rule
rule "Alarm_Clock1"
when
    Item LocalTime_Date received update                                 //Rule triggers when the NTP Time is updated, based on the configured Refresh
then
    
    var String vLocalTime = LocalTime_Date.state.format("%1$tH:%1$tM")  //Format Date String from NTP Binding to HH:MM
    var String vAlarm_Hour = Alarm_Hour.state.toString                  //Format Number Format from UI to String, relevant for Length check below
    var String vAlarm_Minutes = Alarm_Minutes.state.toString
    var String vAlarm_Time

    if (vAlarm_Hour.length == 1) {                                      // Ensure that Hours input from UI has two characters (05)
        vAlarm_Time = "0" + vAlarm_Hour                                 // Build UI Set Alarm Time String
    }
    else {
        vAlarm_Time = vAlarm_Hour
    }
    if (vAlarm_Minutes.length == 1) {                                   // Ensure that Minutes input from UI has two characters (05)
        vAlarm_Time = vAlarm_Time + ":0" + vAlarm_Minutes               // Build UI Set Alarm Time String
        }
    else {
        vAlarm_Time = vAlarm_Time + ":" + vAlarm_Minutes
    }  

    if (vAlarm_Time == vLocalTime) {                                    // Actual Condition, if NTP Time equals Time Set on UI then execute
        Plug_3.sendCommand(ON)
    }

    logInfo("AlarmClock", "vLocalTime= "+ vLocalTime)                   //only used for Debug Logging, can be removed
    logInfo("AlarmClock", "vAlarm_Hour= "+ vAlarm_Hour)
    logInfo("AlarmClock", "vAlarm_Minutes= "+ vAlarm_Minutes)
    logInfo("AlarmClock", "vAlarm_Time= "+ vAlarm_Time)
 
end

Note: I am not a developer, so there might be more elegant ways to write the above code, this is intended to be easily understandable. Also the intend is to showcase the concept of event-based Time items and how they can make scheduling easier, obviously for a proper alarm clock there are things like days and a general on/off switch missing.

However, this works like a charm, can be up and running in 5minutes and is simple and straightforward.

HabPanel Ui (nothing fancy, but it works):


Log Output:

==> /var/log/openhab2/events.log <==

2020-01-11 10:14:55.214 [ome.event.ItemCommandEvent] - Item 'Alarm_Hour' received command 10
2020-01-11 10:14:55.225 [vent.ItemStateChangedEvent] - Alarm_Hour changed from 9 to 10
2020-01-11 10:14:56.981 [ome.event.ItemCommandEvent] - Item 'Alarm_Minutes' received command 15
2020-01-11 10:14:56.989 [vent.ItemStateChangedEvent] - Alarm_Minutes changed from 16 to 15

==> /var/log/openhab2/events.log <==
2020-01-11 10:15:38.075 [vent.ItemStateChangedEvent] - LocalTime_Date changed from 2020-01-11T10:14:38.055+0100 to 2020-01-11T10:15:38.064+0100
2020-01-11 10:15:38.113 [ome.event.ItemCommandEvent] - Item 'Plug_3' received command ON

==> /var/log/openhab2/openhab.log <==
2020-01-11 10:15:38.117 [INFO ] [se.smarthome.model.script.AlarmClock] - vLocalTime= 10:15
2020-01-11 10:15:38.125 [INFO ] [se.smarthome.model.script.AlarmClock] - vAlarm_Hour= 10
2020-01-11 10:15:38.131 [INFO ] [se.smarthome.model.script.AlarmClock] - vAlarm_Minutes= 15
2020-01-11 10:15:38.137 [INFO ] [se.smarthome.model.script.AlarmClock] - vAlarm_Time= 10:15

==> /var/log/openhab2/events.log <==
2020-01-11 10:15:38.142 [nt.ItemStatePredictedEvent] - Plug_3 predicted to become ON
2020-01-11 10:15:38.155 [vent.ItemStateChangedEvent] - Plug_3 changed from OFF to ON

100% Solution (Concept):

Ok, so the above solution is already great, but only because the NTP-Binding provides exactly that missing concept. From my perspective an event based time Component/Item should be a core Item of Openhab. The NTP-Binding again relies on some “outside service” so is not really usable offline (not an issue for most, but might be for some).
So having a OpenHab SystemDateTime Item that offers the same event based update would solve i think the biggest underlying problem for most scheduling attempts

A proper scheduler of course should work slightly different but this is an important concept to actually solve that.

Conclusion: I don’t know if i missed something fundamental, however i have not seen this approach described anywhere, and other automation solutions seem to fall short in this area as well, so it seems like a wider spread issue. I also saw all the counter arguments and options like using caldav etc. but they are either over the top, rely again on complex config, or outside services. and setting these values simply in the rules file in the cron section is just not user friendly, if you are the only person in your home using your automation that might be viable, if you have other people as well you need to be able to set stuff like this on the UI. The WAF is just higher if she can set her own alarm.
Also, i am new on here, have been working with OpenHab now for the last year or so, so if i missed something about creating posts etc. please let me know

3 Likes

You can basically do the same thing with a Cron rule that triggers every minute, right? Then you do not depend on the NTP binding.

when
    Time cron "0 * 0 ? * * *"
then

And the code can be a bit lighter indeed, and you can just use now instead of the date time item of the NTP binding.

Yes, in this particular example i used i could also use cron, however that is not my point and not the reason why i created this Thread.

Every UI based configuration approach can’t utilize Cron, because on the UI we only work with Items.
And that is the entire intend, what is missing so that we actually can do UI based scheduling?

Lets take a look at this from another angle, let’s say i want to utilize a UI based rule engine like the Next Generation Rule Engine for Scheduling (setting aside that it is experimental), that engine needs access to a time item it can use because it likely doesnt have access to cron, and this is where the concept of an event based time item comes in.

I am not trying to put another example of an alarm clock out, i am trying to close the concept-gap so that we can actually have easy ui based configuration in the future.

This is a completely inaccurate depiction of the new rule engine, the current UI rule editor, and of what their capabilities are. They absolutely have access to cron :roll_eyes:! The issue you appear to be addressing is merely a limitation of the old rule engine, which does not exist in the new rule engine. Here is an example of a rule that triggers on Item change and creates/updates another rule that uses a cron trigger… and yes, this could also be created through the REST API or UI rule editor…

Would the widget from this be of any use to your solution?

The over all concept presented here is the same as the standard alarm clock examples that go back to OH 1.6.

  • put the hour and minute on your Sitemap/HABPanel with a slider, setpoint, dial, whatever.
  • trigger a rule one or minute (that’s all the NTP binding is doing for you here, you could also use the Astro binding or a cron triggered rule to achieve the same thing and the cron triggered rule is the one that doesn’t have external dependencies)
  • once a minute check if we’ve just passed the hour and minute by checking the items against getHourOfDay and getMinuteOfHour and trigger the alarm.

There are other approaches too, like setting. A Timer based on the values of the two Items rather than triggering a rule one a minute. I personally prefer the Timer approaches.

And as Scott indicates, this approach is possible using the NGRE unchanged.

The root cause of the problems with both of these approaches are:

  1. there is no Time Item type in OH, only a DateTime which includes the date so we have to deal with handling the date even on a recurring alarm
  2. there is no way to enter a Time or DateTime on any of the UIs; we have to break it apart into separate Items and then rebuild the time from the parts in the Rule

1 isn’t really that big of a deal as it’s not hard to manage and we can provide libraries to manage it in NGRE. But 2 is there root problem. And that is going to take effort in openhab-core, openhab-uis, and both phone app repos to solve, which is probably why it’s never been solved.

The biggest issue i see at the moment is that there are plenty of different approaches to solve the overall issues, but they all have some sort of limitation on how and where i can use them.

Whilst that solution provided by Scott is very clever, it can only be used in the NGRE and it actually has to modify the rule itself to make it work… From a pure process perspective thats the automation of the operator modifying the rule file. Thats a cool approach to fix the problem, but in my view questionable. The rules to me are “config” items, they are set once and then remain untouched, all variance required is to be input by variables and Items, modyifing a rule to change a variable is not a thorough process no matter if it is done manualy or automatically.

The one thing i just dont get:
All the trigger actions i can use in Openhab are exposed as Items: Sensor Data like Temperature, Movement, Door open/Close, Switches and even things like Sunrises / sunsets through the Astro Binding. Why is Time not also exposed as Item? Why not have one common approach to all my potential triggers?
Why do i have to treat time completely differently as all my other triggers? It just doesnt make sense.
When it is exposed as Item i can use it in all rule engines without developing new methods to do so.

It is not the issue to have all the information in the timestamp, the handling of the date is as simple as to extract only the information i require from the string, in that case the time.

I get that the actual UI Part is kind of a challenge, but there are standards on how timestamps are formatted, as long as the UI elements use the same standard as the Item for formatting there isn’t really an issue, that just needs to be defined once and then be used across the system.

All a can say is if you have a solution that is so easy, I look forward to your PR to add the functionality.

2 Likes

Yes, this can only be done in the new rule engine, which will be replacing the old rule engine in OH 3.0.

What?! :worried:

Like is done though a DateTimeItem?

:no_mouth:

as i said, i am not a developer…

in that example you posted you use a trigger to actually modify a rule, that is a workaround to be able to make something variable which is not designed to be variable, i like the idea, but again it does not fix the root cause, it is only another solution to the symptom

and as a core functionality openhab updates that item, so that i actually have an event i can listen to to trigger my rules like the datetime item of the NTP-binding does? Doesnt seem so… thats the missing part

Then maybe don’t argue that something is easy when people on this forum who have been using OH far longer and who are developers tell you it’s not so easy.

Your idea is not original. It’s been brought up many many times over the years. I hope that it gets addressed in OH 3. But the reason we don’t have this fixed today isn’t because no one thought of it. There are significant technical challenges, technical challenges which in your ignorance, you just have your hands at and claim “that’s easy.”

So again I say, if you know something the rest of us with more knowledge and experience don’t know that makes this easy, please file the PR. If not, maybe trust that we just might know what we are talking about.

It’s the same DateTime Item. The only reason it triggers your Rule is because the NTP binding changes that DateTime Item once a minute. All that your example does does differently from a cron is change the source of the one minute perpetual trigger of the rule. It doesn’t actually solve anything.

And indeed, it would be nice to get an event at the time stored in a DateTime. But that does you no good if you can’t actually enter a DateTime Item on any of the UIs. Once that can be done it’s actually trivial to have a rule create a timer to trigger at exactly that time to call a rule you write. I will even volunteer to write the library script so no one else has to deal with it. I’ve pretty much already have written it in the Ephemeris Time of Day submission to the Helper Libraries. I may do so anyway since the Android app will publish the next alarm time to an Item. I’m sure those users would find it useful.

2 Likes

As both Scott and Rich have said, there are many ways of overcoming this current scheduling limitation.

For selecting a time value from UI, I use a combination of a Number item representing a 24 hour time, with a setpoint element in my sitemap e.g:

Setpoint item=UFH_MasterEnSuite_SchedOnTime1 label="Turn on Time 1 [%04d]" minValue=-10 maxValue=2400 step=10 icon="time"

I combine this with a simple validation function that ‘wraps’ the user selected setpoint number, so that if the last 2 digits are 60 or more, it jumps to the next ‘hour’, and when less than zero, the hour is reduced by 1 etc:

def validate_sitemap_ui_time(schedule_item_name):
    newtime = items[schedule_item_name].intValue()
    # log.info("[DEBUG] Rule triggered. event = {}".format(newtime))
    if newtime > 2400:
        hours = 0
        mins = 0
    elif newtime < 0:
        hours = 23
        mins = 50
    else:
        hours = int(newtime/100)
        mins = newtime - hours * 100
        if 75 < mins < 99:
            mins = 50
        elif mins > 59:
            hours = 0 if hours == 23 else hours + 1
            mins = 0

    # log.info("NEW: hours: {}, mins: {}".format(hours, mins))
    revisedtime = 100*hours + mins

    if newtime != revisedtime:
        events.postUpdate(schedule_item_name,str(revisedtime))
        # log.info("[DEBUG][{}] UPDATING UI:  org time: {}, new time: {}".format(schedule_item_name, newtime, revisedtime))

    return revisedtime

The above function is automatically called whenever a change happens on the number item representing the 24hour time. In actual fact, I have a group defined (gValidateUITime), which all of my such ‘dynamic time’ items belong to. This means that I can use a single rule that gets triggered for changes in state of any members of this group. It is thus very easy to add new time items as and when needed (just add them to the group above).

Once the selected time changes, I have cron rules automatically created as per Scott’s example above.

Admittedly there are a number of steps at the beginning in setting the above up, but once done it is very easy to add new items as required. The whole method has been working reliably for me over the last 4 months or so.

1 Like