Bin / Waste Collection


#81

Just a note, not sure if you have changed it for the screenshot, but there is a full address in the top of the website screenshot.


#82

Thanks.

Yes, I did change the address.


#83

Thanks for taking a look @Robbrad Any chance you post the curl url that you found?


#84

This is the un-developed version - note the cookie or session might expire and this needs to be addressed

You can see the address 1 Hedgerow Close at the end of the url

curl 'http://www.stevenage.gov.uk/find/' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0' -H 'Origin: http://www.stevenage.gov.uk' -H 'Upgrade-Insecure-Requests: 1' -H 'Content-Type: application/x-www-form-urlencoded' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' -H 'Referer: http://www.stevenage.gov.uk/find/' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.9' -H 'Cookie: _ga=GA1.3.418271238.1531777870; _gid=GA1.3.2006517484.1531777870; ASP.NET_SessionId=xizub0v52p2bwlbh3rxb5jjc; _gat=1; _hjIncludedInSample=1' --data '__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwUKMTM5NDg2MzcxMQ9kFgJmD2QWAmYPZBYCAgEPZBYEAgEPEGRkFgBkAgMPZBYEAgMPZBYCZg8UKwACDxYEHgtfIURhdGFCb3VuZGceC18hSXRlbUNvdW50AidkZBYCZg9kFk4CAQ9kFgJmDxUDABIvY291bmNpbC1zZXJ2aWNlcy8QQ291bmNpbCBTZXJ2aWNlc2QCAg9kFgJmDxUDABMvYWJvdXQtdGhlLWNvdW5jaWwvEUFib3V0IHRoZSBDb3VuY2lsZAIDD2QWAmYPFQMAES9hYm91dC1zdGV2ZW5hZ2UvD0Fib3V0IFN0ZXZlbmFnZWQCBA9kFgJmDxUDABEvbmV3cy1hbmQtZXZlbnRzLw9OZXdzIGFuZCBFdmVudHNkAgUPZBYCZg8VAwAHL2xpbmtzLwVMaW5rc2QCBg9kFgJmDxUDAA8vaGF2ZS15b3VyLXNheS8NSGF2ZSBZb3VyIFNheWQCBw9kFgJmDxUDABMvY29tbXVuaXR5LWNlbnRyZXMvEUNvbW11bml0eSBDZW50cmVzZAIID2QWAmYPFQMADi9wZXN0LWNvbnRyb2wvDFBlc3QgY29udHJvbGQCCQ9kFgJmDxUDAAovYmVuZWZpdHMvCEJlbmVmaXRzZAIKD2QWAmYPFQMADS9jb3VuY2lsLXRheC8LQ291bmNpbCBUYXhkAgsPZBYCZg8VAwAJL2hvdXNpbmcvB0hvdXNpbmdkAgwPZBYCZg8VAwALL2xpY2Vuc2luZy8cTGljZW5zaW5nIGFuZCBTdHJlZXQgVHJhZGluZ2QCDQ9kFgJmDxUDAAkvZ2FyYWdlcy8HR2FyYWdlc2QCDg9kFgJmDxUDABEvYnVzaW5lc3MtYWR2aWNlLwhCdXNpbmVzc2QCDw9kFgJmDxUDABAvYW5pbWFsLXdlbGZhcmUvDkFuaW1hbCBXZWxmYXJlZAIQD2QWAmYPFQMACy9wb2xsdXRpb24vCVBvbGx1dGlvbmQCEQ9kFgJmDxUDABUvbGFuZHNjYXBlLXdvb2RsYW5kcy8XTGFuZHNjYXBlIGFuZCBXb29kbGFuZHNkAhIPZBYCZg8VAwAVL2xvY2FsLWxhbmQtc2VhcmNoZXMvE0xvY2FsIExhbmQgU2VhcmNoZXNkAhMPZBYCZg8VAwAVL3JlY3ljbGluZy1hbmQtd2FzdGUvE1dhc3RlIGFuZCBSZWN5Y2xpbmdkAhQPZBYCZg8VAwAWL3N0cmVldC1jYXJlLWNsZWFuaW5nLxhTdHJlZXQgQ2FyZSBhbmQgQ2xlYW5pbmdkAhUPZBYCZg8VAwATL2hlYWx0aC1hbmQtc2FmZXR5LxFIZWFsdGggYW5kIFNhZmV0eWQCFg9kFgJmDxUDAA0vZm9vZC1zYWZldHkvC0Zvb2QgU2FmZXR5ZAIXD2QWAmYPFQMAFS9wYXNzZW5nZXItdHJhbnNwb3J0LxNQYXNzZW5nZXIgVHJhbnNwb3J0ZAIYD2QWAmYPFQMAIC9yb2Fkcy1hbmQtcGF0aHdheXMtbWFpbnRlbmFuY2UvHlJvYWRzIGFuZCBQYXRod2F5cyBNYWludGVuYW5jZWQCGQ9kFgJmDxUDAAkvcGFya2luZy8HUGFya2luZ2QCGg9kFgJmDxUDAAcvNDg3MDgvE0NvbW11bml0eSBUcmFuc3BvcnRkAhsPZBYCZg8VAwASL2NvbW1lcmNpYWwtd2FzdGUvHkNvbW1lcmNpYWwgV2FzdGUgYW5kIFJlY3ljbGluZ2QCHA9kFgJmDxUDABAvdGF4aS1saWNlbnNpbmcvDlRheGkgTGljZW5zaW5nZAIdD2QWAmYPFQMACC8xMzU3NjgvFlB1YmxpYyBIZWFsdGggRnVuZXJhbHNkAh4PZBYCZg8VAwAILzE0OTY5MC8rUGxhbm5pbmcsIEJ1aWxkaW5nIENvbnRyb2wgYW5kIFJlZ2VuZXJhdGlvbmQCHw9kFgJmDxUDABAvYnVzaW5lc3MtcmF0ZXMvDkJ1c2luZXNzIFJhdGVzZAIgD2QWAmYPFQMADi9yZWdlbmVyYXRpb24vDFJlZ2VuZXJhdGlvbmQCIQ9kFgJmDxUDAAgvMTYzMzEyLxNMZWlzdXJlIGFuZCBDdWx0dXJlZAIiD2QWAmYPFQMAGi9zcG9ydHMtY2x1YnMtYW5kLWNlbnRyZXMvGFNwb3J0cyBDbHVicyBhbmQgQ2VudHJlc2QCIw9kFgJmDxUDAAcvNTI3MTAvB0N5Y2xpbmdkAiQPZBYCZg8VAwAXL3BhcmtzLWFuZC1vcGVuLXNwYWNlcy8VUGFya3MgYW5kIE9wZW4gU3BhY2VzZAIlD2QWAmYPFQMABi9wbGF5LwRQbGF5ZAImD2QWAmYPFQMABy8zMTAyOC8WQXJ0cyBhbmQgRW50ZXJ0YWlubWVudGQCJw9kFgJmDxUDAAovdGhlLXRvdXIvETIwMTggVG91ciBzZXJpZXMgZAIHD2QWCAIDDxYCHgRUZXh0ZWQCCw8WAh8BAv%2F%2F%2F%2F8PZAINDxYCHwECBBYKZg9kFgJmDxUEJDEgSGVkZ2Vyb3cgQ2xvc2UsIFN0ZXZlbmFnZSwgU0cyIDdFQhRUdWVzZGF5IDI0IEp1bHkgMjAxOAlSZWN5Y2xpbmcdMUhlZGdlcm93Q2xvc2VTdGV2ZW5hZ2VTRzI3RUJkAgEPZBYCZg8VAhRUdWVzZGF5IDMxIEp1bHkgMjAxOAZSZWZ1c2VkAgIPZBYCZg8VAhVUdWVzZGF5IDcgQXVndXN0IDIwMTgJUmVjeWNsaW5nZAIDD2QWAmYPFQIWVHVlc2RheSAxNCBBdWd1c3QgMjAxOAZSZWZ1c2VkAgQPZBYCZg8VAhZUdWVzZGF5IDIxIEF1Z3VzdCAyMDE4CVJlY3ljbGluZ2QCDw8WAh8BAgMWCGYPZBYCZg8VAQVNYW5vcmQCAQ9kFgJmDxUGDEdyYWhhbSBTbmVsbBBMaWJlcmFsIERlbW9jcmF0DzE3NyBEdXJoYW0gUm9hZAwwMTQzOCAyMzgzOTkdZ3JhaGFtLnNuZWxsQHN0ZXZlbmFnZS5nb3YudWsdZ3JhaGFtLnNuZWxsQHN0ZXZlbmFnZS5nb3YudWtkAgIPZBYCZg8VBg9BbmR5IE1jR3Vpbm5lc3MQTGliZXJhbCBEZW1vY3JhdA80MDMgVmFyZG9uIFJvYWQMMDE0MzggMjE5NTY5IEFuZHkuTWNHdWlubmVzc0BzdGV2ZW5hZ2UuZ292LnVrIEFuZHkuTWNHdWlubmVzc0BzdGV2ZW5hZ2UuZ292LnVrZAIDD2QWAmYPFQYMUm9iaW4gUGFya2VyEExpYmVyYWwgRGVtb2NyYXQPOCBDYW1lcm9uIENsb3NlDDAxNDM4IDcyNDc0Nh1yb2Jpbi5wYXJrZXJAc3RldmVuYWdlLmdvdi51ax1yb2Jpbi5wYXJrZXJAc3RldmVuYWdlLmdvdi51a2QYAQUtY3RsMDAkY3RsMDAkQ29udGVudFBsYWNlSG9sZGVyMSRNZW51JFRvcExldmVsDxQrAA5kZGRkZGRkPCsAJwACJ2RkZGYC%2F%2F%2F%2F%2Fw9kKH1nQhBd4loa1ZSkb0JY%2FTs9kUAgjbCbiGfI6QUECxU%3D&__VIEWSTATEGENERATOR=B72F5FBC&__EVENTVALIDATION=%2FwEdAAfj2SGJ%2BYySK10dhT7Z9CxxBRnY16VB%2BawiyIVNhmj7d8EVmI3aJcQdmuXHemHELFCoJoTREh6yQXa%2BkswJLGCOHIAGA40MkPa9Woarpjy083LhTQZWmmHCfz3lo41MX02r%2BnwvbOKjWJAVNQqUlqTebs1TP6IKPXYJxFLrqyK0JfY%2Bj5JSU5pwUWOFAL%2BOlq4%3D&ctl00%24ctl00%24SearchBox=Search+for...&ctl00%24ctl00%24ContentPlaceHolder1%24ContentPlaceHolder1%24txtNumber=1&ctl00%24ctl00%24ContentPlaceHolder1%24ContentPlaceHolder1%24txtStreet=hedgerow+close&ctl00%24ctl00%24ContentPlaceHolder1%24ContentPlaceHolder1%24txtPostcode=&ctl00%24ctl00%24ContentPlaceHolder1%24ContentPlaceHolder1%24BtnLookup=Street+Lookup' --compressed


