From 15cf10cf5077f7fc2d659752fe3f210d43d46afa Mon Sep 17 00:00:00 2001 From: constvariable <33699989+constvariable@users.noreply.github.com> Date: Thu, 8 Dec 2022 12:19:36 -0500 Subject: [PATCH 1/5] Create toronto_ca.py Adding toronto_ca source for Toronto waste collection service --- .../source/toronto_ca.py | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py new file mode 100644 index 00000000..7a0fdca7 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py @@ -0,0 +1,112 @@ +import requests +import csv +import json + +from ..collection import Collection + +from datetime import datetime, timedelta + +TITLE = 'Toronto' +DESCRIPTION = ( + 'Source for Toronto waste collection' +) +URL = 'https://www.toronto.ca' +CSV_URL = 'https://www.toronto.ca/ext/swms/collection_calendar.csv' +TEST_CASES = { + "224 Wallace Ave": {"street_address": "224 Wallace Ave"}, +} + +PROPERTY_LOOKUP_URL = 'https://map.toronto.ca/cotgeocoder/rest/geocoder/suggest' +SCHEDULE_LOOKUP_URL = 'https://map.toronto.ca/cotgeocoder/rest/geocoder/findAddressCandidates' + +ICON_MAP = { + 'GreenBin': 'mdi:compost', + 'Garbage': 'mdi:trash-can', + 'Recycling': 'mdi:recycle', + 'YardWaste': 'mdi:grass', + 'ChristmasTree': 'mdi:pine-tree', +} + +PICTURE_MAP = { + 'GreenBin': 'https://www.toronto.ca/resources/swm_collection_calendar/img/greenbin.png', + 'Garbage': 'https://www.toronto.ca/resources/swm_collection_calendar/img/garbagebin.png', + 'Recycling': 'https://www.toronto.ca/resources/swm_collection_calendar/img/bluebin.png', + 'YardWaste': 'https://www.toronto.ca/resources/swm_collection_calendar/img/yardwaste.png', +} + +class Source: + def __init__(self, street_address): + self._street_address = street_address + + def get_first_result(self, json_data): + results = json_data['result'] + if len(results) == 0: + return '' + + rows = results['rows'] + if len(rows) == 0: + return '' + + return rows[0] + + def fetch(self): + session = requests.Session() + + # lookup the address key for a particular property address + property_download = session.get(PROPERTY_LOOKUP_URL, + params=dict(f='json', matchAddress=1, matchPlaceName=1,matchPostalCode=1,addressOnly=0,retRowLimit=100,searchString=self._street_address)) + + property_content = property_download.content.decode('utf-8'); + property_json = json.loads(property_content); + + first_property_result = self.get_first_result(property_json) + property_address_key = first_property_result['KEYSTRING'] + + # lookup the schedule key for the above property key + schedule_download = session.get(SCHEDULE_LOOKUP_URL, + params=dict(keyString=property_address_key, unit='%', areaTypeCode1='RESW')) + schedule_content = schedule_download.content.decode('utf-8') + schedule_json = json.loads(schedule_content) + + schedule_key = self.get_first_result(schedule_json)['AREACURSOR1']['array'][0]['AREA_NAME'].replace(' ', '') + + # download schedule csv and figure out what column format + csv_download = session.get(CSV_URL) + csv_content = csv_download.content.decode('utf-8') + + csv_lines = list(csv.reader(csv_content.splitlines(), delimiter=',')) + + dbkey_row = csv_lines[0] + + id_index = dbkey_row.index('_id'); + schedule_index = dbkey_row.index('Calendar'); + week_index = dbkey_row.index('WeekStarting') + + format = '%Y-%m-%d' + days_of_week = 'MTWRFSX' + + entries = [] + + for row in csv_lines[1:]: + if row[schedule_index] == schedule_key: + pickup_date = datetime.strptime(row[week_index], format) + startweek_day_key = pickup_date.weekday() + + for i in range(len(row)): + # skip non-waste types + if (i == id_index) or (i == schedule_index) or (i == week_index): + continue + + if row[i] not in days_of_week: + continue + + day_key = days_of_week.index(row[i]) + waste_day = pickup_date + timedelta(day_key - startweek_day_key) + waste_type = dbkey_row[i] + + pic = PICTURE_MAP[waste_type] if waste_type in PICTURE_MAP else "" + icon = ICON_MAP[waste_type] if waste_type in ICON_MAP else "" + + entries.append(Collection(waste_day.date(), waste_type, picture=pic, icon=icon)) + + return entries From 940ef9ba9ec637e47ead03cd78e0f9692cf2e112 Mon Sep 17 00:00:00 2001 From: constvariable <33699989+constvariable@users.noreply.github.com> Date: Sat, 10 Dec 2022 07:54:27 -0500 Subject: [PATCH 2/5] Update toronto_ca.py Cleanup, error proof and add another test case for toronto_ca.py source --- .../source/toronto_ca.py | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py index 7a0fdca7..2f5b14c6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py @@ -14,6 +14,7 @@ URL = 'https://www.toronto.ca' CSV_URL = 'https://www.toronto.ca/ext/swms/collection_calendar.csv' TEST_CASES = { "224 Wallace Ave": {"street_address": "224 Wallace Ave"}, + "324 Weston Rd": {"street_address": "324 Weston Rd"}, } PROPERTY_LOOKUP_URL = 'https://map.toronto.ca/cotgeocoder/rest/geocoder/suggest' @@ -38,7 +39,7 @@ class Source: def __init__(self, street_address): self._street_address = street_address - def get_first_result(self, json_data): + def get_first_result(self, json_data, key): results = json_data['result'] if len(results) == 0: return '' @@ -47,7 +48,10 @@ class Source: if len(rows) == 0: return '' - return rows[0] + if key not in rows[0]: + return '' + + return rows[0][key] def fetch(self): session = requests.Session() @@ -56,30 +60,35 @@ class Source: property_download = session.get(PROPERTY_LOOKUP_URL, params=dict(f='json', matchAddress=1, matchPlaceName=1,matchPostalCode=1,addressOnly=0,retRowLimit=100,searchString=self._street_address)) - property_content = property_download.content.decode('utf-8'); - property_json = json.loads(property_content); + property_json = json.loads(property_download.content.decode('utf-8')) - first_property_result = self.get_first_result(property_json) - property_address_key = first_property_result['KEYSTRING'] + property_address_key = self.get_first_result(property_json, 'KEYSTRING') + if property_address_key == '': + return # lookup the schedule key for the above property key schedule_download = session.get(SCHEDULE_LOOKUP_URL, params=dict(keyString=property_address_key, unit='%', areaTypeCode1='RESW')) - schedule_content = schedule_download.content.decode('utf-8') - schedule_json = json.loads(schedule_content) + schedule_json = json.loads(schedule_download.content.decode('utf-8')) - schedule_key = self.get_first_result(schedule_json)['AREACURSOR1']['array'][0]['AREA_NAME'].replace(' ', '') + schedule_first_result = self.get_first_result(schedule_json, 'AREACURSOR1') + if schedule_first_result == '': + return + + schedule_key = schedule_first_result['array'][0]['AREA_NAME'].replace(' ', '') # download schedule csv and figure out what column format - csv_download = session.get(CSV_URL) - csv_content = csv_download.content.decode('utf-8') + csv_content = session.get(CSV_URL).content.decode('utf-8') csv_lines = list(csv.reader(csv_content.splitlines(), delimiter=',')) dbkey_row = csv_lines[0] - id_index = dbkey_row.index('_id'); - schedule_index = dbkey_row.index('Calendar'); + if ('_id' not in dbkey_row) or ('Calendar' not in dbkey_row) or ('WeekStarting') not in dbkey_row: + return + + id_index = dbkey_row.index('_id') + schedule_index = dbkey_row.index('Calendar') week_index = dbkey_row.index('WeekStarting') format = '%Y-%m-%d' @@ -109,4 +118,4 @@ class Source: entries.append(Collection(waste_day.date(), waste_type, picture=pic, icon=icon)) - return entries + return entries From e8ca746df6761a043c59ffaa845a2fc60fbeff4a Mon Sep 17 00:00:00 2001 From: constvariable <33699989+constvariable@users.noreply.github.com> Date: Sun, 18 Dec 2022 08:06:11 -0500 Subject: [PATCH 3/5] Create toronto_ca.md --- doc/source/toronto_ca.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 doc/source/toronto_ca.md diff --git a/doc/source/toronto_ca.md b/doc/source/toronto_ca.md new file mode 100644 index 00000000..97f57f4e --- /dev/null +++ b/doc/source/toronto_ca.md @@ -0,0 +1,32 @@ +# City of Toronto + +Support for schedules provided by [City of Toronto](https://www.toronto.ca/services-payments/recycling-organics-garbage/houses/collection-schedule/). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: toronto_ca + args: + street_address: STREET_ADDRESS +``` + +### Configuration Variables + +**street_address**
+*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: toronto_ca + args: + street_address: 324 Weston Rd +``` + +## How to verify that your address works + +Visit the [City of Toronto](https://www.toronto.ca/services-payments/recycling-organics-garbage/houses/collection-schedule/) page and search for your address. The string you search for there should match the string in the street_address argument. From 3197b9afd31a99475c9f1ea03ded2442bf84e6a3 Mon Sep 17 00:00:00 2001 From: constvariable <33699989+constvariable@users.noreply.github.com> Date: Sun, 18 Dec 2022 08:08:50 -0500 Subject: [PATCH 4/5] Update README.md with toronto_ca source --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 30cbb618..bf15ba75 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,9 @@ Currently the following service providers are supported: - [Hygea.be](./doc/source/hygea_be.md) - [Recycle! / RecycleApp.be](./doc/source/recycleapp_be.md) +### Canada +- [City of Toronto](./doc/source/toronto_ca.md) + ### Germany - [Abfall.IO / AbfallPlus.de](./doc/source/abfall_io.md) From 3d4fbee4f799e1dd366b5c7be811f68594b334b1 Mon Sep 17 00:00:00 2001 From: constvariable <33699989+constvariable@users.noreply.github.com> Date: Sun, 18 Dec 2022 08:14:57 -0500 Subject: [PATCH 5/5] Update info.md --- info.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/info.md b/info.md index 73f61bfb..0a933d4f 100644 --- a/info.md +++ b/info.md @@ -68,6 +68,9 @@ Currently the following service providers are supported: - [Hygea](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/hygea_be.md) - [Recycle! / RecycleApp.be](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/recycleapp_be.md) +### Canada +- [City of Toronto](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/toronto_ca.md) + ### Germany - [Abfall.IO / AbfallPlus.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfall_io.md)