A Garbage Day automation using Google Calendar and pre-warning that actually works

Tags: #<Tag:0x00007f7390956658> #<Tag:0x00007f7390956590> #<Tag:0x00007f73909564a0>

The situation:

  • Your garbage collection company provides you with yearly electronic calendar data for the Garbage Days.
  • You wish to use HA’s Google Calendar integration for this.
  • You need early notifications (i.e., before midnight when Garbage Day begins), say at 5 p.m. the day before Garbage Day.

Unfortunately, this ain’t as easy as it sounds, because:

  • You need to split up your Google Calendar(s).
  • Google calendar search is too dumb to allow sophisticated searches (i.e. “give me only the events for residual waste and hazardous waste collection”).
  • HA only sees one (the next) calendar event at a time.
  • HA can’t handle overlapping events.
  • You don’t (yet) know how to set all this up.

The solution

Well, here’s how I did it. YMMV, but at least you should get some ideas out of it! :slight_smile:

My situation:

  • My garbage collector delivers me a yearly .ics file with the dates (whole-day events) for residual waste, biodegradable waste, waste paper, and packaging waste. Also, every few months, there is a bring-in event for hazardous waste, which occurs at different times (so not a whole-day event).
  • I use a separate (newly created) Google calendar for this, and import the .ics file once a year manually.
  • I only wish to automate the events for residual waste (collected every 2 weeks) and hazardous waste (bring-in).
  • Garbage Day shall be announced (and shown on a sensor) at 1700 hrs (5 p.m.) and 1900 hrs (7 p.m.) on the day before, so I have time to put out the rubbish bin (or trash can).
  • Garbage Day sensors shall activate (on) as above, and de-activate (off) on Garbage Day at 1700 hrs (5 p.m.), so that I can set up a “Did you haul the trash can in yet?” automation.
  • Bring-in events for hazardous waste shall be announced one hour before, so I have time to put it into the car and drive over.

The How-To

Prepare the Google Calendar stuff

  • Hop over to your online Google calendar and create a new calendar. I called mine “Abfuhrkalender” (waste disposal calendar) since I live in Germany and other people in the household might not be fluent in English.

  • Now import the .ics file you got from your garbage collection company into this new calendar, NOT into your normal calendar. (Remember what I said about splitting, and overlapping events.)

  • Set up HA’s Google Calendar Integration, if you haven’t already.

  • Restart HA.

  • In your config folder, you will now have a file google_calendars.yaml. Open it in a text editor and look for your newly created waste disposal calendar item. This will usually have a long cal_id and look somehow like this:

    - cal_id: [email protected]
      entities:
      - device_id: abfuhrkalender
        ignore_availability: true
        name: Abfuhrkalender
        track: true
    
  • Now go and split this into several “sub-calendars” for the different waste categories you are interested in, one for each category. Mine are Restmüll (residual waste) and Problemmüll (hazardous waste).

    We “split” the main calendar into entities having different device_ids by setting up searches that fish out just the entries we’re interested in. My calender entries look like “Restmüll in Krumbach” and “Problemmüll in Krumbach”, so I set up searches for “Restmüll” and “Problemmüll” to get two new calendars in HA called calendar.restmuell and calendar.problemmuell:

    - cal_id: [email protected]
      # Possible: 'Restmüll', 'Problemmüll', 'Bioabfall', 'Papiertonne', 'Gelbe Tonne'
      entities:
      - device_id: restmuell
        ignore_availability: true
        name: Restmüll
        track: true
        search: 'Restmüll'
      - device_id: problemmuell
        ignore_availability: true
        name: Problemmüll
        track: true
        search: 'Problemmüll'
    

    Note that I deleted the original abfuhrkalender entry, to avoid UI clutter and because it could contain overlapping entries.

    Note also that Google’s calendar search is dumb and can’t search for parts of a word. I could otherwise have gotten both event types by searching for “müll” (contained in the words “Restmüll” and “Problemmüll”).

  • Restart HA.

  • Try out the new calendars “Restmüll” and “Problemmüll” shown in the UI and check if you can see the next upcoming event in these. You might want to set up a few dummy events in your Google calendar for testing.

