Time Based State Machine [3.2.0;3.4.9]

I’m late to the party here, I know.
I’ve been doing a lot of digging around - both on this thread and the one for OH4+, in addition to looking around at the ToToday automation.
I am having a difficult time understanding how the Time and Date Machine processes the objects.

My items file is:

String TimeOfDay "Current Time of Day [MAP(weather.map):%s]" <tod>   // weather.map has mapping from caps to lowercase for times of Day

Group TimesOfDay

DateTime DefaultMorning "Default Morning"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="MORNING" [type="default"] }
DateTime DefaultDay "Default Day"  <sunrise>  (TimesOfDay) { channel="astro:sun:local:rise#end", tod_sm="DAY" [type="default"] }
DateTime DefaultTwilight "Default Twilight"  <sunset>  (TimesOfDay) { channel="astro:sun:local:set#start", tod_sm="TWILIGHT" [type="default"] }
DateTime DefaultEvening "Default Evening"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="EVENING"[type="default"] }
DateTime DefaultNight "Default Night"  <moon>  (TimesOfDay) ["DTUpdate"] { tod_sm="NIGHT"[type="default"] }

DateTime WeekdayMorning "Weekday Morning"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="MORNING" [type="weekday"] }
DateTime WeekdayDay "Weekday Day"  <sunrise>  (TimesOfDay) { channel="astro:sun:local:rise#end", tod_sm="DAY" [type="weekday"] }
DateTime WeekdayTwilight "Weekday Twilight"  <sunset>  (TimesOfDay) { channel="astro:sun:local:set#start", tod_sm="TWILIGHT" [type="weekday"] }
DateTime WeekdayEvening "Weekday Evening"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="EVENING"[type="weekday"] }
DateTime WeekdayNight "Weekday Night"  <moon>  (TimesOfDay) ["DTUpdate"] { tod_sm="NIGHT"[type="weekday"] }

DateTime WeekendMorning "Weekend Morning"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="MORNING" [type="weekend"] }
DateTime WeekendDay "Weekend Day"  <sunrise>  (TimesOfDay) { channel="astro:sun:local:rise#end", tod_sm="DAY" [type="weekend"] }
DateTime WeekendTwilight "Weekend Twilight"  <sunset>  (TimesOfDay) { channel="astro:sun:local:set#start", tod_sm="TWILIGHT" [type="weekend"] }
DateTime WeekendEvening "Weekend Evening"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="EVENING"[type="weekend"] }
DateTime WeekendNight "Weekend Night"  <moon>  (TimesOfDay) ["DTUpdate"] { tod_sm="NIGHT"[type="weekend"] }

DateTime HolidayMorning "Holiday Morning"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="MORNING" [type="holiday"] }
DateTime HolidayDay "Holiday Day"  <sunrise>  (TimesOfDay) { channel="astro:sun:local:rise#end", tod_sm="DAY" [type="holiday"] }
DateTime HolidayTwilight "Holiday Twilight"  <sunset>  (TimesOfDay) { channel="astro:sun:local:set#start", tod_sm="TWILIGHT" [type="holiday"] }
DateTime HolidayEvening "Holiday Evening"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="EVENING"[type="holiday"] }
DateTime HolidayNight "Holiday Night"  <moon>  (TimesOfDay) ["DTUpdate"] { tod_sm="NIGHT"[type="holiday"] }

(the weather.map reference is one that changes lowercase to upper case for the times of day)

I no longer am using the TOD.rules I had in older versions, and have installed the Time Based State Machine for my OH 3.4.2 installation.
I have installed ToToday, for the item tag DTUpdate.
I installed standalone Date and Time Widgets, and have set all the values of the non-astro DateTIme items.

When the day switches over to a weekend, what happens? Does the default item override everything? What happens to the default/weekday, default/weekend, default/holiday, or weekday/holiday conflicts that can occur? (I have my regionalization set up in Ephemeris). Does ToToday blindly move all my holiday start times over to the next non-holiday?
Can I, without further modification, use rules triggered by receiving ‘MORNING’ to do my usual morning things, with the actual time varying by weekday/weekend/holiday parameters?

I haven’t found the answer to this in my reading, although I may have gone cross-eyed a couple times.
Without an exposed dsl rule file that I can tweak, it’s hard to know what happens under the surface.

Thanks
Ben
OH 3.4.2 on a RPi4 running Openhabian

At a high level the rule gets triggered a little after midnight and waits a little because it gets triggered multiple times as To Today and Astro and others change the states of the Items.

When the changes stop the rule runs.

First it looks to see what types of day it is using Ephemeris. I say “types” because it could be more than one (e.g. a holiday and a weekend). Then it looks to see if there is a set of items for that day type and use the first one it finds.

