OH3: time of day again

  • openHABian 3.3.0 on rPi4 with 4GB

I thought I have another crack at the time of day business, by pillaging what Rich invented many years ago.

rule "Astro: calculate time of day state"
    when
        // run at system start in case the time changed when OH was offline
        System started or
        Channel 'astro:sun:local:rise#event'       triggered START or
        Channel 'astro:sun:local:set#event'        triggered START or
        Channel 'astro:sun:local:astroDusk#event'  triggered START or
        //Time cron "5 /5 * * * ? *" or  // temporary during rule development
        // 1 min after midnight for Astro to calculate the new day's times
        Time cron "5 1 0 * * ? *" or
        Time cron "5 0 6 * * ? *" or
        Time cron "5 0 21 * * ? *" or
        Time cron "5 0 22 * * ? *"
    then
        /*
            Started out with https://community.openhab.org/t/deprecated-design-pattern-time-of-day/15407
            until I realised it is deprecated; then found:
            https://community.openhab.org/t/creating-capabilities-with-rule-templates-time-of-day/127965/6
        */
        val ZonedDateTime zdt = ZonedDateTime.now()
        val ZonedDateTime start_of_day = zdt.toLocalDate().atStartOfDay(zdt.getOffset())

        // Calculate the times for the static times of day and populate the
        // associated items. Update when changing static times.
        // Jump to tomorrow and subtract to avoid problems at the change over
        // to/from DST; not applicable to QLD, but left for universal application

        // (static) Lifestyle times of day (set arbitrarily)
        // 06:00
        val morning_start = start_of_day.plusDays(1).minusHours(18)
        tod_MorningTime.postUpdate(morning_start.toString)

        // 21:00
        val night_start = start_of_day.plusDays(1).minusHours(3)
        tod_NightTime.postUpdate(night_start.toString)

        // 22:00
        val bed_start = start_of_day.plusDays(1).minusHours(2)
        tod_BedTime.postUpdate(bed_start.toString)

        // (dynamic) Astro times of day; here early April 2023
        // 06:03
        val day_start       = (tod_SunriseTime.state as DateTimeType).getZonedDateTime()
        // 16:17
        val afternoon_start = (tod_EveningTime.state as DateTimeType).getZonedDateTime()
        // 17:40
        val evening_start   = (tod_SunsetTime.state  as DateTimeType).getZonedDateTime()

        // Calculate the current time of day
        var curr = "UNKNOWN"

        switch now
        {
            //               06:00                            06:03
            case now.isAfter(morning_start)   && now.isBefore(day_start):       curr = "MORNING"
            //               06:03                            16:07
            case now.isAfter(day_start)       && now.isBefore(afternoon_start): curr = "DAY"
            //               16:07                            17:44
            case now.isAfter(afternoon_start) && now.isBefore(evening_start):   curr = "AFTERNOON"
            //               17:44                            21:00
            case now.isAfter(evening_start)   && now.isBefore(night_start):     curr = "EVENING"
            //               21:00                            06:03
            case now.isAfter(night_start):                                      curr = "NIGHT"
            //               22:00                            06:00
            case now.isAfter(bed_start)       && now.isBefore(morning_start):   curr = "BED"
        }

        // Publish the current state in case it has changed
        if (tod_TimeOfDay.state != curr)
        {
            tod_TimeOfDay.sendCommand(curr)
            logInfo(LOG_PREFIX + "01.02", "Updated time of day to..: " + curr)
        }
end

As it stand, the rule does not switch to AFTERNOON. and it is UNKNOWN at midnight till morning.

2023-04-06 00:00:00.913 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: EVENING
2023-04-06 00:01:00.911 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: UNKNOWN
2023-04-06 06:00:00.917 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: MORNING
2023-04-06 06:03:00.008 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: DAY
2023-04-06 17:43:00.841 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: EVENING
2023-04-06 21:00:05.411 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: NIGHT
2023-04-07 00:01:05.400 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: UNKNOWN
2023-04-07 06:00:05.405 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: MORNING
2023-04-07 06:04:00.016 [INFO ] [penhab.core.model.script.Astro.01.02] - Updated time of day to..: DAY

What am I missing?

I was thinking, wouldn’t it be better to ‘abuse’ other astro events with offsets to cater for the other static events?

let’s take it easy
 bed_start is today.midnight + 22 hours, morning_start is today.midnight + 6 hours, so moring:start is always before bed_start (and thus the last case will never be true). night_start will be recalculated after midnight,

I’m pretty sure you’ll have to set two lines for BED, one for “yesterday bed_start” to “today morning_start”, another for “today bed_start” to “tomorrow morning_start”

Please keep in mind, that morning_start and bed_start should be objects of Type ZonedDateTime, so there should be a method .plusDays and .minusDays