#85

holy moly :smiley:


#86

I don’t know about others but cheshire east supports GET so you can use a scrape sensor

Select and value for recycling bin

    select: "tr.wasteType_3"
    value_template: '{{ as_timestamp(strptime(value, " %A %d-%b-%Y Recycling wheeled bin ")) }}'

#87

That’s what I originally did, but there was some issue with the way it worked when I was testing it.

Also I was unable to achieve a throttled way to get the information. While Cheshire East offers the page for general consumption via a web browser, it’s not an api, so I didn’t want to hammer the page. I couldn’t work that out using the scrape sensor.


#88

Very true, Whilst a scrape sensor works I wanted a countdown, which I can’t make work with scrape so i’ve gone down the Node Red route.
It scrapes the data phrases it to number of days until its bin day and passes it back to HA via MQTT
then HA has a automation that kicks off the Node Red flow everyday just after midnight
This allowed me to have one sensor for each bin.
image

I may tweak it yet so it displays “Today” instead of 0 Days and “Tomorrow” instead of 1 Days


#89

That’s amazing nice work


#90

Quick question as I’m a bit stuck. I get the following json from my cities api.

{ "next_pickup_date_long": "Thursday, July 26th", "next_recycling_date": "7-26-2018", "next_recycling": "The next pickup is trash only."}