It looks in this order: custom, holiday, custom day type, weekend, weekday, default.

No, it’s what is used if none of the others apply.

If you have a set of items for holiday, weekend and default and not one for weekdays (for example), if today is a holiday no matter what other type of day it is the holiday set will be used. Only if there is no set of items for the type of day is the default set used.

And again, the type of day is determined by Ephemeris.

To Today blindly moves those date time items tagged to today’s date (with some extra stuff to properly handle DST changes). It knows nothing about holidays, weekdays or anything to do with Ephemeris and it doesn’t need to. They are completely separate and irrelevant to To Today. Only those Items that are not already updated daily should be tagged for To Today to process.

Ephemeris is used only by this rule to pick which set of date time items to use for today’s state machine.

That’s the whole point of this rule template. Set a different set of Items for default, weekend, and holiday. If it’s a holiday that set of Items are used, even if it’s also a weekend. If it’s a weekend and not a holiday, the weekend set of Items are used. If it’s neither a holiday nor a weekend the default Items are used.

It’s not hidden. It’s not Rules DSL but all the code is there (in this case it’s Nashorn JS). Click on the Link to the source code at the bottom of the first link and you’ll see the raw voice. Or once you instantiate the rule from the template you can inspect and modify the code like any other rule.

Note that the 4.0 version of the template is rewritten in the newer JS and only looks at the time part of the Items so there is no more need for To Today.

Thank-you for your many detailed clarifications.

The one missing piece for me that I had been oblivious to was that after installing the Time Based State Machine, I had to add the rule template from Rules (First time doing this for me). Once I saw it was an option there, I added it, and it started to update all the DateTIme items as I expected it.
I am not sure how common this sort of oversight is, but it may be of benefit to add the step ‘and then add the rule’ to the instructions. Now that I’ve started to use these pre-made rule templates, I’ll certainly know to do it in the future.

Thanks again.

This is covered in Getting Started: https://www.openhab.org/docs/tutorial/rules_basic.html#rule-templates and somewhat in the Ruels Concetps page: https://www.openhab.org/docs/concepts/rules.html#rule-templates. It doesn’t make sense to duplicate this generic “how to use OH” in each and every rule template posting. That’s a whole lot of duplicated documentation that could be a real problem if the way to install and instantiate a rule from a template ever changes.

Sorry - another question.
I have many of the day state transitions working fine.
Twilight came at sunset (4:18PM), my outdoor lights came on. Then evening (8PM) happened a few hours later and many lights turned on, and night (11PM) happened and the remaining outdoor lights turned off.

  • this was all exactly as intended.

Then:
Just after midnight, it appears my state was changed to twilight (and as such my outdoor lights all came on). This was not intended. After

From event.log, searching for midnight (grep “2023-12-03 00:00:” events.log):

2023-12-03 00:00:00.136 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - MoveToToday updated: RUNNING
2023-12-03 00:00:00.221 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'DefaultNight' changed from 2023-12-02T23:00:00.000-0800 to 2023-12-03T23:00:00.000-0800
2023-12-03 00:00:00.223 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.228 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'HolidayNight' changed from 2023-12-02T23:00:00.000-0800 to 2023-12-03T23:00:00.000-0800
2023-12-03 00:00:00.230 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.233 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekdayNight' changed from 2023-12-02T23:00:00.000-0800 to 2023-12-03T23:00:00.000-0800
2023-12-03 00:00:00.235 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekdayEvening' changed from 2023-12-02T20:00:00.000-0800 to 2023-12-03T20:00:00.000-0800
2023-12-03 00:00:00.239 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.240 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekendMorning' changed from 2023-12-02T09:00:00.000-0800 to 2023-12-03T09:00:00.000-0800
2023-12-03 00:00:00.242 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekendEvening' changed from 2023-12-02T20:00:00.000-0800 to 2023-12-03T20:00:00.000-0800
2023-12-03 00:00:00.244 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.245 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.248 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.249 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.250 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.252 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.253 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekdayMorning' changed from 2023-12-02T06:55:00.000-0800 to 2023-12-03T06:55:00.000-0800
2023-12-03 00:00:00.255 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'HolidayEvening' changed from 2023-12-02T20:00:00.000-0800 to 2023-12-03T20:00:00.000-0800
2023-12-03 00:00:00.256 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'DefaultEvening' changed from 2023-12-02T20:00:00.000-0800 to 2023-12-03T20:00:00.000-0800
2023-12-03 00:00:00.258 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - MoveToToday updated: IDLE
2023-12-03 00:00:00.260 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekendNight' changed from 2023-12-02T23:00:00.000-0800 to 2023-12-03T23:00:00.000-0800
2023-12-03 00:00:00.262 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'DefaultMorning' changed from 2023-12-02T07:00:00.000-0800 to 2023-12-03T07:00:00.000-0800
2023-12-03 00:00:00.264 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'HolidayMorning' changed from 2023-12-02T09:00:00.000-0800 to 2023-12-03T09:00:00.000-0800
2023-12-03 00:00:00.265 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.267 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.268 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.269 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.271 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.272 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.273 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.275 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.276 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.278 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.279 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.280 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.281 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:00.282 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:00.283 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE

