'Semi-smart' alarm clock

Doesn’t sort work with 1 and -1? It appears to fail when I use Booleans.

Aha, interesting!

Idem

I don’t quite understand how to combine this with e.g. time.toZDT("08:00")

I’m sure it’s got value, but I don’t know how this works, and I’ve got a feeling it’s not ideal for my scenario, where not all days require the same alarm clock.

I tried implementing this, but I don’t really understand the syntax. Trying snippets of it in a scratchpad threw errors/exceptions. So for now, I’m sticking with the syntax I understand.

I was actually too tunnel-visioned. There’s no need for all these different DateTime Items, a simple look-up object does the trick. If my work schedule changes, I’ll just change the object.

New attempt:

var controleperiode = 18; // in uren
var schoolwekkers = {
  monday: "05:45",
  tuesday: null,
  wednesday: "06:00",
  thursday: "06:00",
  friday: "05:45",
  saturday: null,
  sunday: null
};

var eenmaligewekkers = items["wekker_openhab"];
var wekker_schoolwekkers_actief = items["wekker_schoolwekkers_actief"];
var wekker_vakantiewekkers_actief = items["wekker_vakantiewekkers_actief"];
var begin_volgende_schoolvakantie = items["begin_volgende_schoolvakantie"];
var einde_huidige_schoolvakantie = items["einde_huidige_schoolvakantie"];
var wekker_school = items["wekker_school"];
var wekker_vakantie = items["wekker_vakantie"];
var eenderwelkewekkermorgen = false;
var wekkersmorgen = {
  schoolwekkermorgen: false,
  vakantiewekkermorgen: false,
  eenmaligewekkermorgen: false,
  eenmaligewekkers: []
};
var nu = time.toZDT();


function wekkersSorteren(a, b) {
  if (time.toZDT(a).isBefore(time.toZDT(b))) {
    return -1;
  } else {
    return 1;
  }
}


function wekkerSelecteren(wekkers) {
  if (wekkers.length > 1) {
    var melding = "Er is voor morgen meer dan één wekker ingesteld; enkel de vroegste wordt geactiveerd."
    console.log(melding);
    rules.runRule("melding_gsm_DBE", { 'melding': melding });
    wekkers.sort(wekkersSorteren);
  }
  return wekkers[0];
}


function schoolWekkerActief() {
  if
      (
        (
          wekker_schoolwekkers_actief.boolState
          &&
          (
            (
              !begin_volgende_schoolvakantie.isUninitialized
              &&
              !time.toZDT(begin_volgende_schoolvakantie).toLocalDate().equals(nu.plusDays(1).toLocalDate())
            )
            ||
            begin_volgende_schoolvakantie.isUninitialized
          )
        )
        ||
        (!einde_huidige_schoolvakantie.isUninitialized && time.toZDT(einde_huidige_schoolvakantie).toLocalDate().equals(nu.toLocalDate()))
      ) {
    wekker_vakantiewekkers_actief.sendCommandIfDifferent("OFF");
    return true;
  } else {
    return false;
  }
}


function wekkerVoorMorgen() {
  var schoolwekkeractief = schoolWekkerActief();
  if (schoolwekkeractief) {
    var weekdagmorgen = String(time.toZDT("P1D").dayOfWeek()).toLowerCase();
    if (schoolwekkers[weekdagmorgen] != null) {
      eenderwelkewekkermorgen = true;
      wekkersmorgen["schoolwekkermorgen"] = true;
      var schoolwekkermorgen = time.toZDT(schoolwekkers[weekdagmorgen]).plusDays(1);
      wekker_school.sendCommand(schoolwekkermorgen);
    }
  } else if (wekker_vakantiewekkers_actief) {
    var nieuwevakantiewekker = vakantieWekkerInstellen();
  }

  var controlemoment = nu.plusHours(controleperiode);
  for (var x = 0; x < eenmaligewekkers.length; x++) {
    var dezewekker = eenmaligewekkers[x];
    if (!dezewekker.isUninitialized && time.toZDT(dezewekker).isBefore(controlemoment)) {
      eenderwelkewekkermorgen = true;
      wekkersmorgen["eenmaligewekkermorgen"] = true;
      wekkersmorgen["eenmaligewekkers"].push(dezewekker);
    }
  }
  
  if (eenderwelkewekkermorgen) {
    if (wekkersmorgen["eenmaligewekkermorgen"]) {
      var uiteindelijkewekker = wekkerSelecteren(wekkersmorgen["eenmaligewekkers"]);
      items["wekker_eerstvolgende"].sendCommand(uiteindelijkewekker.state);
      if (wekkersmorgen["schoolwekkermorgen"]) {
        var melding = "Er is voor morgen een eenmalige wekker ingesteld; de normale schoolwekker wordt dus genegeerd.";
        meldingDBE(melding);
      } else if (wekkersmorgen["vakantiewekkermorgen"]) {
        var melding = "Er is voor morgen een eenmalige wekker ingesteld; de normale vakantiewekker wordt dus genegeerd.";
        meldingDBE(melding);
      }
    } else if (wekkersmorgen["schoolwekkermorgen"]) {
      items["wekker_eerstvolgende"].sendCommand(schoolwekkermorgen);
    } else if (wekkersmorgen["vakantiewekkermorgen"] && nieuwevakantiewekker != null) {
      items["wekker_eerstvolgende"].sendCommand(nieuwevakantiewekker);
    }
  }
}


