ISY-99i Generic Calendar Using Programs and Variables

From Universal Devices, Inc. Wiki

The Basic Idea of this Series of Programs

ISY’s date function is a bit limited. As the firmware currently supports, there is no direct way to use it for recurring events except related to day of the week. For example, you can have something happen every Monday, or every Monday and Tuesday, but you can’t set up something to run on the third Tuesday of every month, or every 3 rd day, or on even days of the month, or every July 4th, I think you get the idea.

Working outside the built-in firmware, a series of programs using the variable function can solve this problem. The following series of programs starts off with a single program that triggers at midnight and then cascades through a series of additional programs using “run if”, “run then”, and “run else” commands. These programs update a set of variables each day including the current day, day of month, day of year, month, year, and week of month. In addition, there are counters which follow an every other, every third, every fourth, and every fifth day schedule. Also, there is a program that sets a variable according to whether it is an even day or an odd day. Finally, a series of programs sets a variable to 1 on several US holidays. Some of these programs can be expanded upon including the holiday series or the every x number of days or weeks programs to suit your needs.

As an aside here, you may wonder why these programs don't all just run with their own triggers rather than start with a single program and cascade off of that as they do. The main reason is that the order of calculation is critical for many of these variables. When one variable is calculated from the updated value of another variable, that first variable must obviously be calculated first. The only way to be certain that one operation occurs, in order, prior to another, is to have them run, in order, in a single "then" or "else" clause. A sinlge "then" or "else" cluase always runs from top to bottom in order. If it is impossible to put everything in a single clause, then you must have the first program send you to a second program using a "run xxx" statement. Because of the way ISY operates, there is no guarantee that two programs which trigger on their own will actually run in any particular order to completion. If one program triggers at 1:00:00 and another triggers at 1:00:01, the program that runs at 1:00:01 may catch up to or pass the 1:00:00 program. But if a line of one program includes a "run if" of another program, you know that the second program will not do anything until every statement in the first program preceding the "run if" has completed.


The Variables

There are a number of variables that need to be created by the user. By convention, variables that have an “i” to start the name are integer variables and an “s” indicates a state variable. This is only for ease of identification when writing and editing programs.

  • iDay.of.Year starts with 1 on Jan 1 and counts to 365 (or 366 on leap year). This variable is a less used variable that is mostly included for completeness as it is rare that someone needs to know what day of the year it is.
  • iDay.of.Month is how many days into the month we are (i.e. the 5th of the month).
  • iYear is simply the current year.
  • iLeap.Year counts 0,1,2,3,0,1,2,3,etc. where 0 means that we are in a leap year. This is primarily a variable used to keep the other variables in check since rarely do people have cause to include this directly in a program.
  • iDay.of.Week starts with 1 on Monday and counts through to 7 on Sunday. This variable is used to cross check the status of the variables against ISY’s internal day of week function so as to alert you if it gets out of sync. You do not need this variable since ISY has this function built-in but you can choose to use it instead of the ISY’s built-in day function and get the same result.
  • iMonth is simply the current month starting with 1 for Jan.
  • iDay.Counter gets one number higher everyday without regard to the start of a new month or year. The primary purpose of this variable is to serve as a basis for the every x number of days/weeks variables since those programs require a reference that always increases by one each day. I have arbitrarily used MS Excel’s system of setting Jan 1, 1900 equal to day one and then counting from there. This offered an opportunity to test these programs well into the future and have a reference to check the values against.
  • iEver.xxx.Counter starts with 0 and counts up to x-1. For example, iEvery.Fourth.Day.Counter cycles through 0,1,2,3,0,1,2,3,etc. If today the variable was equal to 1 and you wanted to start watering your lawn tomorrow continuing every 4th day after that, set your program to water when the variable is 2.
  • iWeek.of.Month starts with 1 on the first day of the month and increases by 1 every 7 days until the next month starts. So if you wanted something to happen on the second Tuesday of each month, set it to occur on Tuesdays when iWeek.of.Month is equal to 2.
  • iWeek.Counter counts the number of weeks since Jan 1, 1900. The purpose of this variable is to calculate the every x weeks programs as there is likely no value in knowing how many weeks have passed since Jan 1, 1900. This value is produced by dividing iDay.Counter by 7.
  • i.Every.xxx.Week programs start at 0 and count to x-1. This works the same as the iEvery.xxx.Day.Counter programs except it is for weeks.
  • iOdd.Even.Day sets to 0 on even days and 1 on odd days of the month. This is useful in communities with watering restrictions limiting certain addresses to water on even or odd days of the month.
  • iHoliday sets to 1 on the listed holidays and 0 on all other days. Feel free to add your own holidays. I use this value to shut down wake-up alarms.
  • iSync is a variable whose purpose is to test the status of this set of programs against the ISY’s internal day of week function. An email is triggered if the two are out of sync. If this varialbe is correct, it is quite safe to assume all of the variables are correct.
  • The sVariables are simply the same value of the corresponding iVariables copied over to the state variable side. The purpose of this is so that you can use them as program “triggers”. You can certainly add more to the list as you see fit. The difference between a “state” variable and an “integer” variable is discussed elsewhere in the wiki.

