Design Pattern implementation: Javascript Time Of Day

Hi All, sharing a simpler but less powerful or less complete, approach in JavaScript to the excellent Time of Day design pattern found at https://community.openhab.org/t/design-pattern-time-of-day/15407

Why I took this approach:

  • I wanted a table-like-looking structure to hold my times, easy to find and quick to read or edit.
  • I wanted to use Javascript, not Rules DSL.
  • I didn’t need Astro bindings for sunrise, sunset and similar. I suppose I can add it in my code if I later have a use for them.

To implement I have just one vTimeOfDay Item, which holds a String, and a “Set vTimeOfDay” ECMAScript rule, with these triggers:

triggers:
  - id: "1"
    configuration:
      startlevel: 50
    type: core.SystemStartlevelTrigger
  - id: "2"
    configuration:
      cronExpression: 0 * * * * ? *
    type: timer.GenericCronTrigger

The Script is:

var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);

var TimesOfDay = {
  week : [
    // Special short periods come first
    [ 'MIDNIGHT_COOL',  '00:05', '00:06'],
    [ 'SCHOOL_SOON',    '07:30', '07:31'],
    [ 'SCHOOL_NOW',     '07:42', '07:43'],
    [ 'BEDTIME_EDDIE',  '20:30', '20:35'],
    [ 'BEDTIME_JAMIE',  '21:30', '21:35'],

    // Regular periods come after
    [ 'EARLY',          '00:00', '07:05'],
    [ 'MORNING',        '07:05', '12:00'],
    [ 'AFTERNOON',      '12:00', '19:00'],
    [ 'NIGHT',          '19:00', '21:15'],
    [ 'LATE',           '21:15', '23:00'],
    [ 'TOO_LATE',       '23:00', '24:00']
  ],
  saturday : [
    // Special short periods come first
    [ 'MIDNIGHT_COOL',  '00:05', '00:06'],
    [ 'BEDTIME_EDDIE',  '21:30', '21:35'],
    [ 'BEDTIME_JAMIE',  '22:00', '22:05'],

    // Regular periods come after
    [ 'EARLY',          '00:00', '07:05'],
    [ 'MORNING',        '07:05', '12:00'],
    [ 'AFTERNOON',      '12:00', '19:00'],
    [ 'NIGHT',          '19:00', '21:15'],
    [ 'LATE',           '21:15', '23:00'],
    [ 'TOO_LATE',       '23:00', '24:00']
  ],
  sunday: [
    // Special short periods come first
    [ 'MIDNIGHT_COOL',  '00:05', '00:06'],
    [ 'BEDTIME_EDDIE',  '20:30', '20:35'],
    [ 'BEDTIME_JAMIE',  '21:30', '21:35'],

    // Regular periods come after
    [ 'EARLY',          '00:00', '07:05'],
    [ 'MORNING',        '07:05', '12:00'],
    [ 'AFTERNOON',      '12:00', '19:00'],
    [ 'NIGHT',          '19:00', '21:15'],
    [ 'LATE',           '21:15', '23:00'],
    [ 'TOO_LATE',       '23:00', '24:00']
  ]
};


// Determine the Minute Of Day for now & weekday
var now = new Date();
var nowMOD = now.getHours()*60 + now.getMinutes();
var weekday = now.getDay();
var table;

// Define which table to use based on weekday
switch (weekday) {
  case 0:
    table = TimesOfDay.sunday;
    break;
  case 6:
    table = TimesOfDay.saturday;
    break;
  default:
    table = TimesOfDay.week;
}


// Get the current TimeOfDay from Item
var prev = itemRegistry.getItem('vTimeOfDay').getState();
var curr = "UNK"

// Check what should the TimeOfDay be now
var p, pStart, pFinish;

for (p=0; p<table.length; p++) {
  pStart  = minOfDay(table[p][1]);
  pFinish = minOfDay(table[p][2]);
  if ((nowMOD >= pStart) && (nowMOD < pFinish)) {
  	curr=table[p][0];
    break;
  }
}

// If TimeOfDay has just changed this minute
// update vTimeOfDay Item and log it
if (curr != prev) {
  events.sendCommand('vTimeOfDay', curr);
  logger.info('vTimeOfDay: ' + curr);
}

// Function to get the number of minutes since 
// start of day from a String in HH:MM format
function minOfDay(s) {
  var hour=parseInt(s.slice(0,2));
  var minute=parseInt(s.slice(3,5));
  return hour*60 + minute; 
}

As can be seen I have three tables to define the TimesOfDay, one for the work week (M-F), one for Sat and one for Sun. I could have one per day of the week, but right now I don’t need that.

This rule does not care for changes in Daylight Savings Time, so if that is important for you then you would need to add some logic for it. It doesn’t matter for me.

I keep this script that determines vTimeOfDay in OpenHAB rules, but I do almost all of my rules in NodeRED, where I watch for vTimeOfDay changes.

In NodeRED a sample flow looks like this:
image
Where the change node does msg.timeOfDay = msg.payload, and the switch node is obviously a switch based on msg.timeOfDay.