And I’m trying to get the next_recycling and next_pickup_date_long into some sensors:

- platform: rest
  resource: !secret trashapi
  name: Trash Next Recycling
  value_template: '{{json.next_recycling}}'
- platform: rest
  resource: !secret trashapi
  name: Trash Next Pickup
  value_template: '{{json.next_pickup_date_long}}'

And despite being able to use the template tool to get the correct output they give “unknown”.

I’ve tried quite a few things including:

value_template: '{{value_json.next_pickup_date_long}}'

Hoping someone might be able to tell me what a bonehead I am and point me in the right direction.

Thanks in advance.


#91

What does the sensor say in the logs? (Info logging level)


#92

I hadn’t noticed before, but it appears the json is wrapped in square brackets. Won’t be at home for a while to test, but I think I’ll have to find a way to strip them. Thanks, I’m going to blame lack of sleep for ignoring the logs.


#93

Just to follow up. I had tried many variations of this syntax, but never landed on the winning combo until now. So to help the next guy, here it is:

value_template: '{{value_json[0].next_pickup_date_long}}'


#94

Hey,

Trying to pull something similar from my local council’s site (fake address) https://mapping.dorsetforyou.gov.uk/mylocal/viewresults/10091017024

Doesn’t look like I can use your method as the resulting site looks much more complicated than yours. Don’t suppose you could take a look and see if it’s possible?