2023-12-03 00:00:28.201 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'TimeOfDay' received command TWILIGHT
2023-12-03 00:00:28.203 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'TimeOfDay' changed from NIGHT to TWILIGHT
2023-12-03 00:00:28.206 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - lights-15 updated: RUNNING
2023-12-03 00:00:28.218 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'FF_FrontPorch_Light' received command ON
2023-12-03 00:00:28.227 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'CH_Outdoor_Yard_Light' received command ON
2023-12-03 00:00:28.230 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'FF_FrontPorch_Light' predicted to become ON
2023-12-03 00:00:28.246 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'FF_Front_Porch_Accessory_Outlet' received command ON
2023-12-03 00:00:28.248 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - lights-15 updated: IDLE
2023-12-03 00:00:28.249 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'CH_Outdoor_Yard_Light' predicted to become ON
2023-12-03 00:00:28.256 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'FF_FrontPorch_Light' changed from OFF to ON
2023-12-03 00:00:28.257 [INFO ] [hab.event.GroupItemStateChangedEvent] - Item 'Outdoor_Lights' changed from OFF to ON through FF_FrontPorch_Light
2023-12-03 00:00:28.258 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'FF_Front_Porch_Accessory_Outlet' predicted to become ON
2023-12-03 00:00:28.264 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'CH_Outdoor_Yard_Light' changed from OFF to ON
2023-12-03 00:00:28.265 [INFO ] [hab.event.GroupItemStateChangedEvent] - Item 'CH_Outdoor_Lights' changed from OFF to ON through CH_Outdoor_Yard_Light
2023-12-03 00:00:28.266 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'FF_Front_Porch_Accessory_Outlet' changed from OFF to ON
2023-12-03 00:00:30.485 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'DefaultDay' changed from 2023-12-02T07:58:00.000-0800 to 2023-12-03T07:55:00.000-0800
2023-12-03 00:00:30.490 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekdayDay' changed from 2023-12-02T07:58:00.000-0800 to 2023-12-03T07:55:00.000-0800
2023-12-03 00:00:30.498 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.499 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekendDay' changed from 2023-12-02T07:58:00.000-0800 to 2023-12-03T07:55:00.000-0800
2023-12-03 00:00:30.502 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'HolidayDay' changed from 2023-12-02T07:58:00.000-0800 to 2023-12-03T07:55:00.000-0800
2023-12-03 00:00:30.506 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'DefaultDay' changed from 2023-12-03T07:55:00.000-0800 to 2023-12-03T07:59:00.000-0800
2023-12-03 00:00:30.507 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekdayDay' changed from 2023-12-03T07:55:00.000-0800 to 2023-12-03T07:59:00.000-0800
2023-12-03 00:00:30.509 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekendDay' changed from 2023-12-03T07:55:00.000-0800 to 2023-12-03T07:59:00.000-0800
2023-12-03 00:00:30.511 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'HolidayDay' changed from 2023-12-03T07:55:00.000-0800 to 2023-12-03T07:59:00.000-0800
2023-12-03 00:00:30.514 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'DefaultTwilight' changed from 2023-12-02T16:19:00.000-0800 to 2023-12-03T16:18:00.000-0800
2023-12-03 00:00:30.515 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekdayTwilight' changed from 2023-12-02T16:19:00.000-0800 to 2023-12-03T16:18:00.000-0800
2023-12-03 00:00:30.518 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'WeekendTwilight' changed from 2023-12-02T16:19:00.000-0800 to 2023-12-03T16:18:00.000-0800
2023-12-03 00:00:30.519 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'HolidayTwilight' changed from 2023-12-02T16:19:00.000-0800 to 2023-12-03T16:18:00.000-0800
2023-12-03 00:00:30.537 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.539 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.543 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.545 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.549 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.550 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.553 [INFO ] [openhab.event.ChannelTriggeredEvent ] - astro:sun:local:morningNight#event triggered START
2023-12-03 00:00:30.556 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.558 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.562 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.564 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.567 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.568 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.569 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.571 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.572 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.574 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.575 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.577 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.578 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.580 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.581 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE
2023-12-03 00:00:30.582 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: RUNNING
2023-12-03 00:00:30.584 [INFO ] [openhab.event.RuleStatusInfoEvent   ] - TODStateMachine updated: IDLE

