Design Pattern: Simple State Machine (e.g. Time Of Day)

Spaces/tabs do not have any meaning in the Rule DSL (or Xtend for that matter), so feel free to use indentation to your advantage, also from examples from others, that, possibly because of their coding background, use different indentation styles.

1 Like

Great, Iā€™m off to indent the cr@p out of my existing code!

:+1:

Iā€™m just doing a quick scan from my code but I think you can do this with one switch statement. The only difference is the test for ā€œDAWNā€. It feels wrong to duplicate the whole switch statement to handle that one difference.

I donā€™t think you need the test for isBefore. I think you just need to add the second version of the case and it will work.

    switch now {
  	  case now.isAfter(morning_start)   && now.isBefore(day_start),
             now.isAfter(day_start)       && now.isBefore(morning_start):   curr = "DAWN"
  	  case now.isAfter(day_start)       && now.isBefore(afternoon_start): curr = "DAY"
  	  case now.isAfter(afternoon_start) && now.isBefore(evening_start):   curr = "TWILIGHT"
  	  case now.isAfter(evening_start)   && now.isBefore(night_start):     curr = "EVENING"
          case now.isAfter(night_start):                                      curr = "NIGHT"
  	  case now.isAfter(bed_start)       && now.isBefore(morning_start):   curr = "BED"
  }

If morning is before day, then the first case will evaluate to true when the current time is between morning and day. If morning is after day and now itā€™s between the two, the first case statement canā€™t evaluate to true but the second case statement will evaluate to true. If either are true then it gets set to ā€œDAWNā€.

Because the switch statement stops as soon as it finds a true case you donā€™t need to have two versions of the third case statement. It doesnā€™t matter whether morning is before or after day. If the first two case statements are false, we can use day as the isAfter and the third case will run when it needs to.

@rlkoshak I have found my root of issue, described by lots of others here.
Basically cron is really unrealiable, so thatā€™s why my Time Of Day wonā€™t get updated every time (when it should get triggered by a cron).

Here is my original issue: Cron job failing to execute on OH 2.4

Is there a solution to drop cron triggers? Or how this ā€˜problemā€™ could be solved?

Usually when cron triggers get dropped it is because you have too many trying to run at the same time. You can only have two crons or Timers running at the same time. If you exceed that it looks like OH drops the task rather than waiting. do you have a lot of cron triggered rules or timers?

Not really, I have everything based on the Time Of Day, so basically this is the one cron trigger (and another thing which polls a device every 5 min). And it is not like it sometimes ā€œdropā€ the task. It seems like after a time all of the cron jobs are missed (this might be a better wordā€¦). I had enabled debug logging for quartz and after some days I just stop receiving these quartz logsā€¦

One thing was interesting: I have mapdb. It looked like that when a status changes which is persisted by mapdb, a new cron job startsā€¦

Itā€™s not just cron that operates out of that thread pool. Timers come out of that thread pool too.

All I can recommend is trying to upgrade to 2.5 M1 or the snapshot (riskier right now) and seeing if the error goes away. If it doesnā€™t file an issue at openhab-core.

There is anything changed in the Milestone release regarding these problems?

I donā€™t know but if you file an issue the developers will need you to run the latest version to verify it isnā€™t some thing that has already been fixed.

Iā€™ve tackled it with separate astro-bindings. Not using the offset, but I use the earliest/lastest config (https://www.openhab.org/addons/bindings/astro/#channel-config), eg:

astro:sun:day [ geolocation="xxx,xxxx", interval=60 ] {
        Channels:
            Type rangeEvent : rise#event [
                earliest="07:00"
            ]
    }

In this example DAWN will start at 6am (as coded) till at least 7am. DAY will start at least at 7am or whenever sunrise is later than 7am.

1 Like

Hi,

Iā€™m going to trim this DP to give me the name of the day and the week number as separate variables so that I can then run rules depending on the day, (ie I donā€™t want to be woken up at my normal working day time on the weekend for example), so the question is 2 fold:

  1. Where do I find the datetime formatting options to pull out just the day of a datetime item and then also the week number

  2. What option would you use to fill the variable:

just a simple one line: vNameofDay = whatever the correct channel and formatting is to give me ā€œMONDAY / TUESDAY / etcā€
or
would you go down the case route as above to hard set the name of the day? Would it make more sense to perhaps use the case method so I can call Mon ā€œ1ā€, Tues ā€œ2ā€, Wed ā€œ3ā€ etc and then it would save me having to change type in other rules, (or perhaps give me the options to have a rule where vNameofDay >= 6 means the weekend etc)ā€¦

  1. would you tab it into my existing ToD rule so that it all runs and fills the variables in one rule or make it a separate rule - Iā€™m a little worried at the number of cron jobs Iā€™m going to start having and having the same / similarly related variables updated based on one rule seems the best thing to do in my mind but anyone have some alternative thoughts not toā€¦