Setting Up The Programs

  • The first step is to setup the variables. The variables do not load automatically with the programs. There is at present no way to import variable setups so you must do it manually. The screen caputres at the bottom of this page show you all of the locations for the variables. Please note that there are a number of variables in that screen capture that are not part of this series of programs. Please just skip over those id locations or use them for other applications. You must enter the listed variables into ISY AT THE SAME ID LOCATION AS SHOWN. ISY programs refrence the id location NUMBER, not the name. The name you enter in the name column is shown in the program for convenience only. Because I wrote these programs and then worked on some other programs, and then came back to these programs, there are some intervening variables which you need not concern yourself with, except to know that they must be skipped over until you get back to the correct ID number location. If you have already used some of the variable locations for other programs, you must either redirect your current program to another ID location or redirect the appropriate variables from these programs to new ID locations. You are free to change the names if you like so long as you understand what it represents.
  • Import the programs or undertake the task of manually entering the programs. Please observe the screen captures to see how I set up the folder architecture. You are certainly free to do it differently, but keeping the programs organized this way is helpful. Here is a link to a zipped version of the file. Please unzip the file first and then import it. http://www.universal-devices.com/programs/calendar/mmddyyyy.zip
  • Set the start values. You must set the current date including day of month, day of year, month, day of week, year, and leap year and leap year (0 for leap year, 1 for the year after a leap year, etc.) variables to the correct values. If you would like to stay true to the day counter starting with 1 on Jan 1, 1900 then you need to cross reference that number to MS Excel. This is an arbitrary decision on your part as any number would actually work. All of the other variables are calculated from those values and will settle in when the programs run at midnight. The “init” values need not be set as they will automatically populate at the midnight run time (unless you reboot your ISY prior to that time in which case all of the values will be lost). Please look elsewhere in the wiki to learn about how the “init” value works.
  • Go to the Configuration Tab, System sub tab, System box and Uncheck the box next to “Catch up Schedules at Restart”. Not doing so will cause your ISY to run these programs every time the ISY is rebooted pushing it one day into the future which in most cases will be an error. Missed schedule grace period should be left at 15 minutes.
  • Setup the email at ID location number 1 to whom you want the email sent and what you would like the content to be in the event that the ISY’s day of week becomes out of sync with the day of week calculated by these programs. It is assumed that if this variable is accurate, all of the others will be as well. Furthermore, it is the only thing that is possible for ISY to test on its own.

Potential Pitfalls

The primary reason for a fault is a power failure or otherwise taking the ISY offline. If the ISY is continuously not operating between 12 midnight and 12:15 am the programs will fall one day behind. To manually push the programs one day forward, right click on “Advance Day” program and click “run then”. You can do this repeatedly to advance several days forward. The ISY was tested by me to function properly through the year 2015. I did not actually run the program any further out than that but I have no reason to believe it should fail. It is designed to run correctly for several hundred years before the leap year issue no longer is accurate. I trust that will not cause you any inconvenience. At the time of this writing, these programs have been up and running real time for about 4 months without error. Based on this and my tests running these programs through to 2015 in “fake time” I suspect there are no errors. If you think you have found a bug, please let me know. Username: apostolakisl.