Why I didn’t do it all in NodeRED? Because I think the table is quicker to see/edit in OpenHAB, but this code could be in there as well in a subflow.

Hope this helps anyone wanting to use Javascript to implement the same Design Pattern, or use it with NodeRED.

2 Likes

Just a note, there is a JavaScript version of the more complicated Time of Day design pattern posted in the original DP as well. Your post leaves it a little ambiguous as to whether that’s the case or not. That version uses Ephemeris to determine what type of day it is (so one can have a different schedule for weekends and holidays and workdays) and is driven by Items.

Thanks for posting this! The whole purpose of a Design Pattern is to be an approach to solving a given problem. It’s not intended to be a complete solution. It is expected to be customized in ways similar to this.

Just a few notes on your implementation (these are just comments exploring alternatives, not suggestions for making changes to the original code):

  • Instead of triggering once a minute, which isn’t necessarily a problem but it is usually considered an anti-pattern (a design pattern’s evil cousin), one could trigger the rule at midnight and at openHAB startup and create Timers to go off at the specified times. This avoids the polling and if one uses GitHub - rkoshak/openhab-rules-tools: Library functions, classes, and examples to reuse in the development of new Rules. Timer Manager you don’t really even need to deal with keeping up with the Timers.

  • One could put the tables into it’s own file under $OH_CONF/automation/lib/javascript/personal and import it into the rule. For some storing it like that might be more convenient.

  • You can replace the calculation to figure out if it’s a weekend or weekday with Ephemeris. I think by default it is already set up to have Sat and Sun be weekends so you can replace that switch statement with Ephemeris.isWeekend(). But since you have a different set of times for Saturday and Sunday, you would want to define two new daysets, one containing just Saturday and the other just Sunday and then use Ephemeris.isInDayset("saturday") and Ephemeris.isInDayset("sunday").

  • The above could be made even simpler if it were split up into a Script rule and three separate UI rules. The approach would be as follows. Create a rule for “week” that is triggered once per minute as now. Only put in a Condition to only run the rule on weekdays. You can use Ephemeris or a Time Condition. Then you don’t need a complicated structure to store your tables. You only need to put in the “week” tables. And you can skip all the code that determines the day of the week because you already took care of that. To avoid duplicating code, the part of the rule that calculates the current time of day can be put into a Script Rule which get’s called from this “week” rule, or it can be put into a library function imported into this rule. I can post examples if requested. One advantage of this is a lot of the code in this already simple rule goes away.

  • This one might be something you’ll want to address someday. For now you have a dead zone around midnight. You can’t really define a time of day that spans across midnight. You’ve handled that by creating separate EARLY and TOO_LATE time periods. But with a little but of finesse one could update this to handle time periods that span midnight (e.g. from 23:00-06:00).

  • I find it useful to include the old time of day and the new time of day when I log it out during a transition. logger.info("vTimeOfDay transitioning from " + prev + " to " + curr);

Sorry for that, I hadn’t noticed there was a Javascript version of the original DP concept.

I get your point there. For something of low precision (changes at max once per minute, don’t have to happen exactly at the right second) and very fast code execution like this small script, I prefer not to mess with Timers and just use trusty cron/quartz.

This polling pattern is something I’ve got used to using in some Arduino coding in the past. I wrote an Arduino+RTC aquarium LED controller code which simulated sunrise/sunset and clouds passing, where that was an important part of making it work. I miss that fish tank…

Yes, I thought of that file option but dropped it because I wanted to edit from the browser, not have to ssh into the server. I realize as I write this that I could likely edit the file on PC through Samba, but I’m pretty satisfied editing the tables in OpenHAB UI.

Thanks for the note on Ephemeris! I hadn’t discovered it yet, read the docs now. Looks attractive when I decide to do something special or different for holidays. Wouldn’t bother using it for day of the week since the switch statement is small, but definitely seems like a nice option for holidays or similar.

I actually wanted the three tables in 1 place where I could quickly glance at them, compare a day against the other, and so on. I don’t mind the object structure for the tables, it is human readable enough. I also wanted the script to be 1 script, one place to page down/up with everything.

True, however, as I’m thinking over that now I conclude that the DP makes that unnecessary. Why: since the DP’s theory of operation is to trigger or start/stop things when vTimeOfDay changes to a certain state, something that starts before midnight i.e. 23:00 and ends after midnight i.e. 06:00 could be triggered by a vTimeOfDay that changes at 23:00 then stopped by a vTimeOfDay that changes at 06:00, and ignore all the intermediate vTimeOfDay’s between. It is one of the things I found beautiful in your DP in fact, that simplicity and flexibility in the theory of operation.

I like that, updating rule now!

It makes a lot of sense on a microcontroller because of that loop function. But OH is an event driven system. It works best when events occur and OH detects them rather than busy wait polling looking to see if something needs to be done, most of the time doing nothing.

One advantage though is you can change the days of the week through the Ephemeris config and not have to modify the code rule.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.