rule in question (light-15):

rule "Porch light on at dusk"
when
  Item TimeOfDay changed to "TWILIGHT"
then
if (FrontPorchSuppressLights.state == OFF) {
  FF_FrontPorch_Light.sendCommand(ON)
  CH_Outdoor_Yard_Light.sendCommand(ON) // CH parking lights
  logInfo("lights.rules", "TWILIGHT started, entry lights turned on")
}
else {
  logInfo("lights.rules", "Was going to turn porch lights on due to dusk, but already too late or was suppressed")
}
  FF_Front_Porch_Accessory_Outlet.sendCommand(ON) 

end

relevant TOD items:

DateTime WeekendMorning "Weekend Morning"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="MORNING" [type="weekend"] }
DateTime WeekendDay "Weekend Day"  <sunrise>  (TimesOfDay) { channel="astro:sun:local:rise#end", tod_sm="DAY" [type="weekend"] }
DateTime WeekendTwilight "Weekend Twilight"  <sunset>  (TimesOfDay) { channel="astro:sun:local:set#start", tod_sm="TWILIGHT" [type="weekend"] }
DateTime WeekendEvening "Weekend Evening"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="EVENING"[type="weekend"] }
DateTime WeekendNight "Weekend Night"  <moon>  (TimesOfDay) ["DTUpdate"] { tod_sm="NIGHT"[type="weekend"] }

My (unmodified/noncustomized from template implementation) TOD State Machine rule code:
configuration:

  namespace: tod_sm
  timesOfDayGrp: TimesOfDay
  timeOfDay: TimeOfDay
triggers:
  - id: "1"
    configuration:
      groupName: TimesOfDay
    type: core.GroupStateChangeTrigger
  - id: "2"
    configuration:
      startlevel: 20
    type: core.SystemStartlevelTrigger