The Programs

Advance Day: This program is the "trigger". Meaning that the "if" clause contains the only trigger event starting the entire cascade of programs. If you manually initiate a "run then" of this program, all of the programs will run and it will push the entire series of programs one day into the future.

  If
       Time is 12:00:00AM

  Then
       Run Program 'Day of Month Advance' (If)
 
  Else
     -	No Actions - (To add one, press 'Action')



Day of Month Advance: This program advances the day of month forward one day making sure to move back to "1" at the start of a new month. It also is the main trigger for many of the other programs.

  If
       (
            $iDay.of.Month < 31
        And (
                 $iMonth is 1
              Or $iMonth is 3
              Or $iMonth is 5
              Or $iMonth is 7
              Or $iMonth is 8
              Or $iMonth is 10
              Or $iMonth is 12
            )
       )
    Or (
            $iDay.of.Month < 30
        And (
                 $iMonth is 4
              Or $iMonth is 6
              Or $iMonth is 9
              Or $iMonth is 11
            )
       )
    Or (
            $iDay.of.Month < 29
        And $iMonth is 2
        And $iLeap.Year is 0
       )
    Or (
            $iDay.of.Month < 28
        And $iMonth is 2
        And $iLeap.Year > 0
       )

  Then
       $iDay.of.Month += 1
       $iDay.of.Month Init To $iDay.of.Month
       $sDay.of.Month Init To $iDay.of.Month
       $sDay.of.Month  = $iDay.of.Month
       Run Program 'Day of Week' (If)
       Run Program 'Month Advance' (If)
       Run Program 'Month Reset' (If)
       Run Program 'Week of Month' (Then Path)
       Run Program 'Ever X Weeks' (Then Path)
       Run Program 'Odd Even Day' (Then Path)
       Run Program 'Holiday reset' (Then Path)

  Else
       $iDay.of.Month  = 1
       $iDay.of.Month Init To 1
       $sDay.of.Month Init To $iDay.of.Month
       $sDay.of.Month  = $iDay.of.Month
       Run Program 'Day of Week' (If)
       Run Program 'Month Advance' (If)
       Run Program 'Month Reset' (If)
       Run Program 'Week of Month' (Then Path)
       Run Program 'Ever X Weeks' (Then Path)
       Run Program 'Odd Even Day' (Then Path)
       Run Program 'Holiday reset' (Then Path)


Odd Even Day: This program sets a variable to 0 or 1 based on the odd or even nature of the current day of month.

  If
  - No Conditions - (To add one, press 'Schedule' or 'Condition')

  Then
       $iOdd.Even.Day  = $iDay.of.Month
       $iOdd.Even.Day %= 2
       $iOdd.Even.Day Init To $iOdd.Even.Day

  Else
  - No Actions - (To add one, press 'Action')

Day of Week

  If
       $iDay.of.Week < 7

  Then
       $iDay.of.Week += 1
       $iDay.of.Week Init To $iDay.of.Week
       $sDay.of.Week Init To $iDay.of.Week
       $sDay.of.Week  = $iDay.of.Week
       Run Program 'Day of year Advance' (If)

  Else
       $iDay.of.Week  = 1
       $iDay.of.Week Init To 1
       $sDay.of.Week Init To $iDay.of.Week
       $sDay.of.Week  = $iDay.of.Week
       Run Program 'Day of year Advance' (If)

Day of Year Advance: This program counts from 1 to 365 (or 366) as the year progresses.

  If
       (
            $iDay.of.Year < 365
        And $iLeap.Year > 0
       )
    Or (
            $iDay.of.Year < 366
        And $iLeap.Year is 0
       )

  Then
       $iDay.of.Year += 1
       $iDay.of.Year Init To $iDay.of.Year
       $sDay.of.Year Init To $iDay.of.Year
       $sDay.of.Year  = $iDay.of.Year
       Run Program 'Every x Day Counter' (Then Path)
       Run Program 'Year' (If)

  Else
       $iDay.of.Year  = 1
       $iDay.of.Year Init To 1
       $sDay.of.Year  = $iDay.of.Year
       $sDay.of.Year Init To $iDay.of.Year
       Run Program 'Every x Day Counter' (Then Path)
       Run Program 'Year' (If)