I did look at the scrape method for html-tag but don’t think there’s unique selectors for each bin collection.

Thanks


#95

Lots of awesome work here, but I’ve gotta say I think everyone is missing a more elegant and universal solution - iCal - most of these councils provide ical feeds (because that’s actually the right thing for them to provide!)

I’m using a customised version of this: Integrate iCal-Events

and then I have the following configuration (I should ideally have a scan limit/refresh rate set in config, but it’s hardcoded in my custom component):

sensor:
  - platform: ical
    name: "Refuse Collection"
    url: "councils-ical-url!"
    prefix: xxxx
    map:
      - value: "Black box"
        icon: mdi:glass-tulip
        name: "Black - glass and electronics Bin"
      - value: "Green refuse"
        icon: mdi:delete
        name: "Green - refuse Bin"
      - value: "Brown garden bin"
        icon: mdi:nature
        name: "Brown - garden bin"
      - value: "Blue recycling bin"
        icon: mdi:recycle
        name: "Blue - recycling Bin"
      - value: "Food caddy"
        icon: mdi:food
        name: "Grey - food Bin"

automation:
  - alias: Evening bins notification_018383
    trigger:
      - platform: event
        event_type: early_eve
    condition:
      condition: or
      conditions:
        - condition: template
          value_template: '{{ states.sensor.xxxx0.attributes.days == 1 }}'
        - condition: template
          value_template: '{{ states.sensor.xxxx1.attributes.days == 1 }}'
        - condition: template
          value_template: '{{ states.sensor.xxxx2.attributes.days == 1 }}'
        - condition: template
          value_template: '{{ states.sensor.xxxx3.attributes.days == 1 }}'
        - condition: template
          value_template: '{{ states.sensor.xxxx4.attributes.days == 1 }}'
    action:
      - service: notify.slk
        data_template:
          message:  "{% set counter = 0 %} {% if states.sensor.xxxx0.attributes.days == 1  %}{{ states.sensor.xxxx0.attributes.friendly_name}}{% if counter > 0 %},{% endif %}{% endif %}{% if states.sensor.xxxx1.attributes.days == 1  %}{{ states.sensor.xxxx1.attributes.friendly_name }}{% if counter > 0 %},{% endif %} {% endif %}{% if states.sensor.xxxx2.attributes.days == 1  %}{{ states.sensor.xxxx2.attributes.friendly_name }}{% if counter > 0 %},{% endif %} {% endif %} {% if states.sensor.xxxx3.attributes.days == 1  %}{{ states.sensor.xxxx3.attributes.friendly_name }}{% if counter > 0 %},{% endif %} {% endif %}{% if states.sensor.xxxx4.attributes.days == 1  %}{{ states.sensor.xxxx4.attributes.friendly_name }} {% if counter > 0 %},{% endif %}{% endif %}needs to be taken out tommorrow"