conditions: []
actions:
  - inputs: {}
    id: "3"
    label: Determine current time of day
    configuration:
      type: application/javascript
      script: >
        // Imports 

        if(typeof(require) === "function") Object.assign(this, require('@runtime'));

        var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.rules_tools.Time_SM"); 

        this.Ephemeris = (this.Ephemeris === undefined) ? Java.type("org.openhab.core.model.script.actions.Ephemeris") : this.Ephemeris; 

        this.ZonedDateTime = (this.ZonedDateTime === undefined) ? Java.type("java.time.ZonedDateTime") : this.ZonedDateTime; 


        //   Get Metadata query stuff 

        this.FrameworkUtil = (this.FrameworkUtil === undefined) ? Java.type("org.osgi.framework.FrameworkUtil") : this.FrameworkUtil; 

        this.ScriptHandler = Java.type("org.openhab.core.automation.module.script.rulesupport.shared.ScriptedHandler");

        this._bundle = (this._bundle === undefined) ? FrameworkUtil.getBundle(ScriptHandler.class) : this._bundle;

        this.bundle_context = (this.bundle_context === undefined) ? this._bundle.getBundleContext() : this.bundle_context; 

        this.MetadataRegistry_Ref = (this.MetadataRegistry_Ref === undefined) ? bundle_context.getServiceReference("org.openhab.core.items.MetadataRegistry") : this.MetadataRegistry_Ref; 

        this.MetadataRegistry = (this.MetadataRegistry === undefined) ? bundle_context.getService(MetadataRegistry_Ref) : this.MetadataRegistry; 

        this.Metadata = (this.Metadata === undefined) ? Java.type("org.openhab.core.items.Metadata") : this.Metadata; 

        this.MetadataKey = (this.MetadataKey === undefined) ? Java.type("org.openhab.core.items.MetadataKey") : this.MetadataKey; 


        // Constants 

        var ETOD_ITEM = "TimeOfDay"; 

        var ETOD_GROUP = "TimesOfDay"; 

        var DAY_TYPES = ["default", "weekday", "weekend", "dayset", "holiday", "custom"]; 

        var EXPECTED = "Invalid metadata for Item! "
                     + "Expected metadata in the form of tod_sm=\"STATE\"[type=\"daytype\", set=\"dayset\", file=\"uri\"] "
                     + "where set is required if type is dayset and file is required if type is custom.";
        var ETOD_NAMESPACE = "tod_sm"; 


        // TODO Load timer manager from a library

        // Load TimerMgr 

        //this.OPENHAB_CONF = (this.OPENHAB_CONF === undefined) ? java.lang.System.getenv("OPENHAB_CONF") : this.OPENHAB_CONF; 

        //load(OPENHAB_CONF+'/automation/lib/javascript/community/timerMgr.js'); 

        //load(OPENHAB_CONF+'/automation/lib/javascript/community/timeUtils.js'); 

        var TimerMgr = function() {
          var OPENHAB_CONF = java.lang.System.getenv("OPENHAB_CONF");
          this.log = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.rules_tools.rules_tools.TimerMgr");
          this.log.debug("Building timerMgr instance.");
          this.timers = {};
        //  this.log.debug("Loading timeUtils");

        //  load(OPENHAB_CONF+'/automation/lib/javascript/community/timeUtils.js');
          this.ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
          this.log.debug("Timer Mgr is ready to operate");
        }


        TimerMgr.prototype._notFlapping = function(key) {
          this.log.debug("Timer expired for " + key);
          if (key in this.timers && "notFlapping" in this.timers[key]) {
            this.log.debug("Calling expired function " + this.timers[key]["notFlapping"]);
            this.timers[key]["notFlapping"]();
          }
          if (key in this.timers){
            this.log.debug("Deleting the expired timer");
            delete this.timers[key];
          }
        },


        TimerMgr.prototype._noop = function() { },


        TimerMgr.prototype.check = function(key, when, func, reschedule, flappingFunc) {
          this.log.debug("Timer manager check called");
          if (reschedule === undefined) reschedule = false;

          // var timeout = this.toDateTime(when); We will assume it's already a ZonedDateTime
          var timeout = when;
          this.log.debug("Timer to be set for " + timeout.toString());

          // Timer exists
          if (key in this.timers){
            if (reschedule){
              this.log.debug("Rescheduling timer " + key + " for  " + timeout.toString());
              this.timers[key]["timer"].reschedule(timeout);
            }
            else {
              this.log.debug("Cancelling timer " + key);
              this.cancel(key);
            }
            if (flappingFunc !== undefined){
              this.log.debug("Running flapping function for " + key);
              flappingFunc();
            }
          }
          
          // Timer doesn't already exist, create one
          else {
            this.log.debug("Creating timer for " + key);
            var timer = this.ScriptExecution.createTimerWithArgument(timeout, this, function(context) { context._notFlapping(key); });
            this.timers[key] = { "timer": timer,
                                 "flapping": flappingFunc,
                                 "notFlapping": (func !== undefined) ? func : this._noop }
            this.log.debug("Timer created for " + key);
          }
        },


        TimerMgr.prototype.hasTimer = function(key) {
          return key in this.timers;
        },


        TimerMgr.prototype.cancel = function(key) {
          if (key in this.timers) {
            this.timers[key]["timer"].cancel();
            delete this.timers[key];
          }
        },


        TimerMgr.prototype.cancelAll = function() {
          for (var key in this.timers) {
            if (!this.timers[key]["timer"].hasTerminated() && !this.timers[key]["timer"].isRunning()) {
              this.log.debug("Timer has not terminated, cancelling timer " + key);
              this.cancel(key);
            }
            delete this.timers[key];
            this.log.debug("Timer entry has been deleted for " + key);
          }
        }

        // END TODO


        /**
         * Return the value or a key value from the Item's metadata
         * @param {string} item name of the item
         * @param {string} namespace metadata namespace to pull
         * @param {string} key index into the configuration dict for the value
         * @return {string} value assocaited with key or null if it doesn't exist.
         */
        var getValue = function(item, namespace, key) {
          var md = MetadataRegistry.get(new MetadataKey(namespace, item));
          if(md === null || md === undefined) {
            return null;
          }
          else if(key === undefined) {
            return md.value;
          }
          else {
            return md.configuration[key];
          }
        } 


        /**
         * Verify Item and Item metadata
         * @param {string} item name of the Item
         * return {string} error string or null if the metadata checks out
         */
        var verifyMetadata = function(item) {

        //  if(items[item].class == UnDefType.class) {

        //    return item +"'s state is " + items[items];

        //  }
          if(getValue(item, ETOD_NAMESPACE) === null) {
            return item + " lacks metadata or metadata value.";
          }
          var type = getValue(item, ETOD_NAMESPACE, "type");
          if(type === null) {
            return item + " lacks a type key."
          }
          
          if(DAY_TYPES.indexOf(type) < 0) {
            return item + " has " + type + " which is not a valid day type, expected one of " + DAY_TYPES + ".";
          }
          
          if(type == "dayset" && getValue(item, ETOD_NAMESPACE, "set") === null) {
            return item + " has type " + type + " which requires a 'set' value to be defined.";
          }
          
          if(type == "custom" && getValue(item, ETOD_NAMESPACE, "file") === null ) {
            return item + " has type " + type + " which requires a 'file' value to be defined.";
          }
          
          return null;
        } 


        /**
         * Get a list of all the Items that have ephem metadata with type
         * @param {java.util.List} etodItems collection of all the ETOD Items
         * @param {string} type the type of day 
         * @return {java.util.List} those Items with a type metadata matching type
         */
        var getType = function(etodItems, type){
          return etodItems.stream()
                          .filter(function(item){ 
                              return getValue(item.name, ETOD_NAMESPACE, "type") == type;
                           })
                          .toArray();
        } 


        /**
         * Pull the set of Items for today based on Ephemeris
         * @param {java.util.List} etodItems collection of all ETOD Items
         * @return {java.util.List} only those Items defined for today's daytype
         */
        var getTodayItems = function(etodItems) {
          /** 
          Get the Items for today. Hierarchy is:
            - custom
            - holiday
            - dayset
            - weekend
            - weekday
            - default
          */
          var startTimes = {"default": getType(etodItems, "default"),
                            "weekday": (!Ephemeris.isWeekend()) ? getType(etodItems, "weekday") : [],
                            "weekend": (Ephemeris.isWeekend()) ? getType(etodItems, "weekend") : [],
                            "dayset": etodItems.stream()
                                               .filter(function(item) {
                                                  return getValue(item.name, ETOD_NAMESPACE, "type") == "dayset"
                                                         && Ephemeris.isInDayset(getValue(item.name, ETOD_NAMESPACE, "set"));
                                               })
                                               .toArray(),
                            "holiday": (Ephemeris.isBankHoliday()) ? getType(etodItems, "holiday") : [],
                            "custom": etodItems.stream()
                                               .filter(function(item) {
                                                  return getValue(item.name, ETOD_NAMESPACE, "type") == "custom"
                                                         && Ephemeris.isBankHoliday(0, getValue(item.name, ETOD_NAMESPACE, "file"));
                                               })
                                               .toArray()
                           };
          var dayType = null;
          if(startTimes["custom"].length > 0) {
            dayType = "custom";
          }
          else if(startTimes["holiday"].length > 0) {
            dayType = "holiday";
          }
          else if(startTimes["dayset"].length > 0) {
            dayType = "dayset";
          }
          else if(startTimes["weekend"].length > 0) {
            dayType = "weekend";
          }
          else if(startTimes["weekday"].length > 0) {
            dayType = "weekday";
          }
          else if(startTimes["default"].length > 0) {
            dayType = "default";
          }
          logger.info("Today is a " + dayType + " day.");
          return (dayType === null) ? null : startTimes[dayType];

        } 


        /**
         * Update Items to today
         * @param {java.util.List} times list of all the ETOD Items for today
         */
        /** Use To Today rule template

        var moveTimes = function(times) {
          var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 
          var now = ZonedDateTime.now();
          for each(var time in times) {
            if(time.state.zonedDateTime.isBefore(now.withHour(0).withMinute(0).withSecond(0))) {
              events.postUpdate(time.name, toToday(items[time.name]).toString());
              logger.info("Moved " + time.name + " to today.");
            }
          }
        } */


        /**
         * Create timers for all Items with a time in the future
         * @param {java.util.List} times list of all the ETOD Items for todayu
         */
        var createTimersGenerator = function(times, timers) {
          return function() {
            var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 
            var now = ZonedDateTime.now();
            var mostRecentTime = now.minusDays(1);
            var mostRecentState = items[ETOD_ITEM];
            logger.debug("Cancelling any existing timers");
            timers.cancelAll();
            logger.debug("Existing timers have been cancelled");
            for each (var time in times) {
              var name = time.name;
              if(time.state.class === UnDefType.class) {
                logger.warn("Time State Machine Item " + name + " is NULL or UNDEF, ignoring");
              }
              else {
                var dt = time.state.zonedDateTime
                var state = getValue(name, ETOD_NAMESPACE);
                if(dt.isBefore(now) && dt.isAfter(mostRecentTime)) {
                  logger.debug("NOW:    " + state + " start time " + dt + " is in the past " 
                               + " after " + mostRecentTime);
                  mostRecentTime = dt;
                  mostRecentState = state;
                }
                else if(dt.isAfter(now)) {
                  logger.debug("FUTURE: " + state + " scheduleing timer for " + dt);
                  timers.check(state, dt, etodTransitionGenerator(state));
                }
                else {
                  logger.debug("PAST  : " + state + " start time of " + dt + " is before " 
                               + now + " and before " + mostRecentState + " " + mostRecentTime);
                }
              }
            }
            logger.debug("Created " + (Object.keys(timers.timers).length - 1) + " time of day timers");
            logger.info("The current time of day is " + mostRecentState);
            if(items[ETOD_ITEM] != mostRecentState) {
              events.sendCommand(ETOD_ITEM, mostRecentState);
            }
          }
        } 


        /**
         * Transition to a new Time of Day
         * @TODO look into moving this to another rule we can call so it shows up in schedule
         * @param {string} state the new time of day state
         */
        var etodTransitionGenerator = function(state) {
          return function() {
            logger.info("Transitioning Time of Day from " + items[ETOD_ITEM] + " to " + state);
            events.sendCommand(ETOD_ITEM, state);
          }
        } 


        //-------------------------------------------- 

        // Main body of rule 

        if(this.timers === undefined){
          logger.debug("Creating timer manager");
        } 

        this.timers = (this.timers === undefined) ? new TimerMgr() : this.timers; 


        // Skip if we have a flapping timer set 

        if(!this.timers.hasTimer("ephem_tod_rule")) {

          // Check that all the required Items and Groups exist
          if(items[ETOD_ITEM] === undefined) {
            throw "The " + ETOD_ITEM + " Item is not defined!";
          }
          if(items[ETOD_GROUP] === undefined) {
            throw "The " + ETOD_GROUP + " Group is not defined!";
          }
          var etodItems = ir.getItem(ETOD_GROUP).getMembers();
          
          if(etodItems.size() == 0) {
            throw ETOD_GROUP + " has no members!";
          }

          // Check the metadata for all the relevant Items
          for each (var item in etodItems) {
            var verify = verifyMetadata(item.name);
            if(verify !== null) {
              throw verify + "\n" + EXPECTED;
            }
          }

          // Get the time Items for today
          var times = getTodayItems(etodItems);
          if(times === null){
            throw "No set of date times were found for today! Do you have a default set of date times?";
          }
          
          // Update the Items to today
          //moveTimes(times);

          // The times will often be updated all at once, schedule a timer to wait for all the Items to update before creating the Timers.
          var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 
          this.timers.check("ephem_tod_rule", 
                            ZonedDateTime.now().plusSeconds(60), 
                            createTimersGenerator(times, this.timers), 
                            true, 
                            function() { logger.info("Flapping timer, waiting before creating timers for time of day"); });
        }
    type: script.ScriptAction