case now.isAfter(bed_start.minusDays(1)) && now.isBefore(morning_start):   curr = "BED"
case now.isAfter(bed_start) && now.isBefore(morning_start.plusDays(1)) :   curr = "BED"
1 Like

You could just install it now. Time Based State Machine [3.2.0;3.4.9]

You just need to define the Items and set some metadata. Beyond the advantages of not needing to code anything at all, it supports adding and removing times of day without needing to change the code like this old old approach does.

As an alternative, OH now supports a DateTime trigger, as in Time is MorningStart timeOnly where MoringStart is a DateTime Item conataining the time you want your MORNING time of day to start. With that, you can just create a separate very simple rule for each time of day with that trigger (use the Items linked to the Astro Channels for those times) and command the time of day Item as required. (The timeOnly part tells it to ignore the date so it will trigger every day at that time.

I’m about 80% certain that was added before 3.3.

rule "morning"
when
    Time is DayStart timeOnly
then
    tod_TimeOfDay.sendCommand('MORNING')
end

rule "day"
when
    Time is DayStart timeOnly
then
    tod_TimeOfDay.sendCommand('DAY')
end

// and so on

Sooo much simpler than that old complicated rule which was only so complicated because it was a work around for things lacking in OH at the time, mainly the ability to trigger a rule based on a DateTime Item’s state and the ability to easily set the state of a DateTime Item from sitemaps (which will finally be somewhat possible as a single entry in OH 4.

2 Likes

Can RulesDSL use a “library” file like in python/java import, and then call a method from that library?

Then you could just put your time of day logic in that method, and call it every time you needed it, so you don’t need an actual “Time of day” item and all the rules / schedules associated with it.

You could even return different “time of day” for different room / areas of the house. e.g. bedtime for kids is 8pm, but for adults 12am.

For example, inside a “Motion trigger” rule for the hallway, I want to know whether it’s “bed time” and if it is, set the light to dim and turn it on, but outside of that, set it to 100%.

Nope. You can’t even create a proper function, only sort of fake it by creating a lambda and assigning it to a “global” variable (in quotes because it’s only global to the file it’s defined in). But those have no context so you have to pass everything in to it that it needs as an argument, even other “global” variables and you are limited to a mere six arguments.

This whole rule was originally written (way back in 2016 I think under OH 1.6 IIRC) as a kind of work around for that limitation too. No easily reusable functions and libraries so centralize that calculation and put the result into an Item.

But one advantage over putting the current time of day in an Item is you can trigger a rule based on it and you can’t do that with a library. So the time of day can drive automations, not just adjust them.

Note: I’ve reimplemented this rule many many times, each time improving it and taking advantage of new OH features:

  • OH 1.6: That version in Rules DSL in the OP
  • OH 2.?: Straight forward translation of the original Rules DSL to Python
  • OH 2.?: A smarter version in Python that takes into account Ephemeris so you can have different times of day based on the type of day (e.g. weekends, holidays, workdays, etc). This was the first Item/Item metadata driven version. The goal was to make this part of the Helper Library so reuse and simple deployment was the main focus. I used a trick, the script dynamically created two rules, one that dynamically created the rule triggers based on the states of Items and another to reload that rule to recreate the triggers if the Items changed.
  • OH 3.0: Reimplemented in Nashorn JS
  • OH 3.2?: When Rule Templates became supported I reimplemented it as a UI rule and created a template
  • OH 4.0: Reimplemented the Rule Template in JS Scripting and added a ton of error checking and reasonable error messages. If the rule fails, it will tell you exactly why and how to fix it, no guess work required.

Future Plans: Implement a new version of the rule template that creates and manages simple rules like the above instead of using timers. This will make the time of day rule show up in the Settings → Schedule page at their start times.

But the key thing is everything after OH 3.2? is a rule template. No one but I need even look at this code any longer. It’s installable like a custom UI widget or an add-on.

4 Likes

That’s quite an evolution!

Many thanks for all the responses.

BED is fixed, as far as I can tell, but AFTERNOON still does not work.

It looks like the offset for a start time does not work.

I took Astro Night start time, and created a new item called night.
As it so happened this was at 19:00.

 and at 19:00 this rule was triggered

2023-04-09 19:00:00.003 [INFO ] [ab.core.model.script.testRules.02.01] - night#event.............: event

I then added an an offset of 30 minutes, and nothing happened at 19:30.

This is the same problem I have with AFTERNOON; it does not get triggered. Here it is a Dusk start with an offset of -120 minutes.

In both cases, the linked item shows the time according to the entered offset, but these are not actioned/triggered at the time the items are showing.

What could be the reason?

And yes, I could use something else, but I’d like to understand where the problem is (please).

Ahh, here the four rules:

rule "Astro: calculate time of day state"
    when
        Channel 'astro:sun:local:astroDusk#event' triggered START
    then
        logInfo(LOG_PREFIX + "00.01", "astroDust#event.........: event")
end

rule "Astro: calculate time of day state 2"
    when
        Channel 'astro:sun:local:astroDusk#start' triggered START
    then
        logInfo(LOG_PREFIX + "01.01", "astroDust#start.........: start")
end


rule "Astro: calculate time of day state: night 1"
    when
        Channel 'astro:sun:local:night#event' triggered START
    then
        logInfo(LOG_PREFIX + "02.01", "night#event.............: event")
end

rule "Astro: calculate time of day state: night 2"
    when
        Channel 'astro:sun:local:night#start' triggered START
    then
        logInfo(LOG_PREFIX + "03.01", "night#start.............: start")
end

The bit I also do not understand is why is the UID of the astro item called astro:sun:local:night#start but I need to call astro:sun:local:night#event.

astro:sun:local:night#start is the state channel, i.e. one that you can link to an item to retrieve the value of that channel. So at any time of the day, you can get the actual date/time of when “night” is supposed to start, from this “state” channel.

astro:sun:local:night#event is a trigger channel, i.e. one that can trigger a rule when the actual “night” event actually happened.

So in your rules above, you shouldn’t create a “channel” trigger based on the #start channel, because that would never get triggered.

1 Like

And don’t forget, there are four channels for each 
 err
 event (not the channel type):

  • .. start → datetime. it’s a timestamp of the event START. e.g to show it on a page or sitemap.
  • .... end → datetime: it’s another timestamp of the event END. see start.
  • .. event → trigger. it’s triggering rules. you can’t link this channel to an item.
  • duration → number. time between start and end. see start

Each of the first three channels have individual settings, you have to set all of them (if you want to use them in the same way).
So, if setting an offset in the start channel, this will not affect the event channel and vice versa.

The events mark the passage of the sun over elevation lines, and as the sun isn’t a dot but a circle, there is a start of the passage (sun starts to touch the elevation line) and an end of the passage (sun leaves the elevation line), therefor the event channel will trigger START and END (but there may be days and places where sometimes some of the triggers won’t happen at all - simply because they don’t happen
)

1 Like

So, Jim and Udo
 while you made me understand these astro channels (thank you), riddle me this: why does the afternoon not get triggered
 which has an offeset of -150 (minutes) using astroDusk?

image

rule "Astro: calculate time of day state"
    when
        // run at system start in case the time changed when OH was offline
        System started or
        Channel 'astro:sun:local:rise#event'       triggered START or
        Channel 'astro:sun:local:set#event'        triggered START or
        Channel 'astro:sun:local:astroDusk#event'  triggered START or
        //Time cron "5 /5 * * * ? *" or  // temporary during rule development
        // 1 min after midnight for Astro to calculate the new day's times
        Time cron "5 1 0 * * ? *" or
        Time cron "5 0 6 * * ? *" or
        Time cron "5 0 21 * * ? *" or
        Time cron "5 0 22 * * ? *"
    then

I checked for typos and all
 the night#event is being triggered, per this rule (shared before but added for convenience):

rule "Astro: calculate time of day state: night 1"
    when
        Channel 'astro:sun:local:night#event' triggered START
    then
        logInfo(LOG_PREFIX + "02.01", "night#event.............: event")
end

However, this one is not:

rule "Astro: calculate time of day state"
    when
        Channel 'astro:sun:local:astroDusk#event' triggered START
    then
        logInfo(LOG_PREFIX + "00.01", "astroDust#event.........: event")
end
``

Your screen shot show the startchannel which has a -150 offset.

The second screen shot shows the link between the start channel and an Item named Afternoon. It also shows that the Afternoon Item has a proper time of 16:02.

So far, so good.

I wonder if you do because you are still using the event channel to trigger the rule. You use a completely different channel to trigger a rule from the channel you use to link to an Item. Therefore you must apply your -150 offset to both channels.

So either you’ve set that offset on both channels haven’t shown us or you’ve only set the offset to the start channel and thr event channel will trigger the rule 150 minutes later than the time shown in the Afternoon Item.

If the former, we need to see some debug logs from Astro because something has gone wrong. If the latter, you’ve not yet internalized the important distinction between a state channel and event channel.

And, now that OH supports Time is <item> timeOnly triggers, you need not mess with this complexity at all. Replace those Channel triggers with Time is <item> triggers and you can ignore the event channels entirely. You don’t need them.

The reason I posted the above simpler alternative approaches is that you are struggling with things that legacy. There really is no reason for the event channels on the Astro binding to exist any more (though I don’t see them ever being removed). You don’t need them. Using them only adds complexity where it’s no longer needed.

Forget the Channel triggers.

rule "Astro: calculate time of day state"
    when
        // run at system start in case the time changed when OH was offline
        System started or
        Time is Morning
        Time is Night
        Time is Afternoon

Or what ever you’ve named the Item linked to the start channel for the rise, set and astroDusk solar events.

1 Like