#96

Infact - here you go ya filthy animals :joy::

"""
Support for iCal-URLs

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.ical/
"""
import logging
from datetime import timedelta

#import voluptuous as vol



import datetime as dt
from datetime import *
from homeassistant.util import Throttle
from homeassistant.helpers.entity import Entity
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import STATE_UNKNOWN, CONF_NAME
import homeassistant.helpers.config_validation as cv
import voluptuous as vol



DOMAIN='group_avaerage'
DEFAULT_NAME="GROUPING"

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})



import icalendar, requests, arrow


_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['icalendar', 'requests', 'arrow>=0.10.0']

ICON = 'mdi:calendar'
DEFAULT_NAME = 'iCal Sensor'
DEFAULT_MAX_EVENTS = 5
DOMAIN = 'ical'

# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)

def setup_platform(hass, config, add_devices, discovery_info=None):
    """Setup the iCal Sensor."""
    url = config.get('url')
    SNS_PREFIX = config.get("prefix", "sensor")
    name = config.get('name', DEFAULT_NAME)
    mp = config.get("map")
    if mp is not None: 
        mp = list(mp)
    maxevents = config.get('maxevents',DEFAULT_MAX_EVENTS)

    if url is None:
        _LOGGER.error('Missing required variable: "url"')
        return False

    data_object = ICalData(url)
    data_object.update()

    if data_object.data is None:
        _LOGGER.error('Unable to fetch iCal')
        return False

    sensors = []
    for eventnumber in range(maxevents):
        sensors.append(ICalSensor(hass,data_object,eventnumber, SNS_PREFIX, mp))

    add_devices(sensors)

   # add_devices_callback([ICalSensor(hass, data_object,name)])
    return True


def dateparser(calendar,date):
    events = []
    for event in calendar.walk('VEVENT'): 
        if type(event['DTSTART'].dt) is dt.date:
            start = arrow.get(event['DTSTART'].dt)
            start = start.replace(tzinfo='local')
        else: start = event['DTSTART'].dt
        if type(event['DTEND'].dt) is dt.date:
            end = arrow.get(event['DTEND'].dt)
            end = end.replace(tzinfo='local')
        else: end = event['DTEND'].dt
        if start.date() >= date.date():
            events.append(dict(name=event['SUMMARY'],begin=start))
    sorted_events = sorted(events, key=lambda k: k['begin'])
    _LOGGER.info(sorted_events)
    return sorted_events

# pylint: disable=too-few-public-methods
class ICalSensor(Entity):
    """Implementation of a iCal sensor."""
    def __init__(self, hass, data_object, eventnumber,SNS_PREFIX, mp):
        """Initialize the sensor."""
        self._eventno = eventnumber
        self._hass = hass
        self._days = 1000
        self._pickup_type = data_object.data[eventnumber]['name']
        self.data_object = data_object
        self._name = SNS_PREFIX + str(eventnumber)
        self._entity_id = 'sensor.'+SNS_PREFIX + str(eventnumber)
        self.entity_id = 'sensor.'+SNS_PREFIX + str(eventnumber)
        self.object_id = 'sensor.'+SNS_PREFIX + str(eventnumber)
        self._icn = "help-circle-outline"
        self.ma = mp

        self.update()

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._friendly_name

    @property
    def icon(self):
        """Return the icon for the frontend."""
        return ICON

    @property
    def state(self):
        """Return the date of the next event."""
        return self._state

    @property
    def device_state_attributes(self):
        """Return the state attributes of the device."""
        attr = {}
        attr['friendly_name'] = self._friendly_name
        attr['pickup_type'] = self._pickup_type
        attr['icon'] = self._icn
        attr['days'] = self._days
        #attr[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
        return attr


    def update(self):
        """Get the latest update and set the state."""
        self.data_object.update()
        e = self.data_object.data
        if self._eventno not in range(0,len(e)):
            self._state = "No event"
        else:
            if not e:
                self._state = "No event"
            else:
                val = e[self._eventno]
                #self._name = val['name']
                #self._friendlyname = val['name']
                #self._name = val['name']
                # glass = glass-tulip
                # recycling = recycle
                # food = food
                # general = delete
                # garden = nature
                # default = help-circle-outline
                #if ! map is none:
                icon = "help-circle-outline"
                t = val["name"]
                mp = self.ma
                if mp is not None:
                    for m in mp:
                        if m["value"] in val['name']:
                            icon = m['icon']
                            t = m['name']