Every X Day Counter: This program divides the day counter value (this value increases by one every day without ever resetting) by either 2,3,4, or 5 and then sets the remainder as the value of the variable. You could easily add additional lines to this program for any other count of repeating days.

  If
  - No Conditions - (To add one, press 'Schedule' or 'Condition')

  Then
       $iDay.Counter += 1
       $iEvery.Other.Day.Counter  = $iDay.Counter
       $iEvery.Thrid.Day.Counter  = $iDay.Counter
       $iEvery.Fourth.Day.Counter  = $iDay.Counter
       $iEvery.Fifth.Day.Counter  = $iDay.Counter
       $iEvery.Other.Day.Counter %= 2
       $iEvery.Thrid.Day.Counter %= 3
       $iEvery.Fourth.Day.Counter %= 4
       $iEvery.Fifth.Day.Counter %= 5
       $iDay.Counter Init To $iDay.Counter
       $iEvery.Other.Day.Counter Init To  $iEvery.Other.Day.Counter
       $iEvery.Thrid.Day.Counter Init To $iEvery.Thrid.Day.Counter
       $iEvery.Fourth.Day.Counter Init To $iEvery.Fourth.Day.Counter
       $iEvery.Fifth.Day.Counter Init To $iEvery.Fifth.Day.Counter

  Else
  - No Actions - (To add one, press 'Action')

Christmas: This and the following holiday programs set a variable to 1 on the given holiday and back to zero for any other day. New programs should be added by the user for other holidays that they may want to include. Or you could write additional programs that are structured the same but set a different variable depending on the type of holiday. These are national holidays that most people have off of work which would have a very direct impact on how you would want your programs to function. Other holidays might be different. Or, you may live in Canada.

  If
       $iMonth is 12
   And $iDay.of.Month is 25

  Then
       $iHoliday  = 1

  Else
  - No Actions - (To add one, press 'Action')

Holiday Reset: This sets the holiday variable to 0, then triggers the programs that would set it to 1 on a holiday. Make sure to add any new holiday programs you write to the "then" section.

  If
  - No Conditions - (To add one, press 'Schedule' or 'Condition')

  Then
       $iHoliday  = 0
       Run Program 'Christmas' (If)
       Run Program 'Labor Day' (If)
       Run Program 'Memorial Day' (If)
       Run Program 'New Years Day' (If)
       Run Program 'Thanksgiving' (If)

  Else
  - No Actions - (To add one, press 'Action')

Labor Day

  If
       $iDay.of.Week is 1
   And $iWeek.of.Month is 1
   And $iMonth is 9

  Then
       $iHoliday  = 1

  Else
  - No Actions - (To add one, press 'Action')

Memorial Day

  If
       $iDay.of.Week is 1
   And $iMonth is 5
   And (
            (
                 $iWeek.of.Month is 4
             And $iDay.of.Month > 24
            )
         Or (
                 $iWeek.of.Month is 5
             And $iDay.of.Month > 24
            )
       )

  Then
       $iHoliday  = 1

  Else
  - No Actions - (To add one, press 'Action')

New Years Day

  If
       $iDay.of.Month is 1
   And $iMonth is 1

  Then
       $iHoliday  = 1

  Else
  - No Actions - (To add one, press 'Action')

Thanksgiving

  If
       (
            $iDay.of.Week is 4
         Or $iDay.of.Week is 5
       )
   And $iWeek.of.Month is 4
   And $iMonth is 11

  Then
       $iHoliday  = 1

  Else
  - No Actions - (To add one, press 'Action')

Leap Year Calculator: This rolls a varialbe from 0 through 3 depnding on where we are in the leap year cycle. 3 is a leap year. The year 2100 is a special year in that it is not a leap year despite being at that point in the 4 year cycle. The next year like this isn't for several hundred years.

  If
       $iDay.of.Year is 1
   And $iYear is not 2100

  Then
       $iLeap.Year  = $iYear
       $iLeap.Year %= 4
       $iLeap.Year Init To $iLeap.Year

  Else
  - No Actions - (To add one, press 'Action')