Searching my log for all the TimeofDay updates:
grep “Item ‘TimeOfDay’ received command” events.log

2023-12-02 16:50:31.361 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'TimeOfDay' received command TWILIGHT
2023-12-02 20:00:00.043 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'TimeOfDay' received command EVENING
2023-12-02 23:00:00.018 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'TimeOfDay' received command NIGHT
2023-12-03 00:00:28.201 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'TimeOfDay' received command TWILIGHT
2023-12-03 07:59:00.007 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'TimeOfDay' received command DAY
2023-12-03 09:00:00.009 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'TimeOfDay' received command MORNING

It looks like it triggers Twilight, Evening, and Night as expected.
It then triggered twilight for some reason at midnight…
and then went on to trigger day (sunrise) and morning (late start as weekend) as expected.

A quick few searches of the forum do not reveal similar problems with others.
Is there something I need to do to look into this further?

Thanks again!

Most likely whatever updates the Twilight date time Item for today’s day type happened after the time of day rule ran. Because of that, Twilight still had the previous date and therefore it was before all the other DateTimes for today when the rule ran.

Astro doesn’t update Items until 30 seconds after midnight but you’ve configured To Today to run exactly at midnight. So Time of Day runs once at midnight and Twilight is still yesterday, therefore is the earliest DateTime and the rule sends TWILIGHT to the TimeOfDay Item. Then 30 seconds later it runs when Astro updates it’s Items,