Any thoughts would be gratefully received!

The easiest is to just type in now. in VSCode and wait the half second for the dialog to come up that shows you all the methods on that DateTime object.

Otherwise you can look at DateTime (Joda time 2.2 API).

I think you can only get the day of the week as a number anyway. If keeping it as a number helps you in other Rules keep it as a Number and use a map transform if you need the text.

But one must ask what the benefit is of putting just the day of the week into an Item. I would expect to have a vDayOfWeek with states like ā€œWORKā€, ā€œWEEKENDā€, ā€œHOLIDAYā€, etc. If all you use is the day of the week, just use now.getDayOfWeek in your other Rules. The ToD DP is intended to centralize calculations that take places over and over in many Rules. In this case you are not doing any calculations.

I can see either approach as appropriate.

Thanks as always for the response. I do keep forgetting about VSCodes OH add in, I must get into the habit of trying that first.

But one must ask what the benefit is of putting just the day of the week into an Item. I would expect to have a vDayOfWeek with states like ā€œWORKā€, ā€œWEEKENDā€, ā€œHOLIDAYā€, etc. If all you use is the day of the week, just use now.getDayOfWeek in your other Rules. The ToD DP is intended to centralize calculations that take places over and over in many Rules. In this case you are not doing any calculations.

I want to have the day of the week as a number dummy variable for the exact reason that it centralizes it for other rules to use. I wanted to do it as simple as 1-7 as my work days might not be the same every week so setting Mon-Fri as WORK and Sat-Sun as WEEKEND isnā€™t always correct.

I can then combine the vToD and this vDayofWeek into other rules, ie if time of day = BED and vDayofWeek = 6, (ie Saturday night), then set the alarm for later than during the rest of the week so I get a lie in on Sunday morning.

But itā€™s already centralized. Itā€™s one call to now.getDayOfWeek. You are proposing replacing that one call consisting of 16 characters with an Item definition and a rule which is far more work and complex then just using now.

Hi, yes youā€™ve beaten me to it :slightly_smiling_face:

I didnā€™t know about the getdayofweek option until after I followed your joda link and played around with VSCode, I found it last night, along with a load of other interesting datetime things I never knew was possible.

So much easier that having to write rules for them! Thanks again for the link etc, everyday is a school day!

For completion / interestā€¦

  // get day and week numbers
  val week_number = now.getDayOfMonth
  vWeekNumber.postUpdate(week_number.toString)

  val day_of_week = now.getDayOfWeek
  vDayofWeek.postUpdate(day_of_week.toString)

Gives the week number and day of week as numbersā€¦

nope.

a given date: 2019/05/14

now.getYear = 2019
now.getMonthOfYear = 5
now.getDayOfMonth = 14
now.getWeekyear = 20 (ISO8601 Week of Year, the first week of the year is that in which at least 4 days are in the year)

Sorry, you are correct, I must have posted an old bit of code, I changed the variable names to be more accurate after c&p here.

Given date 2019/05/14 (Tuesday)

now.getDayofMonth gives you the day of the month ie 14
now.getDayofWeek gives you a numerical version of the day of the week, ie 2, (1 is Monday, 7 is Sunday)
now.getWeekyear gives you the week number, ie 20.

Iā€™m still having problems with the cron schedulerā€¦ I have moved all my other cron based things to exec binding, these are working nicely. Only this rule depends on the cron scheduler and it stops after a few day of usageā€¦ looks like the thread gets locked somewhere and it gets filled up by timeā€¦ after that no logs, nothing from cronā€¦ no one ever had issues like this? Where I could start to investigate this problem?

Hi, Iā€™m trying to get morning start to equal 6:30am, Iā€™ve changed the val to

val morning_start = now.withTimeAtStartOfDay.plusDays(1).minusHours(18).plusMinutes(30)

Which after some head scratching seems to make logical sense to me 24 - 18 = 6 then add 30 mins so 6:30 but the rule stays as BED until the next trigger, ie sunset minus 90 in my case. So it suggests that my morning_start val isnā€™t working?

I know the syntax is correct but is what Iā€™m trying to do possible or is there a different way to get 6:30? I could do it all in minutes???