function vakantieWekkerInstellen() {
  if (wekker_vakantie.isUninitialized) {
    var melding = "De vakantiewekker is geactiveerd, maar niet ingesteld...";
    meldingDBE(melding);
    return null;
  } else {
    var uur = String(time.toZDT(items.wekker_vakantie).hour()).padStart(2, "0");
    var minuut = String(time.toZDT(items.wekker_vakantie).minute()).padStart(2, "0");
    var nieuwevakantiewekker = time.toZDT(uur + ":" + minuut).plusDays(1);
    wekker_vakantie.sendCommand(nieuwevakantiewekker);
    wekkersmorgen["vakantiewekkermorgen"] = true;
    eenderwelkewekkermorgen = true;
    return nieuwevakantiewekker;
  }
}


function meldingDBE(melding) {
  console.log(melding);
  rules.runRule("melding_gsm_DBE", { 'melding': melding });
}


function main() {
  if (event.eventType == "time" || event.eventType == "manual") {
    wekkerVoorMorgen();
  } else if (event.itemName == "wekker_vakantiewekkers_actief") {
    vakantieWekkerInstellen();
  }
}


main();

The same context as described in my earlier post still applies, beside from the fact that DateTime Group Item eenmaligewekkers now only contains “one-off” alarm clock DateTime Items. I also added a trigger:

triggers:
  - id: "1"
    configuration:
      time: 18:00
    type: timer.TimeOfDayTrigger
  - id: "4"
    configuration:
      itemName: wekker_vakantiewekkers_actief
      state: ON
    type: core.ItemStateChangeTrigger

It that function is being used in a sort, it’s missing the equals condition.

time.toZDT() takes all sorts of inputs and will give you a ZonedDateTime as the result. With no arguments it gives you now. If you pass in a String that matches a time of day, it returns a ZDT with today’s date and the given time. If you pass in an ISO8601 String, it will convert that to a Duration and add that to now. So time.toZDT("08:00") means “08:00 today” and time.toZDT('P1D') means “now plus one day”.

However, if you want to do something like 08:00 tomorrow, I have a method toTomorrow() in OHRT or you can still use the plusDays(): time.toZDT("08:00").plusDays(1).

Consider the following scenario. I work M-F but let’s say that I need to get up earlier on trash days. I create one dayset named “workday” that includes M-F and another dayset named “trash” that only includes Monday. Then I can use something like:

if( actions.Ephemeris.isInDayset("workday") ){
  var alarmTime = (actions.Ephemeris.isInDaySet('trash')) ? time.toZDT('06:00') : time.toZDT('06:30');
}

I can put all the work and school holidays into a custom holidays Ehpemeris configuration file and take it one step further.

if( !actions.Ephemeris.isBankHoliday(0, "path/to/ephemeris.xml") ) {
  if( actions.Ephemeris.isInDayset("workday") ){
    alarmTime = (actions.Ephemeris.isInDaySet('trash')) ? time.toZDT('06:00') : time.toZDT('06:30');
  }
}  

I don’t need to create awkward arrays in the rule to keep track of the days of the week and stuff like that. And if I use the Ephemeris binding I don’t even need to use the actions. I can have all the daysets and holiday stuff in Items.

if(items.BankHoliday.state == "ON") {
  if(items.Weekeday.state == "ON") {
    alarmTime = (items.TrashDay.state == "ON") ? "06:00" : "06:30";
  }
}

And the Items can be used to trigger rules.

You start with an array allewekkers.

We want to filter the Array down to just those entries that match a given criteria. The criteria is implemented in an anonymous function (argument => function code). If the anonymous function were made a regular function it would look like:

function nameFilter(dezewekker) {
  return dezewekker.name.includes(weekdagen[nu.dayOfWeek().value()-2]) 
             && dezewekker.name.includes(wekkersoorten["school"]) 
             || dezewekker.name.includes(wekkersoorten["vakantie"]);
}

...

allewekkers.filter(nameFilter);

The filter returns an array of just those Items that match the criteria. Then we run a forEach on that array. Again, it takes an anonymous function.

See Array - JavaScript | MDN for these and all the other methods available on Arrays with examples. It’s very rare that you would need to create a for(var x = 0...type loop in a rule and the Array functions are almost always going to be easier for future you to understand what’s going on.

Okay, but you do have to create a(n also awkward) xml file, no? And since my alarm could be different every week day, I would need a few different entries in that xml file. And it seems like the actual times still need to be hardcoded in the Rule code?

I extract the holidays from my Google Calendar. Is it possible to automate this conversion to an Ephemeris configuration file?