Change the To Today rule to be at 30 seconds after midnight so there isn’t that much time between them.

Timing issues like these are one reason why the 4.x version of the rule ignores the date and only looks at the time.

Perfect, thanks!
I hadn’t recognized the rule template had filled in the time at midnight (still getting used to using templates). I’ve adjusted it now.
Ben

Sorry, another question here about ZonedDateTime comparison.
I have a morning rule that turns on the outdoor lights when ‘Morning’ arrives (currently 6:55) AM, but I want it to only turn on if it is still before sunrise (defined as Day, set from astro)

My rule doesn’t seem to run past my if-statement, and I have tried adding .state to it as well, resulting in an error that state is not a member of java.time.ZonedDateTime. I again have the feeling like I’m missing something basic. Is a DSL Rule using a java time implementation of now, that I am trying to compare to a DateTime variable? Is there a better way of going about this? It seems that most of the examples I can find online are either comparing/parsing strings and comparing to OH3x, using non DSL rules, or using older (still) versions of openhab.

I am using Openhab 3.4 (plans to upgrade when I get a chunk of free time to fix the problems I will inevitably create).

My relevant items:

DateTime DefaultMorning "Default Morning"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="MORNING" [type="default"] }
DateTime DefaultDay "Default Day"  <sunrise>  (TimesOfDay) { channel="astro:sun:local:rise#end", tod_sm="DAY" [type="default"] }
DateTime DefaultTwilight "Default Twilight"  <sunset>  (TimesOfDay) { channel="astro:sun:local:set#start", tod_sm="TWILIGHT" [type="default"] }
DateTime DefaultEvening "Default Evening"  <time>  (TimesOfDay) ["DTUpdate"] { tod_sm="EVENING"[type="default"] }
DateTime DefaultNight "Default Night"  <moon>  (TimesOfDay) ["DTUpdate"] { tod_sm="NIGHT"[type="default"] }