Month Advance

  If
       $iDay.of.Month is 1
   And $iMonth < 12

  Then
       $iMonth += 1
       $iMonth Init To $iMonth
       $sMonth Init To $iMonth
       $sMonth  = $iMonth

  Else
  - No Actions - (To add one, press 'Action')

Month Reset

  If
       $iDay.of.Month is 1
   And $iMonth is 12

  Then
       $iMonth  = 1
       $iMonth Init To 1
       $sMonth  = 1
       $sMonth Init To 1

  Else
  - No Actions - (To add one, press 'Action')

Friday: This and the following day programs check the current day of week as calculated by these programs against the day of week by ISY's internal calender and reports any issues.

  If
       On Fri
       Time is  1:00:00AM

  Then
       $iSync  = 5
       Run Program 'Sync email alert' (If)

  Else
  - No Actions - (To add one, press 'Action')

Monday

  If
       On Mon
       Time is  1:00:00AM

  Then
       $iSync  = 1
       Run Program 'Sync email alert' (If)

  Else
  - No Actions - (To add one, press 'Action')

Saturday

  If
       On Sat
       Time is  1:00:00AM

  Then
       $iSync  = 6
       Run Program 'Sync email alert' (If)

  Else
  - No Actions - (To add one, press 'Action')

Sunday

  If
       On Sun
       Time is  1:00:00AM

  Then
       $iSync  = 7
       Run Program 'Sync email alert' (If)

  Else
  - No Actions - (To add one, press 'Action')

Sync email alert

  If
       $iSync is not $iDay.of.Week

  Then
       Send Notification to 'dr-apo' content 'Out of Sync'

  Else
  - No Actions - (To add one, press 'Action')

Thursday

  If
       On Thu
       Time is  1:00:00AM

  Then
       $iSync  = 4
       Run Program 'Sync email alert' (If)

  Else
  - No Actions - (To add one, press 'Action')

Tuesday

  If
       On Tue
       Time is  1:00:00AM

  Then
       $iSync  = 2
       Run Program 'Sync email alert' (If)

  Else
  - No Actions - (To add one, press 'Action')

Wednesday

  If
       On Wed
       Time is  1:00:00AM

  Then
       $iSync  = 3
       Run Program 'Sync email alert' (If)

  Else
  - No Actions - (To add one, press 'Action')

Ever X Weeks: This works the same as the every x days programs but counts weeks instead of days.

  If
  - No Conditions - (To add one, press 'Schedule' or 'Condition')

  Then
       $iWeek.Counter  = $iDay.Counter
       $iWeek.Counter /= 7
       $iEvery.Other.Week  = $iWeek.Counter
       $iEvery.Other.Week %= 2
       $iEvery.Thrid.Week  = $iWeek.Counter
       $iEvery.Thrid.Week %= 3
       $iEvery.Fourth.Week  = $iWeek.Counter
       $iEvery.Fourth.Week %= 4
       $iEvery.Fourth.Week Init To $iEvery.Fourth.Week
       $iEvery.Thrid.Week Init To $iEvery.Thrid.Week
       $iEvery.Other.Week Init To $iEvery.Other.Week
       $iWeek.Counter Init To $iWeek.Counter

  Else
  - No Actions - (To add one, press 'Action')

Week of Month

  If
  - No Conditions - (To add one, press 'Schedule' or 'Condition')

  Then
       $iWeek.of.Month  = $iDay.of.Month
       $iWeek.of.Month -= 1
       $iWeek.of.Month /= 7
       $iWeek.of.Month += 1
       $sWeek.of.Month  = $iWeek.of.Month
       $sWeek.of.Month Init To $iWeek.of.Month
       $iWeek.of.Month Init To $iWeek.of.Month

  Else
  - No Actions - (To add one, press 'Action')

Year

  If
       $iDay.of.Year is 1

  Then
       $iYear += 1
       $iYear Init To $iYear
       $sYear  = $iYear
       $sYear Init To $iYear
       Run Program 'Leap year calculator' (If)

  Else
  - No Actions - (To add one, press 'Action')

Screen Captures to Help You Get The Variables Setup at The Proper ID Location