Make intelligent sensors with pre-warning

  • Now it’s time to set up a few sensors, one for each new garbage sub-calendar. So you can see in the UI that Garbage Day (or a waste-bring-in event) is coming.

  • Open your configuration.yaml and add these two new binary sensors:

    binary_sensor:
    # Garbage Day sensors
    - platform: template
      sensors:
        restmuell:
          friendly_name: 'Restmüll'
          icon_template: >
            {% if is_state('binary_sensor.restmuell', 'on') %}
              mdi:delete-alert
            {% else %}
              mdi:delete-outline
            {% endif %}
          # sensors need their entities declared, so they can watch them
          # using sensor.date_time (or sensor.time) makes it update every minute
          entity_id:
            - sensor.date_time
            - calendar.restmuell
          # ON 7 hours (25200s) before all_day event = 17:00 the day before, and OFF at 17:00 on the day,
          # or 1 hour (3600s) before normal event (Problemmüll)
          # CAREFUL: Binary sensors need true/false as input, NOT UI niceties like on/off or the like!
          value_template: >-
            {% set calendar = 'calendar.restmuell' %}
            {# Adapt these for your needs #}
            {% set seconds_before_all_day = 25200 %}
            {% set seconds_before_end_all_day = 25200 %}
            {% set seconds_before_single = 3600 %}
            {% set start = state_attr(calendar,'start_time') %}
            {# Prevent error when no calendar event (we won't have a start_time then) #}
            {% if start != None %}
              {% set _now = now().timestamp() %}
              {% set start = as_timestamp(start) %}
              {% set end = as_timestamp(state_attr(calendar,'end_time')) %}
              {% if _now < end %}
                {% if state_attr(calendar,'all_day') %}
                  {{ _now < end - seconds_before_end_all_day and start - _now < seconds_before_all_day }}
                {% else %}
                  {{ start - _now < seconds_before_single }}
                {% endif %}
              {% else %}
                false
              {% endif %}
            {% else %}
              false
            {% endif %}
    
        problemmuell:
          friendly_name: 'Problemmüll'
          icon_template: >
            {% if is_state('binary_sensor.problemmuell', 'on') %}
              mdi:delete-alert
            {% else %}
              mdi:delete-outline
            {% endif %}
          # sensors need their entities declared, so they can watch them
          # using sensor.date_time (or sensor.time) makes it update every minute
          entity_id:
            - sensor.date_time
            - calendar.problemmuell
          # ON 7 hours (25200s) before all_day event = 17:00 the day before, and OFF at 17:00 on the day,
          # or 1 hour (3600s) before normal event (Problemmüll)
          # CAREFUL: Binary sensors need true/false as input, NOT UI niceties like on/off or the like!
          value_template: >-
            {% set calendar = 'calendar.problemmuell' %}
            {# Adapt these for your needs #}
            {% set seconds_before_all_day = 25200 %}
            {% set seconds_before_end_all_day = 25200 %}
            {% set seconds_before_single = 3600 %}
            {% set start = state_attr(calendar,'start_time') %}
            {# Prevent error when no calendar event (we won't have a start_time then) #}
            {% if start != None %}
              {% set _now = now().timestamp() %}
              {% set start = as_timestamp(start) %}
              {% set end = as_timestamp(state_attr(calendar,'end_time')) %}
              {% if _now < end %}
                {% if state_attr(calendar,'all_day') %}
                  {{ _now < end - seconds_before_end_all_day and start - _now < seconds_before_all_day }}
                {% else %}
                  {{ start - _now < seconds_before_single }}
                {% endif %}
              {% else %}
                false
              {% endif %}
            {% else %}
              false
            {% endif %}
    

    EDIT: value_template modified as per a great simplification by @123—thank you!
    EDIT 2: Changed icons from mdi:trash-can/mdi:trash-can-outline to mdi:delete-alert/mdi:delete-outline. I simply like these better.

  • There are a few noteworthy pitfalls here:

    • HA takes 15 minutes to update the calendar—important for testing.
    • The sensor icon might take an extra minute to update in the UI, since we base it on the sensor.date_time entity. This sensor needs to be there already! If it is not, add it:
      sensor:
        - platform: time_date
          display_options:
            - 'date_time'
      
      By modifying the code above, you could also use an existing sensor.time instead of the sensor.date_time.
    • I recommend naming the binary sensors after your (sub-)calendar names, so nobody gets confused.
    • I set the “pre-warning” time for all-day events to 7 hours (=25200 seconds), so the sensors would go to the “on” state 7 hours before midnight for an all-day event. You might want to change all values 25200 to something else for your case.
    • I set the “pre-warning” time for timed events to 1 hour (=3600 seconds), you also might want to change that.
    • I set a local variable calendar within the value template. This is just to make it easier modifying the code, and avoid repetitions.
    • Whenever there is no new event coming up in your calendar, we don’t have attributes like start_time and end_time, so there is additional logic to avoid this case. (No events = no Garbage Day.)
    • When experimenting yourself, remember that binary sensors need true/false as input, not fancy UI niceties like On/Off, on/off or the like! Errors like these are hard to diagnose, since HA’s template tester will accept such values and show values that look ok but aren’t!
    • The code might seem blown in parts, but I knowingly let seemingly unneded stuff in—the garbage company might decide for different types of events in their next calendar, or Google’s calendar search might get cleverer, who knows?
  • Restart HA to activate the new sensors. You did perform a configuration check, right?

  • Make a few test entries in your Google waste calendar and try out if the sensors work.

  • You should see the calendar switch to On while the actual event is due. (Verify by clicking on the calendar name and check its event attributes.)

  • You should see the corresponding sensors switch on/off (change their trash can symbol) at the specified pre-warning times.

Time for automations!

Our sensors are working now, so let’s make some great automations that use them!

  • For my “Restmüll” calendar (or any garbage whole-day events), I wish to be reminded by audible (TTS) alerts at 1700 hrs (5 p.m.) and 1900 hrs (7 p.m.) the day before Garbage Day, so putting out the rubbish bin (or trash can) won’ŧ be forgotten. I’d also like a reminder on Garbage Day to bring the trash can back in.
  • My “Problemmüll” calendar should remind me one hour before the bring-in event.

So let’s go and put these in:

  • Tomorrow is Garbage Day!

    # Garbage Day coming up!
    # the sensor switches to "on" at 17:00 the day before
    # so we get an alert at 17:00 and 19:00 hrs
    - alias: 'Müllabfuhr Restmüll'
      trigger:
        - platform: state
          entity_id: binary_sensor.restmuell
          to: 'on'
        - platform: state
          entity_id: binary_sensor.restmuell
          to: 'on'
          for: '02:00:00'
      action:
        - service: script.say_next_calendar_event
          data_template:
            calendar: calendar.restmuell
            skip_duration: true
            language: 'de-DE'
            message: "Erinnerung!"
    
  • Did you haul the trash can back in yet?

    # Did ya haul in the rubbish bin yet?
    # the sensor switches to "off" on garbage day at 17:00
    # so we get an alert at 17:00 and 19:00 hrs
    - alias: 'Mülleimer reingeholt?'
      trigger:
        - platform: state
          entity_id: binary_sensor.restmuell
          to: 'off'
        - platform: state
          entity_id: binary_sensor.restmuell
          to: 'off'
          for: '02:00:00'
      condition:
        - condition: template
          value_template: "{{ state_attr('calendar.restmuell', 'all_day') == true }}"
      action:
        - service: script.say
          data_template:
            audiofile: 'picotts-alert.wav'
            language: 'de-DE'
            message: "Hast du den Müll-eimer schon rein-ge-holt?"
    
    • You can probably skip the condition—it is just for unlikely case the garbage collectors ever switch to non-all-day events.
    • Don’t be surprised about the odd spelling of the message—it is just so to make PicoTTS better understandable.
  • Hazardous waste (bring-in) events

    # Special (bring-in) events for hazardous waste; time & location can change
    - alias: 'Müllabfuhr Problemmüll'
      trigger:
        - platform: state
          entity_id: binary_sensor.problemmuell
          to: 'on'
      action:
        - service: script.say_next_calendar_event
          data_template:
            calendar: calendar.problemmuell
            skip_duration: true
            language: 'de-DE'
            message: "Erinnerung!"
    

Conclusion

Phew. This has gotten lengthy, but I thought a newbie should be able to follow. Excuses to all the pro’s out there.
And I’m impressed you held out with me!

This post showed how to …

  • integrate and cleverly split Google calendars for automation within HA.
  • make binary sensors to “pre-alert” before an event actually takes place.
  • use these sensors in automations.

Let me know …

  • how I could possible improve this code without losing functionality.
  • the errors I probably made.
  • how you modified and used this kind of workaround!
3 Likes

Suggestion to simplify the value_template:

      value_template: >-
        {% set calendar = 'calendar.problemmuell' %}
        {% set start = state_attr(calendar,'start_time') %}
        {# Prevent error when no calendar event (we won't have a start_time then) #}
        {% if start != None %}
          {% set now = now().timestamp() %}
          {% set start = as_timestamp(start) %}
          {% set end = as_timestamp(state_attr(calendar,'end_time')) %}
          {% if now < end %}
            {% if state_attr(calendar,'all_day') %}
              {{ now < end - 25200 and start - now < 25200 }}
            {% else %}
              {{ start - now < 3600 }}
            {% endif %}
          {% else %}
            false
          {% endif %}
        {% else %}
          false
        {% endif %}
2 Likes

That’s a good one, thanks! Clearly makes stuff much more readable.
Probably time to also put 25200 and 3600 into some prewarn… variable …

Nice one, I’ll try and test it with my setup. You do not happen to live in the county “Starnberg”, do you? It is exactly like our schedule here. And our waste collector (Awista) isn’t able to give out something like an api, even it is available with their app provider…

Nonetheless, I’ll try to fit that in with my Nextcloud calendar, as I don’t use GoogleCalendar anymore. If it won’t work, than I’ll re-enable my GoogleCalendar just for this. :slight_smile:

I’ll let you know, how it works out! :slight_smile:

@paddy0174: Same here, the software would allow but the garbage collector auto-generates .ics instead, not even at a fixed link …
Starnberg is a little more than 100km away from here. Nice lake, though :wink:

@123: Let’s see if I can edit the first post after testing. Note: now can’t be used as a variable, it’s already a built-in function…

It works in the Template Editor:

Feel free to change it to something else if you think it may cause confusion for users reading the template.

Yeah, I noticed. Probably some inconsistency, better play safe …


It certainly breaks now() after the re-definition.

Changed the code in 1st post after verification.

Changed the icon templates in post #1 to a better-looking set (I think).

I’m using these in my current setup:

name: Gelber Sack
icon: 'mdi:recycle'
---
name: Papier
icon: 'mdi:paperclip'
---
name: Restmüll
icon: 'mdi:trash-can'
---
name: Biomüll
icon: 'mdi:biohazard'

:slight_smile:

:+1: Did it work out for you?

this is awesome Matthias, thank you

I have two instances from my Garbage company on my calendar.

  1. “Trash” ( every Tuesday )
  2. “Recycling and Trash” ( Every other Tuesday )

I`m using two entities “Trash” and “Recycling” inside the google_calendar.yaml

it happens to be today the Recycling and Trash day so both are showing “ON”

but would those 2 entities work?

They sure should. It’s a matter of personal preference how you separate the sensors (and automations) out. You could try to separate them by modifying the code (thus getting separate “Trash” and a “Recycling” sensors) or use some kind of “if” or “condition” for the “Recycling & Trash” vs. “Trash” automations so you don’t get double alerts.