My relevant rule:

rule "Front entry lights on at morning"
when
  Item TimeOfDay changed to "MORNING"
then
// if it before sunrise, then turn on the porch lights, etc
logInfo("lights.rules", "Morning porch lights rule now starting")
  var CurrentDT = now
    logInfo("lights.rules", "Morning rule data: Now is " + CurrentDT.state + ", and DefaultDay is " + DefaultDay.state)
  
  if (CurrentDT.isBefore(DefaultDay)) {  
    logInfo("lights.rules", "Current time is before sunrise")
    FF_FrontPorch_Light.sendCommand(ON)  
	FF_Front_Porch_Accessory_Outlet.sendCommand(ON)
	twinklyTree.sendCommand(ON)
  }
  else {
    logInfo("lights.rules", "Current time is after sunrise")
  }
  
end

The error in openhab.log: (using the current x.state log line 206 )

openhabian@openhab:/var/log/openhab $ grep "2023-12-15 06:55:00" openhab.log
2023-12-15 06:55:00.002 [INFO ] [del.script.Rules.rules_tools.Time_SM] - Transitioning Time of Day from NIGHT to MORNING
2023-12-15 06:55:00.014 [INFO ] [enhab.core.model.script.lights.rules] - Morning porch lights rule now starting
2023-12-15 06:55:00.020 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'lights-13' failed: 'state' is not a member of 'java.time.ZonedDateTime'; line 206, column 58, length 15 in lights
2023-12-15 06:55:00.177 [INFO ] [enhab.core.model.script.lights.rules] - Weekday morning, boys bedroom lights and downstairs centgral lights turned on
2023-12-15 06:55:00.880 [INFO ] [enhab.core.model.script.lights.rules] - 1

Of note, the line 206 referenced is the logInfo line attempting to log the date and time. This rule wasn’t running past the if statement long prior to me entering all the logInfo lines to try and chase it down.

I saw javascript examples for now.toLocalDateTime(), but can’t see any DSL examples of this - would this work? can I set it as
CurrentDT = now.toLocalDateTIme() rather than CurrentDT = now?
Is there a better implementation for my circumstance? Is there something else I am missing?
It has been a slow debugging process, as this rule only runs once per day (and I only get free time every other day or so)

Thanks for your directions/corrections/suggestions!

Ben

Openhab 3.4.2, on Openhabian RPi4

.state before what?

CurrentDT is indeed a ZonedDateTime and it has no .state.

DefaultDay is a DateTimeItem and that is not a ZonedDateTime and cannot be used in the call to isBefore(). DefaultDay does have a .state but that’s a DateTimeType, not a ZonedDateTime so you still cannot use that in the call to isBefore(). You need to get the ZonedDateTime from that.

(DefaultDay.state as DateTimeType).zonedDateTime

See DateTime Conversion (openHAB 3.x) (
note that article remains unchanged for OH 4).

All of this works very differently in JS. There is a utility called time.toZDT() which handles converting just about anything that makes sense into a ZonedDateTime.

But no, using now.toLocalDateTime() isn’t going to change anything because a LocalDateTime doesn’t have a .state either.

Yes, see above and the link above.

@rlkoshak
is there somewhere documented, what are mandatory prerequisists? I don’t mean the creation of items and groups, more the installation of other automation templates and so on. Because I’m starting from a completly blank installation, without any running rule and no JS installation or else.

If you are in fact running OH 3.x then there are no requisites for this rule template.

If you are running OH 4.x+, you are on the wrong posting. See Time Based State Machine [4.0.0.0;4.9.9.9] which has a section that discusses the prequisits. At a high level, the JS Scripting add-on, a certain version of the helper library (which comes with the add-on so you don’t have to worry about it unless you’ve installed the openhab-js manually), and a certain version or above for openhab-rules-tools which can be installed through openhabian-config or by running npm manually.

Ok, I really did not recognize the version in the title. Thank you, now it works for me