#                    if "Food caddy" in val['name']:
#                        icon = "food"
#                        t = "Grey - food Bin"
#                    if "Blue recycling bin" in val['name']:
#                        icon = "recycle"
#                        t = "Blue - recycling Bin"
#                    if "Brown garden bin" in val['name']:
#                        icon = "nature"
#                        t = "Brown - garden bin"
#                    if "Green refuse" in val['name']:
#                        icon = "delete"
#                        t = "Green - refuse Bin"
#                    if "Black box" in val['name']:
#                        icon = "glass-tulip"
#                        t = "Black - glass and electronics Bin"




                self._icn = "mdi:" + icon
                self._pickup_type = t
                self.friendly_name = t
                self._friendly_name = t
                
                today = date.today()
                year =  int(val['begin'].strftime("%Y"))
                month =  int(val['begin'].strftime("%-m"))
                day =  int(val['begin'].strftime("%-d"))
                currentlyis = today.strftime("%a");
                future = date(year,month,day)
                v = int((future - today).days)
                output = ''      
                currentdayindice = int(date.today().strftime("%w"))
                if v == 0:
                    output = "Today"
                if v == 1:
                    output = "Tommorrow"
                w = currentdayindice + v
                currentdayindice = int(date.today().strftime("%w"))
                onday = future.strftime("%A")
                if v >= 2:
                    output = '' 
                    dayarr = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
                    dow = (7-currentdayindice)+v+2
                    
                    while dow >= 7:
                        dow = dow - 7
                    output = "{}".format(onday)
                    #output = dow
                if v >= 7:
                    s = ''
                    if v >= 14: 
                        s = "s"
                    output = "{} week{} from {} ".format(int(v/7), s, onday)
                self._state = output
                self._days = v


                #self._state = (future - today).days

                #self._state = val['begin'].strftime("%-d %B %Y")
                #self._state = "{} - {}".format( val['name'], val['begin'].strftime("%-d %B %Y"))

#pylint: disable=too-few-public-methods
class ICalData(object):
    """Class for handling the data retrieval."""

    def __init__(self, resource):
        self._request = requests.Request('GET', resource).prepare()
        self.data = None

    @Throttle(MIN_TIME_BETWEEN_UPDATES)
    def update(self):
        self.data = []

        try:
            with requests.Session() as sess:
                response = sess.send(self._request, timeout=10)

            cal = icalendar.Calendar.from_ical(response.text)
            today = arrow.utcnow()
            events = dateparser(cal,today)

            self.data = events

        except requests.exceptions.RequestException:
            _LOGGER.error("Error fetching data: %s", self._request)
            self.data = None

#97

i think there are more that provide gcal (and that would be the right thing, because more people use gcal then ical :wink: )


#98

:man_facepalming: google does ical - I’m not referring to “icloud calendar” but rather to ics files (commonly called ical as it’s full name is icalendar) - which is a standard and supported by many companies (including google :+1:) https://tools.ietf.org/html/rfc5545

source: I too use google calendar, but I also use ical feeds as it’s the standardised way to share calendar information


#99

then if thats the same thing, why use a custom component?
just add the google calendar to your HA and the ical file to your google calendar


#100
  1. when I put that together, the google calendar thing was different
  2. I assume the google calendar thing names the sensors (IIRC that was how it did it back then) based on the name of the event - not good for this
  3. the custom component does a little extra
  4. the custom component negates the need to import the bin events in to your Google Calendar
  5. it’s simpler (less 3rd parties involved)