Populate local Home Assistant calendar with school "specials" based on cycle days using Appdaemon
This app provides a way to keep track of school specials based on cycle days rather than days of the week. The app is based on a five cycle day system where school "specials" (art, music, library, etc.) run on the same cycle day but not the same day of the week. Each time there's a snow day or in-service day, etc., the cycle days stop, and then they start back up when school is back in session. This has become very difficult to manage.
This app relies heavily on Home Assistant (HA) created entities, and using the HA interface to trigger different tasks. Below are the helpers that need to be created. Because HA does not (yet) have a way to programmatically delete events, the delete events button deletes the physical calendar file (.ics), and then it's recreated when adding a new calendar entry.
Add the createDate.py
in your apps
folder, and modify apps.yaml
as necessary. The yaml file I included has only this application, so you will need to add the text to any other apps you have already installed. This app is driven off of HA helper entities: input_datetime, input_text, and input_button. The intent was to have HA store the values so they were readable to the user and persisted after restarts.
Each of these helper entities are explained in detail in the comments at the top of the application. You will need to create the following entities (the ones I created are listed):
- non_school_days: input_text.non_school_days
- added_date: input_datetime.add_non_school_day
- cycle_day_holidays: input_text.cycle_day_holidays
- start_date : input_datetime.cycle_start_day
- end_date : input_datetime.cycle_end_day
- cycle_day_1: input_text.cycle_day_1
- cycle_day_2: input_text.cycle_day_2
- cycle_day_3: input_text.cycle_day_3
- cycle_day_4: input_text.cycle_day_4
- cycle_day_5: input_text.cycle_day_5
- day_number: input_number.cycle_day_restart_day
- button_entity_for_adding_dates: input_button.rerun_calendar_cycle_days
- button_entity_to_list_holidays: input_button.cycle_day_list_holidays
- button_entity_to_add_non_school_day: input_button.add_non_school_day
- button_entity_to_clear_non_school_days: input_button.clear_non_school_days
- button_entity_to_delete_non_school_day: input_button.delete_non_school_day
- button_entity_to_delete_calendar_events: input_button.delete_calendar_events
- button_entity_to_delete_holidays: input_button.delete_holidays
- button_entity_to_add_dates_from_other_calendar: input_button.add_dates_from_other_calendar
- button_entity_to_refresh_calendar_list: input_button.refresh_calendar_list
- calendar_list: input_select.calendar_list
- system_message: input_text.system_message
If you change any of the names (the text above before the :), you'll need to replace them in the code. I did my best not to hard code anything, and instead use self.args["INPUT NAME"]
. In addition, you will need to create a Bearer Token to access the REST API. Instructions for creation are provided here. As a warning, you must put the word Bearer
in front of the created token to designate it as a bearer token.
I put the file path for the .ics
file as part of my secrets.yaml
file, but you can just add it directly into apps.yaml
. The same is true for the Bearer
token as described above.
You can add the code from school_cycle_days_dashboard.yml
to make a separate dashboard for this app. This file closely follows the pictures below.
This is the main input/status screen. From here, you can add and delete non-school days, add and delete holidays, and finally, add those cycle days and their associated specials to your local HA calendar.
Once you add holidays and non-school days, this is the interface you can continue to add non-school days or delete entries you already added.
This is where you add non-school days (in-service, snow days, etc.). Any manually added days will be added to the holidays in your selected region. When running the calendar cycle days (to add the dates to your calendar), you can select the date range for it to run. This allows you to start from today or yesterday, for example, when you have to rerun the calendar due to a snow day, etc.
Sometimes schools create their own calendars which can be exported into .ics
format. In that case, you need to add the calendar to HA first and use the upload function. The app then allows you to select that calendar from a dropdown and add entries from the school's .ics
file into the non-school days. This function searches for "No School". Other entries (concerts, field day, etc.) are not included as part of the pull from the .ics
file.
To accomplish the pull since AppDaemon does not yet allow receiving return responses from service calls (like a list of calendar events), I used the icalendar
library to read through the .ics
file, parse the data, and then add the dates from that calendar to the non-school days already in place. The app checks to see if a date has already been added in order to avoid duplication. You can delete non-school day entries from there.
Warning: This process can take a minute or two depending on how many entries are in the other calendar. Please be patient. A system message will appear once complete showing how many entries have been added to the list.
This app incorporates the holidays
python import. The app can be configured as described in its documentation. You can either use the holiday list as specified or delete the holidays and add the non-school days manually. Once you have set up your preferred list of holidays, you need to run the task to add the holidays to the list of non-school days.
HA does not currently have a way to delete individual events through an automation, but I'm hoping that will change in the near future. In the meantime, the Delete Calendar Events
button physically deletes the .ics file from the .storage
folder. HA keeps a pointer to that calendar, and the .ics will be recreated upon adding at least one event.
This is what the local HA calendar looks like once you have added the cycle days.
Sample calendar event when you click on an entry.
This is my first python program, so I am positive that the code is a lot less efficient than it could have been. It works, and so I'm putting it out there for others. If you have suggestions for improving the code and/or want new features, please create a PR, and I'll be happy to do my best. I enjoy this even if it can be a bit frustrating at times.