diff --git a/README.md b/README.md index cfbb99c9..b3ba8770 100644 --- a/README.md +++ b/README.md @@ -1,831 +1,394 @@ + + +Waste Collection Schedule logo + # Waste Collection Schedule -Waste Collection Schedule provides schedules from waste collection service providers to Home Assistant. Additionally, it supports schedules from generic ICS files which can be stored locally or fetched from a web site. There is a high flexibility in providing the information to be displayed. +**A custom component for Home Assistant that retrieves waste collection schedules from a wide range of service providers.** + +Waste Collection Schedule animation + +Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. + +# Supported Service Providers + +Waste collection schedules in the following formats and countries are supported. Click on the section heading to view details of individual service providers. + +
+ICS/iCal and User-Specified + +- [Generic ICS / iCal File](/doc/source/ics.md) +- [User Specified](/doc/source/static.md) +
+ + +
+Australia + +- [Banyule City Council](/doc/source/banyule_vic_gov_au.md) / banyule.vic.gov.au +- [Belmont City Council](/doc/source/belmont_wa_gov_au.md) / belmont.wa.gov.au +- [Brisbane City Council](/doc/source/brisbane_qld_gov_au.md) / brisbane.qld.gov.au +- [Campbelltown City Council](/doc/source/campbelltown_nsw_gov_au.md) / campbelltown.nsw.gov.au +- [City of Canada Bay Council](/doc/source/canadabay_nsw_gov_au.md) / canadabay.nsw.gov.au +- [Gold Coast City Council](/doc/source/goldcoast_qld_gov_au.md) / goldcoast.qld.gov.au +- [Inner West Council (NSW)](/doc/source/innerwest_nsw_gov_au.md) / innerwest.nsw.gov.au +- [Ipswich City Council](/doc/source/ipswich_qld_gov_au.md) / ipswich.qld.gov.au +- [Ku-ring-gai Council](/doc/source/kuringgai_nsw_gov_au.md) / krg.nsw.gov.au +- [Macedon Ranges Shire Council](/doc/source/mrsc_vic_gov_au.md) / mrsc.vic.gov.au +- [Maroondah City Council](/doc/source/maroondah_vic_gov_au.md) / maroondah.vic.gov.au +- [Melton City Council](/doc/source/melton_vic_gov_au.md) / melton.vic.gov.au +- [Nillumbik Shire Council](/doc/source/nillumbik_vic_gov_au.md) / nillumbik.vic.gov.au +- [North Adelaide Waste Management Authority](/doc/source/nawma_sa_gov_au.md) / nawma.sa.gov.au +- [RecycleSmart](/doc/source/recyclesmart_com.md) / recyclesmart.com +- [Stonnington City Council](/doc/source/stonnington_vic_gov_au.md) / stonnington.vic.gov.au +- [The Hills Shire Council, Sydney](/doc/source/thehills_nsw_gov_au.md) / thehills.nsw.gov.au +- [Wyndham City Council, Melbourne](/doc/source/wyndham_vic_gov_au.md) / wyndham.vic.gov.au +
+ +
+Austria + +- [Burgenländischer Müllverband](/doc/source/bmv_at.md) / bmv.at +- [infeo](/doc/source/infeo_at.md) / infeo.at +- [Stadtservice Korneuburg](/doc/source/korneuburg_stadtservice_at.md) / korneuburg.gv.at +- [Umweltprofis](/doc/source/data_umweltprofis_at.md) / umweltprofis.at +- [WSZ Moosburg](/doc/source/wsz_moosburg_at.md) / wsz-moosburg.at +
+ +
+Belgium + +- [Hygea](/doc/source/hygea_be.md) / hygea.be +- [Recycle!](/doc/source/recycleapp_be.md) / recycleapp.be +
+ +
+Canada + +- [City of Toronto](/doc/source/toronto_ca.md) / toronto.ca +
+ +
+Germany + +- [Abfall Stuttgart](/doc/source/stuttgart_de.md) / service.stuttgart.de +- [Abfall.IO / AbfallPlus](/doc/source/abfall_io.md) / abfallplus.de +- [Abfallkalender Würzburg](/doc/source/wuerzburg_de.md) / wuerzburg.de +- [AbfallNavi (RegioIT.de)](/doc/source/abfallnavi_de.md) / regioit.de +- [Abfalltermine Forchheim](/doc/source/abfalltermine_forchheim_de.md) / abfalltermine-forchheim.de +- [Abfallwirtschaft Alb-Donau-Kreis](/doc/source/buergerportal_de.md) / aw-adk.de +- [Abfallwirtschaft Landkreis Harburg](/doc/source/aw_harburg_de.md) / landkreis-harburg.de +- [Abfallwirtschaft Landkreis Wolfenbüttel](/doc/source/alw_wf_de.md) / alw-wf.de +- [Abfallwirtschaft Neckar-Odenwald-Kreis](/doc/source/awn_de.md) / awn-online.de +- [Abfallwirtschaft Nürnberger Land](/doc/source/nuernberger_land_de.md) / nuernberger-land.de +- [Abfallwirtschaft Rendsburg](/doc/source/awr_de.md) / awr.de +- [Abfallwirtschaft Südholstein](/doc/source/awsh_de.md) / awsh.de +- [Abfallwirtschaft Werra-Meißner-Kreis](/doc/source/zva_wmk_de.md) / zva-wmk.de +- [Abfallwirtschaft Zollernalbkreis](/doc/source/abfall_zollernalbkreis_de.md) / abfallkalender-zak.de +- [Abfallwirtschaftsbetrieb Esslingen](/doc/source/awb_es_de.md) / awb-es.de +- [Abfallwirtschaftsbetrieb Landkreis Ahrweiler](/doc/source/meinawb_de.md) / meinawb.de +- [ART Trier](/doc/source/art_trier_de.md) / art-trier.de +- [AWB Bad Kreuznach](/doc/source/awb_bad_kreuznach_de.md) / app.awb-bad-kreuznach.de +- [AWB Köln](/doc/source/awbkoeln_de.md) / awbkoeln.de +- [AWB Landkreis Augsburg](/doc/source/c_trace_de.md) / awb-landkreis-augsburg.de +- [AWB Oldenburg](/doc/source/awb_oldenburg_de.md) / oldenburg.de +- [AWIDO Online](/doc/source/awido_de.md) / awido-online.de +- [Berlin Recycling](/doc/source/berlin_recycling_de.md) / berlin-recycling.de +- [Berliner Stadtreinigungsbetriebe](/doc/source/bsr_de.md) / bsr.de +- [Bielefeld](/doc/source/bielefeld_de.md) / bielefeld.de +- [Bogenschütz Entsorgung](/doc/source/infeo_at.md) / bogenschuetz-entsorgung.de +- [Bremener Stadreinigung](/doc/source/c_trace_de.md) / die-bremer-stadtreinigung.de +- [Bürgerportal](/doc/source/buergerportal_de.md) / c-trace.de +- [C-Trace](/doc/source/c_trace_de.md) / c-trace.de +- [EGN Abfallkalender](/doc/source/egn_abfallkalender_de.md) / egn-abfallkalender.de +- [Jumomind](/doc/source/jumomind_de.md) / jumomind.de +- [KAEV Niederlausitz](/doc/source/kaev_niederlausitz.md) / kaev.de +- [Kreiswirtschaftsbetriebe Goslar](/doc/source/kwb_goslar_de.md) / kwb-goslar.de +- [KV Cochem-Zell](/doc/source/buergerportal_de.md) / cochem-zell-online.de +- [KWU Entsorgung Landkreis Oder-Spree](/doc/source/kwu_de.md) / kwu-entsorgung.de +- [Landkreis Erlangen-Höchstadt](/doc/source/erlangen_hoechstadt_de.md) / erlangen-hoechstadt.de +- [Landkreis Nordwestmecklenburg](/doc/source/geoport_nwm_de.md) / geoport-nwm.de +- [Landkreis Rhön Grabfeld](/doc/source/landkreis_rhoen_grabfeld.md) / abfallinfo-rhoen-grabfeld.de +- [Landkreis Schwäbisch Hall](/doc/source/lrasha_de.md) / lrasha.de +- [Landkreis Wittmund](/doc/source/landkreis_wittmund_de.md) / landkreis-wittmund.de +- [MZV Bidenkopf](/doc/source/buergerportal_de.md) / mzv-biedenkopf.de +- [Müllmax](/doc/source/muellmax_de.md) / muellmax.de +- [Neunkirchen Siegerland](/doc/source/abfall_neunkirchen_siegerland_de.md) / neunkirchen-siegerland.de +- [RegioEntsorgung Städteregion Aachen](/doc/source/regioentsorgung_de.md) / regioentsorgung.de +- [Rhein-Hunsrück Entsorgung (RHE)](/doc/source/rh_entsorgung_de.md) / rh-entsorgung.de +- [Sector 27 - Datteln, Marl, Oer-Erkenschwick](/doc/source/sector27_de.md) / muellkalender.sector27.de +- [Stadt Willich](/doc/source/stadt_willich_de.md) / stadt-willich.de +- [Stadtreinigung Dresden](/doc/source/stadtreinigung_dresden_de.md) / dresden.de +- [Stadtreinigung Hamburg](/doc/source/stadtreinigung_hamburg.md) / stadtreinigung.hamburg +- [Stadtreinigung Leipzig](/doc/source/stadtreinigung_leipzig_de.md) / stadtreinigung-leipzig.de +- [StadtService Brühl](/doc/source/stadtservice_bruehl_de.md) / stadtservice-bruehl.de +- [Städteservice Raunheim Rüsselsheim](/doc/source/staedteservice_de.md) / staedteservice.de +- [Südbrandenburgischer Abfallzweckverband](/doc/source/sbazv_de.md) / sbazv.de +- [Wermelskirchen](/doc/source/wermelskirchen_de.md) / wermelskirchen.de +- [Wolfsburger Abfallwirtschaft und Straßenreinigung](/doc/source/was_wolfsburg_de.md) / was-wolfsburg.de +- [WZV Kreis Segeberg](/doc/source/c_trace_de.md) / wzv.de +
+ +
+Lithuania + +- [Kauno švara](/doc/source/grafikai_svara_lt.md) / grafikai.svara.lt +
+ +
+Netherlands + +- [ACV Group](/doc/source/ximmio_nl.md) / acv-afvalkalender.nl +- [Alpen an den Rijn](/doc/source/hvcgroep_nl.md) / alphenaandenrijn.nl +- [Area Afval](/doc/source/ximmio_nl.md) / area-afval.nl +- [Avalex](/doc/source/ximmio_nl.md) / avalex.nl +- [Avri](/doc/source/ximmio_nl.md) / avri.nl +- [Bar Afvalbeheer](/doc/source/ximmio_nl.md) / bar-afvalbeheer.nl +- [Cyclus NV](/doc/source/hvcgroep_nl.md) / cyclusnv.nl +- [Dar](/doc/source/hvcgroep_nl.md) / dar.nl +- [Den Haag](/doc/source/hvcgroep_nl.md) / denhaag.nl +- [GAD](/doc/source/hvcgroep_nl.md) / gad.nl +- [Gemeente Almere](/doc/source/ximmio_nl.md) / almere.nl +- [Gemeente Berkelland](/doc/source/hvcgroep_nl.md) / gemeenteberkelland.nl +- [Gemeente Cranendonck](/doc/source/hvcgroep_nl.md) / cranendonck.nl +- [Gemeente Hellendoorn](/doc/source/ximmio_nl.md) / hellendoorn.nl +- [Gemeente Lingewaard](/doc/source/hvcgroep_nl.md) / lingewaard.nl +- [Gemeente Meppel](/doc/source/ximmio_nl.md) / meppel.nl +- [Gemeente Middelburg + Vlissingen](/doc/source/hvcgroep_nl.md) / middelburgvlissingen.nl +- [Gemeente Peel en Maas](/doc/source/hvcgroep_nl.md) / peelenmaas.nl +- [Gemeente Schouwen-Duiveland](/doc/source/hvcgroep_nl.md) / schouwen-duiveland.nl +- [Gemeente Sudwest-Fryslan](/doc/source/hvcgroep_nl.md) / sudwestfryslan.nl +- [Gemeente Venray](/doc/source/hvcgroep_nl.md) / venray.nl +- [Gemeente Voorschoten](/doc/source/hvcgroep_nl.md) / voorschoten.nl +- [Gemeente Wallre](/doc/source/hvcgroep_nl.md) / waalre.nl +- [Gemeente Westland](/doc/source/ximmio_nl.md) / gemeentewestland.nl +- [HVC Groep](/doc/source/hvcgroep_nl.md) / hvcgroep.nl +- [Meerlanden](/doc/source/ximmio_nl.md) / meerlanden.nl +- [Mijn Blink](/doc/source/hvcgroep_nl.md) / mijnblink.nl +- [PreZero](/doc/source/hvcgroep_nl.md) / prezero.nl +- [Purmerend](/doc/source/hvcgroep_nl.md) / purmerend.nl +- [RAD BV](/doc/source/ximmio_nl.md) / radbv.nl +- [Reinigingsbedrijf Midden Nederland](/doc/source/hvcgroep_nl.md) / rmn.nl +- [Reinis](/doc/source/ximmio_nl.md) / reinis.nl +- [Spaarne Landen](/doc/source/hvcgroep_nl.md) / spaarnelanden.nl +- [Stadswerk 072](/doc/source/hvcgroep_nl.md) / stadswerk072.nl +- [Twente Milieu](/doc/source/ximmio_nl.md) / twentemilieu.nl +- [Waardlanden](/doc/source/ximmio_nl.md) / waardlanden.nl +- [Ximmio](/doc/source/ximmio_nl.md) / ximmio.nl +- [ZRD](/doc/source/hvcgroep_nl.md) / zrd.nl +
+ +
+New Zealand + +- [Auckland Council](/doc/source/aucklandcouncil_govt_nz.md) / aucklandcouncil.govt.nz +- [Christchurch City Council](/doc/source/ccc_govt_nz.md) / ccc.govt.nz +- [Gore, Invercargill & Southland](/doc/source/wastenet_org_nz.md) / wastenet.org.nz +- [Horowhenua District Council](/doc/source/horowhenua_govt_nz.md) / horowhenua.govt.nz +- [Waipa District Council](/doc/source/waipa_nz.md) / waipadc.govt.nz +- [Wellington City Council](/doc/source/wellington_govt_nz.md) / wellington.govt.nz +
+ +
+Norway + +- [Min Renovasjon](/doc/source/minrenovasjon_no.md) / norkart.no +- [Oslo Kommune](/doc/source/oslokommune_no.md) / oslo.kommune.no +
+ +
+Poland + +- [Ecoharmonogram](/doc/source/ecoharmonogram_pl.md) / ecoharmonogram.pl +- [Warsaw](/doc/source/warszawa19115_pl.md) / warszawa19115.pl +
+ +
+Sweden + +- [Lerum Vatten och Avlopp](/doc/source/lerum_se.md) / vatjanst.lerum.se +- [Ronneby Miljöteknik](/doc/source/miljoteknik_se.md) / fyrfackronneby.se +- [SRV Återvinning](/doc/source/srvatervinning_se.md) / srvatervinning.se +- [SSAM](/doc/source/ssam_se.md) / ssam.se +- [Sysav Sophämntning](/doc/source/sysav_se.md) / sysav.se +- [VA Syd Sophämntning](/doc/source/vasyd_se.md) / vasyd.se +
+ +
+Switzerland + +- [A-Region](/doc/source/a_region_ch.md) / a-region.ch +- [Andwil](/doc/source/a_region_ch.md) / a-region.ch +- [Appenzell](/doc/source/a_region_ch.md) / a-region.ch +- [Berg](/doc/source/a_region_ch.md) / a-region.ch +- [Bühler](/doc/source/a_region_ch.md) / a-region.ch +- [Eggersriet](/doc/source/a_region_ch.md) / a-region.ch +- [Gais](/doc/source/a_region_ch.md) / a-region.ch +- [Gaiserwald](/doc/source/a_region_ch.md) / a-region.ch +- [Goldach](/doc/source/a_region_ch.md) / a-region.ch +- [Grub](/doc/source/a_region_ch.md) / a-region.ch +- [Heiden](/doc/source/a_region_ch.md) / a-region.ch +- [Herisau](/doc/source/a_region_ch.md) / a-region.ch +- [Horn](/doc/source/a_region_ch.md) / a-region.ch +- [Hundwil](/doc/source/a_region_ch.md) / a-region.ch +- [Häggenschwil](/doc/source/a_region_ch.md) / a-region.ch +- [Lindau](/doc/source/lindau_ch.md) / lindau.ch +- [Lutzenberg](/doc/source/a_region_ch.md) / a-region.ch +- [Muolen](/doc/source/a_region_ch.md) / a-region.ch +- [Mörschwil](/doc/source/a_region_ch.md) / a-region.ch +- [Rehetobel](/doc/source/a_region_ch.md) / a-region.ch +- [Rorschach](/doc/source/a_region_ch.md) / a-region.ch +- [Rorschacherberg](/doc/source/a_region_ch.md) / a-region.ch +- [Schwellbrunn](/doc/source/a_region_ch.md) / a-region.ch +- [Schönengrund](/doc/source/a_region_ch.md) / a-region.ch +- [Speicher](/doc/source/a_region_ch.md) / a-region.ch +- [Stein](/doc/source/a_region_ch.md) / a-region.ch +- [Steinach](/doc/source/a_region_ch.md) / a-region.ch +- [Teufen](/doc/source/a_region_ch.md) / a-region.ch +- [Thal](/doc/source/a_region_ch.md) / a-region.ch +- [Trogen](/doc/source/a_region_ch.md) / a-region.ch +- [Tübach](/doc/source/a_region_ch.md) / a-region.ch +- [Untereggen](/doc/source/a_region_ch.md) / a-region.ch +- [Urnäsch](/doc/source/a_region_ch.md) / a-region.ch +- [Wald](/doc/source/a_region_ch.md) / a-region.ch +- [Waldkirch](/doc/source/a_region_ch.md) / a-region.ch +- [Waldstatt](/doc/source/a_region_ch.md) / a-region.ch +- [Wittenbach](/doc/source/a_region_ch.md) / a-region.ch +- [Wolfhalden](/doc/source/a_region_ch.md) / a-region.ch +
+ +
+United Kingdom + +- [Ashfield District Council](/doc/source/ashfield_gov_uk.md) / ashfield.gov.uk +- [Bracknell Forest Council](/doc/source/bracknell_forest_gov_uk.md) / selfservice.mybfc.bracknell-forest.gov.uk +- [Bradford Metropolitan District Council](/doc/source/bradford_gov_uk.md) / bradford.gov.uk +- [Braintree District Council](/doc/source/braintree_gov_uk.md) / braintree.gov.uk +- [Breckland Council](/doc/source/breckland_gov_uk.md) / breckland.gov.uk/mybreckland +- [Cambridge City Council](/doc/source/cambridge_gov_uk.md) / cambridge.gov.uk +- [Canterbury City Council](/doc/source/canterbury_gov_uk.md) / canterbury.gov.uk +- [Cheshire East Council](/doc/source/cheshire_east_gov_uk.md) / cheshireeast.gov.uk +- [Chesterfield Borough Council](/doc/source/chesterfield_gov_uk.md) / chesterfield.gov.uk +- [City of York Council](/doc/source/york_gov_uk.md) / york.gov.uk +- [Colchester Borough Council](/doc/source/colchester_gov_uk.md) / colchester.gov.uk +- [Cornwall Council](/doc/source/cornwall_gov_uk.md) / cornwall.gov.uk +- [Derby City Council](/doc/source/derby_gov_uk.md) / derby.gov.uk +- [Eastbourne Borough Council](/doc/source/environmentfirst_co_uk.md) / lewes-eastbourne.gov.uk +- [Elmbridge Borough Council](/doc/source/elmbridge_gov_uk.md) / elmbridge.gov.uk +- [Environment First](/doc/source/environmentfirst_co_uk.md) / environmentfirst.co.uk +- [FCC Environment](/doc/source/fccenvironment_co_uk.md) / fccenvironment.co.uk +- [Guildford Borough Council](/doc/source/guildford_gov_uk.md) / guildford.gov.uk +- [Harborough District Council](/doc/source/fccenvironment_co_uk.md) / harborough.gov.uk +- [Huntingdonshire District Council](/doc/source/huntingdonshire_gov_uk.md) / huntingdonshire.gov.uk +- [Lewes District Council](/doc/source/environmentfirst_co_uk.md) / lewes-eastbourne.gov.uk +- [London Borough of Lewisham](/doc/source/lewisham_gov_uk.md) / lewisham.gov.uk +- [Manchester City Council](/doc/source/manchester_uk.md) / manchester.gov.uk +- [Middlesbrough Council](/doc/source/middlesbrough_gov_uk.md) / middlesbrough.gov.uk +- [Newcastle City Council](/doc/source/newcastle_gov_uk.md) / community.newcastle.gov.uk +- [North Somerset Council](/doc/source/nsomerset_gov_uk.md) / n-somerset.gov.uk +- [Nottingham City Council](/doc/source/nottingham_city_gov_uk.md) / nottinghamcity.gov.uk +- [Peterborough City Council](/doc/source/peterborough_gov_uk.md) / peterborough.gov.uk +- [Richmondshire District Council](/doc/source/richmondshire_gov_uk.md) / richmondshire.gov.uk +- [Rushmoor Borough Council](/doc/source/rushmoor_gov_uk.md) / rushmoor.gov.uk +- [Salford City Council](/doc/source/salford_gov_uk.md) / salford.gov.uk +- [Sheffield City Council](/doc/source/sheffield_gov_uk.md) / sheffield.gov.uk +- [South Cambridgeshire District Council](/doc/source/scambs_gov_uk.md) / scambs.gov.uk +- [South Hams District Council](/doc/source/fccenvironment_co_uk.md) / southhams.gov.uk +- [South Norfolk and Broadland Council](/doc/source/south_norfolk_and_broadland_gov_uk.md) / area.southnorfolkandbroadland.gov.uk +- [Stevenage Borough Council](/doc/source/stevenage_gov_uk.md) / stevenage.gov.uk +- [Tewkesbury Borough Council](/doc/source/tewkesbury_gov_uk.md) / tewkesbury.gov.uk +- [The Royal Borough of Kingston Council](/doc/source/kingston_gov_uk.md) / kingston.gov.uk +- [Walsall Council](/doc/source/walsall_gov_uk.md) / walsall.gov.uk +- [West Berkshire Council](/doc/source/westberks_gov_uk.md) / westberks.gov.uk +- [West Devon Borough Council](/doc/source/fccenvironment_co_uk.md) / westdevon.gov.uk +- [Wiltshire Council](/doc/source/wiltshire_gov_uk.md) / wiltshire.gov.uk +
+ +
+United States of America + +- [City of Pittsburgh](/doc/source/pgh_st.md) / pgh.st +- [Republic Services](/doc/source/republicservices_com.md) / republicservices.com +- [Seattle Public Utilities](/doc/source/seattle_gov.md) / myutilities.seattle.gov +
+ + + +--- + +# Installation and Configuration + +![hacs badge](https://img.shields.io/badge/HACS-Default-orange) +![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) + +The Waste Collection Schedule can be installed via [HACS](https://hacs.xyz/), or by manually copying the [`waste_collection_schedule`](https://github.com/mampfes/hacs_waste_collection_schedule/tree/master/custom_components) directory to Home Assistant's `config/custom_components/` directory. For further details see the [installation and configuration](/doc/installation.md) page, or the [FAQ](/doc/faq.md). + +# Contributing To The Project + +![python badge](https://img.shields.io/badge/Made%20with-Python-orange) +![github contributors](https://img.shields.io/github/contributors/mampfes/hacs_waste_collection_schedule?color=orange) +![last commit](https://img.shields.io/github/last-commit/mampfes/hacs_waste_collection_schedule?color=orange) +[![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange)](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492) + +There are several ways of contributing to this project, they include: + +- Adding new service providers +- Updating or improving the documentation +- Helping answer/fix any issues raised +- Join in with the Home Assistant Community discussion + +For further details see [contribution](/doc/contributing.md) guidelines, or take a look at our [online](/doc/online.md) mentions. + + + + + +# Known Issues + +The following waste service providers return errors when running the test_source script: + +- `banyule_vic_gov_au`: JSONDecodeError, caused by not supported Captcha wall +- `awn_de`: all tests return 0 entries + +If you can fix any of these, please raise a Pull Request with the updates. + +--- + +## Home Assistant Hangs + +**Problem:** Home Assistant hangs during restart or configuration check. This occurs typically after Waste Collection Schedule has been added to the configuration. -*For developers:* This framework can be easily enhanced to support further waste collection service providers or other services which provide schedules. +**Root Cause:** Home Assistant tries to install the required Python packages and fails somehow. This is not an issue of Waste Collection Schedule. -If you like this component, please give it a star on [github](https://github.com/mampfes/hacs_waste_collection_schedule). +**Solution:** Try to reinstall Waste Collection Schedule (if you are using HACS) or install the required Python packages manually. This list of required packages can be found in [manifest.json](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/custom_components/waste_collection_schedule/manifest.json#L5). -## Table of Contents +The actual procedure depends on your Home Assistant installation type. -- [Examples](#examples) -- [Supported Service Providers](#supported-service-providers) -- [Installation](#installation) -- [Configuration](#configuration) -- [FAQ](#faq) -- *For developers*: [How to add new sources](#how-to-add-new-sources) - -## Examples - -A complete example can be found [here](./doc/configuration.yaml). - -Per default (without further configuration), the time to the next collection will be shown in an [entity card](https://www.home-assistant.io/lovelace/entity/): - -![Default Lovelace Card](./doc/default-entity.png) - -You can also setup dedicated entities per waste type and show the schedule in various formats: - -![Days to next collections](./doc/days-to-next-collections.png) -![Date of next collections](./doc/date-of-next-collections.png) -![Date and days to next collections](./doc/next-collections-date-and-days.png) - -The information in the more-info popup can be displayed in different formats: - -1. List of upcoming collections: - - ![More info: upcoming](./doc/more-info-upcoming.png) - -2. List of waste types and collection date: - - ![More info: waste types](./doc/more-info-appointment-types.png) - -[Button Card](https://github.com/custom-cards/button-card) can be used to create individual Lovelace cards: - -![Button Card](./doc/button-cards.png) - -The collection schedule will be automatically integrated into the Home Assistant calendar: -![Calendar](./doc/calendar.png) - -## Supported Service Providers - -Currently the following service providers are supported: - -- [Generic ICS / iCal File](./doc/source/ics.md) -- [Static source](./doc/source/static.md) - -### Australia - -- [Banyule City Council](./doc/source/banyule_vic_gov_au.md) -- [Belmont City Council](./doc/source/belmont_wa_gov_au.md) -- [Brisbane City Council](./doc/source/brisbane_qld_gov_au.md) -- [Campbelltown City Council](./doc/source/campbelltown_nsw_gov_au.md) -- [City of Canada Bay Council](./doc/source/canadabay_nsw_gov_au.md) -- [Inner West Council (NSW)](./doc/source/innerwest_nsw_gov_au.md) -- [Ku-ring-gai Council](./doc/source/kuringgai_nsw_gov_au.md) -- [Macedon Ranges Shire Council, Melbourne](./doc/source/mrsc_vic_gov_au.md) -- [Maroondah City Council](./doc/source/maroondah_vic_gov_au.md) -- [Melton City Council, Melbourne](./doc/source/melton_vic_gov_au.md) -- [North Adelaide Waste Management Authority, South Australia](./doc/source/nawma_sa_gov_au.md) -- [RecycleSmart](./doc/source/recyclesmart_com.md) -- [Stonnington City Council, Melbourne](./doc/source/stonnington_vic_gov_au.md) -- [The Hills Council, Sydney](./doc/source/thehills_nsw_gov_au.md) -- [Wyndham City Council, Melbourne](./doc/source/wyndham_vic_gov_au.md) - -### Austria - -- [BMV.at](./doc/source/bmv_at.md) -- [Data.Umweltprofis](./doc/source/data_umweltprofis_at.md) -- [Korneuburg Stadtservice](./doc/source/korneuburg_stadtservice_at.md) -- [WSZ-Moosburg.at](./doc/source/wsz_moosburg_at.md) - -### Belgium - -- [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) -- [AbfallNavi.de (RegioIT.de)](./doc/source/abfallnavi_de.md) -- [Abfallkalender Würzburg](./doc/source/wuerzburg_de.md) -- [Abfalltermine Forchheim](./doc/source/abfalltermine_forchheim_de.md) -- [Abfallwirtschaft Bremen](./doc/source/c_trace_de.md) -- [Abfallwirtschaft Landkreis Harburg](./doc/source/aw_harburg_de.md) -- [Abfallwirtschaft Landkreis Wolfenbüttel](./doc/source/alw_wf_de.md) -- [Abfallwirtschaft Neckar-Odenwald-Kreis](./doc/source/awn_de.md) -- [Abfallwirtschaft Rendsburg](./doc/source/awr_de.md) -- [Abfallwirtschaft Stuttgart](./doc/source/stuttgart_de.md) -- [Abfallwirtschaft Südholstein](./doc/source/awsh_de.md) -- [Abfallwirtschaft Zollernalbkreis](./doc/source/abfall_zollernalbkreis_de.md) -- [ART Trier](./doc/source/art_trier_de.md) -- [AVL Ludwigsburg](./doc/source/avl_ludwigsburg_de.md) -- [AWB Bad Kreuznach](./doc/source/awb_bad_kreuznach_de.md) -- [AWB Esslingen](./doc/source/awb_es_de.md) -- [AWB Landkreis Augsburg](./doc/source/c_trace_de.md) -- [AWB Limburg-Weilburg](./doc/source/awb_lm_de.md) -- [AWB Oldenburg](./doc/source/awb_oldenburg_de.md) -- [AWBKoeln.de](./doc/source/awbkoeln_de.md) -- [AWIDO-online.de](./doc/source/awido_de.md) -- [Berlin-Recycling.de](./doc/source/berlin_recycling_de.md) -- [Bogenschuetz-Entsorgung.de](./doc/source/infeo_at.md) -- [BSR.de / Berliner Stadtreinigungsbetriebe](./doc/source/bsr_de.md) -- [C-Trace.de](./doc/source/c_trace_de.md) -- [Cochem-Zell](./doc/source/cochem_zell_online_de.md) -- [EGN-Abfallkalender.de](./doc/source/egn_abfallkalender_de.md) -- [Erlangen-Höchstadt](./doc/source/erlangen_hoechstadt_de.md) -- [Jumomind.de](./doc/source/jumomind_de.md) -- [KAEV Niederlausitz](./doc/source/kaev_niederlausitz_de.md) -- [KWB-Goslar.de](./doc/source/kwb_goslar_de.md) -- [KWU-Entsorgung](./doc/source/kwu_de.md) -- [Landkreis-Wittmund.de](./doc/source/landkreis_wittmund_de.md) -- [Landkreis Rhön Grabfeld](./doc/source/landkreis_rhoen_grabfeld.md) -- [Landkreis Schwäbisch Hall](./doc/source/lrasha_de.md) -- [Muellmax.de](./doc/source/muellmax_de.md) -- [MyMuell App](./doc/source/jumomind_de.md) -- [Neunkirchen Siegerland](./doc/source/abfall_neunkirchen_siegerland_de.md) -- [RegioEntsorgung](./doc/source/regioentsorgung_de.md) -- [Rhein-Hunsrück Entsorgung (RHE)](./doc/source/rh_entsorgung_de.md) -- [Sector27.de](./doc/source/sector27_de.md) -- [Stadtreinigung Dresden](./doc/source/stadtreinigung_dresden_de.md) -- [Stadtreinigung.Hamburg](./doc/source/stadtreinigung_hamburg.md) -- [Stadtreinigung-Leipzig.de](./doc/source/stadtreinigung_leipzig_de.md) -- [Stadt-Willich.de](.doc/source/stadt_willich_de.md) -- [StadtService Brühl](.doc/source/stadtservice_bruehl_de.md) -- [Städteservice Raunheim Rüsselsheim](./doc/source/staedteservice_de.md) -- [Südbrandenburgischer Abfallzweckverband](./doc/source/sbazv_de.md) -- [Umweltbetrieb Stadt Bielefeld](./doc/source/bielefeld_de.md) -- [WAS Wolfsburg](./doc/source/was_wolfsburg_de.md) -- [Wermelskirchen](./doc/source/wermelskirchen_de.md) - -### Lithuania - -- [Kauno švara](./doc/source/grafikai_svara_lt.md) - -### Netherlands - -- [Ximmio](./doc/source/ximmio_nl.md) -- [HVCGroep](./doc/source/hvcgroep_nl.md) - -### New Zealand - -- [Auckland](./doc/source/aucklandcouncil_govt_nz.md) -- [Christchurch](./doc/source/ccc_govt_nz.md) -- [Gore, Invercargill & Southland](./doc/source/wastenet_org_nz.md) -- [Horowhenua District](./doc/source/horowhenua_govt_nz.md) -- [Waipa District](./doc/source/waipa_nz.md) -- [Wellington](./doc/source/wellington_govt_nz.md) - -### Norway - -- [Min Renovasjon](./doc/source/minrenovasjon_no.md) -- [Oslo Kommune](./doc/source/oslokommune_no.md) - -### Poland - -- [Warsaw](./doc/source/warszawa19115_pl.md) -- [Multiple communities - ecoharmonogram](./doc/source/ecoharmonogram_pl.md) - -### Sweden - -- [Lerum.se](./doc/source/lerum_se.md) -- [Ronneby Miljöteknik](./doc/source/miljoteknik_se.md) -- [SSAM.se](./doc/source/ssam_se.md) -- [Sysav.se](./doc/source/sysav_se.md) -- [Vasyd.se](./doc/source/vasyd_se.md) - -### Switzerland - -- [A-Region.ch](./doc/source/a_region_ch.md) -- [Lindau.ch](./doc/source/lindau_ch.md) -- [Münchenstein](./doc/source/muenchenstein_ch.md) - -### United States of America - -- [PGH.ST](./doc/source/pgh_st.md) -- [Republic Services](./doc/source/republicservices_com.md) -- [Seattle Public Utilities](./doc/source/seattle_gov.md) - -### United Kingdom - -- [Bracknell Forest Council - bracknell-forest.gov.uk](./doc/source/bracknell_forest_gov_uk.md) -- [Bradford Metropolitan District Council - bradford.gov.uk](./doc/source/bradford_gov_uk.md) -- [Braintree District Council - bracknell-forest.gov.uk](./doc/source/braintree_gov_uk.md) -- [Cambridge City Council - cambridge.gov.uk](./doc/source/cambridge_gov_uk.md) -- [Canterbury City Council - canterbury.gov.uk](./doc/source/canterbury_gov_uk.md) -- [Cheshire East Council - cheshireeast.gov.uk](./doc/source/cheshire_east_gov_uk.md) -- [Chesterfield Borough Council - chesterfield.gov.uk](./doc/source/chesterfield_gov_uk.md) -- [Colchester Borough Council - colchester.gov.uk](./doc/source/colchester_gov_uk.md) -- [Cornwall Council - cornwall.gov.uk](./doc/source/cornwall_gov_uk.md) -- [Derby City Council](./doc/source/derby_gov_uk.md) -- [Eastbourne Borough Council - lewes-eastbourne.gov.uk](./doc/source/environmentfirst_co_uk.md) -- [Elmbridge Borough Council - elmbridge_gov_uk](./doc/source/elmbridge_gov_uk.md) -- [Guildford Borough Council - guildford.gov.uk](./doc/source/guildford_gov_uk.md) -- [Harborough District Council - www.harborough.gov.uk](./doc/source/fccenvironment_co_uk.md) -- [Huntingdonshire District Council - huntingdonshire.gov.uk](./doc/source/huntingdonshire_gov_uk.md) -- [The Royal Borough of Kingston - kingston.gov.uk](./doc/source/kingston_gov_uk.md) -- [Lewes District Council - lewes-eastbourne.gov.uk](./doc/source/environmentfirst_co_uk.md) -- [London Borough of Lewisham - lewisham.gov.uk](.doc/source/lewisham_gov_uk.md) -- [Manchester City Council - manchester.gov.uk](./doc/source/manchester_uk.md) -- [Middlesbrough Countil - middlesbrough.gov.uk](./doc/source/middlesbrough_gov_uk.md) -- [Newcastle City Council - newcastle.gov.uk](./doc/source/newcastle_gov_uk.md) -- [North Somerset Council - n-somerset.gov.uk](./doc/source/nsomerset_gov_uk.md) -- [Nottingham City Council - nottinghamcity.gov.uk](./doc/source/nottingham_city_gov_uk.md) -- [Peterborough City Council - peterborough.gov.uk](./doc/source/peterborough_gov_uk.md) -- [Richmondshire District Council - richmondshire.gov.uk](./doc/source/richmondshire_gov_uk.md) -- [Rushmoor Borough Council - rushmoor.gov.uk](./doc/source/rushmoor_gov_uk.md) -- [Sheffield City Council - sheffield.gov.uk](./doc/source/sheffield_gov_uk.md) -- [South Cambridgeshire District Council - scambs.gov.uk](./doc/source/scambs_gov_uk.md) -- [South Norfolk and Broadland Council - southnorfolkandbroadland.gov.uk](./doc/source/south_norfolk_and_broadland_gov_uk.md) -- [Stevenage Borough Council - stevenage.gov.uk](./doc/source/stevenage_gov_uk.md) -- [Tewkesbury Borough Council](./doc/source/tewkesbury_gov_uk.md) -- [City of York Council - york.gov.uk](./doc/source/york_gov_uk.md) -- [Walsall Council - walsall.gov.uk](./doc/source/walsall_gov_uk.md) -- [West Berkshire Council - westberks.gov.uk](./doc/source/westberks_gov_uk.md) -- [Wiltshire Council - wiltshire.gov.uk](./doc/source/wiltshire_gov_uk.md) - -## Installation - -1. Ensure that [HACS](https://github.com/hacs/integration) is installed. -2. Install the "Waste Collection Schedule" integration. -3. [Configure the integration](#configuration). -4. Restart Home Assistant. - -In case you would like to install manually: - -1. Copy the folder `waste_collection_schedule` to `custom_components` in your Home Assistant `config` folder. -2. [Configure the integration](#configuration). -3. Restart Home Assistant. - -## Configuration - -The configuration consists of two entries in the file `configuration.yaml`: - -1. Source configuration - - For each service provider, a source has to be added to the configuration. The source takes care of the arguments which are required to get the correct information from the service provider's web page, e.g. district, city, street, house number, etc. - - If you have to fetch data from multiple service providers, you have to add multiple sources. You can also add the same service provider multiple times (which only makes sense if you use this with different arguments), e.g. if you are looking for displaying the waste collection schedules for multiple districts. - -2. Sensor configuration - - A sensor is used to visualize the retrieved information, e.g. waste type, next collection date or number of days to next collection. The sensor state (which is shown in a Lovelace card) can be customized using templates. As an example, you may display the collection type only or the next collection date or a combination of all available information. - - You can also add multiple sensors per source if you are going to display the information in separate entities like the available collection types or the next collection date. - - If you are looking for displaying one entity per collection type, you just have to add one sensor per collection type. - -## 1. Configure the source(s) - -```yaml -waste_collection_schedule: - sources: - - name: SOURCE - args: - SOURCE_SPECIFIC_ARGUMENTS - customize: - - type: TYPE - alias: ALIAS - show: SHOW - icon: ICON - picture: PICTURE - use_dedicated_calendar: USE_DEDICATED_CALENDAR - dedicated_calendar_title: DEDICATED_CALENDAR_TITLE - calendar_title: CALENDAR_TITLE - fetch_time: FETCH_TIME - random_fetch_time_offset: RANDOM_FETCH_TIME_OFFSET - day_switch_time: DAY_SWITCH_TIME - separator: SEPARATOR -``` - -### Configuration Variables - -**sources** - -*(list) (required)* - -List of service providers (waste collectors). See [Source Configuration Variables](#source-configuration-variables) for a list of available configuration variables. - -**fetch_time** - -*(time) (optional, default: ```"01:00"```)* - -Time of day when to fetch new data from the source. Data will be fetched once per day. - -**random_fetch_time_offset** - -*(int) (optional, default: ```60```)* - -Random offset to the `fetch_time` in minutes. Used to distribute the fetch commands of all Home Assistant instances over a larger period of time to avoid peak loads at the service providers. - -**day_switch_time** - -*(time) (optional, default: ```"10:00"```)* - -Time of day when today's collection is going to expire and hence will not be displayed anymore. - -How it works: If you set the ```day_switch_time``` to 10:00 the sensor will display today's collections until 10:00. After 10:00, today's collections will not be displayed anymore. - -**separator** - -*(string) (optional, default: ```", "```)* - -Used to join entries if there are multiple entries for one day (n/a if value_templates are used). - -### Source Configuration Variables - -**name** - -*(string) (required)* - -Name of the source (service provider). Equates to the file name (without ```.py```) of the source. See [Supported Service Providers](#supported-service-providers) for a list of available sources. - -**args** - -*(dict) (optional)* - -Source (service provider) specific arguments, e.g. district, city, street, waste type, etc. See [Supported Service Providers](#supported-service-providers) for details. - -**customize** - -*(dict) (optional)* - -Used to customize the retrieved data from a source (service provider). See [Customize Source](#customize-source) for a list of available configuration variables. - -**calendar_title** - -*(string) (optional)* - -Alternative title for source in Home Assistant calendar. - -### Customize Source - -Used to customize the retrieved data from a source (service provider). - -**type** - -*(dict) (required)* - -Type of waste as it has been retrieved by the source (service provider). - -**alias** - -*(string) (optional, default: ```None```)* - -Optional, usually better readable name for type of waste to be collected. - -**show** - -*(boolean) (optional, default: ```True```)* - -Show or hide collections with the given waste type. - -**icon** - -*(string) (optional, default: ```None```)* - -Alternative icon for waste type. - -**picture** - -*(string) (optional, default: ```None```)* - -Optional picture for waste type. - -**use_dedicated_calendar** - -*(boolean) (optional, default: ```False```)* - -Create a dedicated calendar for this type. - -**dedicated_calendar_title** - -*(string) (optional, default: ```None```)* - -Optional title of the dedicated calendar. If not set, the waste type will be used. - -## 2. Add sensor(s) to a source - -Add the following lines to your `configuration.yaml` file: - -```yaml -sensor: - - platform: waste_collection_schedule - source_index: SOURCE_INDEX - name: NAME - details_format: DETAILS_FORMAT - count: COUNT - leadtime: LEADTIME - value_template: VALUE_TEMPLATE - date_template: DATE_TEMPLATE - add_days_to: ADD_DAYS_TO - types: - - Waste Type 1 - - Waste Type 2 -``` - -### Configuration Variables - -**source_index** - -*(integer or list of integers) (optional, default: ```0```)* - -Reference to source (service provider). Used to assign a sensor to a specific source. Only required if you defined more than one source. The first defined source in `configuration.yaml` has the source_index 0, the second source 1, ... -If you want to have a sensor which combines the data from multiple sources, just add a list of sources here. Example: -```yaml - source_index: [0, 1] -#or - source_index: - - 0 - - 1 -``` - -**name** - -*(string) (required)* - -Name of the sensor. - -**details_format** - -*(string) (optional, default: ```"upcoming"```)* - -Used to specify the format of the information displayed in the more-info popup of a Lovelace card. - -Possible choices: - -- ```upcoming``` shows a list of upcoming collections. - - ![Upcoming](./doc/more-info-upcoming.png) - -- ```appointment_types``` shows a list of waste types and their next collection date. - - ![Waste Types](/doc/more-info-appointment-types.png) - -- ```generic``` provides all attributes as generic Python data types. This can be used by a specialized Lovelace card (which doesn't exist so far). - - ![Generic](./doc/more-info-generic.png) - -**count** - -*(integer) (optional, default = infinite)* - -Used to limit the number of collections displayed in the more-info popup of a Lovelace card by ```count```. - -**leadtime** - -*(integer) (optional, default = infinite)* - -Used to limit the number of collections displayed in the more-info popup of a Lovelace card. Only collections within the next ```leadtime``` days will be displayed. - -**value_template** - -*(string) (optional)* - -Template string used to format the state of an entity. - -See [Template Variables](#template-variables) for a list of available variables. - -**date_template** - -*(string) (optional)* - -Template string used to format collection dates within the more-info popup. - -See [Template Variables](#template-variables) for a list of available variables. - -**add_days_to** - -*(boolean) (optional, default: ```False```)* - -Adds an attribute with the label `daysTo` and the number of days to the next collection to the entity state of the source. - -**types** - -*(list of strings) (optional)* - -Used to filter waste types. The sensor will only display collections with these type(s). - -## Template Variables - -The following variables can be used within `value_template` and `date_template`: - -| Variable | Description | Type | Comments | -|--------------------|--------------------|--------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| -| ```value.date``` | Collection date | [datetime.date](https://docs.python.org/3/library/datetime.html#datetime.date) | Use [strftime](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) to format the output. | -| ```value.daysTo``` | Days to collection | int | 0 = today, 1 = tomorrow, ... | -| ```value.types``` | Waste types | list of strings | Use `join` filter to join types. | - -## FAQ - -### 1. My Service Provider isn't supported. What can I do? - -1. A lot of service providers provide ICS/iCal data as downloads or persistent links. This can be used together with the generic [iCS/iCal](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ics.md) source. - -2. In case your schedule follows a static schema, you can use the [static](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/static.md) source. - -3. Implement a new [source](https://github.com/mampfes/hacs_waste_collection_schedule#how-to-add-new-sources) and create a PR. - -4. Raise an [issue](https://github.com/mampfes/hacs_waste_collection_schedule/issues). - -### 2. How do I format dates? - -Use [strftime](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) in `value_template` or `date_template`: - -```yaml -# returns "20.03.2020" -value_template: '{{value.date.strftime("%d.%m.%Y")}}' -date_template: '{{value.date.strftime("%d.%m.%Y")}}' - -# returns "03/20/2020" -value_template: '{{value.date.strftime("%m/%d/%Y")}}' -date_template: '{{value.date.strftime("%m/%d/%Y")}}' - -# returns "Fri, 03/20/2020" -value_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' -date_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' -``` - -### 3. How do I show the number of days to the next collection? - -Set `value_template` within the sensor configuration: - -```yaml -value_template: 'in {{value.daysTo}} days' -``` - -### 4. How do I show *Today* / *Tomorrow* instead of *in 0/1 days*? - -Set `value_template` within the sensor configuration: - -```yaml -# returns "Today" if value.daysTo == 0 -# returns "Tomorrow" if value.daysTo == 1 -# returns "in X days" if value.daysTo > 1 -value_template: '{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}' -``` - -### 5. How do I join waste types in a `value_template`? - -Use the `join` filter: - -```yaml -# returns "Garbage, Recycle" -value_template: '{{value.types|join(", ")}}' - -# returns "Garbage+Recycle" -value_template: '{{value.types|join("+")}}' -``` - -Note: If you don't specify a `value_template`, waste types will be joined using the `separator` configuration variable. - -### 6. How do I setup a sensor which shows only the days to the next collection? - -Set `value_template` within the sensor configuration: - -```yaml -value_template: '{{value.daysTo}}' -``` - -### 7. How do I setup a sensor which shows only the date of the next collection? - -Set `value_template` within the sensor configuration: - -```yaml -value_template: '{{value.date.strftime("%m/%d/%Y")}}' -``` - -### 8. How do I configure a sensor which shows only the waste type of the next collection? - -Set `value_template` within the sensor configuration: - -```yaml -value_template: '{{value.types|join(", ")}}' -``` - -### 9. How do I configure a sensor to show only collections of a specific waste type? - -Set `types` within the sensor configuration: - -```yaml -sensor: - - platform: waste_collection_schedule - name: next_garbage_collection - types: - - Garbage - - - platform: waste_collection_schedule - name: next_recycle_collection - types: - - Recycle -``` - -Note: If you have set an alias for a waste type, you must use the alias name. - -### 10. How can I rename an waste type? - -Set `alias` in the customize section of a source: - -```yaml -waste_collection_schedule: - sources: - - name: NAME - customize: - - type: Very long garbage name - alias: Garbage - - type: Very long recycle name - alias: Recycle -``` - -### 11. How can I hide inappropriate waste types? - -Set `show` configuration variable to *false* in the customize section of a source: - -```yaml -waste_collection_schedule: - sources: - - name: NAME - customize: - - type: Inappropriate Waste Type - show: false -``` - -### 12. How do I show a colored Lovelace card depending on the due date? - -You can use [Button Card](https://github.com/custom-cards/button-card) to create a colored Lovelace cards: - -![Button Card](./doc/button-cards.png) - -```yaml -# configuration.yaml -sensor: - - platform: waste_collection_schedule - name: MyButtonCardSensor - value_template: '{{value.types|join(", ")}}|{{value.daysTo}}|{{value.date.strftime("%d.%m.%Y")}}|{{value.date.strftime("%a")}}' -``` - -```yaml -# button-card configuration -type: 'custom:button-card' -entity: sensor.mybuttoncardsensor -layout: icon_name_state2nd -show_label: true -label: | - [[[ - var days_to = entity.state.split("|")[1] - if (days_to == 0) - { return "Today" } - else if (days_to == 1) - { return "Tomorrow" } - else - { return "in " + days_to + " days" } - ]]] -show_name: true -name: | - [[[ - return entity.state.split("|")[0] - ]]] -state: - - color: red - operator: template - value: '[[[ return entity.state.split("|")[1] == 0 ]]]' - - color: orange - operator: template - value: '[[[ return entity.state.split("|")[1] == 1 ]]]' - - value: default -``` - -### 13. Can I also use the **Garbage Collection Card** instead? - -Yes, the [Garbage Collection Card](https://github.com/amaximus/garbage-collection-card) can also be used with *Waste Collection Schedule*: - -```yaml -# configuration.yaml -sensor: - - platform: waste_collection_schedule - name: garbage_days - details_format: appointment_types - value_template: "{{ value.daysTo }}" - types: - - Garbage - - - platform: template - sensors: - garbage: - value_template: > - {% if states('sensor.garbage_days')|int > 2 %} - 2 - {% else %} - {{ states('sensor.garbage_days')|int }} - {% endif %} - attribute_templates: - next_date: "{{ state_attr('sensor.garbage_days', 'Garbage') }}" - days: "{{ states('sensor.garbage_days')|int }}" -``` - -```yaml -# garbage-collection-card configuration -entity: sensor.garbage -type: 'custom:garbage-collection-card' -``` - -### 14. How can I sort waste type specific entities? - -Prerequisites: You already have dedicated sensors per waste type and want to show the sensor with the next collection in a Lovelace card. - -Add `add_days_to: True` to the configuration of all sensors you want to sort. This will add the attribute `daysTo` which can be used by e.g. [auto-entities](https://github.com/thomasloven/lovelace-auto-entities) to sort entities by day of next collection. - -### 15. How can I disable the calendar? - -If you don't like the calendar provided by Waste Collection Schedule or you have configured some dedicated calendars per waste type and therefore don't need the global calendar any more, you can disable it so that it doesn't show up in the Calendar Dashboard any more: - -Go to `Settings` --> `Entities` and select the calendar entity provided by Waste Collection Schedule. Now disable it using the menu items. - -[![entities](https://my.home-assistant.io/badges/entities.svg)](https://my.home-assistant.io/redirect/entities/) - -### 16. I have configured multiple sources, but the sensors show only *UNAVAILABLE* - -You probably missed to add `source_index` to the sensor configuration. - -## How to add new sources - -1. Create a new source in folder `custom_components/waste_collection_schedule/waste_collection_schedule/source` with the lower case url of your service provider (e.g. `abc_com.py` for `http://www.abc.com`). -2. Don't forget to add test cases (= sample data to query the service api). -3. Run `test_sources.py` script to ensure that your source works. -4. Add documentation in folder `docs/source` and add a link to your new source on `README.md` and `info.md`. - -### Guidelines - -- A source shall return data for all available waste types. A source shall **not** provide a configuration option to limit the returned waste types. -- A source shall return data for the entire available period (including past). A source shall **not** provide a configuration option to limit the requested period. - -Filtering of data for waste types or time periods is a functionality of the framework and shall not be done by a source. - -### Source Code Example - -Example for `abc_com.py`: - -```py -import datetime -from waste_collection_schedule import Collection - - -DESCRIPTION = "Example source for abc.com" # Describe your source -URL = "abc.com" # Insert url to service homepage -TEST_CASES = { # Insert arguments for test cases using test_sources.py script - "TestName": {"arg1": 100, "arg2": "street"} -} - - -class Source: - def __init__(self, arg1, arg2): # argX correspond to the args dict in the source configuration - self._arg1 = arg1 - self._arg2 = arg2 - - def fetch(self): - entries = [] - - entries.append( - Collection( - datetime.datetime(2020, 4, 11), - "Waste Type", - ) - ) - - return entries -``` - -See also: [custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py](./custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py) - -### Debugging - -Debugging a source within Home Assistant is not recommended because startup of HA is far too slow for fast debugging cycles. - -Instead, there is a test fixture which allows to run a source from the command line. The fixture is a Python script which is located here: - -`custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py`. - -The script uses the test cases defined in the source file and runs the source with the arguments of every test case. - -By default (without additional arguments), the script tests every source file in the `source` folder and prints the number of found entries for every test case. - -Example output for `abfall_io`: - -```text -Testing source abfall_io ... - found 269 entries for Waldenbuch - found 55 entries for Landshut - found 101 entries for Schoenmackers - found 139 entries for Freudenstadt - found 190 entries for Ludwigshafen am Rhein -``` - -The script supports the following options: - -| Option | Argument | Description | -|--------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| `-s` | SOURCE | [Source name](https://github.com/mampfes/hacs_waste_collection_schedule#source-configuration-variables) (source file name without ending `.py`) | -| `-l` | - | List all found dates | -| `-i` | - | Add icon name to output. Only effective together with `-l`. | - -For debugging purposes of a single source, it is recommended to use the `-s SOURCE` option. - -Example for `abc_com.py`: ```bash -test_sources.py -s abc_com -l -i +sudo docker exec -it homeassistant /bin/bash +pip list +pip install recurring_ical_events # in case recurring_ical_events is missing ``` -## Videos +# Licence -There are some videos on YouTube: +![github licence](https://img.shields.io/badge/Licence-MIT-orange) -### German +This project uses the MIT Licence, for more details see the [licence](/doc/licence.md) document. -- [Bunte Mülltonnenerinnerung mit Home Assistant](https://youtu.be/MzQgARDvRww) -- [Abfall Kalender in Home Assistant mit Erinnerung in Home Assistant](https://youtu.be/aCKLKGYiA7w) +# Showing Your Appreciation -Please note that all these videos are **not** created by the developer of this component and therefore may be outdated, point in the wrong direction or contain errors. If you have questions, please create an issue here on GitHub. Do not ask your question in the YouTube comments because you may get wrong answers there. +If you like this project, please give it a star on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule) or consider becoming a [Sponsor](https://github.com/sponsors/mampfes). diff --git a/custom_components/waste_collection_schedule/__init__.py b/custom_components/waste_collection_schedule/__init__.py index 72f05442..42fc848a 100644 --- a/custom_components/waste_collection_schedule/__init__.py +++ b/custom_components/waste_collection_schedule/__init__.py @@ -123,6 +123,12 @@ async def async_setup(hass: HomeAssistant, config: dict): # initial fetch of all data hass.add_job(api._fetch) + + def fetch_data(): + hass.add_job(api._fetch) + + # Register new Service fetch_data + hass.services.async_register(DOMAIN, 'fetch_data', fetch_data) return True diff --git a/custom_components/waste_collection_schedule/manifest.json b/custom_components/waste_collection_schedule/manifest.json index 3e5a66bc..2275c81a 100644 --- a/custom_components/waste_collection_schedule/manifest.json +++ b/custom_components/waste_collection_schedule/manifest.json @@ -6,5 +6,5 @@ "dependencies": [], "codeowners": ["@mampfes"], "iot_class": "cloud_polling", - "version": "1.31.0" + "version": "1.32.0" } diff --git a/custom_components/waste_collection_schedule/services.yaml b/custom_components/waste_collection_schedule/services.yaml new file mode 100644 index 00000000..1a316482 --- /dev/null +++ b/custom_components/waste_collection_schedule/services.yaml @@ -0,0 +1,3 @@ +fetch_data: + name: fetch_data + description: fetch current source data \ No newline at end of file diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py b/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py index 551f9eea..16f2b910 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py @@ -1,8 +1,14 @@ import datetime +from typing import Optional class CollectionBase(dict): # inherit from dict to enable JSON serialization - def __init__(self, date: datetime.date, icon: str = None, picture: str = None): + def __init__( + self, + date: datetime.date, + icon: Optional[str] = None, + picture: Optional[str] = None, + ): dict.__init__(self, date=date.isoformat(), icon=icon, picture=picture) self._date = date # store date also as python date object @@ -31,7 +37,11 @@ class CollectionBase(dict): # inherit from dict to enable JSON serialization class Collection(CollectionBase): def __init__( - self, date: datetime.date, t: str, icon: str = None, picture: str = None + self, + date: datetime.date, + t: str, + icon: Optional[str] = None, + picture: Optional[str] = None, ): CollectionBase.__init__(self, date=date, icon=icon, picture=picture) self["type"] = t diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py index 3009e9f7..765b6c7b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py @@ -18,12 +18,7 @@ class ICS_v1: def convert(self, ics_data): # parse ics file - try: - calendar = icalendar.Calendar.from_ical(ics_data) - except Exception as err: - _LOGGER.error(f"Parsing ics data failed:{str(err)}") - _LOGGER.debug(ics_data) - return [] + calendar = icalendar.Calendar.from_ical(ics_data) # calculate start- and end-date for recurring events start_date = datetime.datetime.now().replace( diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/SSLError.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/SSLError.py new file mode 100644 index 00000000..4239f2cd --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/SSLError.py @@ -0,0 +1,27 @@ +#Work around SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED errors using method discussed in +# https://stackoverflow.com/questions/71603314/ssl-error-unsafe-legacy-renegotiation-disabled + +import requests +import ssl +import urllib3 + +class CustomHttpAdapter (requests.adapters.HTTPAdapter): + # "Transport adapter" that allows us to use custom ssl_context. + + def __init__(self, ssl_context=None, **kwargs): + self.ssl_context = ssl_context + super().__init__(**kwargs) + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = urllib3.poolmanager.PoolManager( + num_pools=connections, maxsize=maxsize, + block=block, ssl_context=self.ssl_context) + + +def get_legacy_session(): + ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) + ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT + session = requests.session() + session.mount('https://', CustomHttpAdapter(ctx)) + return session + diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/a_region_ch.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/a_region_ch.py index 8fd8e945..ae38fd34 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/a_region_ch.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/a_region_ch.py @@ -8,6 +8,12 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "A-Region" DESCRIPTION = "Source for A-Region, Switzerland waste collection." URL = "https://www.a-region.ch" + + +def EXTRA_INFO(): + return [{"title": m} for m in MUNICIPALITIES] + + TEST_CASES = { "Andwil": {"municipality": "Andwil"}, "Rorschach": {"municipality": "Rorschach", "district": "Unteres Stadtgebiet"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py index bbc9fda4..86988763 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py @@ -7,11 +7,12 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AbfallPlus" +TITLE = "Abfall.IO / AbfallPlus" DESCRIPTION = ( "Source for AbfallPlus.de waste collection. Service is hosted on abfall.io." ) URL = "https://www.abfallplus.de" +COUNTRY = "de" TEST_CASES = { "Waldenbuch": { "key": "8215c62763967916979e0e8566b6172e", @@ -56,7 +57,13 @@ TEST_CASES = { "f_id_strasse": 621, "f_id_strasse_hnr": 872, "f_abfallarten": [27, 28, 17, 67], - } + }, + "ALBA Berlin": { + "key": "9583a2fa1df97ed95363382c73b41b1b", + "f_id_kommune": 3227, + "f_id_strasse": 3475, + "f_id_strasse_hnr": 185575, + }, } _LOGGER = logging.getLogger(__name__) @@ -148,10 +155,10 @@ class Source: # - AWB Limburg-Weilheim uses this list to select a "Sonderabfall " # waste type. The warning could be removed by adding the extra config # option "f_abfallarten" with the following values [27, 28, 17, 67] - html_warnings = re.findall("\ 1: - raise Exception (" to many addresses found, specify more detailed street name") + raise Exception( + " to many addresses found, specify more detailed street name" + ) - args = {"ModID":48, "call": "ical", "pois": ids[0][0], "kat": 1, "alarm":0} - r = requests.get("https://www.neunkirchen-siegerland.de/output/options.php", params=args,headers=header) - - if r.status_code != 200: - _LOGGER.error("Error querying calender data") - return [] + args = {"ModID": 48, "call": "ical", "pois": ids[0][0], "kat": 1, "alarm": 0} + r = requests.get( + "https://www.neunkirchen-siegerland.de/output/options.php", + params=args, + headers=header, + ) + r.raise_for_status() dates = self._ics.convert(r.text) entries = [] for d in dates: - entries.append(Collection(d[0],d[1])) + entries.append(Collection(d[0], d[1])) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_zollernalbkreis_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_zollernalbkreis_de.py index 245a964c..a139e04a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_zollernalbkreis_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_zollernalbkreis_de.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfall Zollernalbkreis" +TITLE = "Abfallwirtschaft Zollernalbkreis" DESCRIPTION = "Source for Abfallwirtschaft Zollernalbkreis waste collection." URL = "https://www.abfallkalender-zak.de" TEST_CASES = { @@ -42,6 +42,16 @@ TEST_CASES = { }, } +ICON_MAP = { + "Restmüll": "mdi:trash-can", + "Grünabfall" : "mdi:leaf", + "Gelber Sack" : "mdi:sack", + "Papiertonne" : "mdi:package-variant", + "Bildschirm-/Kühlgeräte" : "mdi:television-classic", + "Schadstoffsammlung" : "mdi:biohazard", + "altmetalle" : "mdi:nail", +} + class Source: def __init__(self, city, types, street=None): @@ -49,15 +59,6 @@ class Source: self._street = street self._types = types self._ics = ICS() - self._iconMap = { - "Restmüll": "mdi:trash-can", - "Grünabfall" : "mdi:leaf", - "Gelber Sack" : "mdi:sack", - "Papiertonne" : "mdi:package-variant", - "Bildschirm-/Kühlgeräte" : "mdi:television-classic", - "Schadstoffsammlung" : "mdi:biohazard", - "altmetalle" : "mdi:nail", - } def fetch(self): now = datetime.now() @@ -95,6 +96,6 @@ class Source: waste_type = d[1] next_pickup_date = d[0] - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=self._iconMap.get(waste_type,"mdi:trash-can"))) + entries.append(Collection(date=next_pickup_date, t=waste_type, icon=ICON_MAP.get(waste_type,"mdi:trash-can"))) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallnavi_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallnavi_de.py index b6e14a02..bf1d552d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallnavi_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallnavi_de.py @@ -1,7 +1,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.AbfallnaviDe import AbfallnaviDe -TITLE = "AbfallNavi" +TITLE = "AbfallNavi (RegioIT.de)" DESCRIPTION = ( "Source for AbfallNavi waste collection. AbfallNavi is a brand name of regioit.de." ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py index 244c036b..537e59b1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py @@ -1,16 +1,19 @@ -import requests -from waste_collection_schedule import Collection # type: ignore[attr-defined] -from waste_collection_schedule.service.ICS import ICS - import urllib -TITLE = "Landkreis Forchheim" +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] +from waste_collection_schedule.service.ICS import ICS + +TITLE = "Abfalltermine Forchheim" DESCRIPTION = "Source for Landkreis Forchheim" URL = "https://www.abfalltermine-forchheim.de/" TEST_CASES = { "Dormitz": {"city": "Dormitz", "area": "Dormitz"}, "Rüsselbach": {"city": "Igensdorf", "area": "Oberrüsselbach"}, - "Kellerstraße": {"city": "Forchheim", "area": "Untere Kellerstraße (ab Adenauerallee bis Piastenbrücke)"} + "Kellerstraße": { + "city": "Forchheim", + "area": "Untere Kellerstraße (ab Adenauerallee bis Piastenbrücke)", + }, } @@ -25,7 +28,7 @@ class Source: r = requests.get( f"http://www.abfalltermine-forchheim.de/Forchheim/Landkreis/{place}/ics?RESTMUELL=true&RESTMUELL_SINGLE=true&BIO=true&YELLOW_SACK=true&PAPER=true" ) - r.encoding = r.apparent_encoding + r.encoding = "utf-8" dates = self._ics.convert(r.text) entries = [] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py index 3df86d2e..a6902173 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py @@ -5,9 +5,17 @@ import pytz import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "ALW Wolfenbüttel" +# With verify=True the POST fails due to a SSLCertVerificationError. +# Using verify=False works, but is not ideal. The following links may provide a better way of dealing with this: +# https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings +# https://urllib3.readthedocs.io/en/1.26.x/user-guide.html#ssl +# These two lines areused to suppress the InsecureRequestWarning when using verify=False +import urllib3 +urllib3.disable_warnings() + +TITLE = "Abfallwirtschaft Landkreis Wolfenbüttel" DESCRIPTION = "Source for ALW Wolfenbüttel." -URL = "https://abfallapp.alw-wf.de" +URL = "https://alw-wf.de" TEST_CASES = { "Linden alte Straße": {"ort": "Linden mit Okertalsiedlung", "strasse": "Siedlung"}, "Linden neuere Straße": { @@ -17,6 +25,7 @@ TEST_CASES = { "Dettum": {"ort": "Dettum", "strasse": "Egal!"}, } +API_URL = "https://abfallapp.alw-wf.de" AUTH_DATA = { "auth": { "Name": "ALW", @@ -41,7 +50,7 @@ class Source: auth_params = json.dumps(AUTH_DATA) # ALW WF uses a self-signed certificate so we need to disable certificate verification - r = requests.post(f"{URL}/GetOrte.php", data=auth_params, verify=False) + r = requests.post(f"{API_URL}/GetOrte.php", data=auth_params, verify=False) orte = r.json() if orte["result"][0]["StatusCode"] != 200: raise Exception(f"Error getting Orte: {orte['result'][0]['StatusMsg']}") @@ -53,7 +62,7 @@ class Source: if ort_id is None: raise Exception(f"Error finding Ort {self._ort}") - r = requests.post(f"{URL}/GetStrassen.php", data=auth_params, verify=False) + r = requests.post(f"{API_URL}/GetStrassen.php", data=auth_params, verify=False) strassen = r.json() if strassen["result"][0]["StatusCode"] != 200: raise Exception( @@ -73,7 +82,7 @@ class Source: if strasse_id is None: raise Exception(f"Error finding Straße {self._strasse}") - r = requests.post(f"{URL}/GetArten.php", data=auth_params, verify=False) + r = requests.post(f"{API_URL}/GetArten.php", data=auth_params, verify=False) arten = r.json() if arten["result"][0]["StatusCode"] != 200: raise Exception(f"Error getting Arten: {arten['result'][0]['StatusMsg']}") @@ -84,7 +93,7 @@ class Source: entries = [] r = requests.post( - f"{URL}/GetTermine.php/{strasse_id}", data=auth_params, verify=False + f"{API_URL}/GetTermine.php/{strasse_id}", data=auth_params, verify=False ) termine = r.json() if termine["result"][0]["StatusCode"] != 200: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py index 744e8f0d..16727adc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py @@ -1,13 +1,13 @@ import contextlib from datetime import datetime -from urllib.parse import quote from typing import Optional +from urllib.parse import quote import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfall ART Trier" +TITLE = "ART Trier" DESCRIPTION = "Source for waste collection of ART Trier." URL = "https://www.art-trier.de" TEST_CASES = { @@ -43,26 +43,25 @@ ICON_MAP = { "Restmüll": "mdi:trash-can", "Gelber Sack": "mdi:recycle", } -SPECIAL_CHARS = { - ord("ä"): "ae", - ord("ü"): "ue", - ord("ö"): "oe", - ord("ß"): "ss", - ord("("): "", - ord(")"): "", - ord("."): "", -} +SPECIAL_CHARS = str.maketrans( + { + " ": "_", + "ä": "ae", + "ü": "ue", + "ö": "oe", + "ß": "ss", + "(": None, + ")": None, + ",": None, + ".": None, + } +) class Source: def __init__(self, district: str, zip_code: str): self._district = quote( - district.lower() - .removeprefix("stadt ") - .replace(" ", "_") - .replace(",", "") - .translate(SPECIAL_CHARS) - .strip() + district.lower().removeprefix("stadt ").translate(SPECIAL_CHARS).strip() ) self._zip_code = zip_code self._ics = ICS(regex=r"^A.R.T. Abfuhrtermin: (.*)", split_at=r" & ") @@ -70,12 +69,12 @@ class Source: def fetch(self): url = f"{API_URL}/{self._zip_code}_{self._district}_{REMINDER_DAY}-{REMINDER_TIME}.ics" - r = requests.get(url) - schedule = self._ics.convert(r.text) + res = requests.get(url) + res.raise_for_status() + + schedule = self._ics.convert(res.text) return [ - Collection( - date=entry[0], t=entry[1], icon=ICON_MAP.get(entry[1], "mdi:trash-can") - ) + Collection(date=entry[0], t=entry[1], icon=ICON_MAP.get(entry[1])) for entry in schedule ] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py new file mode 100644 index 00000000..78a34f3f --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py @@ -0,0 +1,91 @@ +import datetime + +import requests +from waste_collection_schedule import Collection + +TITLE = "Ashfield District Council" +DESCRIPTION = "Source for ashfield.gov.uk, Ashfield District Council, UK" +URL = "https://www.ashfield.gov.uk" +TEST_CASES = { + "11 Maun View Gardens, Sutton-in-Ashfield": {"uprn": 10001336299}, + "4A Station Street, Kirkby-in-Ashfield": {"post_code": "NG177AR", "number": "4A"}, + "Ashfield District Council": { + "post_code": "NG17 8DA", + "name": "Ashfield District Council", + }, +} + +API_URLS = { + "address_search": "https://www.ashfield.gov.uk/api/powersuite/getaddresses/{postcode}", + "collection": "https://www.ashfield.gov.uk/api/powersuite/GetCollectionByUprnAndDate/{uprn}", +} + +ICON_MAP = { + "Residual Waste Collection Service": "mdi:trash-can", + "Domestic Recycling Collection Service": "mdi:recycle", + "Domestic Glass Collection Service": "mdi:glass-fragile", + "Garden Waste Collection Service": "mdi:leaf", +} + +NAMES = { + "Residual Waste Collection Service": "Red (rubbish)", + "Domestic Recycling Collection Service": "Green (recycling)", + "Domestic Glass Collection Service": "Blue (glass)", + "Garden Waste Collection Service": "Brown (garden)", +} + + +class Source: + def __init__(self, post_code=None, number=None, name=None, uprn=None): + self._post_code = post_code + self._number = number + self._name = name + self._uprn = uprn + + def fetch(self): + if not self._uprn: + # look up the UPRN for the address + q = str(API_URLS["address_search"]).format(postcode=self._post_code) + r = requests.get(q) + r.raise_for_status() + addresses = r.json()["data"] + + if self._name: + self._uprn = [ + int(x["AccountSiteUprn"]) + for x in addresses + if x["SiteAddressName"].capitalize() == self._name.capitalize() + ][0] + elif self._number: + self._uprn = [ + int(x["AccountSiteUprn"]) + for x in addresses + if x["SiteAddressNumber"] == self._number + ][0] + + if not self._uprn: + raise Exception( + f"Could not find address {self._post_code} {self._number}{self._name}" + ) + + q = str(API_URLS["collection"]).format(uprn=self._uprn) + + r = requests.get(q) + r.raise_for_status() + + collections = r.json()["data"] + entries = [] + + if collections: + for collection in collections: + entries.append( + Collection( + date=datetime.datetime.strptime( + collection["Date"], "%d/%m/%Y %H:%M:%S" + ).date(), + t=NAMES.get(collection["Service"]), + icon=ICON_MAP.get(collection["Service"]), + ) + ) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py index bbc30841..f183ebc4 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py @@ -1,16 +1,13 @@ import datetime from html.parser import HTMLParser -import requests +# import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -# Updated to work around SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error using method discussed in -# https://stackoverflow.com/questions/71603314/ssl-error-unsafe-legacy-renegotiation-disabled -import ssl -import urllib3 +# Include work around for SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error +from waste_collection_schedule.service.SSLError import get_legacy_session - -TITLE = "Auckland council" +TITLE = "Auckland Council" DESCRIPTION = "Source for Auckland council." URL = "https://aucklandcouncil.govt.nz" TEST_CASES = { @@ -34,30 +31,6 @@ MONTH = { } -# Additional code snippet to work around SSL issue -class CustomHttpAdapter (requests.adapters.HTTPAdapter): - # "Transport adapter" that allows us to use custom ssl_context. - - def __init__(self, ssl_context=None, **kwargs): - self.ssl_context = ssl_context - super().__init__(**kwargs) - - def init_poolmanager(self, connections, maxsize, block=False): - self.poolmanager = urllib3.poolmanager.PoolManager( - num_pools=connections, maxsize=maxsize, - block=block, ssl_context=self.ssl_context) - - -def get_legacy_session(): - ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) - ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT - session = requests.session() - session.mount('https://', CustomHttpAdapter(ctx)) - return session -# End SSL issue code snippet - - - def toDate(formattedDate): items = formattedDate.split() return datetime.date(int(items[3]), MONTH[items[2]], int(items[1])) @@ -144,13 +117,6 @@ class Source: # verify=False, ) - # Original request code - # r = requests.get( - # "https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/collection-day-detail.aspx", - # params=params, - # verify=False, - # ) - p = WasteSearchResultsParser() p.feed(r.text) return p.entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/avl_ludwigsburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/avl_ludwigsburg_de.py deleted file mode 100644 index 374b7f6a..00000000 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/avl_ludwigsburg_de.py +++ /dev/null @@ -1,93 +0,0 @@ -import requests -from bs4 import BeautifulSoup -from waste_collection_schedule import Collection # type: ignore[attr-defined] -from waste_collection_schedule.service.ICS import ICS - -TITLE = "avl-ludwigsburg.de" -DESCRIPTION = "Abfallverwertungsgesellschaft des Landkreises Ludwigsburg mbH" -URL = "https://www.avl-ludwigsburg.de/privatkunden/termine/abfallkalender/suche/" - -TEST_CASES = { - "CityWithoutStreet": {"city": "Möglingen"}, - "CityWithStreet": {"city": "Ludwigsburg", "street": "Bahnhofstraße"}, -} - - -class Source: - def __init__(self, city, street=None): - self._city = city - self._street = street - self._ics = ICS() - - def fetch(self): - # Get the hidden parameters by loading the page - session = requests.Session() - r = session.get(URL) - r.raise_for_status() - - soup = BeautifulSoup(r.text, features="html.parser") - hidden_tags = soup.find_all("input", type="hidden") - - # Prepare data for the real web request - data = {} - for tag in hidden_tags: - data[tag.get("name")] = tag.get("value") - - # Find the cities which do need a street name - data_cities_with_streets = soup.find_all( - "input", type="text", placeholder="Ort eingeben" - ) - cities_with_streets = "" - for tag in data_cities_with_streets: - cities_with_streets += tag.get("data-cities-with-streets") - cities_with_streets = cities_with_streets.split(",") - - data["tx_avlcollections_pi5[wasteCalendarLocationItem]"] = self._city - data["tx_avlcollections_pi5[wasteCalendarStreetItem]"] = self._street - - # Remove some data which the webserver doesn't like - data.pop("id", None) - data.pop("tx_kesearch_pi1[page]", None) - data.pop("tx_kesearch_pi1[resetFilters]", None) - data.pop("tx_kesearch_pi1[sortByField]", None) - data.pop("tx_kesearch_pi1[sortByDir]", None) - - # Depending on the city remove the street from the data set - if self._city.lower() not in cities_with_streets: - data.pop("tx_avlcollections_pi5[wasteCalendarStreetItem]", None) - - # Get the final data - r = session.post(URL, data=data) - r.raise_for_status() - - if r.text.find("Ort konnte nicht gefunden werden.") != -1: - raise Exception("Error: Ort konnte nicht gefunden werden.") - - if r.text.find("Straße konnte nicht gefunden werden.") != -1: - raise Exception("Error: Ort konnte nicht gefunden werden.") - - if r.text.find(".ics") == -1: - raise Exception("Error: No ics link found.") - - soup = BeautifulSoup(r.text, features="html.parser") - downloads = soup.find_all("a", href=True) - ics_link = "" - for download in downloads: - link = download.get("href") - if ".ics" in link: - ics_link = link - full_url = "https://www.avl-ludwigsburg.de" + ics_link - return self.fetch_ics(full_url) - - def fetch_ics(self, url): - r = requests.get(url) - r.raise_for_status() - - # Parse ics file - r.encoding = "utf-8" - dates = self._ics.convert(r.text) - - entries = [] - for d in dates: - entries.append(Collection(d[0], d[1])) - return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py index 52487925..c21fee25 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py @@ -3,9 +3,9 @@ from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AW Harburg" +TITLE = "Abfallwirtschaft Landkreis Harburg" DESCRIPTION = "Abfallwirtschaft Landkreis Harburg" -URL = "https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/" +URL = "https://www.landkreis-harburg.de" TEST_CASES = { "CityWithTwoLevels": {"level_1": "Hanstedt", "level_2": "Evendorf"}, @@ -16,6 +16,9 @@ TEST_CASES = { }, } +API_URL = ( + "https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/" +) HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)", } @@ -33,11 +36,11 @@ class Source: # Get the IDs of the districts on the first level # Double loading is on purpose because sometimes the webpage has an overlay # which is gone on the second try in a session - r = session.get(URL, headers=HEADERS) + r = session.get(API_URL, headers=HEADERS) + r.raise_for_status() if "Zur aufgerufenen Seite" in r.text: - r = session.get(URL, headers=HEADERS) - if r.status_code != 200: - raise Exception(f"Error: failed to fetch first url: {URL}") + r = session.get(API_URL, headers=HEADERS) + r.raise_for_status() # Get the IDs of the districts on the first level id = self.parse_level(r.text, 1) @@ -53,8 +56,7 @@ class Source: "selected_ebene": 0, } r = session.get(url, params=params, headers=HEADERS) - if r.status_code != 200: - raise Exception(f"Error: failed to fetch second url: {url}") + r.raise_for_status() # Get the IDs of the districts on the second level id = self.parse_level(r.text, 2) @@ -69,8 +71,7 @@ class Source: "selected_ebene": 0, } r = session.get(url, params=params, headers=HEADERS) - if r.status_code != 200: - raise Exception(f"Error: failed to fetch third url: {url}") + r.raise_for_status() # Get the IDs of the districts on the third level id = self.parse_level(r.text, 3) @@ -82,6 +83,7 @@ class Source: "owner": 20100, } r = session.get(url, params=params, headers=HEADERS) + r.raise_for_status() # Sometimes there is no garbage calendar available if "Es sind keine Abfuhrbezirke hinterlegt." in r.text: @@ -110,7 +112,7 @@ class Source: for d in dates: entries.append(Collection(d[0], d[1])) except ValueError: - pass # during year transition the ical for the next year may be empty + pass # during year transition the ical for the next year may be empty return entries def parse_level(self, response, level): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_es_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_es_de.py index 1ccc80b2..18a963e3 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_es_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_es_de.py @@ -37,24 +37,25 @@ class Source: soup = BeautifulSoup(r.text, features="html.parser") downloads = soup.find_all("a", href=True) - ics_url = None + ics_urls = list() for download in downloads: href = download.get("href") - if "t=ics" in href: - ics_url = href - break + if "t=ics" in href and href not in ics_urls: #The website lists the same url multiple times, we only want it once + ics_urls.append(href) - if ics_url is None: + if not ics_urls: raise Exception(f"ics url not found") - # get ics file - r = session.get(ics_url, headers=HEADERS) - r.raise_for_status() - - # parse ics file - dates = self._ics.convert(r.text) - entries = [] - for d in dates: - entries.append(Collection(d[0], d[1])) + for ics_url in ics_urls: + # get ics file + r = session.get(ics_url, headers=HEADERS) + r.raise_for_status() + + # parse ics file + dates = self._ics.convert(r.text) + + for d in dates: + entries.append(Collection(d[0], d[1])) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_oldenburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_oldenburg_de.py old mode 100755 new mode 100644 index be1c644d..3ada8634 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_oldenburg_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_oldenburg_de.py @@ -8,11 +8,13 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "AWB Oldenburg" DESCRIPTION = "Source for 'Abfallwirtschaftsbetrieb Stadt Oldenburg (Oldb)'." -URL = "https://www.oldenburg.de/startseite/leben-umwelt/awb/abfall-a-z/abfuhrkalender.html" +URL = "https://www.oldenburg.de" TEST_CASES = { "Polizeiinspektion Oldenburg": {"street": "Friedhofsweg", "house_number": 30} } +API_URL = "https://www.oldenburg.de/startseite/leben-umwelt/awb/abfall-a-z/abfuhrkalender.html" + _LOGGER = logging.getLogger(__name__) class Source: @@ -51,7 +53,7 @@ class Source: args = urllib.parse.urlencode(args, quote_via=urllib.parse.quote) # post request - r = requests.get(URL, params=args) + r = requests.get(API_URL, params=args) dates = self._ics.convert(r.text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py index a58abaf4..3d330af5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py @@ -1,11 +1,10 @@ import datetime -import json import logging import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "AWIDO" +TITLE = "AWIDO Online" DESCRIPTION = "Source for AWIDO waste collection." URL = "https://www.awido-online.de/" TEST_CASES = { @@ -44,14 +43,14 @@ class Source: r = requests.get( f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getPlaces/client={self._customer}" ) - places = json.loads(r.text) + r.raise_for_status() + places = r.json() # create city to key map from retrieved places city_to_oid = {place["value"].strip(): place["key"] for (place) in places} if self._city not in city_to_oid: - _LOGGER.error(f"city not found: {self._city}") - return [] + raise Exception(f"city not found: {self._city}") oid = city_to_oid[self._city] @@ -62,7 +61,8 @@ class Source: f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getGroupedStreets/{oid}", params={"client": self._customer}, ) - streets = json.loads(r.text) + r.raise_for_status() + streets = r.json() # create street to key map from retrieved places street_to_oid = { @@ -78,7 +78,8 @@ class Source: f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getGroupedStreets/{oid}", params={"client": self._customer}, ) - streets = json.loads(r.text) + r.raise_for_status() + streets = r.json() # create street to key map from retrieved places street_to_oid = { @@ -86,8 +87,7 @@ class Source: } if self._street not in street_to_oid: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") oid = street_to_oid[self._street] @@ -96,7 +96,8 @@ class Source: f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getStreetAddons/{oid}", params={"client": self._customer}, ) - hsnbrs = json.loads(r.text) + r.raise_for_status() + hsnbrs = r.json() # create housenumber to key map from retrieved places hsnbr_to_oid = { @@ -104,8 +105,7 @@ class Source: } if self._housenumber not in hsnbr_to_oid: - _LOGGER.error(f"housenumber not found: {self._housenumber}") - return [] + raise Exception(f"housenumber not found: {self._housenumber}") oid = hsnbr_to_oid[self._housenumber] @@ -114,7 +114,8 @@ class Source: f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getData/{oid}", params={"fractions": "", "client": self._customer}, ) - cal_json = json.loads(r.text) + r.raise_for_status() + cal_json = r.json() # map fraction code to fraction name fractions = {fract["snm"]: fract["nm"] for (fract) in cal_json["fracts"]} diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awn_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awn_de.py index 685f6dce..058d38da 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awn_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awn_de.py @@ -5,7 +5,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AWN" +TITLE = "Abfallwirtschaft Neckar-Odenwald-Kreis" DESCRIPTION = "Source for AWN (Abfallwirtschaft Neckar-Odenwald-Kreis)." URL = "https://www.awn-online.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py index 8f6fef4b..363db3fb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py @@ -1,11 +1,10 @@ -import json import logging import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AWR" +TITLE = "Abfallwirtschaft Rendsburg" DESCRIPTION = "Source for Abfallwirtschaft Rendsburg" URL = "https://www.awr.de" TEST_CASES = { @@ -24,7 +23,8 @@ class Source: def fetch(self): # retrieve list of cities r = requests.get("https://www.awr.de/api_v2/collection_dates/1/orte") - cities = json.loads(r.text) + r.raise_for_status() + cities = r.json() # create city to id map from retrieved cities city_to_id = { @@ -32,8 +32,7 @@ class Source: } if self._city not in city_to_id: - _LOGGER.error(f"city not found: {self._city}") - return [] + raise Exception(f"city not found: {self._city}") cityId = city_to_id[self._city] @@ -41,7 +40,8 @@ class Source: r = requests.get( f"https://www.awr.de/api_v2/collection_dates/1/ort/{cityId}/strassen" ) - streets = json.loads(r.text) + r.raise_for_status() + streets = r.json() # create street to id map from retrieved cities street_to_id = { @@ -50,8 +50,7 @@ class Source: } if self._street not in street_to_id: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") streetId = street_to_id[self._street] @@ -59,7 +58,8 @@ class Source: r = requests.get( f"https://www.awr.de/api_v2/collection_dates/1/ort/{cityId}/abfallarten" ) - waste_types = json.loads(r.text) + r.raise_for_status() + waste_types = r.json() wt = "-".join([t["id"] for t in waste_types["abfallarten"]]) # get ics file @@ -73,4 +73,3 @@ class Source: for d in dates: entries.append(Collection(d[0], d[1])) return entries - diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py index ca9b61c3..e82ec0e6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py @@ -1,11 +1,10 @@ -import json import logging import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AWSH" +TITLE = "Abfallwirtschaft Südholstein" DESCRIPTION = "Source for Abfallwirtschaft Südholstein" URL = "https://www.awsh.de" TEST_CASES = { @@ -24,7 +23,8 @@ class Source: def fetch(self): # retrieve list of cities r = requests.get("https://www.awsh.de/api_v2/collection_dates/1/orte") - cities = json.loads(r.text) + r.raise_for_status() + cities = r.json() # create city to id map from retrieved cities city_to_id = { @@ -32,8 +32,7 @@ class Source: } if self._city not in city_to_id: - _LOGGER.error(f"city not found: {self._city}") - return [] + raise Exception(f"city not found: {self._city}") cityId = city_to_id[self._city] @@ -41,7 +40,8 @@ class Source: r = requests.get( f"https://www.awsh.de/api_v2/collection_dates/1/ort/{cityId}/strassen" ) - streets = json.loads(r.text) + r.raise_for_status() + streets = r.json() # create street to id map from retrieved cities street_to_id = { @@ -50,8 +50,7 @@ class Source: } if self._street not in street_to_id: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") streetId = street_to_id[self._street] @@ -59,13 +58,15 @@ class Source: r = requests.get( f"https://www.awsh.de/api_v2/collection_dates/1/ort/{cityId}/abfallarten" ) - waste_types = json.loads(r.text) + r.raise_for_status() + waste_types = r.json() wt = "-".join([t["id"] for t in waste_types["abfallarten"]]) # get ics file r = requests.get( f"https://www.awsh.de/api_v2/collection_dates/1/ort/{cityId}/strasse/{streetId}/hausnummern/0/abfallarten/{wt}/kalender.ics" ) + r.raise_for_status() dates = self._ics.convert(r.text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/banyule_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/banyule_vic_gov_au.py index ca13e793..7cf8e163 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/banyule_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/banyule_vic_gov_au.py @@ -10,7 +10,7 @@ from waste_collection_schedule import Collection TITLE = 'Banyule City Council' DESCRIPTION = 'Source for Banyule City Council rubbish collection.' -URL = 'https://www.banyule.vic.gov.au/binday' +URL = 'https://www.banyule.vic.gov.au' TEST_CASES = { 'Monday A': {'street_address': '6 Mandall Avenue, IVANHOE'}, 'Monday A Geolocation ID': {'geolocation_id': '4f7ebfca-1526-4363-8b87-df3103a10a87'}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py index 82537d83..cc91b474 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py @@ -5,7 +5,15 @@ from html.parser import HTMLParser import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Berline Recycling" +# With verify=True the POST fails due to a SSLCertVerificationError. +# Using verify=False works, but is not ideal. The following links may provide a better way of dealing with this: +# https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings +# https://urllib3.readthedocs.io/en/1.26.x/user-guide.html#ssl +# These two lines areused to suppress the InsecureRequestWarning when using verify=False +import urllib3 +urllib3.disable_warnings() + +TITLE = "Berlin Recycling" DESCRIPTION = "Source for Berlin Recycling waste collection." URL = "https://berlin-recycling.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bielefeld_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bielefeld_de.py index 3906e187..accb8e44 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bielefeld_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bielefeld_de.py @@ -4,6 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS +from waste_collection_schedule.service.SSLError import get_legacy_session TITLE = "Bielefeld" DESCRIPTION = "Source for Stadt Bielefeld." @@ -45,12 +46,17 @@ class Source: self._ics = ICS() def fetch(self): - session = requests.session() + s = get_legacy_session() + # session = requests.session() - r = session.get( + r = s.get( SERVLET, params={"SubmitAction": "wasteDisposalServices", "InFrameMode": "TRUE"}, ) + # r = session.get( + # SERVLET, + # params={"SubmitAction": "wasteDisposalServices", "InFrameMode": "TRUE"}, + # ) r.raise_for_status() r.encoding = "utf-8" @@ -68,17 +74,26 @@ class Source: args["ContainerGewaehlt_2"] = "on" args["ContainerGewaehlt_3"] = "on" args["ContainerGewaehlt_4"] = "on" - r = session.post( + + r = s.post( SERVLET, data=args, ) + # r = session.post( + # SERVLET, + # data=args, + # ) r.raise_for_status() args["SubmitAction"] = "forward" - r = session.post( + r = s.post( SERVLET, data=args, ) + # r = session.post( + # SERVLET, + # data=args, + # ) r.raise_for_status() reminder_day = "keine Erinnerung" # "keine Erinnerung", "am Vortag", "2 Tage vorher", "3 Tage vorher" @@ -88,10 +103,14 @@ class Source: args["SubmitAction"] = "filedownload_ICAL" args["ICalErinnerung"] = reminder_day args["ICalZeit"] = reminder_time - r = session.post( + r = s.post( SERVLET, data=args, ) + # r = session.post( + # SERVLET, + # data=args, + # ) r.raise_for_status() dates = self._ics.convert(r.text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bmv_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bmv_at.py index 9ab5d21d..cd58d464 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bmv_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bmv_at.py @@ -5,7 +5,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "BMV.at" +TITLE = "Burgenländischer Müllverband" DESCRIPTION = "Source for BMV, Austria" URL = "https://www.bmv.at" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bracknell_forest_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bracknell_forest_gov_uk.py index 720bf7ac..f423633a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bracknell_forest_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bracknell_forest_gov_uk.py @@ -4,7 +4,7 @@ import requests from dateutil import parser from waste_collection_schedule import Collection -TITLE = "bracknell-forest.gov.uk" +TITLE = "Bracknell Forest Council" DESCRIPTION = "Bracknell Forest Council, UK - Waste Collection" URL = "https://selfservice.mybfc.bracknell-forest.gov.uk" TEST_CASES = { @@ -13,7 +13,8 @@ TEST_CASES = { "32 Ashbourne": {"house_number": "32", "post_code": "RG12 8SG"}, "1 Acacia Avenue": {"house_number": "1", "post_code": "GU47 0RU"}, } -ICONS = { + +ICON_MAP = { "General Waste": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden": "mdi:leaf", @@ -68,7 +69,7 @@ class Source: collection_lookup.raise_for_status() collections = collection_lookup.json()["response"]["collections"] entries = [] - for waste_type in ICONS.keys(): + for waste_type in ICON_MAP.keys(): try: entries.append( Collection( @@ -78,7 +79,7 @@ class Source: ]["date"] ).date(), t=waste_type, - icon=ICONS[waste_type], + icon=ICON_MAP[waste_type], ) ) except (StopIteration, TypeError): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bradford_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bradford_gov_uk.py index 2e6bcd6a..869dc058 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bradford_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bradford_gov_uk.py @@ -11,18 +11,19 @@ import http.client as http_client import ssl import urllib3 -TITLE = "Bradford.gov.uk" +TITLE = "Bradford Metropolitan District Council" DESCRIPTION = ( "Source for Bradford.gov.uk services for Bradford Metropolitan Council, UK." ) -URL = "https://onlineforms.bradford.gov.uk/ufs/" +URL = "https://bradford.gov.uk" TEST_CASES = { "Ilkley": {"uprn": "100051250665"}, "Bradford": {"uprn": "100051239296"}, "Baildon": {"uprn": "10002329242"}, } -ICONS = { +API_URL = "https://onlineforms.bradford.gov.uk/ufs/" +ICON_MAP = { "REFUSE": "mdi:trash-can", "RECYCLING": "mdi:recycle", "GARDEN": "mdi:leaf", @@ -30,16 +31,16 @@ ICONS = { from pprint import pprint -class CustomHttpAdapter (requests.adapters.HTTPAdapter): - '''Transport adapter" that allows us to use custom ssl_context.''' - - def __init__(self, ssl_context=None, **kwargs): - self.ssl_context = ssl_context - super().__init__(**kwargs) - - def init_poolmanager(self, connections, maxsize, block=False): - self.poolmanager = urllib3.poolmanager.PoolManager( - num_pools=connections, maxsize=maxsize, +class CustomHttpAdapter (requests.adapters.HTTPAdapter): + '''Transport adapter" that allows us to use custom ssl_context.''' + + def __init__(self, ssl_context=None, **kwargs): + self.ssl_context = ssl_context + super().__init__(**kwargs) + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = urllib3.poolmanager.PoolManager( + num_pools=connections, maxsize=maxsize, block=block, ssl_context=self.ssl_context) class Source: @@ -59,7 +60,7 @@ class Source: s.cookies.set( "COLLECTIONDATES", self._uprn, domain="onlineforms.bradford.gov.uk" ) - r = s.get(f"{URL}/collectiondates.eb") + r = s.get(f"{API_URL}/collectiondates.eb") soup = BeautifulSoup(r.text, features="html.parser") div = soup.find_all("table", {"role": "region"}) @@ -87,7 +88,7 @@ class Source: entry.text.strip(), "%a %b %d %Y" ).date(), t=type, - icon=ICONS[type], + icon=ICON_MAP[type], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/braintree_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/braintree_gov_uk.py index b76826c9..e3c9c19a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/braintree_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/braintree_gov_uk.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup from dateutil import parser from waste_collection_schedule import Collection -TITLE = "braintree.gov.uk" +TITLE = "Braintree District Council" DESCRIPTION = "Braintree District Council, UK - Waste Collection" URL = "https://www.braintree.gov.uk" TEST_CASES = { @@ -13,7 +13,7 @@ TEST_CASES = { "20 Peel Crescent": {"house_number": "20", "post_code": "CM7 2RS"}, } -ICONS = { +ICON_MAP = { "Grey Bin": "mdi:trash-can", "Clear Sack": "mdi:recycle", "Green Bin": "mdi:leaf", @@ -51,7 +51,7 @@ class Source: Collection( date=parser.parse(collection_date).date(), t=collection_type, - icon=ICONS[collection_type] + icon=ICON_MAP[collection_type] ) ) except (StopIteration, TypeError): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py new file mode 100644 index 00000000..44753dc8 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py @@ -0,0 +1,82 @@ +import logging +from datetime import datetime + +import requests +from waste_collection_schedule import Collection + +TITLE = "Breckland Council" +DESCRIPTION = "Source for breckland.gov.uk" +URL = "https://www.breckland.gov.uk/mybreckland" +TEST_CASES = { + "test1": {"postcode": "IP22 2LJ", "address": "glen travis"}, + "test2": {"uprn": "10011977093"}, +} + +API_URL = "https://www.breckland.gov.uk/apiserver/ajaxlibrary" +ICON_MAP = { + "Refuse Collection Service": "mdi:trash-can", + "Recycling Collection Service": "mdi:recycle", + "Garden Waste Service": "mdi:leaf", +} + +_LOGGER = logging.getLogger(__name__) + +headers = {"referer": URL} + + +class Source: + def __init__(self, postcode=None, address=None, uprn=None): + self._postcode = postcode + self._address = address + self._uprn = uprn + + if postcode is None and address is None and uprn is None: + raise Exception("no attributes - specify postcode and address or just uprn") + + def fetch(self): + if self._uprn is None: + json_data = { + "jsonrpc": "2.0", + "id": "", + "method": "Breckland.Whitespace.JointWasteAPI.GetSiteIDsByPostcode", + "params": {"postcode": self._postcode, "environment": "live"}, + } + + r = requests.post(API_URL, json=json_data, headers=headers) + r.raise_for_status() + + json_response = r.json() + + for key in json_response["result"]: + if self._address.lower() in key["name"].lower(): + self._uprn = key["uprn"] + + if self._uprn is None: + raise Exception("Error querying calendar data") + + json_data = { + "jsonrpc": "2.0", + "id": "", + "method": "Breckland.Whitespace.JointWasteAPI.GetBinCollectionsByUprn", + "params": {"uprn": self._uprn, "environment": "live"}, + } + + r = requests.post(API_URL, json=json_data, headers=headers) + r.raise_for_status() + + waste = r.json()["result"] + + entries = [] + + for data in waste: + entries.append( + Collection( + datetime.strptime( + data["nextcollection"], "%d/%m/%Y %H:%M:%S" + ).date(), + data["collectiontype"], + ICON_MAP.get(data["collectiontype"]), + ) + ) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/brisbane_qld_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/brisbane_qld_gov_au.py index 510b4761..5288aeeb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/brisbane_qld_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/brisbane_qld_gov_au.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Brisbane City Council" DESCRIPTION = "Source for Brisbane City Council rubbish collection." -URL = "https://www.brisbane.qld.gov.au/clean-and-green/rubbish-tips-and-bins/rubbish-collections/bin-collection-calendar" +URL = "https://www.brisbane.qld.gov.au" TEST_CASES = { "Suburban Social": { "suburb": "Chapel Hill", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bsr_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bsr_de.py index fa420fee..f4a0675f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bsr_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bsr_de.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "BSR" +TITLE = "Berliner Stadtreinigungsbetriebe" DESCRIPTION = "Source for Berliner Stadtreinigungsbetriebe waste collection." URL = "https://bsr.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py new file mode 100644 index 00000000..bbe653dd --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py @@ -0,0 +1,299 @@ +import datetime +import re +from dataclasses import dataclass +from typing import List, Literal, Optional, TypedDict, Union + +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Bürgerportal" +URL = "https://www.c-trace.de" +DESCRIPTION = "Source for waste collection in multiple service areas." + + +def EXTRA_INFO(): + return [{"title": s["title"], "url": s["url"]} for s in SERVICE_MAP] + + +TEST_CASES = { + "Cochem-Zell": { + "operator": "cochem_zell", + "district": "Bullay", + "subdistrict": "Bullay", + "street": "Layenweg", + "number": 3, + }, + "Alb-Donau": { + "operator": "alb_donau", + "district": "Blaubeuren", + "street": "Alberstraße", + "number": 3, + }, + "Biedenkopf": { + "operator": "biedenkopf", + "district": "Biedenkopf", + "subdistrict": "Breidenstein", + "street": "Auf dem Hammer", + "number": 1, + }, +} + +ICON_MAP = { + "mobil": "mdi:truck", + "bio": "mdi:leaf", + "papier": "mdi:package-variant", + "verpackung": "mdi:recycle", + "gelb": "mdi:recycle", + "lvp": "mdi:recycle", + "rest": "mdi:trash-can", + "gruen": "mdi:forest", + "grün": "mdi:forest", + "baum": "mdi:forest", + "schnitt": "mdi:forest", + "schad": "mdi:biohazard", +} +API_HEADERS = { + "Accept": "application/json, text/plain;q=0.5", + "Cache-Control": "no-cache", +} +Operator = Literal["cochem_zell", "alb_donau", "biedenkopf"] + +SERVICE_MAP = [ + { + "title": "KV Cochem-Zell", + "url": "https://www.cochem-zell-online.de/", + "api_url": "https://buerger-portal-cochemzell.azurewebsites.net/api", + "operator": "cochem_zell", + }, + { + "title": "Abfallwirtschaft Alb-Donau-Kreis", + "url": "https://www.aw-adk.de/", + "api_url": "https://buerger-portal-albdonaukreisabfallwirtschaft.azurewebsites.net/api", + "operator": "alb_donau", + }, + { + "title": "MZV Bidenkopf", + "url": "https://mzv-biedenkopf.de/", + "api_url": "https://biedenkopfmzv.buergerportal.digital/api", + "operator": "biedenkopf", + }, +] + + +# This datalcass is used for adding entries to a set and remove duplicate entries. +# The default `Collection` extends the standard dict and thus is not hashable. +@dataclass(frozen=True, eq=True) +class CollectionEntry: + date: datetime.date + waste_type: str + icon: Optional[str] + + def export(self) -> Collection: + return Collection(self.date, self.waste_type, self.icon) + + +def quote_none(value: Optional[str]) -> str: + if value is None: + return "null" + + return f"'{value}'" + + +def get_api_map(): + return {s["operator"]: s["api_url"] for s in SERVICE_MAP} + + +class Source: + def __init__( + self, + operator: Operator, + district: str, + street: str, + subdistrict: Optional[str] = None, + number: Union[int, str, None] = None, + show_volume: bool = False, + ): + self.api_url = get_api_map()[operator] + self.district = district + self.subdistrict = subdistrict + self.street = street + self.number = number + self.show_volume = show_volume + + def fetch(self) -> list[Collection]: + session = requests.session() + session.headers.update(API_HEADERS) + + year = datetime.datetime.now().year + entries: set[CollectionEntry] = set() + + district_id = self.fetch_district_id(session) + street_id = self.fetch_street_id(session, district_id) + # Eventually verify house number in the future + + params = { + "$expand": "Abfuhrplan,Abfuhrplan/GefaesstarifArt/Abfallart,Abfuhrplan/GefaesstarifArt/Volumen", + "$orderby": "Abfuhrplan/GefaesstarifArt/Abfallart/Name,Abfuhrplan/GefaesstarifArt/Volumen/VolumenWert", + "orteId": district_id, + "strassenId": street_id, + "jahr": year, + } + + if self.number: + params["hausNr"] = (f"'{self.number}'",) + + res = session.get( + f"{self.api_url}/AbfuhrtermineAbJahr", + params=params, + ) + res.raise_for_status() + payload: CollectionsRes = res.json() + + date_regex = re.compile(r"\d+") + + for collection in payload["d"]: + if date_match := re.search(date_regex, collection["Termin"]): + timestamp = float(date_match.group()) + date = datetime.datetime.utcfromtimestamp(timestamp / 1000).date() + waste_type = collection["Abfuhrplan"]["GefaesstarifArt"]["Abfallart"][ + "Name" + ] + icon = None + + for icon_type, tested_icon in ICON_MAP.items(): + if icon_type.lower() in waste_type.lower(): + icon = tested_icon + + if self.show_volume: + volume = int( + collection["Abfuhrplan"]["GefaesstarifArt"]["Volumen"][ + "VolumenWert" + ] + ) + waste_type = f"{waste_type} ({volume} l)" + + entries.add(CollectionEntry(date, waste_type, icon)) + + if len(entries) == 0: + raise ValueError( + "No collections found! Please verify that your configuration is correct." + ) + + return [entry.export() for entry in entries] + + def fetch_district_id(self, session: requests.Session) -> int: + res = session.get( + f"{self.api_url}/OrteMitOrtsteilen", + headers=API_HEADERS, + ) + res.raise_for_status() + payload: DistrictsRes = res.json() + + try: + return next( + entry["OrteId"] + for entry in payload["d"] + if entry["Ortsname"] == self.district + and entry["Ortsteilname"] == self.subdistrict + ) + except StopIteration: + raise ValueError( + "District id cannot be fetched. " + "Please make sure that you entered a subdistrict if there is a comma on the website." + ) + + def fetch_street_id(self, session: requests.Session, district_id: int): + res = session.get( + f"{self.api_url}/Strassen", + params={ + "$filter": f"Ort/OrteId eq {district_id} and OrtsteilName eq {quote_none(self.subdistrict)}", + "$orderby": "Name asc", + }, + headers=API_HEADERS, + ) + res.raise_for_status() + payload: StreetsRes = res.json() + + try: + return next( + entry["StrassenId"] + for entry in payload["d"] + if entry["Name"] == self.street + ) + except StopIteration: + raise ValueError( + "Street ID cannot be fetched. Please verify your configuration." + ) + + +# Typed dictionaries for the API +# Automatically generated using https://pytyper.dev/ + + +class DistrictRes(TypedDict): + OrteId: int + Ortsname: str + Ortsteilname: Optional[str] + + +class DistrictsRes(TypedDict): + d: List[DistrictRes] + + +class StreetRes(TypedDict): + StrassenId: int + Name: str + Plz: str + + +class StreetsRes(TypedDict): + d: List[StreetRes] + + +class Capacity(TypedDict): + VolumenId: int + VolumenWert: str + + +class WasteType(TypedDict): + AbfallartenId: int + Code: str + Name: str + Farbe: str + IsBio: bool + IsPapier: bool + IsRest: bool + IsWertstoff: bool + Bemerkung: None + Aktiv: None + IsSchadstoff: None + + +class ContainerType(TypedDict): + GefaesstarifArtenId: int + BescheidText: None + BescheidTextLeerungsgebuehr: None + Bezeichnung: str + GefaesstarifArtVerwenden: bool + GefaesstarifArtVerwendenAbfallkalender: bool + Bemerkung: None + Volumen: Capacity + Abfallart: WasteType + # Abfuhrrhythmus: Abfuhrrhythmus + + +class CollectionPlan(TypedDict): + AbfuhrplaeneId: int + Jahr: int + GefaesstarifArt: ContainerType + # AbfallartenObj: Abfuhrrhythmus + + +class CollectionRes(TypedDict): + AbfuhrtermineId: int + Termin: str + Abfuhrplan: CollectionPlan + + +class CollectionsRes(TypedDict): + d: List[CollectionRes] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py index 8a02c081..44d220e2 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py @@ -2,9 +2,23 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "C-Trace.de" +TITLE = "C-Trace" DESCRIPTION = "Source for C-Trace.de." URL = "https://c-trace.de/" +EXTRA_INFO = [ + { + "title": "Bremener Stadreinigung", + "url": "https://www.die-bremer-stadtreinigung.de/", + }, + { + "title": "AWB Landkreis Augsburg", + "url": "https://www.awb-landkreis-augsburg.de/", + }, + { + "title": "WZV Kreis Segeberg", + "url": "https://www.wzv.de/", + }, +] TEST_CASES = { "Bremen": {"ort": "Bremen", "strasse": "Abbentorstraße", "hausnummer": 5}, "AugsburgLand": { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cambridge_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cambridge_gov_uk.py index 1e041599..ced93d21 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cambridge_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cambridge_gov_uk.py @@ -4,11 +4,11 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "Cambridge.gov.uk" +TITLE = "Cambridge City Council" DESCRIPTION = ( "Source for cambridge.gov.uk services for Cambridge and part of Cambridgeshire" ) -URL = "cambridge.gov.uk" +URL = "https://cambridge.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "CB13JD", "number": 37}, "houseName": {"post_code": "cb215hd", "number": "ROSEMARY HOUSE"}, @@ -19,7 +19,7 @@ API_URLS = { "collection": "https://servicelayer3c.azure-api.net/wastecalendar/collection/search/{}/", } -ICONS = { +ICON_MAP = { "DOMESTIC": "mdi:trash-can", "RECYCLE": "mdi:recycle", "ORGANIC": "mdi:leaf", @@ -63,7 +63,7 @@ class Source: collection["date"], "%Y-%m-%dT%H:%M:%SZ" ).date(), t=round_type.title(), - icon=ICONS.get(round_type), + icon=ICON_MAP.get(round_type), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/canadabay_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/canadabay_nsw_gov_au.py index b4ca87c2..c4db33bc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/canadabay_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/canadabay_nsw_gov_au.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "City of Canada Bay Council" DESCRIPTION = "Source for City of Canada Bay Council rubbish collection." -URL = "https://www.canadabay.nsw.gov.au/residents/waste-and-recycling/my-bins/my-bin-collection" +URL = "https://www.canadabay.nsw.gov.au" TEST_CASES = { "Harry's Shed": { "suburb": "Concord", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/canterbury_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/canterbury_gov_uk.py index 7bd4e472..e5b4ae9a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/canterbury_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/canterbury_gov_uk.py @@ -5,11 +5,11 @@ import json import requests from waste_collection_schedule import Collection -TITLE = "canterbury.gov.uk" +TITLE = "Canterbury City Council" DESCRIPTION = ( "Source for canterbury.gov.uk services for canterbury" ) -URL = "canterbury.gov.uk" +URL = "https://canterbury.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "ct68ru", "number": "63"}, "houseName": {"post_code": "ct68ru", "number": "KOWLOON"}, @@ -20,7 +20,7 @@ API_URLS = { "collection": "https://zbr7r13ke2.execute-api.eu-west-2.amazonaws.com/Beta/get-bin-dates", } -ICONS = { +ICON_MAP = { "General": "mdi:trash-can", "Recycling": "mdi:recycle", "Food": "mdi:food-apple", @@ -77,7 +77,7 @@ class Source: date, "%Y-%m-%dT%H:%M:%S" ).date(), t=collection, - icon=ICONS.get(collection), + icon=ICON_MAP.get(collection), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py index 0d098489..ca3c4a6c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py @@ -3,9 +3,13 @@ import datetime import requests from waste_collection_schedule import Collection +# Include work around for SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error +from waste_collection_schedule.service.SSLError import get_legacy_session + + TITLE = "Christchurch City Council" DESCRIPTION = "Source for Christchurch City Council." -URL = "https://ccc.govt.nz/services/rubbish-and-recycling/collections" +URL = "https://ccc.govt.nz" TEST_CASES = {"53 Hereford Street": {"address": "53 Hereford Street"}} @@ -14,6 +18,9 @@ class Source: self._address = address def fetch(self): + + s = get_legacy_session() + entries = [] # Find the Rating Unit ID by the physical address @@ -24,9 +31,10 @@ class Source: "crs": "epsg:4326", "limit": 1, } - r = requests.get( - "https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", + + r = s.get("https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", params=addressQuery, + # verify=False, ) address = r.json() @@ -35,9 +43,11 @@ class Source: "client_id": "69f433c880c74c349b0128e9fa1b6a93", "client_secret": "139F3D2A83E34AdF98c80566f2eb7212" } - r = requests.get( - "https://ccc-data-citizen-api-v1-prod.au-s1.cloudhub.io/api/v1/properties/" + str(address[0]["RatingUnitID"]), + + # Updated request using SSL code snippet + r = s.get("https://ccc-data-citizen-api-v1-prod.au-s1.cloudhub.io/api/v1/properties/" + str(address[0]["RatingUnitID"]), headers=binsHeaders + # verify=False, ) bins = r.json() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py index b9abf0ba..bd63ebc3 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py @@ -4,11 +4,9 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "cheshireeast.gov.uk" +TITLE = "Cheshire East Council" DESCRIPTION = "Source for cheshireeast.gov.uk services for Cheshire East" -URL = "cheshireeast.gov.uk" - - +URL = "https://cheshireeast.gov.uk" TEST_CASES = { "houseUPRN": {"uprn": "100010132071"}, "houseAddress": {"postcode": "WA16 0AY", "name_number": "1"}, @@ -43,7 +41,7 @@ class Source: r.raise_for_status() soup = BeautifulSoup(r.text, features="html.parser") s = soup.find("a", attrs={"class": "get-job-details"}) - print(s) + if s is None: raise Exception("address not found") self._uprn = s["data-uprn"] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/chesterfield_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/chesterfield_gov_uk.py index da6da807..929e1ab3 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/chesterfield_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/chesterfield_gov_uk.py @@ -14,12 +14,8 @@ import urllib3 urllib3.disable_warnings() -TITLE = "chesterfield.gov.uk" - -DESCRIPTION = ( - "Source for waste collection services for Chesterfield Borough Council" -) - +TITLE = "Chesterfield Borough Council" +DESCRIPTION = "Source for waste collection services for Chesterfield Borough Council" URL = "https://www.chesterfield.gov.uk/" HEADERS = { @@ -33,13 +29,13 @@ TEST_CASES = { "Test_004": {"uprn": "74020930"}, } -ICONS = { +ICON_MAP = { "DOMESTIC REFUSE": "mdi:trash-can", "DOMESTIC RECYCLING": "mdi:recycle", "DOMESTIC ORGANIC": "mdi:leaf", } -APIS = { +API_URLS = { "session": "https://www.chesterfield.gov.uk/bins-and-recycling/bin-collections/check-bin-collections.aspx", "fwuid": "https://myaccount.chesterfield.gov.uk/anonymous/c/cbc_VE_CollectionDaysLO.app?aura.format=JSON&aura.formatAdapter=LIGHTNING_OUT", "search": "https://myaccount.chesterfield.gov.uk/anonymous/aura?r=2&aura.ApexAction.execute=1", @@ -57,13 +53,13 @@ class Source: s = requests.Session() r = s.get( - APIS["session"], + API_URLS["session"], headers=HEADERS, ) # Capture fwuid value r = s.get( - APIS["fwuid"], + API_URLS["fwuid"], verify=False, headers=HEADERS, ) @@ -83,7 +79,7 @@ class Source: "aura.token": "null", } r = s.post( - APIS["search"], + API_URLS["search"], data=payload, verify=False, headers=HEADERS, @@ -108,7 +104,7 @@ class Source: Collection( date=dt_local.date(), t=waste_type, - icon=ICONS.get(waste_type.upper()), + icon=ICON_MAP.get(waste_type.upper()), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cochem_zell_online_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cochem_zell_online_de.py deleted file mode 100644 index c2e43d95..00000000 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cochem_zell_online_de.py +++ /dev/null @@ -1,69 +0,0 @@ -import contextlib -from datetime import datetime - -import requests -from waste_collection_schedule import Collection # type: ignore[attr-defined] -from waste_collection_schedule.service.ICS import ICS - -TITLE = "Abfall Cochem-Zell" -DESCRIPTION = "Source for waste collection in district Cochem-Zell." -URL = "https://www.cochem-zell-online.de/abfallkalender/" -TEST_CASES = { - "Alf": {"district": "Alf"}, - "Bullay": {"district": "Bullay"}, - "Zell-Stadt": {"district": "Zell-Stadt"}, - "Pünderich": {"district": "Pünderich"}, -} - -API_URL = "https://abfallkalender10.app.moba.de/Cochem_Zell/api" -REMINDER_DAY = 0 # The calendar event should be on the same day as the waste collection -REMINDER_HOUR = 6 # The calendar event should start on any hour of the correct day, so this does not matter much -FILENAME = "Abfallkalender.ics" -ICON_MAP = { - "Biotonne": "mdi:leaf", - "Gruengut": "mdi:forest", - "Papierabfall": "mdi:package-variant", - "Restmülltonne": "mdi:trash-can", - "Umweltmobil": "mdi:truck", - "Verpackungsabfall": "mdi:recycle", -} - - -class Source: - def __init__(self, district: str): - self._district = district - self._ics = ICS() - - def fetch(self): - now = datetime.now() - entries = self._fetch_year(now.year) - - if now.month == 12: - # also get data for next year if we are already in december - with contextlib.suppress(Exception): - entries.extend(self._fetch_year(now.year + 1)) - - return entries - - def _fetch_year(self, year: int): - url = "/".join( - str(param) - for param in ( - API_URL, - self._district, - year, - REMINDER_DAY, - REMINDER_HOUR, - FILENAME, - ) - ) - - r = requests.get(url) - schedule = self._ics.convert(r.text) - - return [ - Collection( - date=entry[0], t=entry[1], icon=ICON_MAP.get(entry[1], "mdi:trash-can") - ) - for entry in schedule - ] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/colchester_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/colchester_gov_uk.py index 42ac875b..ce0fea32 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/colchester_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/colchester_gov_uk.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Colchester.gov.uk" +TITLE = "Colchester Borough Council" DESCRIPTION = "Source for Colchester.gov.uk services for the borough of Colchester, UK." URL = "https://colchester.gov.uk" TEST_CASES = { @@ -13,7 +13,7 @@ TEST_CASES = { "The Lane, Colchester": {"llpgid": "7cd96a3d-6027-e711-80fa-5065f38b56d1"}, } -ICONS = { +ICON_MAP = { "Black bags": "mdi:trash-can", "Glass": "mdi:glass-fragile", "Cans": "mdi:trash-can", @@ -63,7 +63,7 @@ class Source: Collection( date=date.date(), t=day["Name"].title(), - icon=ICONS[day["Name"]], + icon=ICON_MAP[day["Name"]], ) ) # As Colchester.gov.uk only provides the current collection cycle, the next must be extrapolated @@ -73,7 +73,7 @@ class Source: Collection( date=date.date() + timedelta(days=14), t=day["Name"].title(), - icon=ICONS[day["Name"]], + icon=ICON_MAP[day["Name"]], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cornwall_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cornwall_gov_uk.py index 11896234..6b57b8cc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cornwall_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cornwall_gov_uk.py @@ -4,13 +4,14 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "Cornwall Council, UK" +TITLE = "Cornwall Council" DESCRIPTION = "Source for cornwall.gov.uk services for Cornwall Council" -URL = "cornwall.gov.uk" +URL = "https://cornwall.gov.uk" TEST_CASES = { "known_uprn": {"uprn": "100040118005"}, "unknown_uprn": {"postcode": "TR261SP", "housenumberorname": "7"}, } + SEARCH_URLS = { "uprn_search": "https://www.cornwall.gov.uk/my-area/", "collection_search": "https://www.cornwall.gov.uk/umbraco/Surface/Waste/MyCollectionDays?subscribe=False", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py index 8ccd1004..296244b4 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py @@ -1,20 +1,26 @@ import logging -import requests from datetime import datetime from xml.dom.minidom import parseString + +import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "UMWELTPROFIS" +TITLE = "Umweltprofis" DESCRIPTION = "Source for Umweltprofis" URL = "https://www.umweltprofis.at" TEST_CASES = { - "Ebensee": {"url": "https://data.umweltprofis.at/OpenData/AppointmentService/AppointmentService.asmx/GetIcalWastePickupCalendar?key=KXX_K0bIXDdk0NrTkk3xWqLM9-bsNgIVBE6FMXDObTqxmp9S39nIqwhf9LTIAX9shrlpfCYU7TG_8pS9NjkAJnM_ruQ1SYm3V9YXVRfLRws1"}, - "Rohrbach": {"xmlurl": "https://data.umweltprofis.at/opendata/AppointmentService/AppointmentService.asmx/GetTermineForLocationSecured?Key=TEMPKeyabvvMKVCic0cMcmsTEMPKey&StreetNr=118213&HouseNr=Alle&intervall=Alle"}, + "Ebensee": { + "url": "https://data.umweltprofis.at/OpenData/AppointmentService/AppointmentService.asmx/GetIcalWastePickupCalendar?key=KXX_K0bIXDdk0NrTkk3xWqLM9-bsNgIVBE6FMXDObTqxmp9S39nIqwhf9LTIAX9shrlpfCYU7TG_8pS9NjkAJnM_ruQ1SYm3V9YXVRfLRws1" + }, + "Rohrbach": { + "xmlurl": "https://data.umweltprofis.at/opendata/AppointmentService/AppointmentService.asmx/GetTermineForLocationSecured?Key=TEMPKeyabvvMKVCic0cMcmsTEMPKey&StreetNr=118213&HouseNr=Alle&intervall=Alle" + }, } _LOGGER = logging.getLogger(__name__) + def getText(element): s = "" for e in element.childNodes: @@ -22,6 +28,7 @@ def getText(element): s += e.nodeValue return s + class Source: def __init__(self, url=None, xmlurl=None): self._url = url @@ -38,11 +45,11 @@ class Source: def fetch_ics(self): r = requests.get(self._url) - if r.status_code != 200: - _LOGGER.error("Error querying calendar data") - return [] + r.raise_for_status() - fixed_text = r.text.replace("REFRESH - INTERVAL; VALUE = ", "REFRESH-INTERVAL;VALUE=") + fixed_text = r.text.replace( + "REFRESH - INTERVAL; VALUE = ", "REFRESH-INTERVAL;VALUE=" + ) dates = self._ics.convert(fixed_text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py index 897ac858..628a1e73 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py @@ -1,15 +1,14 @@ +import logging from datetime import datetime +from urllib.parse import parse_qs, urlsplit import requests +from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -from bs4 import BeautifulSoup -from urllib.parse import urlsplit, parse_qs -import logging - -TITLE = "Derby.gov.uk" +TITLE = "Derby City Council" DESCRIPTION = "Source for Derby.gov.uk services for Derby City Council, UK." -URL = "https://secure.derby.gov.uk/binday/" +URL = "https://derby.gov.uk" TEST_CASES = { # Derby City council wants specific addresses, hopefully these are generic enough. "Community Of The Holy Name, Morley Road, Derby, DE21 4TB": { @@ -21,7 +20,7 @@ TEST_CASES = { }, } -ICONS = { +ICON_MAP = { "Black bin": "mdi:trash-can", "Blue bin": "mdi:recycle", "Brown bin": "mdi:leaf", @@ -38,7 +37,7 @@ class Source: self._post_code = post_code self._house_number = house_number if not any([self._premises_id, self._post_code and self._house_number]): - _LOGGER.error( + raise Exception( "premises_id or post_code and house number must be provided in config" ) self._session = requests.Session() @@ -75,7 +74,7 @@ class Source: try: date = datetime.strptime(date.text, "%A, %d %B %Y:").date() except ValueError: - _LOGGER.error(f"Skipped {date} as it does not match time format") + _LOGGER.info(f"Skipped {date} as it does not match time format") continue img_tag = result.find("img") collection_type = img_tag["alt"] @@ -83,7 +82,7 @@ class Source: Collection( date=date, t=collection_type, - icon=ICONS[collection_type], + icon=ICON_MAP[collection_type], ) ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ecoharmonogram_pl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ecoharmonogram_pl.py index 568fdeb0..679ccaa9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ecoharmonogram_pl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ecoharmonogram_pl.py @@ -3,8 +3,9 @@ from ..collection import Collection from ..service.EcoHarmonogramPL import Ecoharmonogram +TITLE = "Ecoharmonogram" DESCRIPTION = "Source for ecoharmonogram.pl" -URL = "ecoharmonogram.pl" +URL = "https://ecoharmonogram.pl" TEST_CASES = { "Simple test case": {"town": "Krzeszowice", "street": "Wyki", "house_number": ""}, "Sides multi test case": {"town": "Częstochowa", "street": "Boczna", "additional_sides_matcher": "wie"}, @@ -14,7 +15,6 @@ TEST_CASES = { "additional_sides_matcher": "Wielorodzinna - powyżej 7 lokali"}, "Simple test with community": {"town": "Gdańsk", "street": "Jabłoniowa", "house_number": "55", "additional_sides_matcher": "", "community": "108" }, } -TITLE = "ecoharmonogram.pl" class Source: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py index 5a3971b3..7a1561c9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py @@ -7,7 +7,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "EGN Abfallkalender" DESCRIPTION = "Source for EGN Abfallkalender" -URL = "https://www.egn-abfallkalender.de/kalender" +URL = "https://www.egn-abfallkalender.de" TEST_CASES = { "Grevenbroich": { "city": "Grevenbroich", @@ -31,7 +31,8 @@ TEST_CASES = { _LOGGER = logging.getLogger(__name__) -IconMap = { +API_URL = "https://www.egn-abfallkalender.de/kalender" +ICON_MAP = { "Grau": "mdi:trash-can", "Gelb": "mdi:sack", "Blau": "mdi:package-variant", @@ -48,7 +49,7 @@ class Source: def fetch(self): s = requests.session() - r = s.get(URL) + r = s.get(API_URL) soup = BeautifulSoup(r.text, features="html.parser") tag = soup.find("meta", {"name": "csrf-token"}) @@ -62,14 +63,19 @@ class Source: "street": self._street, "street_number": self._housenumber, } - r = s.post(URL, data=post_data, headers=headers) + r = s.post(API_URL, data=post_data, headers=headers) data = r.json() if data.get("error"): - for type, errormsg in data["errors"].items(): - _LOGGER.error(f"{type} - {errormsg}") - return [] + raise Exception( + "\n".join( + [ + f"{type} - {errormsg}" + for type, errormsg in data["errors"].items() + ] + ) + ) entries = [] for year, months in data["waste_discharge"].items(): @@ -85,7 +91,7 @@ class Source: .capitalize() ) entries.append( - Collection(date=date, t=color, icon=IconMap.get(color)) + Collection(date=date, t=color, icon=ICON_MAP.get(color)) ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py index 209375ba..0431d923 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py @@ -1,23 +1,15 @@ import logging -import requests - -from bs4 import BeautifulSoup from datetime import datetime, timedelta + +import requests +from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = 'elmbridge.gov.uk' -DESCRIPTION = ( - 'Source for waste collection services for Elmbridge Borough Council' -) -URL = 'https://www.elmbridge.gov.uk/waste-and-recycling/' - - -HEADERS = { - "user-agent": "Mozilla/5.0", -} - +TITLE = "Elmbridge Borough Council" +DESCRIPTION = "Source for waste collection services for Elmbridge Borough Council" +URL = "https://www.elmbridge.gov.uk" TEST_CASES = { - "Test_001" : {"uprn": 10013119164}, + "Test_001": {"uprn": 10013119164}, "Test_002": {"uprn": "100061309206"}, "Test_003": {"uprn": 100062119825}, "Test_004": {"uprn": "100061343923"}, @@ -25,28 +17,32 @@ TEST_CASES = { } API_URLS = { - 'session': 'https://emaps.elmbridge.gov.uk/myElmbridge.aspx', - 'search': 'https://emaps.elmbridge.gov.uk/myElmbridge.aspx?action=SetAddress&UniqueId={}', - 'schedule': 'https://emaps.elmbridge.gov.uk/myElmbridge.aspx?tab=0#Refuse_&_Recycling', + "session": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx", + "search": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx?action=SetAddress&UniqueId={}", + "schedule": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx?tab=0#Refuse_&_Recycling", } OFFSETS = { - 'Monday': 0, - 'Tuesday': 1, - 'Wednesday': 2, - 'Thursday': 3, - 'Friday': 4, - 'Saturday': 5, - 'Sunday': 6, + "Monday": 0, + "Tuesday": 1, + "Wednesday": 2, + "Thursday": 3, + "Friday": 4, + "Saturday": 5, + "Sunday": 6, } -ICONS = { +ICON_MAP = { "REFUSE": "mdi:trash-can", "RECYCLING": "mdi:recycle", "FOOD": "mdi:food", "GARDEN": "mdi:leaf", } +HEADERS = { + "user-agent": "Mozilla/5.0", +} + _LOGGER = logging.getLogger(__name__) @@ -61,63 +57,65 @@ class Source: # This script assumes the week-commencing dates are for the current year. # This'll cause problems in December as upcoming January collections will have been assigned dates in the past. # Some clunky logic can deal with this: - # If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occured. + # If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occurred. # If a date is more than 1 month in the past, assume it's an incorrectly assigned date and increment the year by 1. - # Once that's been done, offset the week-commencing dates to match day of the week each waste collection type is scheduled. + # Once that's been done, offset the week-commencing dates to match day of the week each waste collection type is scheduled. # If you have a better way of doing this, feel free to update via a Pull Request! # Get current date and year in format consistent with API result today = datetime.now() - today = today.replace(hour = 0, minute = 0, second = 0, microsecond = 0) + today = today.replace(hour=0, minute=0, second=0, microsecond=0) year = today.year s = requests.Session() - r0 = s.get(API_URLS['session'], headers=HEADERS) + r0 = s.get(API_URLS["session"], headers=HEADERS) r0.raise_for_status() - r1 = s.get(API_URLS['search'].format(self._uprn), headers=HEADERS) + r1 = s.get(API_URLS["search"].format(self._uprn), headers=HEADERS) r1.raise_for_status() - r2 = s.get(API_URLS['schedule'], headers=HEADERS) + r2 = s.get(API_URLS["schedule"], headers=HEADERS) r2.raise_for_status() - + responseContent = r2.content - soup = BeautifulSoup(responseContent, 'html.parser') + soup = BeautifulSoup(responseContent, "html.parser") entries = [] - notice = soup.find('div', {'class': 'atPanelContent atFirst atAlt0'}) - notices = notice.text.replace('\nRefuse and recycling collection days\n', '').split('.') - notices.pop(-1) # Remove superflous element - frame = soup.find('div', {'class': 'atPanelContent atAlt1 atLast'}) - table = frame.find('table') + notice = soup.find("div", {"class": "atPanelContent atFirst atAlt0"}) + notices = notice.text.replace( + "\nRefuse and recycling collection days\n", "" + ).split(".") + notices.pop(-1) # Remove superfluous element + frame = soup.find("div", {"class": "atPanelContent atAlt1 atLast"}) + table = frame.find("table") - for tr in table.find_all('tr'): + for tr in table.find_all("tr"): row = [] - for td in tr.find_all('td'): + for td in tr.find_all("td"): row.append(td.text.strip()) - row.pop(1) # removes superflous element - dt = row[0] + ' ' + str(year) - dt = datetime.strptime(dt, '%d %b %Y') + row.pop(1) # removes superfluous element + dt = row[0] + " " + str(year) + dt = datetime.strptime(dt, "%d %b %Y") # Amend year, if necessary - if (dt - today) < timedelta(days = -31): - dt += timedelta(year = 1) + if (dt - today) < timedelta(days=-31): + dt = dt.replace(year=dt.year + 1) row[0] = dt # Separate out same-day waste collections - wastetypes = row[1].split(' + ') + wastetypes = row[1].split(" + ") # Sort out date offsets for each collection type for waste in wastetypes: for day, offset in OFFSETS.items(): for sentence in notices: if (waste in sentence) and (day in sentence): - new_date = row[0] + timedelta(days = offset) + new_date = row[0] + timedelta(days=offset) entries.append( Collection( - date = new_date.date(), - t = waste + ' bin', - icon = ICONS.get(waste.upper()), + date=new_date.date(), + t=waste + " bin", + icon=ICON_MAP.get(waste.upper()), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/environmentfirst_co_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/environmentfirst_co_uk.py index 1bdfc1b3..ed9a8a3d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/environmentfirst_co_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/environmentfirst_co_uk.py @@ -5,24 +5,31 @@ from bs4 import BeautifulSoup from dateutil.parser import parse from waste_collection_schedule import Collection -TITLE = "environmentfirst.co.uk" - +TITLE = "Environment First" +URL = "https://environmentfirst.co.uk" +EXTRA_INFO = [ + { + "title": "Eastbourne Borough Council", + "url": "https://lewes-eastbourne.gov.uk" + }, + { + "title": "Lewes District Council", + "url": "https://lewes-eastbourne.gov.uk" + }, +] DESCRIPTION = ( """Consolidated source for waste collection services from: Eastbourne Borough Council Lewes District Council """ ) - -URL = "https://environmentfirst.co.uk" - TEST_CASES = { "houseUPRN" : {"uprn": "100060063421"}, "houseNumber": {"post_code": "BN228SG", "number": 3}, "houseName": {"post_code": "BN73LG", "number": "Garden Cottage"}, } -ICONS = { +ICON_MAP = { "RUBBISH": "mdi:trash-can", "RECYCLING": "mdi:recycle", "GARDEN WASTE": "mdi:leaf", @@ -92,13 +99,13 @@ class Source: x = soup.findAll("p") for i in x[1:-1]: # ignores elements containing address and marketing message if " day " in i.text: - for round_type in ICONS: + for round_type in ICON_MAP: if round_type in i.text.upper(): entries.append( Collection( date = parse(str.split(i.text, ":")[1]).date(), t = round_type, - icon = ICONS.get(round_type), + icon = ICON_MAP.get(round_type), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py index d366bdef..3d0ced12 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py @@ -1,17 +1,16 @@ -import requests import datetime + +import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -import urllib - TITLE = "Landkreis Erlangen-Höchstadt" DESCRIPTION = "Source for Landkreis Erlangen-Höchstadt" URL = "https://www.erlangen-hoechstadt.de/" TEST_CASES = { "Höchstadt": {"city": "Höchstadt", "street": "Böhmerwaldstraße"}, "Brand": {"city": "Eckental", "street": "Eckenhaid, Amselweg"}, - "Ortsteile": {"city": "Wachenroth", "street": "Wachenroth Ort ink. aller Ortsteile"} + "Ortsteile": {"city": "Wachenroth", "street": "Ort inkl. aller Ortsteile"}, } @@ -22,20 +21,25 @@ class Source: self._ics = ICS(split_at=" / ") def fetch(self): - city = self._city.upper() - street = self._street today = datetime.date.today() - year = today.year - - payload = {"ort": city, "strasse": street, - "abfallart": "Alle", "jahr": year} - r = requests.get( - "https://www.erlangen-hoechstadt.de/komx/surface/dfxabfallics/GetAbfallIcs", params=payload - ) - r.encoding = r.apparent_encoding - dates = self._ics.convert(r.text) + dates = self.fetch_year(today.year) + if today.month == 12: + dates.extend(self.fetch_year(today.year + 1)) entries = [] for d in dates: entries.append(Collection(d[0], d[1])) return entries + + def fetch_year(self, year): + city = self._city.upper() + street = self._street + + payload = {"ort": city, "strasse": street, "abfallart": "Alle", "jahr": year} + r = requests.get( + "https://www.erlangen-hoechstadt.de/komx/surface/dfxabfallics/GetAbfallIcs", + params=payload, + ) + r.raise_for_status() + r.encoding = "utf-8" + return self._ics.convert(r.text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py index 3558a20b..5312407e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py @@ -1,74 +1,139 @@ -import logging -import requests +from urllib.parse import urlparse +import requests from bs4 import BeautifulSoup -from datetime import datetime +from dateutil import parser from waste_collection_schedule import Collection -TITLE = "fccenvironment.co.uk" - -DESCRIPTION = ( - """Consolidated source for waste collection services for ~60 local authorities. - Currently supports: - Market Harborough - """ -) - +TITLE = "FCC Environment" +DESCRIPTION = """ + Consolidated source for waste collection services for ~60 local authorities. + Currently supports: + West Devon (Generic Provider) + South Hams (Generic Provider) + Market Harborough (Custom Provider) + """ URL = "https://fccenvironment.co.uk" +EXTRA_INFO = [ + { + "title": "Harborough District Council", + "url": "https://harborough.gov.uk" + }, + { + "title": "South Hams District Council", + "url": "https://southhams.gov.uk/" + }, + { + "title": "West Devon Borough Council", + "url": "https://www.westdevon.gov.uk/" + }, +] TEST_CASES = { - "Test_001" : {"uprn": "100030491624"}, - "Test_002": {"uprn": "100030491614"}, - "Test_003": {"uprn": "100030493289"}, - "Test_004": {"uprn": "200001136341"} + "14_LE16_9QX": {"uprn": "100030491624"}, # region ommited to test default values + "4_LE16_9QX": {"uprn": "100030491614", "region": "harborough"}, + "16_LE16_7NA": {"uprn": "100030493289", "region": "harborough"}, + "10_LE16_8ER": {"uprn": "200001136341", "region": "harborough"}, + "9_PL20_7SH": {"uprn": "10001326315", "region": "westdevon"}, + "3_PL20_7RY": {"uprn": "10001326041", "region": "westdevon"}, + "2_PL21_9BN": {"uprn": "100040279446", "region": "southhams"}, + "4_SL21_0HZ": {"uprn": "100040281987", "region": "southhams"}, } - -ICONS = { - "NON-RECYCLABLE WASTE BIN COLLECTION": "mdi:trash-can", - "RECYCLING COLLECTION": "mdi:recycle", - "GARDEN WASTE COLLECTION": "mdi:leaf", +ICON_MAP = { + "Refuse": "mdi:trash-can", + "Recycling": "mdi:recycle", + "Garden": "mdi:leaf", } -_LOGGER = logging.getLogger(__name__) - - class Source: - def __init__(self, uprn=None): - self._uprn = uprn + def __init__(self, uprn: str, region: str = "harborough") -> None: + self.uprn = uprn + self.region = region - def fetch(self): + def getcollectiondetails(self, endpoint: str) -> list[Collection]: + domain = urlparse(endpoint).netloc + session = requests.Session() + cookies = session.get(f"https://{domain}/") + response = session.post( + endpoint, + headers={ + "x-requested-with": "XMLHttpRequest", + }, + data={ + "fcc_session_token": cookies.cookies["fcc_session_cookie"], + "uprn": self.uprn, + }, + ) + results = {} + for item in response.json()["binCollections"]["tile"]: + try: + soup = BeautifulSoup(item[0], "html.parser") + date = parser.parse(soup.find_all("b")[2].text.split(",")[1].strip()).date() + service = soup.text.split("\n")[0] + except parser._parser.ParserError: + continue - s = requests.Session() - - if self._uprn: - # POST request returns schedule for matching uprn - payload = { - "Uprn": self._uprn - } - r = s.post("https://www.fccenvironment.co.uk/harborough/detail-address", data = payload) - responseContent = r.text + """ + Handle duplication before creating the list of Collections + """ + for type in ICON_MAP: + if type in service: + if type in results.keys(): + if date < results[type]: + results[type] = date + else: + results[type] = date entries = [] - # Extract waste types and dates from responseContent - soup = BeautifulSoup(responseContent, "html.parser") - services = soup.find("div", attrs={"class": "blocks block-your-next-scheduled-bin-collection-days"}) - items = services.find_all("li") - for item in items: - date_text = item.find("span", attrs={"class": "pull-right"}).text.strip() - try: - date = datetime.strptime(date_text, "%d %B %Y").date() - except ValueError: - continue - else: - waste_type = item.text.split(' (')[0] - entries.append( - Collection( - date=date, - t=waste_type, - icon=ICONS.get(waste_type.upper()), - ) + for result in results: + entries.append( + Collection( + date=results[result], + t=result, + icon=ICON_MAP[result], ) - + ) return entries + + def harborough(self) -> list[Collection]: + _icons = { + "NON-RECYCLABLE WASTE BIN COLLECTION": "mdi:trash-can", + "RECYCLING COLLECTION": "mdi:recycle", + "GARDEN WASTE COLLECTION": "mdi:leaf", + } # Custom icons to avoid a breaking change + r = requests.post("https://www.fccenvironment.co.uk/harborough/detail-address", data={"Uprn": self.uprn}) + soup = BeautifulSoup(r.text, "html.parser") + services = soup.find("div", attrs={"class": "blocks block-your-next-scheduled-bin-collection-days"}).find_all( + "li" + ) + entries = [] + for service in services: + for type in _icons: + if type.lower() in service.text.lower(): + try: + date = parser.parse(service.find("span", attrs={"class": "pull-right"}).text.strip()).date() + except parser._parser.ParserError: + continue + + entries.append( + Collection( + date=date, + t=type, + icon=_icons[type.upper()], + ) + ) + return entries + + def fetch(self) -> list[Collection]: + if self.region == "harborough": + return self.harborough() + elif self.region == "westdevon": + return self.getcollectiondetails( + endpoint="https://westdevon.fccenvironment.co.uk/ajaxprocessor/getcollectiondetails" + ) + elif self.region == "southhams": + return self.getcollectiondetails( + endpoint="https://waste.southhams.gov.uk/mycollections/getcollectiondetails" + ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py new file mode 100644 index 00000000..a7cff602 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py @@ -0,0 +1,66 @@ +import datetime +import urllib + +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] +from waste_collection_schedule.service.ICS import ICS + +TITLE = "Landkreis Nordwestmecklenburg" +DESCRIPTION = "Source for Landkreis Nordwestmecklenburg" +URL = "https://www.geoport-nwm.de" +TEST_CASES = { + "Rüting": {"district": "Rüting"}, + "Grevenstein u. ...": {"district": "Grevenstein u. Ausbau"}, + "Seefeld": {"district": "Seefeld/ Testorf- Steinfort"}, + "1100l": {"district": "Groß Stieten (1.100 l Behälter)"}, + "kl. Bünsdorf": {"district": "Klein Bünsdorf"}, +} + + +class Source: + def __init__(self, district): + self._district = district + self._ics = ICS() + + def fetch(self): + today = datetime.date.today() + dates = [] + if today.month == 12: + # On Dec 27 2022, the 2022 schedule was no longer available for test case "Seefeld", all others worked + try: + dates = self.fetch_year(today.year) + except Exception: + pass + try: + dates.extend(self.fetch_year(today.year + 1)) + except Exception: + pass + else: + dates = self.fetch_year(today.year) + + entries = [] + for d in dates: + entries.append(Collection(d[0], d[1])) + return entries + + def fetch_year(self, year): + arg = convert_to_arg(self._district) + r = requests.get( + f"https://www.geoport-nwm.de/nwm-download/Abfuhrtermine/ICS/{year}/{arg}.ics" + ) + r.raise_for_status() + return self._ics.convert(r.text) + + +def convert_to_arg(district): + district = district.replace("(1.100 l Behälter)", "1100_l") + district = district.replace("ü", "ue") + district = district.replace("ö", "oe") + district = district.replace("ä", "ae") + district = district.replace("ß", "ss") + district = district.replace("/", "") + district = district.replace("- ", "-") + district = district.replace(".", "") + district = district.replace(" ", "_") + arg = urllib.parse.quote("Ortsteil_" + district) + return arg diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py new file mode 100644 index 00000000..3e049df6 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py @@ -0,0 +1,78 @@ +import logging +import re +from datetime import datetime + +import requests +from bs4 import BeautifulSoup +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Gold Coast City Council" +DESCRIPTION = "Source for Gold Coast Council rubbish collection." +URL = "https://www.goldcoast.qld.gov.au" +TEST_CASES = { + "MovieWorx": { "street_address": "50 Millaroo Dr Helensvale" }, + "The Henchman": { "street_address": "6/8 Henchman Ave Miami" }, + "Pie Pie": { "street_address": "1887 Gold Coast Hwy Burleigh Heads" } +} + +_LOGGER = logging.getLogger(__name__) + +ICON_MAP = { # Dict of waste types and suitable mdi icons + "General waste": "mdi:trash-can", + "Recycling": "mdi:recycle", + "Green organics": "mdi:leaf", +} + +class Source: + def __init__(self, street_address): + self._street_address = street_address + + def fetch(self): + session = requests.Session() + + # Making a get request + response = session.get( + "https://www.goldcoast.qld.gov.au/api/v1/myarea/searchfuzzy?maxresults=1", + params={"keywords": self._street_address}, + ) + response.raise_for_status() + addressSearchApiResults = response.json() + if ( + addressSearchApiResults["Items"] is None + or len(addressSearchApiResults["Items"]) < 1 + ): + raise Exception( + f"Address search for '{self._street_address}' returned no results. Check your address on https://www.goldcoast.qld.gov.au/Services/Waste-recycling/Find-my-bin-day" + ) + + addressSearchTopHit = addressSearchApiResults["Items"][0] + _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) + + geolocationid = addressSearchTopHit["Id"] + _LOGGER.debug("Geolocationid: %s", geolocationid) + + response = session.get( + "Https://www.goldcoast.qld.gov.au/ocapi/Public/myarea/wasteservices?ocsvclang=en-AU", + params={"geolocationid": geolocationid}, + ) + response.raise_for_status() + + wasteApiResult = response.json() + _LOGGER.debug("Waste API result: %s", wasteApiResult) + + soup = BeautifulSoup(wasteApiResult["responseContent"], "html.parser") + + entries = [] + for article in soup.find_all("article"): + waste_type = article.h3.string + icon = ICON_MAP.get(waste_type, "mdi:trash-can") + next_pickup = article.find(class_="next-service").string.strip() + if re.match(r"[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): + next_pickup_date = datetime.strptime( + next_pickup.split(sep=" ")[1], "%d/%m/%Y" + ).date() + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py index 6992518d..e15b77a4 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py @@ -4,17 +4,16 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Grafikai.svara.lt" -DESCRIPTION = "Source for UAB \"Kauno švara\"." +TITLE = "Kauno švara" +DESCRIPTION = 'Source for UAB "Kauno švara".' URL = "http://grafikai.svara.lt" TEST_CASES = { "Demokratų g. 7, Kaunas": { "region": "Kauno m. sav.", "street": "Demokratų g.", "house_number": "7", - "waste_object_ids": [101358, 100858, 100860] + "waste_object_ids": [101358, 100858, 100860], }, - "Alytaus g. 2, Išlaužo k., Išlaužo sen. Prienų r. sav.": { "region": "Prienų r. sav.", "street": "Alytaus g.", @@ -23,7 +22,7 @@ TEST_CASES = { }, } -ICONS = { +ICON_MAP = { "mišrių atliekų": "mdi:trash-can", "antrinių žaliavų (popierius/plastikas)": "mdi:recycle", "antrinių žaliavų (stiklas)": "mdi:glass-fragile", @@ -34,7 +33,9 @@ ICONS = { class Source: API_URL = "http://grafikai.svara.lt/api/" - def __init__(self, region, street, house_number, district=None, waste_object_ids=None): + def __init__( + self, region, street, house_number, district=None, waste_object_ids=None + ): if waste_object_ids is None: waste_object_ids = [] self._region = region @@ -68,10 +69,8 @@ class Source: for collection in data["data"]: try: type = collection["descriptionPlural"].casefold() - if self.check_if_waste_object_defined(collection['wasteObjectId']): - waste_object_query = { - "wasteObjectId": collection['wasteObjectId'] - } + if self.check_if_waste_object_defined(collection["wasteObjectId"]): + waste_object_query = {"wasteObjectId": collection["wasteObjectId"]} rwo = requests.get( self.API_URL + "schedule", @@ -81,14 +80,13 @@ class Source: self.check_for_error_status(data_waste_object) for collection_waste_object in data_waste_object: - print(collection_waste_object["date"]) entries.append( Collection( date=datetime.strptime( collection_waste_object["date"], "%Y-%m-%dT%H:%M:%S" ).date(), t=collection["descriptionFmt"].title(), - icon=ICONS.get(type, "mdi:trash-can"), + icon=ICON_MAP.get(type, "mdi:trash-can"), ) ) except ValueError: @@ -107,6 +105,6 @@ class Source: if "status" in collection: raise Exception( "Error: failed to fetch get data, got status: {}".format( - collection['status'] + collection["status"] ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/guildford_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/guildford_gov_uk.py index da5aa643..2dd6761d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/guildford_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/guildford_gov_uk.py @@ -4,9 +4,8 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "guildford.gov.uk" +TITLE = "Guildford Borough Council" DESCRIPTION = "Source for guildford.gov.uk services for Guildford, UK." -# Find the UPRN of your address using https://www.findmyaddress.co.uk/search URL = "https://guildford.gov.uk" TEST_CASES = { "GU12": {"uprn": "10007060305"}, @@ -14,7 +13,7 @@ TEST_CASES = { "GU2": {"uprn": "100061391831"}, } -ICONS = { +ICON_MAP = { "Refuse": "mdi:trash-can", "Food": "mdi:food-apple", "Recycling": "mdi:recycle", @@ -74,7 +73,7 @@ class Source: collection["NextDate"], "%Y-%m-%dT%H:%M:%S.000Z" ).date(), t=collection["FeatureName"], - icon=ICONS[collection["FeatureName"]], + icon=ICON_MAP[collection["FeatureName"]], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/huntingdonshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/huntingdonshire_gov_uk.py index 9d37fbc3..e209a01e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/huntingdonshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/huntingdonshire_gov_uk.py @@ -4,7 +4,7 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Huntingdonshire.gov.uk" +TITLE = "Huntingdonshire District Council" DESCRIPTION = "Source for Huntingdonshire.gov.uk services for Huntingdonshire District Council." URL = "https://www.huntingdonshire.gov.uk" TEST_CASES = { @@ -12,7 +12,7 @@ TEST_CASES = { "Inkerman Rise, St. Neots": {"uprn": "10000144271"}, } -ICONS = { +ICON_MAP = { "Refuse": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden": "mdi:leaf", @@ -45,7 +45,7 @@ class Source: collection["date"], "%Y-%m-%dT%H:%M:%SZ" ).date(), t=round_type.title(), - icon=ICONS.get(round_type), + icon=ICON_MAP.get(round_type), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py index 4ca4d264..635bc059 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py @@ -1,56 +1,184 @@ -import json import logging from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "HVCGroep" +TITLE = None DESCRIPTION = "Source for the Dutch HVCGroep waste management." -URL = "https://www.hvcgroep.nl/zelf-regelen/afvalkalender" -TEST_CASES = {"Tollebeek": {"postal_code": "8309AV", "house_number": "1"}} +URL = "https://www.hvcgroep.nl" + + +def EXTRA_INFO(): + return [ + {"title": s["title"], "url": get_main_url(s["api_url"])} for s in SERVICE_MAP + ] + + +TEST_CASES = { + "Tollebeek": {"postal_code": "8309AV", "house_number": "1"}, + "Hvgroep: Tollebeek": { + "postal_code": "8309AV", + "house_number": "1", + "service": "hvcgroep", + }, + "Cyclus": {"postal_code": "2841ML", "house_number": "1090", "service": "cyclusnv"}, + "Mijnblink": { + "postal_code": "5741BV", + "house_number": "76", + "service": "mijnblink", + }, +} _LOGGER = logging.getLogger(__name__) +SERVICE_MAP = [ + { + "title": "Alpen an den Rijn", + "api_url": "https://afvalkalender.alphenaandenrijn.nl", + }, + { + "title": "Gemeente Cranendonck", + "api_url": "https://afvalkalender.cranendonck.nl", + }, + { + "title": "Cyclus NV", + "api_url": "https://afvalkalender.cyclusnv.nl", + }, + { + "title": "Dar", + "api_url": "https://afvalkalender.dar.nl", + }, + { + "title": "Den Haag", + "api_url": "https://huisvuilkalender.denhaag.nl", + }, + { + "title": "GAD", + "api_url": "https://inzamelkalender.gad.nl", + }, + { + "title": "Gemeente Berkelland", + "api_url": "https://afvalkalender.gemeenteberkelland.nl", + }, + { + "title": "HVC Groep", + "api_url": "https://inzamelkalender.hvcgroep.nl", + }, + { + "title": "Gemeente Lingewaard", + "api_url": "https://afvalwijzer.lingewaard.nl", + }, + { + "title": "Gemeente Middelburg + Vlissingen", + "api_url": "https://afvalwijzer.middelburgvlissingen.nl", + }, + { + "title": "Mijn Blink", + "api_url": "https://mijnblink.nl", + }, + { + "title": "Gemeente Peel en Maas", + "api_url": "https://afvalkalender.peelenmaas.nl", + }, + { + "title": "PreZero", + "api_url": "https://inzamelwijzer.prezero.nl", + }, + { + "title": "Purmerend", + "api_url": "https://afvalkalender.purmerend.nl", + }, + { + "title": "Reinigingsbedrijf Midden Nederland", + "api_url": "https://inzamelschema.rmn.nl", + }, + { + "title": "Gemeente Schouwen-Duiveland", + "api_url": "https://afvalkalender.schouwen-duiveland.nl", + }, + { + "title": "Spaarne Landen", + "api_url": "https://afvalwijzer.spaarnelanden.nl", + }, + { + "title": "Stadswerk 072", + "api_url": "https://www.stadswerk072.nl", + }, + { + "title": "Gemeente Sudwest-Fryslan", + "api_url": "https://afvalkalender.sudwestfryslan.nl", + }, + { + "title": "Gemeente Venray", + "api_url": "https://afvalkalender.venray.nl", + }, + { + "title": "Gemeente Voorschoten", + "api_url": "https://afvalkalender.voorschoten.nl", + }, + { + "title": "Gemeente Wallre", + "api_url": "https://afvalkalender.waalre.nl", + }, + { + "title": "ZRD", + "api_url": "https://afvalkalender.zrd.nl", + }, +] + + +def get_service_name_map(): + def extract_service_name(api_url): + name = api_url.split(".")[-2] + name = name.split("/")[-1] + return name + + return {extract_service_name(s["api_url"]): s["api_url"] for s in SERVICE_MAP} + + +def get_main_url(url): + x = url.split(".")[-2:] + x[0] = x[0].removeprefix("https://") + return "https://" + ".".join(x) + + +ICON_MAP = { + "plastic-blik-drinkpak": "mdi:recycle", + "gft": "mdi:leaf", + "papier-en-karton": "mdi:archive", + "restafval": "mdi:trash-can", +} + class Source: - def __init__(self, postal_code, house_number): + def __init__(self, postal_code, house_number, service="hvcgroep"): self.postal_code = postal_code self.house_number = house_number - self.icons = { - "plastic-blik-drinkpak": "mdi:recycle", - "gft": "mdi:leaf", - "papier-en-karton": "mdi:archive", - "restafval": "mdi:trash-can", - } + self._url = get_service_name_map()[service] def fetch(self): - bag_id = 0 # Retrieve bagid (unique waste management identifier) - r = requests.get( - f"https://inzamelkalender.hvcgroep.nl/adressen/{self.postal_code}:{self.house_number}" - ) - data = json.loads(r.text) + r = requests.get(f"{self._url}/adressen/{self.postal_code}:{self.house_number}") + r.raise_for_status() + data = r.json() # Something must be wrong, maybe the address isn't valid? No need to do the extra requests so just return here. if len(data) == 0: - _LOGGER.error("no data found for this address") - return [] + raise Exception("no data found for this address") bag_id = data[0]["bagid"] # Retrieve the details about different waste management flows (for example, paper, plastic etc.) - r = requests.get( - f"https://inzamelkalender.hvcgroep.nl/rest/adressen/{bag_id}/afvalstromen" - ) - waste_flows = json.loads(r.text) + r = requests.get(f"{self._url}/rest/adressen/{bag_id}/afvalstromen") + r.raise_for_status() + waste_flows = r.json() # Retrieve the coming pickup dates for waste. - r = requests.get( - f"https://inzamelkalender.hvcgroep.nl/rest/adressen/{bag_id}/ophaaldata" - ) - data = json.loads(r.text) + r = requests.get(f"{self._url}/rest/adressen/{bag_id}/ophaaldata") + r.raise_for_status() + data = r.json() entries = [] @@ -62,7 +190,7 @@ class Source: Collection( date=datetime.strptime(item["ophaaldatum"], "%Y-%m-%d").date(), t=waste_details[0]["title"], - icon=self.icons.get(waste_details[0]["icon"], "mdi:trash-can"), + icon=ICON_MAP.get(waste_details[0]["icon"], "mdi:trash-can"), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hygea_be.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hygea_be.py index fa905c76..291d7c24 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hygea_be.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hygea_be.py @@ -39,9 +39,7 @@ class Source: response = requests.get( "https://www.hygea.be/displaycalws.html", params=params ) - - if not response.ok: - return [] + response.raise_for_status() data = json.loads(response.text) entries = [] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py index 1ded17f2..afae579a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py @@ -1,5 +1,6 @@ import datetime import logging +from os import getcwd from pathlib import Path import requests @@ -94,7 +95,7 @@ TEST_CASES = { "year_field": "year", }, "EAW Rheingau Taunus": { - "url": "https://www.eaw-rheingau-taunus.de/abfallkalender/calendar.ics?streetid=1429", + "url": "https://www.eaw-rheingau-taunus.de/abfallsammlung/abfuhrtermine/feed.ics?tx_vierwdeaw_garbagecalendarics%5Baction%5D=ics&tx_vierwdeaw_garbagecalendarics%5Bcontroller%5D=GarbageCalendar&tx_vierwdeaw_garbagecalendarics%5Bstreet%5D=38", "split_at": ",", }, "Recollect, Ottawa": { @@ -196,20 +197,17 @@ class Source: raise RuntimeError( "Error: unknown method to fetch URL, use GET or POST; got {self._method}" ) + r.raise_for_status() + r.encoding = "utf-8" # requests doesn't guess the encoding correctly - - # check the return code - if not r.ok: - _LOGGER.error( - "Error: the response is not ok; need code 200, but got code %s" - % r.status_code - ) - return [] - return self._convert(r.text) def fetch_file(self, file): - f = open(file) + try: + f = open(file) + except FileNotFoundError as e: + _LOGGER.error(f"Working directory: '{getcwd()}'") + raise return self._convert(f.read()) def _convert(self, data): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py index b9201e8f..94f5e325 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py @@ -1,14 +1,21 @@ -import json import logging + import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS _LOGGER = logging.getLogger(__name__) -TITLE = "INFEO" +TITLE = "infeo" DESCRIPTION = "Source for INFEO waste collection." URL = "https://www.infeo.at/" +EXTRA_INFO = [ + { + "title": "Bogenschütz Entsorgung", + "url": "https://bogenschuetz-entsorgung.de", + "country": "de", + }, +] TEST_CASES = {"Bogenschütz": {"customer": "bogenschütz", "zone": "Dettenhausen"}} @@ -20,25 +27,25 @@ class Source: def fetch(self): baseUrl = f"https://services.infeo.at/awm/api/{self._customer}/wastecalendar" - issueUrl = "https://github.com/mampfes/hacs_waste_collection_schedule/issues/new" - + issueUrl = ( + "https://github.com/mampfes/hacs_waste_collection_schedule/issues/new" + ) + params = { "showUnpublishedCalendars": "false", } - + # get the available published calendar years url = f"{baseUrl}/calendars" response = requests.get(url, params=params) + response.raise_for_status() # data validation - if(response.status_code != 200): - _LOGGER.error(f"problems during api calendar year access, please file an issue at {issueUrl} and mention @dm82m and add this: {response.text}") - return [] - response = response.json() if len(response) <= 0: - _LOGGER.error(f"no calendars found, please file an issue at {issueUrl} and mention @dm82m") - return [] + raise Exception( + f"no calendars found, please file an issue at {issueUrl} and mention @dm82m" + ) entries = [] @@ -54,15 +61,14 @@ class Source: # get available zones for calendar year url = f"{baseUrl}/zones" response = requests.get(url, params=params) + response.raise_for_status() # data validation - if(response.status_code != 200): - _LOGGER.error(f"problems during api zones for calendar year access, please file an issue at {issueUrl} and mention @dm82m and add this: {response.text}") - return [] - response = response.json() if len(response) <= 0: - _LOGGER.warning(f"no zones found for calendar year {calendarYearName}, continuing with next calendar year ...") + _LOGGER.warning( + f"no zones found for calendar year {calendarYearName}, continuing with next calendar year ..." + ) continue zoneId = 0 @@ -73,7 +79,9 @@ class Source: zoneId = zone["id"] if zoneId == 0: - _LOGGER.warning(f"zone '{self._zone}' not found in calendar year {calendarYearName}, continuing with next calendar year ...") + _LOGGER.warning( + f"zone '{self._zone}' not found in calendar year {calendarYearName}, continuing with next calendar year ..." + ) continue params = { @@ -85,19 +93,17 @@ class Source: # get ical data for year and zone url = f"{baseUrl}/v2/export" response = requests.get(url, params=params) - - # data validation - if(response.status_code != 200): - _LOGGER.error(f"problems during api ical data for zone in calendar year, please file an issue at {issueUrl} and mention @dm82m and add this: {response.text}") - return [] + response.raise_for_status() dates = self._ics.convert(response.text) for d in dates: entries.append(Collection(d[0], d[1])) - + # validate that we processed some data and show an error if not if len(entries) <= 0: - _LOGGER.error(f"we were not able to get any waste entries for you! please file an issue at {issueUrl} and mention @dm82m and add this zone: '{self._zone}'") - + _LOGGER.warning( + f"we were not able to get any waste entries for you! please file an issue at {issueUrl} and mention @dm82m and add this zone: '{self._zone}'" + ) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/innerwest_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/innerwest_nsw_gov_au.py index 46b99c11..cf3888ad 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/innerwest_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/innerwest_nsw_gov_au.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Inner West Council (NSW)" DESCRIPTION = "Source for Inner West Council (NSW) rubbish collection." -URL = "https://www.innerwest.nsw.gov.au/live/waste-and-recycling/bins-and-clean-ups/waste-calendar" +URL = "https://www.innerwest.nsw.gov.au" TEST_CASES = { "Random address": { "suburb": "Tempe", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ipswich_qld_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ipswich_qld_gov_au.py index 591702fb..46c4ce48 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ipswich_qld_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ipswich_qld_gov_au.py @@ -7,14 +7,14 @@ from waste_collection_schedule import Collection TITLE = "Ipswich City Council" DESCRIPTION = "Source for Ipswich City Council rubbish collection." -URL = "https://www.ipswich.qld.gov.au/live/waste-and-recycling/bin-collection-calendar" +URL = "https://www.ipswich.qld.gov.au" TEST_CASES = { "Camira State School": {"street": "184-202 Old Logan Rd", "suburb": "Camira"}, "Random": {"street": "50 Brisbane Road", "suburb": "Redbank"}, } -ICONS = { +ICON_MAP = { "Waste Bin": "mdi:trash-can", "Recycle Bin": "mdi:recycle", "FOGO Bin": "mdi:leaf", @@ -91,7 +91,7 @@ class IpswichGovAuParser(HTMLParser): self._entries.append( Collection( - self._loaded_date, data, icon=ICONS.get(data, "mdi:trash-can") + self._loaded_date, data, icon=ICON_MAP.get(data, "mdi:trash-can") ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kaev_niederlausitz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kaev_niederlausitz.py index a9ac3429..88f1ef04 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kaev_niederlausitz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kaev_niederlausitz.py @@ -6,9 +6,9 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS TITLE = "KAEV Niederlausitz" -DESCRIPTION = "Source for Kommunaler Abfallverband niederlausitz waste collection." +DESCRIPTION = "Source for Kommunaler Abfallverband Niederlausitz waste collection." URL = "https://www.kaev.de/" -URL_ADDRESS = 'https://www.kaev.de/Templates/Content/DetailTourenplanWebsite/ajax.aspx/getAddress' +COUNTRY = "de" TEST_CASES = { "Luckau / OT Zieckau": { "abf_suche": "Luckau / OT Zieckau", @@ -21,11 +21,13 @@ TEST_CASES = { }, } +API_URL = 'https://www.kaev.de/Templates/Content/DetailTourenplanWebsite/ajax.aspx/getAddress' + def get_kalender_id(search): s=requests.Session() s.get('https://www.kaev.de/') payload={"query": search} - resp = s.post(URL_ADDRESS, json=payload).json() + resp = s.post(API_URL, json=payload).json() abf_cal = json.loads(resp["d"]) return abf_cal diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py index 347bbd55..e0aa859a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py @@ -1,90 +1,123 @@ import logging -import requests import time - from datetime import datetime + +import requests from waste_collection_schedule import Collection -TITLE = 'www.kingston.gov.uk' +TITLE = "The Royal Borough of Kingston Council" DESCRIPTION = ( - 'Source for waste collection services for The Royal Borough of Kingston Council' + "Source for waste collection services for The Royal Borough of Kingston Council" ) -URL = 'https://kingston-self.achieveservice.com/service/in_my_area?displaymode=collections' +URL = "kingston.gov.uk" HEADERS = { "user-agent": "Mozilla/5.0", } -COOKIES = { - -} +COOKIES = {} TEST_CASES = { - "Blagdon Road - number" : {"uprn": 100021772910}, - "Blagdon Road - string" : {"uprn": "100021772910"}, + "Blagdon Road - number": {"uprn": 100021772910}, + "Blagdon Road - string": {"uprn": "100021772910"}, } API_URLS = { - 'session': 'https://kingston-self.achieveservice.com/service/In_my_Area_Results?uprn=100021772910&displaymode=collections&altVal=', - 'auth': 'https://kingston-self.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Fkingston-self.achieveservice.com%252Fservice%252FIn_my_Area_Results%253Fuprn%253D100021772910%2526displaymode%253Dcollections%2526altVal%253D&hostname=kingston-self.achieveservice.com&withCredentials=true', - 'schedule': 'https://kingston-self.achieveservice.com/apibroker/runLookup?id=601a61f9a3188&repeat_against=&noRetry=true&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&' + "session": "https://kingston-self.achieveservice.com/service/In_my_Area_Results?uprn=100021772910&displaymode=collections&altVal=", + "auth": "https://kingston-self.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Fkingston-self.achieveservice.com%252Fservice%252FIn_my_Area_Results%253Fuprn%253D100021772910%2526displaymode%253Dcollections%2526altVal%253D&hostname=kingston-self.achieveservice.com&withCredentials=true", + "schedule": "https://kingston-self.achieveservice.com/apibroker/runLookup?id=601a61f9a3188&repeat_against=&noRetry=true&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&", } _LOGGER = logging.getLogger(__name__) + class Source: def __init__(self, uprn: str): self._uprn = str(uprn) + def fetch(self): s = requests.Session() - #This request sets up the cookies - r0 = s.get(API_URLS['session'], headers=HEADERS) + # This request sets up the cookies + r0 = s.get(API_URLS["session"], headers=HEADERS) r0.raise_for_status() - #This request gets the session key from the PHPSESSID (in the cookies) - authRequest = s.get(API_URLS['auth'], headers=HEADERS) + # This request gets the session key from the PHPSESSID (in the cookies) + authRequest = s.get(API_URLS["auth"], headers=HEADERS) authData = authRequest.json() - sessionKey = authData['auth-session'] + sessionKey = authData["auth-session"] now = time.time_ns() // 1_000_000 - #now query using the uprn - payload = { "formValues": { "Section 1": { "UPRN_FromUrl": { "value": self._uprn }, "borough_code": { "value": "RBK" }, "show_wasteCollection": { "value": "1" }, "echo_borough": { "value": "RBK" }, "echo_uprn": { "value": self._uprn } } } } + # now query using the uprn + payload = { + "formValues": { + "Section 1": { + "UPRN_FromUrl": {"value": self._uprn}, + "borough_code": {"value": "RBK"}, + "show_wasteCollection": {"value": "1"}, + "echo_borough": {"value": "RBK"}, + "echo_uprn": {"value": self._uprn}, + } + } + } - scheduleRequest = s.post(API_URLS['schedule'] + '&_' + str(now) + '&sid=' + sessionKey , headers=HEADERS, json=payload) - data = scheduleRequest.json()['integration']['transformed']['rows_data']['0'] - print(data) + scheduleRequest = s.post( + API_URLS["schedule"] + "&_" + str(now) + "&sid=" + sessionKey, + headers=HEADERS, + json=payload, + ) + data = scheduleRequest.json()["integration"]["transformed"]["rows_data"]["0"] entries = [] - entries.append(Collection( - date = datetime.strptime(data['echo_refuse_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'refuse bin', - icon = 'mdi:trash-can' - )) + entries.append( + Collection( + date=datetime.strptime( + data["echo_refuse_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="refuse bin", + icon="mdi:trash-can", + ) + ) + + entries.append( + Collection( + date=datetime.strptime( + data["echo_food_waste_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="food waste bin", + icon="mdi:trash-can", + ) + ) + + entries.append( + Collection( + date=datetime.strptime( + data["echo_paper_and_card_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="paper and card recycling bin", + icon="mdi:recycle", + ) + ) + + entries.append( + Collection( + date=datetime.strptime( + data["echo_mixed_recycling_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="mixed recycling bin", + icon="mdi:recycle", + ) + ) + + entries.append( + Collection( + date=datetime.strptime( + data["echo_garden_waste_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="garden waste bin", + icon="mdi:leaf", + ) + ) - entries.append(Collection( - date = datetime.strptime(data['echo_food_waste_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'food waste bin', - icon = 'mdi:trash-can' - )) - - entries.append(Collection( - date = datetime.strptime(data['echo_paper_and_card_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'paper and card recycling bin', - icon = 'mdi:recycle' - )) - - entries.append(Collection( - date = datetime.strptime(data['echo_mixed_recycling_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'mixed recycling bin', - icon = 'mdi:recycle' - )) - - entries.append(Collection( - date = datetime.strptime(data['echo_garden_waste_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'garden waste bin', - icon = 'mdi:leaf' - )) - return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py index ac0c48c8..db67794c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py @@ -4,24 +4,27 @@ from urllib.parse import urljoin import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -from waste_collection_schedule.service.ICS import ICS # type: ignore[attr-defined] +from waste_collection_schedule.service.ICS import ICS # type: ignore[attr-defined] -TITLE = 'Müllabfuhr Korneuburg' -DESCRIPTION = 'Source for Stadtservice Korneuburg' -URL = 'https://www.korneuburg.gv.at' +TITLE = "Stadtservice Korneuburg" +DESCRIPTION = "Source for Stadtservice Korneuburg" +URL = "https://www.korneuburg.gv.at" +TEST_CASES = { + "Rathaus": {"street_name": "Hauptplatz", "street_number": 39}, # Teilgebiet 4 + "Rathaus using Teilgebiet": { + "street_name": "SomeStreet", + "street_number": "1A", + "teilgebiet": "4", + }, # Teilgebiet 4 + "Werft": {"street_name": "Am Hafen", "street_number": 6}, # Teilgebiet 2 +} # Mapping of teilgebiete to calendar urls WASTE_TYPE_URLS = { - '1': ('Biomuell_3', 'Restmuell_3', 'Papier_2', 'Gelber_Sack_4'), - '2': ('Biomuell_4', 'Restmuell_2', 'Papier_3', 'Gelber_Sack_1'), - '3': ('Biomuell_1', 'Restmuell_1', 'Papier_1', 'Gelber_Sack_2'), - '4': ('Biomuell_2', 'Restmuell', 'Papier', 'Gelber_Sack_3') -} - -TEST_CASES = { - "Rathaus": {"street_name": "Hauptplatz", "street_number": 39}, # Teilgebiet 4 - "Rathaus using Teilgebiet": {"street_name": "SomeStreet", "street_number": "1A", "teilgebiet": "4"}, # Teilgebiet 4 - "Werft": {"street_name": "Am Hafen", "street_number": 6} # Teilgebiet 2 + "1": ("Biomuell_3", "Restmuell_3", "Papier_2", "Gelber_Sack_4"), + "2": ("Biomuell_4", "Restmuell_2", "Papier_3", "Gelber_Sack_1"), + "3": ("Biomuell_1", "Restmuell_1", "Papier_1", "Gelber_Sack_2"), + "4": ("Biomuell_2", "Restmuell", "Papier", "Gelber_Sack_3"), } @@ -29,18 +32,15 @@ class Source: def __init__(self, street_name, street_number, teilgebiet=-1): self.street_name = street_name self.street_number = street_number + self.teilgebiet = teilgebiet + self._region = None self._street_name_id = -1 self._street_number_id = -1 - self._headers = {'User-Agent': 'Mozilla/5.0'} - self._cookies = {'ris_cookie_setting': 'g7750'} # Accept Cookie Consent + self._headers = {"User-Agent": "Mozilla/5.0"} + self._cookies = {"ris_cookie_setting": "g7750"} # Accept Cookie Consent self._ics = ICS() - if 0 < int(teilgebiet) <= 4: - self.region = str(teilgebiet) - else: - self.region = self.determine_region() - @staticmethod def extract_street_numbers(soup): @@ -53,19 +53,31 @@ class Source: street_number_idx += 1 possible_numbers = json.loads( - scripts[street_number_idx].string[19:].replace('\r\n', '').replace(', ]', ']').replace('\'', '"')) + scripts[street_number_idx] + .string[19:] + .replace("\r\n", "") + .replace(", ]", "]") + .replace("'", '"') + ) number_dict = dict() for idx, street_id in enumerate(possible_numbers): - number_dict[street_id[0]] = {e[1]: (e[0], e[2]) for _idx, e in enumerate(possible_numbers[idx][1])} + number_dict[street_id[0]] = { + e[1]: (e[0], e[2]) for _idx, e in enumerate(possible_numbers[idx][1]) + } return number_dict @staticmethod def extract_street_names(soup): - street_selector = soup.find("select", {"id": "225991280_boxmuellkalenderstrassedd"}).findAll("option") - available_streets = {street.string: int(street["value"]) for _idx, street in enumerate(street_selector)} + street_selector = soup.find( + "select", {"id": "225991280_boxmuellkalenderstrassedd"} + ).findAll("option") + available_streets = { + street.string: int(street["value"]) + for _idx, street in enumerate(street_selector) + } return available_streets @@ -74,8 +86,8 @@ class Source: region = -1 for span in soup.findAll("span"): - if span.parent.name == 'td' and "teilgebiet" in span.string.lower(): - region = span.string.split(' ')[1] + if span.parent.name == "td" and "teilgebiet" in span.string.lower(): + region = span.string.split(" ")[1] break return region @@ -83,6 +95,9 @@ class Source: def determine_region(self): """finds the target region for the street and street number""" + if 0 < int(self.teilgebiet) <= 4: + return str(self.teilgebiet) + # request address selection form url = urljoin(URL, "Rathaus/Buergerservice/Muellabfuhr") page = requests.get(url=url, headers=self._headers, cookies=self._cookies) @@ -95,44 +110,68 @@ class Source: street_found = self.street_name in available_streets.keys() if not street_found: - raise Exception(f"{self.street_name} not found. Please check back spelling with the official site: {url}") + raise Exception( + f"{self.street_name} not found. Please check back spelling with the official site: {url}" + ) self._street_name_id = available_streets.get(self.street_name) self._street_number_id, street_number_link = number_dict.get( - available_streets.get(self.street_name)).get(str(self.street_number), (-1, 'not found')) + available_streets.get(self.street_name) + ).get(str(self.street_number), (-1, "not found")) - if street_number_link == 'not found': - raise Exception(f"{self.street_number} not found. Available numbers for {self.street_name} are\ - {list(number_dict.get(available_streets['Am Hafen']).keys())}") + if street_number_link == "not found": + raise Exception( + f"{self.street_number} not found. Available numbers for {self.street_name} are\ + {list(number_dict.get(available_streets['Am Hafen']).keys())}" + ) # add selection cookie - self._cookies['riscms_muellkalender'] = str(f"{self._street_name_id}_{self._street_number_id}") + self._cookies["riscms_muellkalender"] = str( + f"{self._street_name_id}_{self._street_number_id}" + ) # request overview with address selection to get the region url = urljoin(URL, "system/web/kalender.aspx") - page = requests.get(url=url, headers=self._headers, cookies=self._cookies, - params={"sprache": "1", "menuonr": "225991280", "typids": street_number_link}) + page = requests.get( + url=url, + headers=self._headers, + cookies=self._cookies, + params={ + "sprache": "1", + "menuonr": "225991280", + "typids": street_number_link, + }, + ) soup = BeautifulSoup(page.content, "html.parser") region = self.extract_region(soup) if region == -1: - raise Exception(f"Region could not be found") + raise Exception("Region could not be found") return str(region) def get_region_links(self): """traverses the pages for different waste types and collects download links for the iCals""" + if self._region is None: + self._region = self.determine_region() + # create waste type urls ical_urls = [] - urls = [urljoin(URL, u) for u in WASTE_TYPE_URLS.get(self.region)] + urls = [urljoin(URL, u) for u in WASTE_TYPE_URLS.get(self._region)] for u in urls: r = requests.get(url=u, headers=self._headers, cookies=self._cookies) soup = BeautifulSoup(r.content, "html.parser") - download_link = soup.findAll("a", {"class": "piwik_download_tracker", "data-trackingtyp": "iCal/Kalender"}) + download_link = soup.findAll( + "a", + { + "class": "piwik_download_tracker", + "data-trackingtyp": "iCal/Kalender", + }, + ) if len(download_link): ical_urls.append(urljoin(URL, download_link[0].get("href"))) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwb_goslar_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwb_goslar_de.py index 94a755da..1f8f765a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwb_goslar_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwb_goslar_de.py @@ -2,9 +2,9 @@ import requests from waste_collection_schedule import Collection from waste_collection_schedule.service.ICS import ICS -TITLE = "KreisWirtschaftsBetriebe Goslar" +TITLE = "Kreiswirtschaftsbetriebe Goslar" DESCRIPTION = "Source for kwb-goslar.de waste collection." -URL = "https://www.kwb-goslar.de/Abfallwirtschaft/Abfuhr/" +URL = "https://www.kwb-goslar.de" TEST_CASES = { "Berliner Straße (Clausthal-Zellerfeld)": {"pois": "2523.602"}, "Braunschweiger Straße (Seesen)": {"pois": "2523.409"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py index cf5b0e3f..5cbebed7 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py @@ -1,19 +1,25 @@ +from datetime import date + import requests from bs4 import BeautifulSoup -from datetime import date from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS TITLE = "KWU Entsorgung Landkreis Oder-Spree" DESCRIPTION = "Source for KWU Entsorgung, Germany" URL = "https://www.kwu-entsorgung.de/" - TEST_CASES = { "Erkner": {"city": "Erkner", "street": "Heinrich-Heine-Straße", "number": "11"}, - "Bad Saarow": {"city": "Bad Saarow", "street": "Ahornallee", "number": "1"} + "Bad Saarow": {"city": "Bad Saarow", "street": "Ahornallee", "number": "1"}, } HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} +ICON_MAP = { + "Restabfall": "mdi:trash-can-outline", + "Gelber Sack": "mdi:recycle", + "Papiertonne": "mdi:package-variant", + "Biotonne": "mdi:food-apple-outline", +} class Source: @@ -22,16 +28,11 @@ class Source: self._street = street self._number = number self._ics = ICS() - self._iconMap = { - "Restabfall": "mdi:trash-can-outline", - "Gelber Sack" : "mdi:recycle", - "Papiertonne" : "mdi:package-variant", - "Biotonne": "mdi:food-apple-outline", - } def fetch(self): session = requests.Session() + params = { "city": self._city, "street": self._street, @@ -39,62 +40,94 @@ class Source: "direct": "true", } - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_objauswahl.php", headers=HEADERS) + r = requests.get( + "https://kalender.kwu-entsorgung.de", + headers=HEADERS, + verify=False + ) + parsed_html = BeautifulSoup(r.text, "html.parser") - Orte = parsed_html.find_all('option') + Orte = parsed_html.find_all("option") for Ort in Orte: if self._city in Ort.text: - OrtValue = Ort['value'] + OrtValue = Ort["value"] break - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_str2ort.php", params={"ort": OrtValue}, headers=HEADERS) + r = requests.get( + "https://kalender.kwu-entsorgung.de/kal_str2ort.php", + params={"ort": OrtValue}, + headers=HEADERS, + verify=False + ) + parsed_html = BeautifulSoup(r.text, "html.parser") - Strassen = parsed_html.find_all('option') + Strassen = parsed_html.find_all("option") for Strasse in Strassen: if self._street in Strasse.text: - StrasseValue = Strasse['value'] + StrasseValue = Strasse["value"] break - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_str2ort.php", params={"ort": OrtValue, "strasse": StrasseValue}, headers=HEADERS) - parsed_html = BeautifulSoup(r.text, "html.parser") - Objekte = parsed_html.find_all('option') + r = requests.get( + "https://kalender.kwu-entsorgung.de/kal_str2ort.php", + params={"ort": OrtValue, "strasse": StrasseValue}, + headers=HEADERS, + verify=False + ) - for Objekt in Objekte: - if self._number in Objekt.text: - ObjektValue = Objekt['value'] + parsed_html = BeautifulSoup(r.text, "html.parser") + objects = parsed_html.find_all("option") + + for obj in objects: + if self._number in obj.text: + ObjektValue = obj["value"] break - r = requests.post("https://www.kwu-entsorgung.de/inc/wordpress/kal_uebersicht-2020.php", data={"ort": OrtValue, "strasse": StrasseValue, "objekt": ObjektValue, "jahr": date.today().year}, headers=HEADERS) + + r = requests.post( + "https://kalender.kwu-entsorgung.de/kal_uebersicht-2023.php", + data={ + "ort": OrtValue, + "strasse": StrasseValue, + "objekt": ObjektValue, + "jahr": date.today().year + }, + headers=HEADERS, + verify=False + ) parsed_html = BeautifulSoup(r.text, "html.parser") - Links = parsed_html.find_all('a') + Links = parsed_html.find_all("a") for Link in Links: - if 'ICal herunterladen' in Link.text: - ics_url = Link['href'] + if "ICal herunterladen" in Link.text: + ics_url = Link["href"] if ics_url is None: - raise Exception(f"ics url not found") + raise Exception("ics url not found") # get ics file - r = session.get(ics_url, headers=HEADERS) + r = session.get(ics_url, headers=HEADERS, verify=False) r.raise_for_status() # parse ics file dates = self._ics.convert(r.text) entries = [] - #for d in dates: + # for d in dates: # entries.append(Collection(d[0], d[1])) - #return entries + # return entries for d in dates: -# _LOGGER.error(d) waste_type = d[1].strip() next_pickup_date = d[0] - - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=self._iconMap.get(waste_type,"mdi:trash-can"))) + + entries.append( + Collection( + date=next_pickup_date, + t=waste_type, + icon=ICON_MAP.get(waste_type, "mdi:trash-can"), + ) + ) return entries - diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_rhoen_grabfeld.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_rhoen_grabfeld.py index c1f16c05..430afe52 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_rhoen_grabfeld.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_rhoen_grabfeld.py @@ -3,18 +3,10 @@ import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Source for Rhön Grabfeld" +TITLE = "Landkreis Rhön Grabfeld" DESCRIPTION = "Source for Rhönn Grabfeld uses service by offizium." -URL = 'https://fs-api-rg.offizium.com/abfalltermine' -ICON_MAP = { - "Restmüll/Gelber Sack/Biotonne": "mdi:trash-can", - "Papiersammlung": "mdi:package-variant", - "Problemmüllsammlung": "mdi:biohazard" -} -EVENT_BLACKLIST = ['Wertstoffhof Mellrichstadt', - 'Wertstoffhof Bad Königshofen', 'Wertstoffzentrum Bad Neustadt', - 'Wertstoffsammelstelle Ostheim', - 'Wertstoffsammelstelle Bischofsheim'] +URL = "https://www.abfallinfo-rhoen-grabfeld.de/" +COUNTRY = "de" TEST_CASES = { "City only": {"city": "Ostheim"}, "City + District": {"city": "Ostheim", "district": "Oberwaldbehrungen"}, @@ -22,6 +14,19 @@ TEST_CASES = { "empty": {} } +API_URL = 'https://fs-api-rg.offizium.com/abfalltermine' + +EVENT_BLACKLIST = ['Wertstoffhof Mellrichstadt', + 'Wertstoffhof Bad Königshofen', 'Wertstoffzentrum Bad Neustadt', + 'Wertstoffsammelstelle Ostheim', + 'Wertstoffsammelstelle Bischofsheim'] + +ICON_MAP = { + "Restmüll/Gelber Sack/Biotonne": "mdi:trash-can", + "Papiersammlung": "mdi:package-variant", + "Problemmüllsammlung": "mdi:biohazard" +} + class Source: def __init__(self, city: str = None, district: str = None): @@ -31,7 +36,7 @@ class Source: def fetch(self): now = datetime.datetime.now().date() - r = requests.get(URL, params={ + r = requests.get(API_URL, params={ "stadt": self._city, "ortsteil": self._district }) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_wittmund_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_wittmund_de.py index df3a3f14..8ad0ab90 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_wittmund_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_wittmund_de.py @@ -4,12 +4,9 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS from bs4 import BeautifulSoup -TITLE = "Landkreis-Wittmund.de" +TITLE = "Landkreis Wittmund" DESCRIPTION = "Source for Landkreis Wittmund waste collection." -URL = "https://www.landkreis-wittmund.de/Leben-Wohnen/Wohnen/Abfall/Abfuhrkalender/" -AUTOCOMPLETE_URL = "https://www.landkreis-wittmund.de/output/autocomplete.php?out=json&type=abto&mode=&select=2&refid={}&term=" -DOWNLOAD_URL = "https://www.landkreis-wittmund.de/output/options.php?ModID=48&call=ical&ArtID%5B0%5D=3105.1&ArtID%5B1%5D=1.4&ArtID%5B2%5D=1.2&ArtID%5B3%5D=1.3&ArtID%5B4%5D=1.1&pois={}&alarm=0" - +URL = "https://www.landkreis-wittmund.de" TEST_CASES = { "CityWithoutStreet": { "city": "Werdum", @@ -20,6 +17,11 @@ TEST_CASES = { }, } +API_URL = "https://www.landkreis-wittmund.de/Leben-Wohnen/Wohnen/Abfall/Abfuhrkalender/" +AUTOCOMPLETE_URL = "https://www.landkreis-wittmund.de/output/autocomplete.php?out=json&type=abto&mode=&select=2&refid={}&term=" +DOWNLOAD_URL = "https://www.landkreis-wittmund.de/output/options.php?ModID=48&call=ical&ArtID%5B0%5D=3105.1&ArtID%5B1%5D=1.4&ArtID%5B2%5D=1.2&ArtID%5B3%5D=1.3&ArtID%5B4%5D=1.1&pois={}&alarm=0" + + class Source: def __init__(self, city, street=None): self._city = city @@ -36,11 +38,11 @@ class Source: return tag['value'] != "" and tag.string == self._city def fetch_city_id(self, cityName): - r = requests.get(URL) + r = requests.get(API_URL) if not r.ok: raise Exception( "Error: failed to fetch url: {}".format( - URL + API_URL ) ) @@ -64,7 +66,7 @@ class Source: def fetch_street_id(self, cityId, streetName): r = requests.get(AUTOCOMPLETE_URL.format(cityId, streetName), headers={ - "Referer": URL + "Referer": API_URL }) if not r.ok: @@ -100,7 +102,7 @@ class Source: def fetch_ics(self, url): r = requests.get(url, headers={ - "Referer": URL + "Referer": API_URL }) if not r.ok: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lerum_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lerum_se.py index f761e770..6927a878 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lerum_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lerum_se.py @@ -8,7 +8,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Lerum Vatten och Avlopp" DESCRIPTION = "Source for Lerum Vatten och Avlopp waste collection." -URL = "https://vatjanst.lerum.se/FutureWeb/SimpleWastePickup/SimpleWastePickup" +URL = "https://vatjanst.lerum.se" TEST_CASES = { "PRO": {"street_address": "Floda stationsväg 5, Floda"}, "Polisen": {"street_address": "Göteborgsvägen 16, Lerum"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lewisham_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lewisham_gov_uk.py index 4272d6a4..b0e03193 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lewisham_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lewisham_gov_uk.py @@ -9,8 +9,7 @@ TITLE = "London Borough of Lewisham" DESCRIPTION = ( "Source for services from the London Borough of Lewisham" ) - -URL = "lewisham.gov.uk" +URL = "https://lewisham.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "SE41LR", "number": 4}, "houseName": {"post_code": "SE233TE", "name": "The Haven"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lindau_ch.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lindau_ch.py index 8fd31cb5..1efb835a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lindau_ch.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lindau_ch.py @@ -5,16 +5,16 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Abfall Lindau" +TITLE = "Lindau" DESCRIPTION = "Source for Lindau waste collection." -URL = "https://www.lindau.ch/abfalldaten" +URL = "https://www.lindau.ch" TEST_CASES = { "Tagelswangen": {"city": "Tagelswangen"}, "Grafstal": {"city": "190"}, } -IconMap = { +ICON_MAP = { "kehricht": "mdi:trash-can", "grungut": "mdi:leaf", "hackseldienst": "mdi:leaf", @@ -51,7 +51,7 @@ class Source: Collection( date=next_pickup_date, t=waste_type, - icon=IconMap.get(waste_type_sorted, "mdi:trash-can"), + icon=ICON_MAP.get(waste_type_sorted, "mdi:trash-can"), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lrasha_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lrasha_de.py index fe32359a..f1d8f796 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lrasha_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lrasha_de.py @@ -6,13 +6,12 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "Landkreis Schwäbisch Hall" DESCRIPTION = "Source for lrasha.de - Landkreis Schwäbisch Hall" -URL = "http://exchange.cmcitymedia.de/landkreis-schwaebisch-hallt3/wasteCalendarExport.php?location=" -# https://www.lrasha.de/de/buergerservice/abfallwirtschaft/abfallkalender - +URL = "https://www.lrasha.de" TEST_CASES = { "Ilshofen": {"location": "114"} } +API_URL = "http://exchange.cmcitymedia.de/landkreis-schwaebisch-hallt3/wasteCalendarExport.php?location=" HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} @@ -23,7 +22,7 @@ class Source: def fetch(self): # get ics file - full_url = URL + str(self._location) + full_url = API_URL + str(self._location) r = requests.get(full_url, headers=HEADERS) r.raise_for_status() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py index a6c935e6..370a2c04 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py @@ -1,20 +1,19 @@ +import logging from datetime import datetime import requests +from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -from bs4 import BeautifulSoup -from urllib.parse import urlsplit, parse_qs -import logging - -TITLE = "manchester.gov.uk" +TITLE = "Manchester City Council" DESCRIPTION = "Source for bin collection services for Manchester City Council, UK." -URL = "https://www.manchester.gov.uk/bincollections/" +URL = "https://www.manchester.gov.uk" TEST_CASES = { - "domestic": {'uprn': '000077065560'}, + "domestic": {"uprn": "000077065560"}, } -ICONS = { +API_URL = "https://www.manchester.gov.uk/bincollections/" +ICON_MAP = { "Black / Grey Bin": "mdi:trash-can", "Blue Bin": "mdi:recycle", "Brown Bin": "mdi:glass-fragile", @@ -25,25 +24,15 @@ _LOGGER = logging.getLogger(__name__) class Source: - def __init__( - self, uprn: int = None - ): + def __init__(self, uprn: int): self._uprn = uprn - if not self._uprn: - _LOGGER.error( - "uprn must be provided in config" - ) - self._session = requests.Session() def fetch(self): entries = [] r = requests.post( - URL, - data={ - "mcc_bin_dates_uprn": self._uprn, - "mcc_bin_dates_submit": "Go" - }, + API_URL, + data={"mcc_bin_dates_uprn": self._uprn, "mcc_bin_dates_submit": "Go"}, ) soup = BeautifulSoup(r.text, features="html.parser") @@ -53,21 +42,18 @@ class Source: date = result.find("p", {"class": "caption"}) dates = [] dates.append(str(date.text).replace("Next collection ", "", 1)) - for date in result.find_all('li'): + for date in result.find_all("li"): dates.append(date.text) img_tag = result.find("img") collection_type = img_tag["alt"] for current_date in dates: - try: - date = datetime.strptime(current_date, "%A %d %b %Y").date() - entries.append( - Collection( - date=date, - t=collection_type, - icon=ICONS[collection_type], - ) + date = datetime.strptime(current_date, "%A %d %b %Y").date() + entries.append( + Collection( + date=date, + t=collection_type, + icon=ICON_MAP[collection_type], ) - except ValueError: - _LOGGER.error(f"Skipped {current_date} as it does not match time format") + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py new file mode 100644 index 00000000..3353cc5e --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py @@ -0,0 +1,103 @@ +import html +import logging +import random +import re +import string +from datetime import datetime + +import requests +from waste_collection_schedule import Collection + +_LOGGER = logging.getLogger(__name__) + +TITLE = "Abfallwirtschaftsbetrieb Landkreis Ahrweiler" +URL = "https://www.meinawb.de" +DESCRIPTION = "Bin collection service from Kreis Ahrweiler/Germany" +API_URL = "https://extdienste01.koblenz.de/WasteManagementAhrweiler/WasteManagementServlet" + +ICON_MAP = { + "Restabfall": "mdi:trash-can", + "Restabfall Plus": "mdi:trash-can", + "Bioabfall": "mdi:leaf", + "Altpapier": "mdi:package-variant", + "Verpackungen": "mdi:recycle", + "Grünabfall / Weihnachtsbäume": "mdi:forest", +} +TYPES = { + "RM": "Restabfall", + "RG": "Restabfall Plus", + "BM": "Bioabfall", + "PA": "Altpapier", + "GT": "Verpackungen", + "GS": "Grünabfall / Weihnachtsbäume", +} + +TEST_CASES = { + "Oberzissen": {"city": "Oberzissen", "street": "Lindenstrasse", "house_number": "1"}, + "Niederzissen": {"city": "Niederzissen", "street": "Brohltalstrasse", "house_number": "189"}, + "Bad Neuenahr": {"city": "Bad Neuenahr-Ahrweiler", "street": "Hauptstrasse", "house_number": "91", + "address_suffix": "A"}, +} + + +class Source: + def __init__(self, city, street, house_number, address_suffix=""): + self._city = city + self._street = street + self._house_number = house_number + self._address_suffix = address_suffix + self._boundary = "WebKitFormBoundary" + "".join(random.sample(string.ascii_letters + string.digits, 16)) + + def __str__(self): + return f"{self._city} {self._street} {self._house_number} {self._address_suffix}" + + @staticmethod + def _parse_data(data, boundary): + result = "" + for key, value in data.items(): + result += f'------{boundary}\r\nContent-Disposition: form-data; name="{key}"\r\n\r\n{value}\r\n' + result += f"------{boundary}--\r\n" + return result.encode() + + @staticmethod + def _parse_response_input(text): + parsed = re.findall("([^<]+?)<", final_response.text): + _LOGGER.warning(f"{self} - {html.unescape(error[0])}") + return [] + return re.findall('

[A-Z][a-z]. ([0-9.]{10})

', final_response.text) + + def fetch(self): + session = requests.Session() + init_request = session.get(f"{API_URL}?SubmitAction=wasteDisposalServices&InFrameMode=true").text + if calendars := re.findall('NAME="Zeitraum" VALUE=\"([^\"]+?)\"', init_request): + dates = [date for calendar in calendars for date in self._get_dates(session, init_request, calendar)] + else: + dates = self._get_dates(session, init_request) + entries = [] + for bin_type, date in dates: + name = TYPES[next(x for x in list(TYPES) if x in bin_type)] + entries.append(Collection(datetime.strptime(date, "%d.%m.%Y").date(), name, ICON_MAP[name])) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py index f7ed62e7..ce775880 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py @@ -8,7 +8,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Melton City Council" DESCRIPTION = "Source for Melton City Council rubbish collection." -URL = "https://www.melton.vic.gov.au/My-Area" +URL = "https://www.melton.vic.gov.au" TEST_CASES = { "Tuesday A": {"street_address": "23 PILBARA AVENUE BURNSIDE 3023"}, "Tuesday B": {"street_address": "29 COROWA CRESCENT BURNSIDE 3023"}, @@ -45,10 +45,9 @@ class Source: addressSearchApiResults["Items"] is None or len(addressSearchApiResults["Items"]) < 1 ): - _LOGGER.error( + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on https://www.melton.vic.gov.au/My-Area" ) - return [] addressSearchTopHit = addressSearchApiResults["Items"][0] _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/middlesbrough_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/middlesbrough_gov_uk.py index 85930e84..1e55946b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/middlesbrough_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/middlesbrough_gov_uk.py @@ -5,21 +5,9 @@ import time from datetime import datetime from waste_collection_schedule import Collection -TITLE = 'middlesbrough.gov.uk' -DESCRIPTION = ( - 'Source for waste collection services for Middlesbrough Council' -) -URL = 'https://www.middlesbrough.gov.uk/bin-collection-dates' - - -HEADERS = { - "user-agent": "Mozilla/5.0", -} - -COOKIES = { - -} - +TITLE = 'Middlesbrough Council' +DESCRIPTION = 'Source for waste collection services for Middlesbrough Council' +URL = 'https://www.middlesbrough.gov.uk' TEST_CASES = { "Tollesby Road - number" : {"uprn": 100110140843}, "Tollesby Road - string" : {"uprn": "100110140843"}, @@ -33,6 +21,13 @@ API_URLS = { 'schedule': 'https://my.middlesbrough.gov.uk/apibroker/runLookup?id=5d78f40439054&repeat_against=&noRetry=true&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&' } +HEADERS = { + "user-agent": "Mozilla/5.0", +} + +COOKIES = { +} + _LOGGER = logging.getLogger(__name__) class Source: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/miljoteknik_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/miljoteknik_se.py index c1d6a039..840b9ce7 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/miljoteknik_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/miljoteknik_se.py @@ -33,15 +33,15 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] # those bins, only for the so called "Fyrfack" bins (meaning four slots). # -TITLE = "Ronneby Miljöteknik Sophämntning" +TITLE = "Ronneby Miljöteknik" DESCRIPTION = "Source for Ronneby Miljöteknik waste collection." -URL = ( - "http://www.fyrfackronneby.se/hamtningskalender/" -) +URL = "http://www.fyrfackronneby.se" TEST_CASES = { "Home": {"street_address": "Hjortsbergavägen 16, Johannishus"} } +API_URL = "http://www.fyrfackronneby.se/hamtningskalender/" + class Source: def __init__(self, street_address): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/minrenovasjon_no.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/minrenovasjon_no.py index 9ddfbdef..a66b4a2b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/minrenovasjon_no.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/minrenovasjon_no.py @@ -9,7 +9,7 @@ from pprint import pprint TITLE = "Min Renovasjon" DESCRIPTION = "Source for Norkart Komtek MinRenovasjon (Norway)." -URL = "https://www.norkart.no/komtek/renovasjon/" +URL = "https://www.norkart.no" # **street_code:** \ # **county_id:** \ @@ -29,8 +29,28 @@ TEST_CASES = { } } -BASE_URL = "https://komteksky.norkart.no/komtek.renovasjonwebapi/api/" +API_URL = "https://komteksky.norkart.no/komtek.renovasjonwebapi/api/" APP_KEY = "AE13DEEC-804F-4615-A74E-B4FAC11F0A30" +ICON_MAP = { + "": "mdi:trash-can", + "brush": "mdi:trash-can", + "elektriskogelektronisk": "mdi:chip", + "farligavfall": "mdi:trash-can", + "glassogmetallemballasje": "mdi:trash-can", + "hageavfall": "mdi:leaf", + "klaerogsko": "mdi:hanger", + "matavfall": "mdi:trash-can", + "matrestavfall": "mdi:trash-can", + "matrestavfallplast": "mdi:trash-can", + "metall": "mdi:trash-can", + "papir": "mdi:newspaper-variant-multiple", + "pappogkartong": "mdi:archive", + "plastemballasje": "mdi:trash-can", + "restavfall": "mdi:trash-can", + "drikkekartong": "mdi:newspaper-variant-multiple", + "papppapirdrikkekartong": "mdi:newspaper-variant-multiple", + "trevirke": "mdi:trash-can", + } class Source: def __init__(self, street_name, house_number, street_code, county_id): @@ -38,28 +58,6 @@ class Source: self._house_number = house_number self._street_code = street_code self._county_id = county_id - self._icon_map = { - "": "mdi:trash-can", - "brush": "mdi:trash-can", - "elektriskogelektronisk": "mdi:chip", - "farligavfall": "mdi:trash-can", - "glassogmetallemballasje": "mdi:trash-can", - "hageavfall": "mdi:leaf", - "klaerogsko": "mdi:hanger", - "matavfall": "mdi:trash-can", - "matrestavfall": "mdi:trash-can", - "matrestavfallplast": "mdi:trash-can", - "metall": "mdi:trash-can", - "papir": "mdi:newspaper-variant-multiple", - "pappogkartong": "mdi:archive", - "plastemballasje": "mdi:trash-can", - "restavfall": "mdi:trash-can", - "drikkekartong": "mdi:newspaper-variant-multiple", - "papppapirdrikkekartong": "mdi:newspaper-variant-multiple", - "trevirke": "mdi:trash-can" - - - } def fetch(self): headers = { @@ -69,15 +67,13 @@ class Source: } args = {} - r = requests.get(BASE_URL + 'fraksjoner', params = args, headers = headers) + r = requests.get(API_URL + 'fraksjoner', params = args, headers = headers) type = {} for f in json.loads(r.content): # pprint(f) - icon = "mdi:trash-can" icon_name = re.sub(r"^.*?/(\w+)\.\w{3,4}$", "\\1", f['Ikon']) - if icon_name in self._icon_map: - icon = self._icon_map[icon_name] + icon = ICON_MAP.get(icon_name, "mdi:trash-can") type[f['Id']] = { 'name': f['Navn'], 'image': f['Ikon'], @@ -88,10 +84,9 @@ class Source: 'gatenavn': self._street_name, 'husnr': self._house_number, 'gatekode': self._street_code, - } - r = requests.get(BASE_URL + 'tommekalender', params = args, headers = headers) + r = requests.get(API_URL + 'tommekalender', params = args, headers = headers) entries = [] for f in json.loads(r.content): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py index f0dec057..ab6abc48 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py @@ -1,6 +1,6 @@ import logging -from datetime import datetime import re +from datetime import datetime import requests from bs4 import BeautifulSoup @@ -8,7 +8,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Macedon Ranges Shire Council" DESCRIPTION = "Source for Macedon Ranges Shire Council rubbish collection." -URL = "https://www.mrsc.vic.gov.au/Live-Work/Bins-Rubbish-Recycling/Bins-and-collection-days/Bin-collection-days" +URL = "https://www.mrsc.vic.gov.au" TEST_CASES = { "Macedon IGA": {"street_address": "20 Victoria Street, Macedon"}, "ALDI Gisborne": {"street_address": "45 Aitken Street, Gisborne"}, @@ -45,10 +45,9 @@ class Source: addressSearchApiResults["Items"] is None or len(addressSearchApiResults["Items"]) < 1 ): - _LOGGER.error( + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on https://www.mrsc.vic.gov.au/Live-Work/Bins-Rubbish-Recycling/Bins-and-collection-days/Bin-collection-days" ) - return [] addressSearchTopHit = addressSearchApiResults["Items"][0] _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) @@ -72,10 +71,12 @@ class Source: waste_type = article.h3.string icon = ICON_MAP.get(waste_type, "mdi:trash-can") next_pickup = article.find(class_="next-service").string.strip() - if re.match("[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): + if re.match(r"[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): next_pickup_date = datetime.strptime( next_pickup.split(sep=" ")[1], "%d/%m/%Y" ).date() - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=icon)) + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/muenchenstein_ch.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/muenchenstein_ch.py deleted file mode 100755 index 3a5cb59f..00000000 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/muenchenstein_ch.py +++ /dev/null @@ -1,76 +0,0 @@ -import json -from datetime import datetime, timedelta - -import requests -from bs4 import BeautifulSoup -from waste_collection_schedule import Collection # type: ignore[attr-defined] - -TITLE = "Abfallsammlung Münchenstein" -DESCRIPTION = "Source for Muenchenstein waste collection." -URL = "https://www.muenchenstein.ch/abfallsammlung" -TEST_CASES = { - "Abfuhrkreis Ost": {"waste_district": "Abfuhrkreis Ost"}, - "Abfuhrkreis West": {"waste_district": "492"}, -} - - -IconMap = { - "kehricht": "mdi:trash-can", - "hackseldienst": "mdi:leaf", - "papierabfuhr": "mdi:newspaper-variant-multiple-outline", - "kartonabfuhr": "mdi:package-variant", - "altmetalle": "mdi:nail", -} - - -class Source: - def __init__(self, waste_district): - self._waste_district = waste_district - - def fetch(self): - response = requests.get(URL) - - html = BeautifulSoup(response.text, "html.parser") - - table = html.find("table", attrs={"id": "icmsTable-abfallsammlung"}) - data = json.loads(table.attrs["data-entities"]) - - entries = [] - for item in data["data"]: - if ( - self._waste_district in item["abfallkreisIds"] - or self._waste_district in item["abfallkreisNameList"] - ): - next_pickup = item["_anlassDate-sort"].split()[0] - next_pickup_date = datetime.fromisoformat(next_pickup).date() - - waste_type = BeautifulSoup(item["name"], "html.parser").text - waste_type_sorted = BeautifulSoup(item["name-sort"], "html.parser").text - - entries.append( - Collection( - date=next_pickup_date, - t=waste_type, - icon=IconMap.get(waste_type_sorted, "mdi:trash-can"), - ) - ) - - # Collection of "Kehricht und Kleinsperrgut brennbar" are not listed with dates as events on website. - # Instead it states the day of the week for each waste district: tuesday for east and friday for west - # So we're going to set those collections programmatically for the next 4 occurrences - weekday_collection = 2 if self._waste_district == 'Abfuhrkreis Ost' or self._waste_district == 491 else 4 - weekday_today = datetime.now().isoweekday() - for x in range(4): - days_to_pickup = (x * 7) + ((weekday_collection - weekday_today) % 7) - next_pickup_date = (datetime.now() + timedelta(days=days_to_pickup)).date() - waste_type = "Kehricht und Kleinsperrgut brennbar" - - entries.append( - Collection( - date=next_pickup_date, - t=waste_type, - icon=IconMap.get(waste_type_sorted, "mdi:trash-can"), - ) - ) - - return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nawma_sa_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nawma_sa_gov_au.py index 7eae80eb..c28544f7 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nawma_sa_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nawma_sa_gov_au.py @@ -8,7 +8,7 @@ TITLE = "North Adelaide Waste Management Authority" DESCRIPTION = ( "Source for nawma.sa.gov.au (Salisbury, Playford, and Gawler South Australia)." ) -URL = "http://www.nawma.sa.gov.au" +URL = "https://www.nawma.sa.gov.au" TEST_CASES = { "128 Bridge Road": { "street_number": "128", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py index 4e7a2b4e..9aedb156 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py @@ -1,62 +1,47 @@ import logging -from datetime import datetime import re +from datetime import datetime import requests -# These lines are needed to suppress the InsecureRequestWarning resulting from the POST verify=False option -# With verify=True the POST fails due to a SSLCertVerificationError. -import urllib3 -from waste_collection_schedule import Collection +from waste_collection_schedule import Collection # type: ignore[attr-defined] -urllib3.disable_warnings() -# The following links may provide a better way of dealing with this, as using verify=False is not ideal: -# https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings -# https://urllib3.readthedocs.io/en/1.26.x/user-guide.html#ssl _LOGGER = logging.getLogger(__name__) -TITLE = "newcastle.gov.uk" -DESCRIPTION = ( - """Source for waste collection services for Newcastle City Council""" -) -URL = "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php" -REGEX = "(Green|Blue|Brown) [bB]in \\((Domestic|Recycling|Garden)( Waste)?\\) details: <\\/strong>" \ - "collection day : [a-zA-Z]*day" \ - "Next collection : ([0-9]{2}-[A-Za-z]+-[0-9]{4})" -ICONS = { +TITLE = "Newcastle City Council" +DESCRIPTION = "Source for waste collection services for Newcastle City Council" +URL = "https://community.newcastle.gov.uk" +TEST_CASES = {"Test_001": {"uprn": "004510053797"}, "Test_002": {"uprn": 4510053797}} + + +API_URL = "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php" +REGEX = ( + "[Green|Blue|Brown] [Bb]in \(([A-Za-z]+)( Waste)?\) .*? ([0-9]{2}-[A-Za-z]+-[0-9]{4})" + ) +ICON_MAP = { "DOMESTIC": "mdi:trash-can", "RECYCLING": "mdi:recycle", "GARDEN": "mdi:leaf", } -TEST_CASES = { - "Test_001": {"uprn": "004510053797"}, - "Test_002": {"uprn": 4510053797} -} - class Source: - def __init__(self, uprn=None): + def __init__(self, uprn): self._uprn = str(uprn).zfill(12) - if not self._uprn: - _LOGGER.error( - "uprn must be provided in config" - ) - self._uprn = self._uprn.zfill(12) self._session = requests.Session() def fetch(self): entries = [] - res = requests.get(f"{URL}?uprn={self._uprn}") + res = requests.get(f"{API_URL}?uprn={self._uprn}") collections = re.findall(REGEX, res.text) for collection in collections: - collection_type = collection[1] - collection_date = collection[3] + collection_type = collection[0] + collection_date = collection[2] entries.append( Collection( - date=datetime.strptime(collection_date, '%d-%b-%Y').date(), + date=datetime.strptime(collection_date, "%d-%b-%Y").date(), t=collection_type, - icon=ICONS.get(collection_type.upper()), + icon=ICON_MAP.get(collection_type.upper()), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py new file mode 100755 index 00000000..9f9b06bb --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py @@ -0,0 +1,79 @@ +import logging +import re +from datetime import datetime + +import requests +from bs4 import BeautifulSoup +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Nillumbik Shire Council" +DESCRIPTION = "Source for Nillumbik Shire Council rubbish collection." +URL = "https://www.nillumbik.vic.gov.au" +TEST_CASES = {"Test": {"street_address": "11 Sunnyside Crescent, WATTLE GLEN, 3096"}} + +_LOGGER = logging.getLogger(__name__) + +ICON_MAP = { + "Food and Green Waste": "mdi:leaf", + "Hard Waste": "mdi:sofa", + "Recycling": "mdi:recycle", +} + + +class Source: + def __init__(self, street_address): + self._street_address = street_address + + def fetch(self): + session = requests.Session() + + response = session.get( + "https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day" + ) + response.raise_for_status() + + response = session.get( + "https://www.nillumbik.vic.gov.au/api/v1/myarea/search", + params={"keywords": self._street_address}, + ) + response.raise_for_status() + addressSearchApiResults = response.json() + if ( + addressSearchApiResults["Items"] is None + or len(addressSearchApiResults["Items"]) < 1 + ): + raise Exception( + f"Address search for '{self._street_address}' returned no results. Check your address on https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day" + ) + + addressSearchTopHit = addressSearchApiResults["Items"][0] + _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) + + geolocationid = addressSearchTopHit["Id"] + _LOGGER.debug("Geolocationid: %s", geolocationid) + + response = session.get( + "https://www.nillumbik.vic.gov.au/ocapi/Public/myarea/wasteservices?ocsvclang=en-AU", + params={"geolocationid": geolocationid}, + ) + response.raise_for_status() + + wasteApiResult = response.json() + _LOGGER.debug("Waste API result: %s", wasteApiResult) + + soup = BeautifulSoup(wasteApiResult["responseContent"], "html.parser") + + entries = [] + for article in soup.find_all("article"): + waste_type = article.h3.string + icon = ICON_MAP.get(waste_type, "mdi:trash-can") + next_pickup = article.find(class_="next-service").string.strip() + if re.match(r"[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): + next_pickup_date = datetime.strptime( + next_pickup.split(sep=" ")[1], "%d/%m/%Y" + ).date() + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nottingham_city_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nottingham_city_gov_uk.py index 7c59774f..7f60304b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nottingham_city_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nottingham_city_gov_uk.py @@ -5,7 +5,7 @@ import time import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "nottinghamcity.gov.uk" +TITLE = "Nottingham City Council" DESCRIPTION = "Source for nottinghamcity.gov.uk services for the city of Nottingham, UK." URL = "https://nottinghamcity.gov.uk" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nsomerset_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nsomerset_gov_uk.py index 74efd6a0..83b0b616 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nsomerset_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nsomerset_gov_uk.py @@ -5,7 +5,7 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "North Somerset.gov.uk" +TITLE = "North Somerset Council" DESCRIPTION = "Source for n-somerset.gov.uk services for North Somerset, UK." URL = "n-somerset.gov.uk" diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nuernberger_land_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nuernberger_land_de.py new file mode 100644 index 00000000..1d7e2b5b --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nuernberger_land_de.py @@ -0,0 +1,52 @@ +import requests +from waste_collection_schedule import Collection +from waste_collection_schedule.service.ICS import ICS + +TITLE = "Abfallwirtschaft Nürnberger Land" +DESCRIPTION = "Source for Nürnberger Land" +URL = "https://nuernberger-land.de" +TEST_CASES = { + "Schwarzenbruck, Mühlbergstraße": {"id": 16952001}, + "Burgthann, Brunhildstr": {"id": 14398001}, + "Kirchensittenbach, Erlenweg": {"id": 15192001}, +} + +API_URL = "https://abfuhrkalender.nuernberger-land.de/waste_calendar" + +ICON_MAP = { + "Restmüll": "mdi:trash-can", + "Biotonne": "mdi:leaf", + "Gelber Sack": "mdi:recycle", + "Papier": "mdi:package-variant", + "Giftmobil": "mdi:biohazard", +} + + +class Source: + def __init__(self, id): + self._id = id + self._ics = ICS(split_at="/") + + def fetch(self): + # fetch the ical + r = requests.get(f"{API_URL}/ical?id={self._id}") + r.raise_for_status() + + # replace non-ascii character in UID, otherwise ICS converter will fail + ics = "" + for line in r.text.splitlines(): + if line.startswith("UID"): + line = line.replace("ä", "ae") + line = line.replace("ö", "oe") + line = line.replace("ü", "ue") + ics += line + ics += "\n" + + dates = self._ics.convert(ics) + + entries = [] + + for d in dates: + entries.append(Collection(date=d[0], t=d[1], icon=ICON_MAP.get(d[1]))) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/oslokommune_no.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/oslokommune_no.py index 87a7d594..064627e4 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/oslokommune_no.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/oslokommune_no.py @@ -9,7 +9,7 @@ from pprint import pprint TITLE = "Oslo Kommune" DESCRIPTION = "Oslo Kommune (Norway)." -URL = "https://www.oslo.kommune.no/avfall-og-gjenvinning/nar-hentes-avfallet/" +URL = "https://www.oslo.kommune.no" # **street_code:** \ # **county_id:** \ @@ -29,7 +29,12 @@ TEST_CASES = { } } -BASE_URL = "https://www.oslo.kommune.no/xmlhttprequest.php" +API_URL = "https://www.oslo.kommune.no/xmlhttprequest.php" +ICON_MAP = { + "": "mdi:trash-can", + "restavfall": "mdi:trash-can", + "papir": "mdi:newspaper-variant-multiple" +} class Source: def __init__(self, street_name, house_number, house_letter, street_id): @@ -37,11 +42,6 @@ class Source: self._house_number = house_number self._house_letter = house_letter self._street_id = street_id - self._icon_map = { - "": "mdi:trash-can", - "restavfall": "mdi:trash-can", - "papir": "mdi:newspaper-variant-multiple" - } def fetch(self): headers = { @@ -56,7 +56,7 @@ class Source: 'street_id': self._street_id, } - r = requests.get(BASE_URL, params = args, headers = headers) + r = requests.get(API_URL, params = args, headers = headers) entries = [] res = json.loads(r.content)['data']['result'][0]['HentePunkts'] @@ -70,7 +70,7 @@ class Source: tjeneste['TommeDato'], "%d.%m.%Y" ).date(), t = tekst, - icon = self._icon_map[tekst.lower()] or "mdi:trash-can" + icon = ICON_MAP.get(tekst.lower(), "mdi:trash-can") ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/peterborough_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/peterborough_gov_uk.py index afa6ca0f..f4859667 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/peterborough_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/peterborough_gov_uk.py @@ -4,11 +4,11 @@ import datetime import requests from waste_collection_schedule import Collection -TITLE = "Peterborough.gov.uk" +TITLE = "Peterborough City Council" DESCRIPTION = ( "Source for peterborough.gov.uk services for Peterborough" ) -URL = "peterborough.gov.uk" +URL = "https://peterborough.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "PE57AX", "number": 1}, "houseName": {"post_code": "PE57AX", "name": "CASTOR HOUSE"}, @@ -20,7 +20,7 @@ API_URLS = { "collection": "https://www.peterborough.gov.uk/api/jobs/{start}/{end}/{uprn}", } -ICONS = { +ICON_MAP = { "Empty Bin 240L Black": "mdi:trash-can", "Empty Bin 240L Green": "mdi:recycle", "Empty Bin 240L Brown": "mdi:leaf", @@ -74,7 +74,7 @@ class Source: collection["nextDate"], "%Y-%m-%dT%H:%M:%S" ).date(), t=collection["jobDescription"], - icon=ICONS.get(collection["jobDescription"]), + icon=ICON_MAP.get(collection["jobDescription"]), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/pgh_st.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/pgh_st.py index 678f132e..aebd8e74 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/pgh_st.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/pgh_st.py @@ -5,10 +5,10 @@ from urllib.parse import quote import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "PGH.ST" +TITLE = "City of Pittsburgh" DESCRIPTION = "Source for PGH.ST services for the city of Pittsburgh, PA, USA." -URL = "http://www.pgh.st" -TEST_CASES = {} +URL = "https://www.pgh.st" +COUNTRY = "us" TEST_CASES = { "Pittsburgh, Negley": { "house_number": 800, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recycleapp_be.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recycleapp_be.py index 652ef7b5..b7edde96 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recycleapp_be.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recycleapp_be.py @@ -51,20 +51,17 @@ class Source: "Authorization": "", } r = requests.get(f"{url}/access-token", headers=headers) + r.raise_for_status() headers["Authorization"] = r.json()["accessToken"] params = {"q": self._postcode} r = requests.get(f"{url}/zipcodes", params=params, headers=headers) - if r.status_code != 200: - _LOGGER.error("Get zip code failed") - return [] + r.raise_for_status() zipcodeId = r.json()["items"][0]["id"] params = {"q": self._street, "zipcodes": zipcodeId} r = requests.post(f"{url}/streets", params=params, headers=headers) - if r.status_code != 200: - _LOGGER.error("Get street id failed") - return [] + r.raise_for_status() streetId = None for item in r.json()["items"]: @@ -85,9 +82,7 @@ class Source: # "size":100, } r = requests.get(f"{url}/collections", params=params, headers=headers) - if r.status_code != 200: - _LOGGER.error("Get data failed") - return [] + r.raise_for_status() entries = [] for item in r.json()["items"]: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclesmart_com.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclesmart_com.py index c9d28064..cbb3da6f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclesmart_com.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclesmart_com.py @@ -7,6 +7,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "RecycleSmart" DESCRIPTION = "Source for RecycleSmart collection." URL = "https://www.recyclesmart.com/" +COUNTRY = "au" TEST_CASES = { "pickup": { "email": "!secret recyclesmart_email", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/regioentsorgung_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/regioentsorgung_de.py index 83c9909f..7ae988bf 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/regioentsorgung_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/regioentsorgung_de.py @@ -2,15 +2,14 @@ import requests from waste_collection_schedule import Collection from waste_collection_schedule.service.ICS import ICS -TITLE = "RegioEntsorgung" +TITLE = "RegioEntsorgung Städteregion Aachen" DESCRIPTION = "RegioEntsorgung Städteregion Aachen" -URL = "https://regioentsorgung.de/service/abfallkalender/" - +URL = "https://regioentsorgung.de" TEST_CASES = { "Merzbrück": {"city": "Würselen", "street": "Merzbrück", "house_number": 200 }, } -BASE_URL = "https://tonnen.regioentsorgung.de/WasteManagementRegioentsorgung/WasteManagementServlet" +API_URL = "https://tonnen.regioentsorgung.de/WasteManagementRegioentsorgung/WasteManagementServlet" HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)", @@ -31,7 +30,7 @@ class Source: payload = { 'SubmitAction': 'wasteDisposalServices', } - r = session.get(BASE_URL, headers=HEADERS, params=payload) + r = session.get(API_URL, headers=HEADERS, params=payload) r.raise_for_status() payload = { @@ -41,7 +40,7 @@ class Source: 'Strasse': '', 'Hausnummer': '', } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() payload = { @@ -51,7 +50,7 @@ class Source: 'Strasse': self.street, 'Hausnummer': '', } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() payload = { @@ -61,14 +60,14 @@ class Source: 'Strasse': self.street, 'Hausnummer': self.house_number, } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() payload = { 'ApplicationName': 'com.athos.kd.regioentsorgung.AbfuhrTerminModel', 'SubmitAction': 'forward', } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() payload = { @@ -86,7 +85,7 @@ class Source: 'ICalZeit': '06:00 Uhr', 'SubmitAction': 'filedownload_ICAL', } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() # Parse ics file diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/republicservices_com.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/republicservices_com.py index 4c4eddd4..d163c766 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/republicservices_com.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/republicservices_com.py @@ -7,6 +7,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Republic Services" DESCRIPTION = "Source for Republic Services Collection." URL = "https://www.republicservices.com" +COUNTRY = "us" TEST_CASES = { "Scott Country Clerk": {"street_address": "101 E Main St, Georgetown, KY 40324"}, "Branch County Clerk": {"street_address": "31 Division St. Coldwater, MI 49036"} diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/rh_entsorgung_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/rh_entsorgung_de.py index a2225889..d63d758e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/rh_entsorgung_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/rh_entsorgung_de.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "RH Entsorgung" +TITLE = "Rhein-Hunsrück Entsorgung (RHE)" DESCRIPTION = "Source for RHE (Rhein Hunsrück Entsorgung)." URL = "https://www.rh-entsorgung.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py index 86b27ad1..119e74f1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py @@ -1,19 +1,24 @@ from datetime import datetime + import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -import json -TITLE = "Richmondshire (North Yorkshire)" +TITLE = "Richmondshire District Council" DESCRIPTION = "To find your UPRN, visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value" -URL = "https://www.richmondshire.gov.uk/collectionCalendar" -TEST_CASES = {"test 1": {"uprn": 200001767082}, "test 2": {"uprn": 200001767078}, "test3": {"uprn": 200001767079 }} +URL = "https://www.richmondshire.gov.uk" +TEST_CASES = { + "test1": {"uprn": 200001767082}, + "test2": {"uprn": 200001767078}, + "test3": {"uprn": 200001767079}, +} -ICONS = { +ICON_MAP = { "240L GREY RUBBISH BIN": "mdi:trash-can", "55L RECYCLING BOX": "mdi:recycle", "140L GARDEN BIN": "mdi:leaf", } + class Source: def __init__( self, uprn @@ -24,9 +29,7 @@ class Source: r = requests.get( f"https://www.richmondshire.gov.uk/Umbraco/Api/MyAreaApi/GetBinRoundData?uprn={self._uprn}" ) -# print(r.text) ids = r.json() -# print (len(ids)) entries = [] @@ -35,7 +38,7 @@ class Source: Collection( date=datetime.strptime(id["start"], "%Y-%m-%dT%H:%M:%S").date(), t=id["title"], - icon=ICONS.get(id["title"]), + icon=ICON_MAP.get(id["title"]), ) ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/rushmoor_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/rushmoor_gov_uk.py index 5ee7e3f5..af7461bf 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/rushmoor_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/rushmoor_gov_uk.py @@ -2,9 +2,8 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "rushmoor.gov.uk" +TITLE = "Rushmoor Borough Council" DESCRIPTION = "Source for rushmoor.gov.uk services for Rushmoor, UK." -# Find the UPRN of your address using https://www.findmyaddress.co.uk/search URL = "https://rushmoor.gov.uk" TEST_CASES = { "GU14": {"uprn": "100060551749"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py new file mode 100644 index 00000000..2ca44656 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py @@ -0,0 +1,57 @@ +import logging +from datetime import datetime + +import requests +from bs4 import BeautifulSoup +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Salford City Council" +DESCRIPTION = "Source for bin collection services for Salford City Council, UK." +URL = "https://www.salford.gov.uk" +TEST_CASES = { + "domestic": {"uprn": "100011404886"}, +} + +ICON_MAP = { + "Domestic waste": "mdi:trash-can", + "Blue recycling (paper and card)": "mdi:recycle", + "Brown recycling (bottles and cans)": "mdi:glass-fragile", + "Food and garden waste": "mdi:leaf", +} + +_LOGGER = logging.getLogger(__name__) + + +class Source: + def __init__(self, uprn: int): + self._uprn = uprn + + def fetch(self): + params = {"UPRN": self._uprn} + r = requests.get( + "https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/your-bin-collections/", + params=params, + ) + r.raise_for_status() + + soup = BeautifulSoup(r.text, features="html.parser") + results = soup.find_all("div", {"class": "col-xs-12 col-md-6"}) + + entries = [] + + for result in results: + dates = [] + for date in result.find_all("li"): + dates.append(date.text) + collection_type = (result.find("strong").text).replace(":", "") + for current_date in dates: + date = datetime.strptime(current_date, "%A %d %B %Y").date() + entries.append( + Collection( + date=date, + t=collection_type, + icon=ICON_MAP.get(collection_type), + ) + ) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py index dad2e2fb..9c135114 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py @@ -1,36 +1,33 @@ from datetime import datetime -import logging import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfall SBAZV" +TITLE = "Südbrandenburgischer Abfallzweckverband" DESCRIPTION = "SBAZV Brandenburg, Deutschland" -URL = "https://www.sbazv.de/entsorgungstermine/klein.ics" +URL = "https://www.sbazv.de" TEST_CASES = { - "Wildau": { - "city": "wildau", - "district": "Wildau", - "street": "Miersdorfer Str." - } + "Wildau": {"city": "wildau", "district": "Wildau", "street": "Miersdorfer Str."} +} + +ICON_MAP = { + "Restmülltonnen": "mdi:trash-can", + "Laubsäcke": "mdi:leaf", + "Gelbe Säcke": "mdi:sack", + "Papiertonnen": "mdi:package-variant", + "Weihnachtsbäume": "mdi:pine-tree", } # _LOGGER = logging.getLogger(__name__) + class Source: def __init__(self, city, district, street=None): self._city = city self._district = district self._street = street self._ics = ICS() - self._iconMap = { - "Restmülltonnen": "mdi:trash-can", - "Laubsäcke" : "mdi:leaf", - "Gelbe Säcke" : "mdi:sack", - "Papiertonnen" : "mdi:package-variant", - "Weihnachtsbäume": "mdi:pine-tree", - } def fetch(self): now = datetime.now() @@ -57,17 +54,24 @@ class Source: # get ics file # https://www.sbazv.de/entsorgungstermine/klein.ics?city=Wildau&district=Wildau&street=Miersdorfer+Str. - r = requests.get("https://www.sbazv.de/entsorgungstermine/klein.ics", params=args) + r = requests.get( + "https://www.sbazv.de/entsorgungstermine/klein.ics", params=args + ) # parse ics file dates = self._ics.convert(r.text) entries = [] for d in dates: -# _LOGGER.error(d) waste_type = d[1].strip() next_pickup_date = d[0] - - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=self._iconMap.get(waste_type,"mdi:trash-can"))) + + entries.append( + Collection( + date=next_pickup_date, + t=waste_type, + icon=ICON_MAP.get(waste_type, "mdi:trash-can"), + ) + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/scambs_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/scambs_gov_uk.py index 177b5165..56427d99 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/scambs_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/scambs_gov_uk.py @@ -4,20 +4,21 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "Scambs.gov.uk" +TITLE = "South Cambridgeshire District Council" DESCRIPTION = ( "Source for scambs.gov.uk services for South Cambridgeshire District Council" ) -URL = "scambs.gov.uk" +URL = "https://scambs.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "CB236GZ", "number": 53}, "houseName": {"post_code": "CB225HT", "number": "Rectory Farm Cottage"}, } + API_URLS = { "address_search": "https://servicelayer3c.azure-api.net/wastecalendar/address/search/", "collection": "https://servicelayer3c.azure-api.net/wastecalendar/collection/search/{}/", } -ICONS = { +ICON_MAP = { "DOMESTIC": "mdi:trash-can", "RECYCLE": "mdi:recycle", "ORGANIC": "mdi:leaf", @@ -68,7 +69,7 @@ class Source: round_type, round_type.title() ), # returns concise values: Black Bin, Blue Bin, Green Bin # t = round_type.title(), # returns standard Scambs values: Black Bin Collection, Blue Bin Collection, Green Bin Collection - icon=ICONS.get(round_type), + icon=ICON_MAP.get(round_type), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/seattle_gov.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/seattle_gov.py index a03ce1e5..c831b27f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/seattle_gov.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/seattle_gov.py @@ -6,7 +6,8 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Seattle Public Utilities" DESCRIPTION = "Source for Seattle Public Utilities waste collection." -URL = "https://myutilities.seattle.gov/eportal/#/accountlookup/calendar" +URL = "https://myutilities.seattle.gov" +COUNTRY = "us" TEST_CASES = { "City Hall": {"street_address": "600 4th Ave"}, "Ballard Builders": {"street_address": "7022 12th Ave NW"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py index 805ca2d1..d3cccb6c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py @@ -6,7 +6,7 @@ import re import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Sector 27" +TITLE = "Sector 27 - Datteln, Marl, Oer-Erkenschwick" DESCRIPTION = "Source for Muellkalender in Kreis RE." URL = "https://muellkalender.sector27.de" TEST_CASES = { @@ -53,8 +53,7 @@ class Source: def fetch(self): city = CITIES.get(self._city) if city is None: - _LOGGER.error(f"city not found {self._city}") - return [] + raise Exception(f"city not found {self._city}") args = city args["searchFor"] = self._street @@ -64,13 +63,13 @@ class Source: params=args, headers=HEADERS, ) + r.raise_for_status() streets = { e["name"].strip(): e["id"] for (e) in json.loads(extractJson(r.text)) } if self._street not in streets: - _LOGGER.error(f"street not found {self._street}") - return [] + raise Exception(f"street not found {self._street}") args = { "licenseKey": city["licenseKey"], @@ -89,6 +88,7 @@ class Source: params=args, headers=HEADERS, ) + r.raise_for_status() data = json.loads(extractJson(r.text)) for ts, pickups in data["pickups"].items(): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sheffield_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sheffield_gov_uk.py index bba1eb8d..2ff042c9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sheffield_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sheffield_gov_uk.py @@ -4,20 +4,9 @@ from dateutil import parser import logging from waste_collection_schedule import Collection -TITLE = "Sheffield.gov.uk" - -DESCRIPTION = ( - "Source for waste collection services from Sheffield City Council (SCC)" -) - -# Base URL for waste collection services -URL = "https://wasteservices.sheffield.gov.uk/" - -# Headers to mimic the browser -HEADERS = { - "user-agent": "Mozilla/5.0", -} - +TITLE = "Sheffield City Council" +DESCRIPTION = "Source for waste collection services from Sheffield City Council (SCC)" +URL = "https://sheffield.gov.uk/" TEST_CASES = { # These are random addresses around Sheffield # If your property is listed here and you don't want it, please raise an issue and I'll amend @@ -26,8 +15,16 @@ TEST_CASES = { "test003" : {"uprn": "100050920796"}, } + +API_URL = "https://wasteservices.sheffield.gov.uk/" + +# Headers to mimic the browser +HEADERS = { + "user-agent": "Mozilla/5.0", +} + # Icons for the different bin types -ICONS = { +ICON_MAP = { "BLACK": "mdi:delete-empty", # General Waste "BROWN": "mdi:glass-fragile", # Glass, Tins, Cans & Plastics "BLUE": "mdi:newspaper", # Paper & Cardboard @@ -44,7 +41,7 @@ class Source: if self._uprn: # Get the page containing bin details # /calendar gives further future informaion over just the "Services" page - req = urllib.request.Request(f"{URL}/property/{self._uprn}/calendar",headers=HEADERS) + req = urllib.request.Request(f"{API_URL}/property/{self._uprn}/calendar",headers=HEADERS) with urllib.request.urlopen(req) as response: html_doc = response.read() @@ -65,7 +62,7 @@ class Source: Collection( date = collection_date, t = collection_type, - icon = ICONS.get(collection_type.replace(" Bin","").upper()), + icon = ICON_MAP.get(collection_type.replace(" Bin","").upper()), ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/south_norfolk_and_broadland_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/south_norfolk_and_broadland_gov_uk.py index 7fdf5b12..056cfa9d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/south_norfolk_and_broadland_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/south_norfolk_and_broadland_gov_uk.py @@ -9,9 +9,8 @@ import requests from bs4 import BeautifulSoup as soup from waste_collection_schedule import Collection -TITLE = "South Norfolk and Broadland Council UK" +TITLE = "South Norfolk and Broadland Council" DESCRIPTION = "Source for southnorfolkandbroadland.gov.uk services for South Norfolk and Broadland, UK" - URL = "https://area.southnorfolkandbroadland.gov.uk/" TEST_CASES = { "Random address": { @@ -42,7 +41,7 @@ TEST_CASES = { } } -ICONS = { +ICON_MAP = { "Rubbish": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden (if applicable)": "mdi:leaf" @@ -75,7 +74,7 @@ class Source: Collection( parse_date(tuple(bin_category.children)[3].strip()), tuple(bin_category.children)[1].text.strip(), - icon=ICONS.get(tuple(bin_category.children)[1].text.strip()) + icon=ICON_MAP.get(tuple(bin_category.children)[1].text.strip()) ) for bin_category in bin_categories diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py new file mode 100644 index 00000000..ffcf929c --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py @@ -0,0 +1,45 @@ +import logging +from datetime import datetime + +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "SRV Återvinning" +DESCRIPTION = "Source for SRV återvinning AB, Sweden" +URL = "https://www.srvatervinning.se" +TEST_CASES = { + "Skansvägen": {"address": "Skansvägen"}, + "Test1": {"address": "tun"}, + "Tullinge 1": {"address": "Hanvedens allé 78"}, + "Tullinge 2": {"address": "Skogsmulles Väg 22"}, +} + +_LOGGER = logging.getLogger(__name__) + + +class Source: + def __init__(self, address): + self._address = address + + def fetch(self): + + params = { + "query": self._address, + "city": "", + } + r = requests.get( + "https://www.srvatervinning.se/rest-api/srv-slamsok-rest-new/search", params + ) + r.raise_for_status() + + data = r.json() + + entries = [] + + for container in data["results"][0]["containers"]: + type = container["contentType"] + for calentry in container["calendars"]: + date_obj = datetime.strptime(calentry["startDate"], "%Y-%m-%d").date() + entries.append(Collection(date_obj, type)) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py index ff16a326..5231537d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py @@ -4,9 +4,9 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "SSAM Sophämntning" +TITLE = "SSAM" DESCRIPTION = "Source for SSAM waste collection." -URL = "https://edpfuture.ssam.se/FutureWeb/SimpleWastePickup/GetWastePickupSchedule" +URL = "https://ssam.se" TEST_CASES = { "Home": {"street_address": "Asteroidvägen 1, Växjö"}, "Bostadsrätt": {"street_address": "Långa Gatan 29 -81, Växjö"}, @@ -19,7 +19,6 @@ class Source: def fetch(self): params = {"searchText": self._street_address} - print(self._street_address) response = requests.post( "https://edpfuture.ssam.se/FutureWeb/SimpleWastePickup/SearchAdress", params=params, @@ -44,27 +43,48 @@ class Source: entries = [] for item in data["RhServices"]: - waste_type ="" + waste_type = "" next_pickup = item["NextWastePickup"] next_pickup_date = datetime.fromisoformat(next_pickup).date() if item["WasteType"] == "FNI1": - waste_type = "Kärl 1, "+ item["BinType"]["ContainerType"]+" " +str(item["BinType"]["Size"])+item["BinType"]["Unit"] + waste_type = ( + "Kärl 1, " + + item["BinType"]["ContainerType"] + + " " + + str(item["BinType"]["Size"]) + + item["BinType"]["Unit"] + ) icon = "mdi:trash-can" elif item["WasteType"] == "FNI2": - waste_type = "Kärl 2, "+ item["BinType"]["ContainerType"]+" " +str(item["BinType"]["Size"])+item["BinType"]["Unit"] + waste_type = ( + "Kärl 2, " + + item["BinType"]["ContainerType"] + + " " + + str(item["BinType"]["Size"]) + + item["BinType"]["Unit"] + ) icon = "mdi:trash-can" elif item["BinType"]["Code"] == "KM140": waste_type = "Matpåsar" icon = "mdi:recycle" else: - waste_type = item["WasteType"] +" "+ item["BinType"]["ContainerType"]+" " +str(item["BinType"]["Size"])+item["BinType"]["Unit"] + waste_type = ( + item["WasteType"] + + " " + + item["BinType"]["ContainerType"] + + " " + + str(item["BinType"]["Size"]) + + item["BinType"]["Unit"] + ) icon = "mdi:trash-can" if item["WasteType"] == "Trädgårdsavfall": icon = "mdi:leaf" found = 0 for x in entries: - if (x.date==next_pickup_date and x.type==waste_type): + if x.date == next_pickup_date and x.type == waste_type: found = 1 if found == 0: - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=icon)) + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadt_willich_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadt_willich_de.py index afbc7fee..dfa06595 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadt_willich_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadt_willich_de.py @@ -6,17 +6,18 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "Stadt Willich" DESCRIPTION = "Source for Stadt Willich waste collection." URL = "https://www.stadt-willich.de" -ICONS = { +TEST_CASES = { + "Altufer": {"street": "Altufer"}, + "Zum Schickerhof": {"street": "Zum Schickerhof"}, +} + +ICON_MAP = { "Graue Tonne": "mdi:trash-can", "Blaue Tonne": "mdi:newspaper-variant-multiple", "Gelbe Tonne": "mdi:recycle", "Bio Tonne": "mdi:bio", "Grünbündel": "mdi:tree", } -TEST_CASES = { - "Altufer": {"street": "Altufer"}, - "Zum Schickerhof": {"street": "Zum Schickerhof"}, -} class Source: @@ -59,7 +60,7 @@ class Source: entries = [] for d in dates: - icon = ICONS.get(d[1], "mdi:trash-can") + icon = ICON_MAP.get(d[1], "mdi:trash-can") entries.append(Collection( date=d[0], t=d[1], diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_dresden_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_dresden_de.py index f389a43d..78d71d55 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_dresden_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_dresden_de.py @@ -6,7 +6,7 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "Stadtreinigung Dresden" DESCRIPTION = "Source for Stadtreinigung Dresden waste collection." -URL = "https://www.dresden.de/apps_ext/AbfallApp/wastebins?0" +URL = "https://www.dresden.de" TEST_CASES = { "Neumarkt 6": {"standort": 80542}, } diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_leipzig_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_leipzig_de.py index 960036ef..44d36468 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_leipzig_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_leipzig_de.py @@ -31,17 +31,14 @@ class Source: data = json.loads(r.text) if len(data["results"]) == 0: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") street_entry = data["results"].get(self._street) if street_entry is None: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") id = street_entry.get(str(self._house_number)) if id is None: - _LOGGER.error(f"house_number not found: {self._house_number}") - return [] + raise Exception(f"house_number not found: {self._house_number}") # get ics file params = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py index 407cf79b..b9c2ec3c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py @@ -1,24 +1,24 @@ -import logging import datetime +import logging + import requests from bs4 import BeautifulSoup -from waste_collection_schedule import Collection +from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS TITLE = "StadtService Brühl" DESCRIPTION = "Source für Abfallkalender StadtService Brühl" -URL = "https://services.stadtservice-bruehl.de/abfallkalender/" -TEST_CASES = { - "TEST1" : {"strasse":"Badorfer Straße","hnr":"1" } -} +URL = "https://stadtservice-bruehl.de" +TEST_CASES = {"TEST1": {"strasse": "Badorfer Straße", "hnr": "1"}} _LOGGER = logging.getLogger(__name__) + class Source: def __init__(self, strasse, hnr): self._strasse = strasse self._hnr = hnr - self._ics = ICS() + self._ics = ICS(regex="(.*?) \\- ", split_at=", ") def fetch(self): @@ -26,58 +26,55 @@ class Source: year = today.year # Get District data = { - 'street': self._strasse, - 'street_number': self._hnr, - 'send_street_and_nummber_data': '' + "street": self._strasse, + "street_number": self._hnr, + "send_street_and_nummber_data": "", } - r = requests.post('https://services.stadtservice-bruehl.de/abfallkalender/', data=data) + r = requests.post( + "https://services.stadtservice-bruehl.de/abfallkalender/", data=data + ) + r.raise_for_status() - if r.status_code != 200: - _LOGGER.error("Error querying calender data") - return [] - - #print(r.text) soup = BeautifulSoup(r.text, "html.parser") for tag in soup.find_all("input", type="hidden"): - #print(tag["name"]) - #print(tag["value"]) - if (tag["name"] == "post_district"): post_district = tag["value"] + # print(tag["name"]) + # print(tag["value"]) + if tag["name"] == "post_district": + post_district = tag["value"] - if post_district == '': - _LOGGER.error("Unable to get district") - return [] + if post_district == "": + raise Exception("Unable to get district") - #print(post_district); - #Get ICAL + # print(post_district); + # Get ICAL data = { - 'post_year': year, - 'post_district': post_district, - 'post_street_name': self._strasse, - 'post_street_number': self._hnr, - 'checked_waste_type_hausmuell': 'on', - 'checked_waste_type_gelber_sack': 'on', - 'checked_waste_type_altpapier': 'on', - 'checked_waste_type_bio': 'on', - 'checked_waste_type_weihnachtsbaeume': 'on', - 'checked_waste_type_strassenlaub': 'on', - 'form_page_id': '9', - 'reminder_time': '8', - 'send_ics_download_configurator_data': '' + "post_year": year, + "post_district": post_district, + "post_street_name": self._strasse, + "post_street_number": self._hnr, + "checked_waste_type_hausmuell": "on", + "checked_waste_type_gelber_sack": "on", + "checked_waste_type_altpapier": "on", + "checked_waste_type_bio": "on", + "checked_waste_type_weihnachtsbaeume": "on", + "checked_waste_type_strassenlaub": "on", + "form_page_id": "9", + "reminder_time": "8", + "send_ics_download_configurator_data": "", } - r = requests.post('https://services.stadtservice-bruehl.de/abfallkalender/individuellen-abfuhrkalender-herunterladen/', data=data) + r = requests.post( + "https://services.stadtservice-bruehl.de/abfallkalender/individuellen-abfuhrkalender-herunterladen/", + data=data, + ) + r.raise_for_status() - if r.status_code != 200: - _LOGGER.error("Error querying calender data") - return [] - - print(r.text) dates = self._ics.convert(r.text) entries = [] for d in dates: - entries.append(Collection(d[0],d[1])) + entries.append(Collection(d[0], d[1])) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/staedteservice_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/staedteservice_de.py index 83d527d9..89b2c586 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/staedteservice_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/staedteservice_de.py @@ -4,10 +4,9 @@ import datetime from waste_collection_schedule import Collection from waste_collection_schedule.service.ICS import ICS -TITLE = "Städteservice" +TITLE = "Städteservice Raunheim Rüsselsheim" DESCRIPTION = "Städteservice Raunheim Rüsselsheim" URL = "https://www.staedteservice.de" - TEST_CASES = { "Rüsselsheim": { "city": "Rüsselsheim", @@ -19,7 +18,7 @@ TEST_CASES = { }, } -BASE_URL = "https://www.staedteservice.de/abfallkalender" +API_URL = "https://www.staedteservice.de/abfallkalender" CITY_CODE_MAP = { "Rüsselsheim": 1, @@ -64,7 +63,7 @@ class Source: def get_calendar_from_site(self, session: requests.Session, year: int) -> str: # example format: https://www.staedteservice.de/abfallkalender_1_477_2023.ics - URL = f"{BASE_URL}_{self.city_code}_{self.street_number}_{str(year)}.ics" + URL = f"{API_URL}_{self.city_code}_{self.street_number}_{str(year)}.ics" r = session.get(URL) r.raise_for_status() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stevenage_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stevenage_gov_uk.py index ef2931ed..a0037690 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stevenage_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stevenage_gov_uk.py @@ -12,11 +12,12 @@ TEST_CASES = { "Wansbeck Close schedule": {"road": "Wansbeck Close", "postcode": "SG1 6AA"}, "Chepstow Close schedule": {"road": "Chepstow Close", "postcode": "SG1 5TT"}, } + SEARCH_URLS = { "round_search": "https://services.stevenage.gov.uk/~?a=find&v=1&p=P1&c=P1_C33_&act=P1_A43_", "collection_search": "https://services.stevenage.gov.uk/~?a=find&v=1&p=P1&c=P1_C37_&act=P1_A64_", } -ICONS = { +ICON_MAP = { "REFUSE": "mdi:trash-can", "RECYCLING": "mdi:recycle", } @@ -83,7 +84,7 @@ class Source: Collection( date=datetime.strptime(collection[1], "%d/%m/%Y").date(), t="Recycling", - icon=ICONS["RECYCLING"], + icon=ICON_MAP["RECYCLING"], ) ) elif collection[2] == "Refuse collection": @@ -91,7 +92,7 @@ class Source: Collection( date=datetime.strptime(collection[1], "%d/%m/%Y").date(), t="Refuse", - icon=ICONS["REFUSE"], + icon=ICON_MAP["REFUSE"], ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py index aa79389e..221923f8 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py @@ -1,6 +1,6 @@ import logging -from datetime import datetime import re +from datetime import datetime import requests from bs4 import BeautifulSoup @@ -8,7 +8,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Stonnington City Council" DESCRIPTION = "Source for Stonnington City Council rubbish collection." -URL = "https://www.stonnington.vic.gov.au/Services/Waste-and-recycling" +URL = "https://www.stonnington.vic.gov.au" TEST_CASES = { "The Jam Factory": {"street_address": "500 Chapel Street, South Yarra"}, "Malvern Library": {"street_address": "1255 High Street, Malvern"}, @@ -45,10 +45,9 @@ class Source: addressSearchApiResults["Items"] is None or len(addressSearchApiResults["Items"]) < 1 ): - _LOGGER.error( + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on https://www.stonnington.vic.gov.au/Services/Waste-and-recycling" ) - return [] addressSearchTopHit = addressSearchApiResults["Items"][0] _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) @@ -72,10 +71,12 @@ class Source: waste_type = article.h3.string icon = ICON_MAP.get(waste_type, "mdi:trash-can") next_pickup = article.find(class_="next-service").string.strip() - if re.match("[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): + if re.match(r"[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): next_pickup_date = datetime.strptime( next_pickup.split(sep=" ")[1], "%d/%m/%Y" ).date() - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=icon)) + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stuttgart_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stuttgart_de.py index 57ad6d10..4d6926ce 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stuttgart_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stuttgart_de.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Abfall Stuttgart" DESCRIPTION = "Source for waste collections for the city of Stuttgart, Germany." -URL = "https://service.stuttgart.de/lhs-services/aws/" +URL = "https://service.stuttgart.de" TEST_CASES = {"Im Steinengarten 7": {"street": "Im Steinengarten", "streetnr": 7}} diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sysav_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sysav_se.py index 9152c45f..ecb6f2e6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sysav_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sysav_se.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Sysav Sophämntning" DESCRIPTION = "Source for Sysav waste collection." -URL = "https://www.sysav.se/Privat/min-sophamtning/" +URL = "https://www.sysav.se" TEST_CASES = { "Home": {"street_address": "Sommargatan 1, Svedala"}, "Polisen": {"street_address": "Stationsplan 1, Svedala"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/tewkesbury_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/tewkesbury_gov_uk.py index f6a11e10..a5ddc65e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/tewkesbury_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/tewkesbury_gov_uk.py @@ -4,9 +4,9 @@ from urllib.parse import quote as urlquote import requests from waste_collection_schedule import Collection -TITLE = "Tewkesbury Borough Council Waste and Recycling" +TITLE = "Tewkesbury Borough Council" DESCRIPTION = "Home waste collection schedule for Tewkesbury Borough Council" -URL = "https://www.tewkesbury.gov.uk/waste-and-recycling" +URL = "https://www.tewkesbury.gov.uk" TEST_CASES = { "Council Office": {"postcode": "GL20 5TT"}, "Council Office No Spaces": {"postcode": "GL205TT"}, @@ -14,7 +14,7 @@ TEST_CASES = { API_URL = "https://api-2.tewkesbury.gov.uk/general/rounds/%s/nextCollection" -ICONS = { +ICON_MAP = { "Refuse": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden": "mdi:leaf", @@ -47,7 +47,7 @@ class Source: date=datetime.strptime( schedule_entry["NextCollection"], "%Y-%m-%d").date(), t=schedule_entry["collectionType"], - icon=ICONS.get(schedule_entry["collectionType"]) + icon=ICON_MAP.get(schedule_entry["collectionType"]) ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py index 32468ab1..53f613f1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py @@ -5,7 +5,7 @@ import logging import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "The Hills Shire Council" +TITLE = "The Hills Shire Council, Sydney" DESCRIPTION = "Source for Hills Shire Council, Sydney, Australia waste collection." URL = "https://www.thehills.nsw.gov.au/" TEST_CASES = { @@ -37,8 +37,7 @@ class Source: # check if suburb exists if self._suburb not in suburbs: - _LOGGER.error(f"suburb not found: {self._suburb}") - return [] + raise Exception(f"suburb not found: {self._suburb}") suburbKey = suburbs[self._suburb] # get list of streets for selected suburb @@ -51,14 +50,14 @@ class Source: # check if street exists if self._street not in streets: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") streetKey = streets[self._street] # get list of house numbers for selected street params = {"streetkey": streetKey, "suburbKey": suburbKey} r = requests.get( - f"{self._url}/properties/GetPropertiesByStreetAndSuburbKey", params=params, + f"{self._url}/properties/GetPropertiesByStreetAndSuburbKey", + params=params, ) data = json.loads(r.text) @@ -70,8 +69,7 @@ class Source: # check if house number exists if self._houseNo not in houseNos: - _LOGGER.error(f"house number not found: {self._houseNo}") - return [] + raise Exception(f"house number not found: {self._houseNo}") propertyKey = houseNos[self._houseNo] # get collection schedule 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 2f5b14c6..a38a984b 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 @@ -6,17 +6,15 @@ from ..collection import Collection from datetime import datetime, timedelta -TITLE = 'Toronto' -DESCRIPTION = ( - 'Source for Toronto waste collection' -) +TITLE = 'City of 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"}, "324 Weston Rd": {"street_address": "324 Weston Rd"}, } +CSV_URL = 'https://www.toronto.ca/ext/swms/collection_calendar.csv' PROPERTY_LOOKUP_URL = 'https://map.toronto.ca/cotgeocoder/rest/geocoder/suggest' SCHEDULE_LOOKUP_URL = 'https://map.toronto.ca/cotgeocoder/rest/geocoder/findAddressCandidates' diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/vasyd_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/vasyd_se.py index 0111d2dd..d9e4b9f5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/vasyd_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/vasyd_se.py @@ -6,9 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "VA Syd Sophämntning" DESCRIPTION = "Source for VA Syd waste collection." -URL = ( - "https://www.vasyd.se/Artiklar/Avfall-och-soptomning-privat/sopt%C3%B6mning-schema/" -) +URL = "https://www.vasyd.se" TEST_CASES = { "Home": {"street_address": "Industrigatan 13, Malmö"}, "Polisen": {"street_address": "Drottninggatan 20, Malmö"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/waipa_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/waipa_nz.py index 03c00010..9c9a6709 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/waipa_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/waipa_nz.py @@ -9,8 +9,8 @@ TITLE = "Waipa District Council" DESCRIPTION = "Source for Waipa District Council. Finds both general and glass recycling dates." URL = "https://www.waipadc.govt.nz/" TEST_CASES = { - "10 Queen Street": {"address": "10 Queen Street"} #Monday - ,"1 Acacia Avenue": {"address": "1 Acacia Avenue"}#Wednesday + "10 Queen Street": {"address": "10 Queen Street"}, # Monday + "1 Acacia Avenue": {"address": "1 Acacia Avenue"}, # Wednesday } diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/walsall_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/walsall_gov_uk.py index d14ac65c..a501c681 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/walsall_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/walsall_gov_uk.py @@ -6,18 +6,9 @@ from datetime import datetime from waste_collection_schedule import Collection -TITLE = "walsall.gov.uk" - -DESCRIPTION = ( - "Source for waste collection services from Walsall Council" -) - -URL = "https://cag.walsall.gov.uk" - -HEADERS = { - "user-agent": "Mozilla/5.0", -} - +TITLE = "Walsall Council" +DESCRIPTION = "Source for waste collection services from Walsall Council" +URL = "https://www.walsall.gov.uk/" TEST_CASES = { "test001" : {"uprn": "100071103746"}, "test002" : {"uprn": 100071105627}, @@ -25,11 +16,15 @@ TEST_CASES = { "test004" : {"uprn": 100071048794}, } -ICONS = { +API_URL = "https://cag.walsall.gov.uk" +ICON_MAP = { "GREY": "mdi:trash-can", "GREEN": "mdi:recycle", "BROWN": "mdi:leaf", } +HEADERS = { + "user-agent": "Mozilla/5.0", +} _LOGGER = logging.getLogger(__name__) @@ -55,7 +50,7 @@ class Source: if "roundname" in item["href"]: #get bin colour bincolour = item["href"].split("=")[-1].split("%")[0].upper() - binURL = URL + item["href"] + binURL = API_URL + item["href"] r = s.get(binURL, headers=HEADERS) responseContent = r.text soup = BeautifulSoup(responseContent, "html.parser") @@ -67,7 +62,7 @@ class Source: Collection( date = collection_date.date(), t = bincolour, - icon = ICONS.get(bincolour), + icon = ICON_MAP.get(bincolour), ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/warszawa19115_pl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/warszawa19115_pl.py index 51e2c0b5..593150a5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/warszawa19115_pl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/warszawa19115_pl.py @@ -4,9 +4,9 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "Warszawa19115.pl" +TITLE = "Warsaw" DESCRIPTION = "Source for Warsaw city garbage collection" -URL = "https://warszawa19115.pl/harmonogramy-wywozu-odpadow" +URL = "https://warszawa19115.pl" TEST_CASES = { "Street Name": {"street_address": "MARSZAŁKOWSKA 84/92, 00-514 Śródmieście"}, "Geolocation ID": {"geolocation_id": "76802934"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wastenet_org_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wastenet_org_nz.py index 425089fc..5cc5dae8 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wastenet_org_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wastenet_org_nz.py @@ -5,7 +5,7 @@ from html.parser import HTMLParser import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Wastenet" +TITLE = "Gore, Invercargill & Southland" DESCRIPTION = "Source for Wastenet.org.nz." URL = "http://www.wastenet.org.nz" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wermelskirchen_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wermelskirchen_de.py index 1f2b4b4b..0336d9fb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wermelskirchen_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wermelskirchen_de.py @@ -4,17 +4,16 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfallkalender Wermelskirchen" +TITLE = "Wermelskirchen" DESCRIPTION = "Source for Abfallabholung Wermelskirchen, Germany" -URL = "https://www.wermelskirchen.de/rathaus/buergerservice/formulare-a-z/abfallkalender-online/" - +URL = "https://www.wermelskirchen.de" TEST_CASES = { "Rathaus": {"street": "Telegrafenstraße", "house_number": "29"}, "Krankenhaus": {"street": "Königstraße", "house_number": "100"}, "Mehrzweckhalle": {"street": "An der Mehrzweckhalle", "house_number": "1"}, } -INFOS = { +ICON_MAP = { "Restabfall 2-woechentlich": { "icon": "mdi:trash-can", "image": "https://abfallkalender.citkomm.de/fileadmin/_processed_/1/b/csm_Restmuell_6b2b32c774.png", @@ -80,7 +79,7 @@ class Source: entries = [] for d in dates: - info = INFOS.get(d[1], {"icon": "mdi:trash-can", "image": ""}) + info = ICON_MAP.get(d[1], {"icon": "mdi:trash-can", "image": ""}) entries.append( Collection(d[0], d[1], picture=info["image"], icon=info["icon"]) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/westberks_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/westberks_gov_uk.py index 9a24b47d..df98987b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/westberks_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/westberks_gov_uk.py @@ -6,10 +6,9 @@ import requests from waste_collection_schedule import Collection -TITLE = "West Berkshire Council, UK" +TITLE = "West Berkshire Council" DESCRIPTION = "Source for westberks.gov.uk services for West Berkshire Council" -URL = "westberks.gov.uk" - +URL = "https://westberks.gov.uk" TEST_CASES = { "known_uprn": {"uprn": "100080241094"}, "unknown_uprn_by_name": {"postcode": "RG7 6NZ", "housenumberorname": "PARROG HOUSE"}, @@ -17,7 +16,7 @@ TEST_CASES = { "unknown_uprn_business": {"postcode": "RG18 4GE", "housenumberorname": "3"} } -ICONS = { +ICON_MAP = { "RUBBISH": "mdi:trash-can", "RECYCLING": "mdi:recycle", } @@ -90,7 +89,7 @@ class Source: Collection( date=dt_local.date(), t=waste_type, - icon=ICONS.get(waste_type.upper()), + icon=ICON_MAP.get(waste_type.upper()), ) ) @@ -106,7 +105,7 @@ class Source: Collection( date=dt_local.date(), t=waste_type, - icon=ICONS.get(waste_type.upper()), + icon=ICON_MAP.get(waste_type.upper()), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wiltshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wiltshire_gov_uk.py index b4a6bc0e..6c2d5ca1 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wiltshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wiltshire_gov_uk.py @@ -4,12 +4,13 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "Wiltshire Council, UK" +TITLE = "Wiltshire Council" DESCRIPTION = "Source for wiltshire.gov.uk services for Wiltshire Council" -URL = "wiltshire.gov.uk" +URL = "https://wiltshire.gov.uk" TEST_CASES = { "house_uprn": {"uprn": "100121085972", "postcode": "BA149QP"}, } + SEARCH_URLS = { "collection_search": "https://ilforms.wiltshire.gov.uk/wastecollectiondays/collectionlist" } diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py index 45a5c5c9..0334b7fb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py @@ -8,30 +8,30 @@ TITLE = "WSZ Moosburg" DESCRIPTION = ( "Source for WSZ Moosburg/Kärnten, including Moosburg, Pörtschach, Techelsberg" ) -URL = "https://wsz-moosburg.at/calendar" +URL = "https://wsz-moosburg.at" TEST_CASES = { - "Address_Id_Moosburg_Obergöriach_Obergöriach": {"address_id": 70265}, - "Address_Id_Moosburg_Moosburg_Pestalozzistr": {"address_id": 70082}, - "Address_Id_Pörtschach_10OktoberStr_10OktoberStr": {"address_id": 69866}, - "Address_Id_Techelsberg_SüdlichDerBahn-BahnhofTöschlingBisSaagNr-19": { + "Id: Moosburg, Obergöriach": {"address_id": 70265}, + "Id: Moosburg, Pestalozzistr": {"address_id": 70082}, + "Id: Pörtschach, 10. OktoberStr": {"address_id": 69866}, + "Id: Techelsberg, Südlich der Bahn: Bahnhof Töschling bis Saag Nr. 19": { "address_id": 69980 }, - "Address_Data_Moosburg_Obergöriach_Obergöriach": { + "Full: Moosburg, Obergöriach": { "municipal": "Moosburg", "address": "Obergöriach", "street": "Obergöriach", }, - "Address_Data_Moosburg_Moosburg_Pestalozzistr": { + "Full: Moosburg, Pestalozzistr": { "municipal": "Moosburg", "address": "Moosburg", "street": "Pestalozzistraße", }, - "Address_Data_Pörtschach_10OktoberStr_10OktoberStr": { + "Full: Pörtschach, 10. OktoberStr": { "municipal": "Pörtschach", "address": "10.-Oktober-Straße", "street": "10.-Oktober-Straße", }, - "Address_Data_Techelsberg_SüdlichDerBahn-BahnhofTöschlingBisSaagNr-19": { + "Data: Techelsberg, Südlich der Bahn: Bahnhof Töschling bis Saag Nr. 19": { "municipal": "Techelsberg", "address": "Südlich der Bahn: Bahnhof Töschling bis Saag Nr. 19", "street": "Südlich der Bahn: Bahnhof Töschling bis Saag Nr. 19", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wuerzburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wuerzburg_de.py index 9d16dd84..e06f31bc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wuerzburg_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wuerzburg_de.py @@ -6,8 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Abfallkalender Würzburg" DESCRIPTION = "Source for waste collection in the city of Würzburg, Germany." -URL = "https://www.wuerzburg.de/themen/umwelt-klima/vorsorge-entsorgung/abfallkalender/32208.Abfallkalender.html" -HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} +URL = "https://www.wuerzburg.de" TEST_CASES = { "District only": {"district": "Altstadt"}, "Street only": {"street": "Juliuspromenade"}, @@ -15,6 +14,9 @@ TEST_CASES = { "District + Street diff": {"district": "Altstadt", "street": "Oberer Burgweg"}, } +API_URL = "https://www.wuerzburg.de/themen/umwelt-klima/vorsorge-entsorgung/abfallkalender/32208.Abfallkalender.html" +HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} + class Source: def __init__(self, district: str = None, street: str = None): @@ -31,7 +33,7 @@ class Source: if not district and not street: raise ValueError("One of ['district', 'street'] is required.") - r = requests.get(URL, headers=HEADERS) + r = requests.get(API_URL, headers=HEADERS) r.raise_for_status() selects = BeautifulSoup(r.content, "html.parser").body.find_all("select") @@ -49,7 +51,7 @@ class Source: return strdict[street] except KeyError: raise KeyError( - f"Unable to find street '{street}'. Please compare exact typing with {URL}" + f"Unable to find street '{street}'. Please compare exact typing with {API_URL}" ) if district: @@ -64,7 +66,7 @@ class Source: return regdict[district] except KeyError: raise KeyError( - f"Unable to find district '{district}'. Please compare exact typing with {URL}" + f"Unable to find district '{district}'. Please compare exact typing with {API_URL}" ) def fetch(self): @@ -78,7 +80,7 @@ class Source: now = datetime.datetime.now().date() r = requests.get( - URL, + API_URL, headers=HEADERS, params={ "_func": "evList", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py index 9c3d1c9c..116c7470 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py @@ -1,20 +1,27 @@ import logging +from datetime import datetime import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -from datetime import datetime -TITLE = "Wyndham City Council" +TITLE = "Wyndham City Council, Melbourne" DESCRIPTION = "Source for Wyndham City Council rubbish collection." -URL = "https://digital.wyndham.vic.gov.au/myWyndham/" +URL = "https://wyndham.vic.gov.au" TEST_CASES = { - "Truganina South Primary School": {"street_address": "3-19 Parkvista Drive TRUGANINA 3029"}, + "Truganina South Primary School": { + "street_address": "3-19 Parkvista Drive TRUGANINA 3029" + }, "Westbourne Grammar School": {"street_address": "300 Sayers Road TRUGANINA 3029"}, - "Werribee Mercy Hospital": {"street_address": "300-310 Princes Highway WERRIBEE 3030"}, - "Wyndham Park Primary School": {"street_address": "59-77 Kookaburra Avenue WERRIBEE 3030"}, + "Werribee Mercy Hospital": { + "street_address": "300-310 Princes Highway WERRIBEE 3030" + }, + "Wyndham Park Primary School": { + "street_address": "59-77 Kookaburra Avenue WERRIBEE 3030" + }, } +API_URL = "https://digital.wyndham.vic.gov.au/myWyndham/" ICON_MAP = { "Green Waste": "mdi:leaf", "Garbage": "mdi:trash-can-outline", @@ -30,49 +37,54 @@ class Source: def fetch(self): session = requests.Session() - response = session.get(URL) + response = session.get(API_URL) response.raise_for_status() - response = session.get("https://digital.wyndham.vic.gov.au/myWyndham/ajax/address-search-suggestions.asp?", - params=dict(ASEARCH=self._street_address), - ) + response = session.get( + "https://digital.wyndham.vic.gov.au/myWyndham/ajax/address-search-suggestions.asp?", + params=dict(ASEARCH=self._street_address), + ) response.raise_for_status() html = response.content - property_address = BeautifulSoup(html, 'html.parser').find("li").get_text() + property_address = BeautifulSoup(html, "html.parser").find("li").get_text() _LOGGER.debug("Fetched Property Address: %s", property_address) - if property_address == 'No match found.': - _LOGGER.error( - f"Address search for '{self._street_address}' returned no results. Check your address on " - f"https://digital.wyndham.vic.gov.au/myWyndham/ " - ) - if property_address.upper() == self._street_address.upper(): - property_number = BeautifulSoup(html, 'html.parser').find('span').get_text() - _LOGGER.debug("Fetched Property Number: %s", property_number) - response = session.get( - "https://digital.wyndham.vic.gov.au/myWyndham/init-map-data.asp", - params=dict(propnum=property_number, radius="1000", mapfeatures="23,37,22,33,35"), - ) - response.raise_for_status() - wasteApiResult = response.content - soup = BeautifulSoup(wasteApiResult, 'html.parser') - entries = [] - - for article in soup.findAll("div", {"class": "waste"}): - if article.get_text().startswith('Next'): - waste_type = article.get_text().strip().split(':')[0][5:].replace(' Collection', '') - _LOGGER.debug("Waste Type: %s", waste_type) - icon = ICON_MAP.get(waste_type, 'mdi:trash-can') - _LOGGER.debug("Icon: %s", icon) - next_pickup_date = datetime.strptime(article.get_text().split(':')[1].strip(), "%A, %d %B %Y").date() - _LOGGER.debug("Next Pickup Date: %s", next_pickup_date) - entries.append( - Collection(date=next_pickup_date, t=waste_type, icon=icon) - ) - return entries - else: - _LOGGER.error( + if ( + property_address == "No match found." + or property_address.upper() != self._street_address.upper() + ): + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on " f"https://digital.wyndham.vic.gov.au/myWyndham/ " ) + property_number = BeautifulSoup(html, "html.parser").find("span").get_text() + _LOGGER.debug("Fetched Property Number: %s", property_number) + response = session.get( + "https://digital.wyndham.vic.gov.au/myWyndham/init-map-data.asp", + params=dict( + propnum=property_number, radius="1000", mapfeatures="23,37,22,33,35" + ), + ) + response.raise_for_status() + wasteApiResult = response.content + soup = BeautifulSoup(wasteApiResult, "html.parser") + entries = [] - + for article in soup.findAll("div", {"class": "waste"}): + if article.get_text().startswith("Next"): + waste_type = ( + article.get_text() + .strip() + .split(":")[0][5:] + .replace(" Collection", "") + ) + _LOGGER.debug("Waste Type: %s", waste_type) + icon = ICON_MAP.get(waste_type, "mdi:trash-can") + _LOGGER.debug("Icon: %s", icon) + next_pickup_date = datetime.strptime( + article.get_text().split(":")[1].strip(), "%A, %d %B %Y" + ).date() + _LOGGER.debug("Next Pickup Date: %s", next_pickup_date) + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py index b4c057b3..5a52dbbc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py @@ -6,6 +6,12 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Ximmio" DESCRIPTION = "Source for Ximmio B.V. waste collection." URL = "https://www.ximmio.nl" + + +def EXTRA_INFO(): + return [{"title": s["title"], "url": s["url"]} for s in SERVICE_MAP] + + TEST_CASES = { "ACV Group": {"company": "acv", "post_code": "6721MH", "house_number": 1}, "Meerlanden": {"company": "meerlanden", "post_code": "1435BX", "house_number": 650}, @@ -14,25 +20,102 @@ TEST_CASES = { SERVICE_URLS = { + "avalex": "https://wasteprod2api.ximmio.com", "meerlanden": "https://wasteprod2api.ximmio.com", "rad": "https://wasteprod2api.ximmio.com", + "westland": "https://wasteprod2api.ximmio.com", } -SERVICE_IDS = { - "acv": "f8e2844a-095e-48f9-9f98-71fceb51d2c3", - "almere": "53d8db94-7945-42fd-9742-9bbc71dbe4c1", - "areareiniging": "adc418da-d19b-11e5-ab30-625662870761", - "avri": "78cd4156-394b-413d-8936-d407e334559a", - "bar": "bb58e633-de14-4b2a-9941-5bc419f1c4b0", - "hellendoorn": "24434f5b-7244-412b-9306-3a2bd1e22bc1", - "meerlanden": "800bf8d7-6dd1-4490-ba9d-b419d6dc8a45", - "meppel": "b7a594c7-2490-4413-88f9-94749a3ec62a", - "rad": "13a2cad9-36d0-4b01-b877-efcb421a864d", - "reinis": "9dc25c8a-175a-4a41-b7a1-83f237a80b77", - "twentemilieu": "8d97bb56-5afd-4cbc-a651-b4f7314264b4", - "waardlanden": "942abcf6-3775-400d-ae5d-7380d728b23c", - "ximmio": "800bf8d7-6dd1-4490-ba9d-b419d6dc8a45", -} +SERVICE_MAP = [ + { + "title": "ACV Group", + "url": "https://www.acv-afvalkalender.nl/", + "uuid": "f8e2844a-095e-48f9-9f98-71fceb51d2c3", + "company": "acv", + }, + { + "title": "Gemeente Almere", + "url": "https://www.almere.nl/", + "uuid": "53d8db94-7945-42fd-9742-9bbc71dbe4c1", + "company": "almere", + }, + { + "title": "Area Afval", + "url": "https://www.area-afval.nl/", + "uuid": "adc418da-d19b-11e5-ab30-625662870761", + "company": "areareiniging", + }, + { + "title": "Avalex", + "url": "https://www.avalex.nl/", + "uuid": "f7a74ad1-fdbf-4a43-9f91-44644f4d4222", + "company": "avalex", + }, + { + "title": "Avri", + "url": "https://www.avri.nl/", + "uuid": "78cd4156-394b-413d-8936-d407e334559a", + "company": "avri", + }, + { + "title": "Bar Afvalbeheer", + "url": "https://www.bar-afvalbeheer.nl/", + "uuid": "bb58e633-de14-4b2a-9941-5bc419f1c4b0", + "company": "bar", + }, + { + "title": "Gemeente Hellendoorn", + "url": "https://www.hellendoorn.nl/", + "uuid": "24434f5b-7244-412b-9306-3a2bd1e22bc1", + "company": "hellendoorn", + }, + { + "title": "Meerlanden", + "url": "https://meerlanden.nl/", + "uuid": "800bf8d7-6dd1-4490-ba9d-b419d6dc8a45", + "company": "meerlanden", + }, + { + "title": "Gemeente Meppel", + "url": "https://www.meppel.nl/", + "uuid": "b7a594c7-2490-4413-88f9-94749a3ec62a", + "company": "meppel", + }, + { + "title": "RAD BV", + "url": "https://www.radbv.nl", + "uuid": "13a2cad9-36d0-4b01-b877-efcb421a864d", + "company": "rad", + }, + { + "title": "Reinis", + "url": "https://www.reinis.nl/", + "uuid": "9dc25c8a-175a-4a41-b7a1-83f237a80b77", + "company": "reinis", + }, + { + "title": "Twente Milieu", + "url": "https://www.twentemilieu.nl/", + "uuid": "8d97bb56-5afd-4cbc-a651-b4f7314264b4", + "company": "twentemilieu", + }, + { + "title": "Waardlanden", + "url": "https://www.waardlanden.nl/", + "uuid": "942abcf6-3775-400d-ae5d-7380d728b23c", + "company": "waardlanden", + }, + { + "title": "Gemeente Westland", + "url": "https://www.gemeentewestland.nl/", + "uuid": "6fc75608-126a-4a50-9241-a002ce8c8a6c", + "company": "westland", + }, +] + + +def get_service_name_map(): + return {s["company"]: s["uuid"] for s in SERVICE_MAP} class Source: @@ -40,7 +123,7 @@ class Source: self._post_code = post_code self._house_number = house_number self._url = SERVICE_URLS.get(company, "https://wasteapi.ximmio.com") - self._company_code = SERVICE_IDS[company] + self._company_code = get_service_name_map()[company] def fetch(self): data = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/york_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/york_gov_uk.py index 77c09fa5..69d71654 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/york_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/york_gov_uk.py @@ -4,7 +4,7 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "York.gov.uk" +TITLE = "City of York Council" DESCRIPTION = "Source for York.gov.uk services for the city of York, UK." URL = "https://york.gov.uk" TEST_CASES = { @@ -12,7 +12,7 @@ TEST_CASES = { "Granary Walk, York": {"uprn": "010093236548"}, } -ICONS = { +ICON_MAP = { "REFUSE": "mdi:trash-can", "RECYCLING": "mdi:recycle", "GARDEN": "mdi:leaf", @@ -42,7 +42,7 @@ class Source: collection["date"], "%Y-%m-%dT%H:%M:%S" ).date(), t=collection["roundType"].title(), - icon=ICONS[collection["roundType"]], + icon=ICON_MAP[collection["roundType"]], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py new file mode 100644 index 00000000..5eeb113d --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py @@ -0,0 +1,54 @@ +import datetime + +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] +from waste_collection_schedule.service.ICS import ICS + +TITLE = "Abfallwirtschaft Werra-Meißner-Kreis" +DESCRIPTION = "Source for Zweckverband Abfallwirtschaft Werra-Meißner-Kreis" +URL = "https://www.zva-wmk.de/" +TEST_CASES = { + "Frankenhain": {"city": "Berkatal - Frankenhain", "street": "Teichhof"}, + "Hebenshausen": {"city": "Neu-Eichenberg - Hebenshausen", "street": "Bachstraße"}, + "Vockerode": {"city": "Meißner - Vockerode", "street": "Feuerwehr"}, +} + + +class Source: + def __init__(self, city, street): + city = city.replace("ß", "ẞ").upper().replace("ẞ", "ß") + city = city.replace(" - ", "_") + self._city = city + self._street = street + self._ics = ICS(split_at=" / ") + + def fetch(self): + today = datetime.date.today() + + entries = self._fetch_year(today.year) + if today.month == 12: + entries.extend(self._fetch_year(today.year + 1)) + + return entries + + def _fetch_year(self, year): + if year == 2022: + yearstr = "" + street = self._street.upper() + else: + yearstr = f"-{year}" + street = self._street + + params = {"city": self._city, "street": street, "type": "all", "link": "ical"} + + r = requests.get( + f"https://www.zva-wmk.de/termine/schnellsuche{yearstr}", params=params + ) + r.raise_for_status() + + dates = self._ics.convert(r.text) + + entries = [] + for d in dates: + entries.append(Collection(d[0], d[1])) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py b/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py index a00be9a0..f48928aa 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py @@ -25,6 +25,12 @@ def main(): parser.add_argument( "-i", "--icon", action="store_true", help="Show waste type icon" ) + parser.add_argument( + "-t", + "--traceback", + action="store_true", + help="Print exception information and stack trace", + ) args = parser.parse_args() # read secrets.yaml @@ -53,7 +59,7 @@ def main(): map(lambda x: x.stem, source_dir.glob("*.py")), ) - for f in files: + for f in sorted(files): # iterate through all *.py files in waste_collection_schedule/source print(f"Testing source {f} ...") module = importlib.import_module(f"waste_collection_schedule.source.{f}") @@ -73,10 +79,18 @@ def main(): replace_secret(secrets, tc) # create source - source = module.Source(**tc) try: + source = module.Source(**tc) result = source.fetch() - print(f" found {len(result)} entries for {name}") + count = len(result) + if count > 0: + print( + f" found {bcolors.OKGREEN}{count}{bcolors.ENDC} entries for {name}" + ) + else: + print( + f" found {bcolors.WARNING}0{bcolors.ENDC} entries for {name}" + ) # test if source is returning the correct date format if ( @@ -88,7 +102,7 @@ def main(): > 0 ): print( - " ERROR: source returns invalid date format (datetime.datetime instead of datetime.date?)" + f"{bcolors.FAIL} ERROR: source returns invalid date format (datetime.datetime instead of datetime.date?){bcolors.ENDC}" ) if args.list: @@ -97,8 +111,10 @@ def main(): print(f" {x.date.isoformat()}: {x.type}{icon_str}") except KeyboardInterrupt: exit() - except Exception: - print(traceback.format_exc()) + except Exception as exc: + print(f" {name} {bcolors.FAIL}failed{bcolors.ENDC}: {exc}") + if args.traceback: + print(indent(traceback.format_exc(), 4)) def replace_secret(secrets, d): @@ -116,5 +132,22 @@ def replace_secret(secrets, d): print(f"identifier '{id}' not found in {SECRET_FILENAME}") +def indent(s, count): + indent = " " * count + return "\n".join([indent + line for line in s.split("\n")]) + + +class bcolors: + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + if __name__ == "__main__": main() diff --git a/doc/contributing.md b/doc/contributing.md new file mode 100644 index 00000000..e7e1bed2 --- /dev/null +++ b/doc/contributing.md @@ -0,0 +1,285 @@ +Waste Collection Schedule logo + +# Contributing To Waste Collection Schedule + +![python badge](https://img.shields.io/badge/Made%20with-Python-orange) +![github contributors](https://img.shields.io/github/contributors/mampfes/hacs_waste_collection_schedule?color=orange) +![last commit](https://img.shields.io/github/last-commit/mampfes/hacs_waste_collection_schedule?color=orange) + +There are several ways of contributing to this project, including: + +- Providing new service providers +- Updating or improving the documentation +- Helping answer/fix any issues raised +- Join in with the Home Assistant Community discussion + +## Adding New Service Providers + +### Fork And Clone The Repository, And Checkout A New Branch + +In GitHub, navigate to the repository [homepage](https://github.com/mampfes/hacs_waste_collection_schedule). Click the `fork` button at the top-right side of the page to fork the repository. + +![fork](/images/wcs_fork_btn.png) + +Navigate to your fork's homepage, click the `code` button and copy the url. + +![code](/images/wcs_code_btn.png) + +On your local machine, open a terminal and navigate to the location where you want the cloned directory. Type `git clone` and paste in the url copied earlier. It should look something like this, but with your username replacing `YOUR-GITHUB-USERNAME`: + +```bash +git clone https://github.com/YOUR-GITHUB-USERNAME/hacs_waste_collection_schedule +``` + +Before making any changes, create a new branch to work on. + +```bash +git branch +``` + +For example, if you were adding a new provider called abc.com, you could do + +```bash +git branch adding_abc_com +``` + +For more info on forking/cloning a repository, see GitHub's [fork-a-repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo) document. + +### Files Required For A New Service Provider + +The following files need to be provided to support a new service provider: + +- A python `source script` that retrieves the collection schedule information, formats it appropriately, and has test cases that can be used to confirm functionality. +- A `source markdown (.md)` file that describes how to configure the new source and sensor, with examples. +- An updated `README.md` file containing details of the new service provider. +- An updated `info.md` file containing details of the new service provider. + +The framework contains a [test script](#test-the-new-source-file) that can be used to confirm source scripts are retrieving and returning correctly formatted waste collection schedules. + +### Python Source Script + +Create a new file in the `custom_components/waste_collection_schedule/waste_collection_schedule/source` folder. The file name should be the url of your service provider in lower case, for example `abc_com.py` for `https://www.abc.com`. + +The script should have the following general structure + +```py +import datetime +from waste_collection_schedule import Collection + +TITLE = "My Council" # Title will show up in README.md and info.md +DESCRIPTION = "Source script for abc.com" # Describe your source +URL = "https://abc.com" # Insert url to service homepage. URL will show up in README.md and info.md +TEST_CASES = { # Insert arguments for test cases to be used by test_sources.py script + "TestName1": {"arg1": 100, "arg2": "street"}, + "TestName2": {"arg1": 200, "arg2": "road"}, + "TestName3": {"arg1": 300, "arg2": "lane"} +} + +API_URL = "https://abc.com/search/" +ICON_MAP = { # Optional: Dict of waste types and suitable mdi icons + "DOMESTIC": "mdi:trash-can", + "RECYCLE": "mdi:recycle", + "ORGANIC": "mdi:leaf", +} + + +class Source: + def __init__(self, arg1, arg2): # argX correspond to the args dict in the source configuration + self._arg1 = arg1 + self._arg2 = arg2 + + def fetch(self): + + # replace this comment with + # api calls or web scraping required + # to capture waste collection schedules + # and extract date and waste type details + + entries = [] # List that holds collection schedule + + entries.append( + Collection( + date = datetime.datetime(2020, 4, 11), # Collection date + t = "Waste Type", # Collection type + icon = ICON_MAP.get("Waste Type"), # Collection icon + ) + ) + + return entries +``` + +Filtering of data for waste types or time periods is a functionality of the framework and should not be done by the source script. Therefore: + +- A source script should return all data for all available waste types. +- A source script should **not** provide options to limit the returned waste types. +- A source script should return all data for the entire time period available (including past dates if they are returned). +- A source script should **not** provide a configuration option to limit the requested time frame. + +### Service Provider Markdown File + +Create a new markdown file in the `custom_components/waste_collection_schedule/doc/source` folder. The file name should be the url of your service provider in lower case, for example `abc_com.md` for `https://www.abc.com`. + +The markdown file should have the following general structure: + +1. A description of how the source should be configured. +2. A description of the arguments required, the type, and whether they are optional/mandatory. +3. A working example (usually one of the test cases from the `.py` file) + +For example: + +**Configuration via configuration.yaml** + +```yaml +waste_collection_schedule: + sources: + - name: abc_com + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER +``` + +**Configuration Variables** + +**uprn** _(string) (required)_ : The unique 12-digit identifier for your property + +Example: + +```yaml +waste_collection_schedule: + sources: + - name: abc_com + args: + uprn: "e3850cac2d5b" +``` + +Note: Your uprn can be found on invoices from your service provider + +### Update Links in README.md and info.md + +The `README.md` file in the top level folder contains a list of supported service providers. + +The `info.md` is rendered in the HACS user interface within Home Assistant and gives potential users a summary of what the component does, and the service providers supported. + +The links in both files can be updated automatically using the script `update_docu_links.py` in the top-level directory: + +```bash +./update_docu_links.py +``` + +The script iterates through all source files and extracts some meta information like title and url. It is therefore important to set the attributes in the source file correctly. By default, the country classification is derived from the file name. If this doesn't match, the country code can be overwritten with the attribute `COUNTRY`. + +| Attribute | Type | Description | +|-|-|-| +| TITLE | String | Title of the source. Used as link title in README.md and info.md. | +| URL | String | Service provider homepage URL. The idea is to help users to identify their service provider if they search for an URL instead of a service provider name. The abbreviated domain name is therefore displayed next to the source title in README.md. | +| COUNTRY | String | [Optional] Overwrite default country code which is derived from source file name. | +| EXTRA_INFO | List of Dicts or Callable | [Optional] Used to add extra links in README.md and info.md if the source supports multiple service providers at the same time. The following keys can be added to the dict: `title`, `url`, `country`. In case a key is missing, the corresponding value from the attributes above will be used instead. | + +Examples: + +```python +# Standard case: Source supports one service provider +TITLE = "ART Trier" +URL = "https://www.art-trier.de" + +# Special case: Overwrite country code +TITLE = "RecycleSmart" +URL = "https://www.recyclesmart.com/" +COUNTRY = "au" + +# Special case: Source supports multiple service provider which should be added to README.md and info.md +TITLE = "FCC Environment" +URL = "https://fccenvironment.co.uk" +EXTRA_INFO = [ + { + "title": "Harborough District Council", + "url": "https://harborough.gov.uk" + }, + { + "title": "South Hams District Council", + "url": "https://southhams.gov.uk/" + }, +] + +# Special case: Same as before, but EXTRA_INFO created by a function from existing data +TITLE = "Bürgerportal" +URL = "https://www.c-trace.de" +def EXTRA_INFO(): + return [ { "title": s["title"], "url": s["url"] } for s in SERVICE_MAP ] +``` + +### Test The New Source File + +Debugging a source script within Home Assistant is not recommended. Home Assistant's start-up process is too slow for fast debugging cycles. To help with debugging/troubleshooting, the Waste Collection Schedule framework contains a command line script that can be used to test source scripts. The script iterates through the `test cases` defined in the source script passing each set of arguments to the source script and prints the results. + +The script supports the following options: + +| Option | Argument | Description | +|--------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| `-s` | SOURCE | [Source name](https://github.com/mampfes/hacs_waste_collection_schedule#source-configuration-variables) (source file name without ending `.py`) | +| `-l` | - | List all found dates. | +| `-i` | - | Add icon name to output. Only effective together with `-l`. | +| `-t` | - | Show extended exception info and stack trace. | + +For debugging purposes of a single source, it is recommended to use the `-s SOURCE` option. If used without any arguments provided, the script tests every script in the `custom_components/waste_collection_schedule/waste_collection_schedule/source` folder and prints the number of found entries for every test case. + +To use it: + +1. Navigate to the `custom_components/waste_collection_schedule/waste_collection_schedule/test/` directory +2. Confirm the `test_sources.py` script is present +3. Execute the test script. For example, testing the abfall_io.py source script would be: + + ```bash + test_sources.py -s abfall_io + ``` + +4. Confirm the results returned match expectation. For example, testing abfall_io returns: + + ```text + Testing source abfall_io ... + found 285 entries for Waldenbuch + found 58 entries for Landshut + found 109 entries for Schoenmackers + found 3 entries for Freudenstadt + found 211 entries for Ludwigshafen am Rhein + found 119 entries for Traunstein + found 287 entries for Thalheim + ``` + +5. To view individual date entries and assigned icons, use the `-i -l` arguments, for example: + + ```bash + test_sources.py -s richmondshire_gov_uk -i -l + Testing source richmondshire_gov_uk ... + found 53 entries for test 1 + 2023-01-02: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-01-07: 55L RECYCLING BOX [mdi:recycle] + 2023-01-13: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-01-20: 55L RECYCLING BOX [mdi:recycle] + 2023-01-27: 240L GREY RUBBISH BIN [mdi:trash-can] + ... + 2023-12-01: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-12-08: 55L RECYCLING BOX [mdi:recycle] + 2023-12-15: 240L GREY RUBBISH BIN [mdi:trash-can] + ``` + +### Sync Branch and Create A Pull Request + +Having completed your changes, sync your local branch to your GitHub repo, and then create a pull request. When creating a pull request, please provide a meaningful description of what the pull request covers. Ideally it should cite the service provider, confirm the .py, .md, README and info.md files have all been updated, and the output of the test_sources.py script demonstrating functionality. Once submitted a number of automated tests are run against the updated files to confirm they can be merged into the master branch. Note: Pull requests from first time contributors also undergo a manual code review before a merge confirmation in indicated. + +Once a pull request has been merged into the master branch, you'll receive a confirmation message. At that point you can delete your branch if you wish. + +## Update Or Improve The Documentation + +Non-code contributions are welcome. If you find typos, spelling mistakes, or think there are other ways to improve the documentation please submit a pull request with updated text. Sometimes a picture paints a thousand words, so if a screenshots would better explain something, those are also welcome. + +## Help Answer/Fix Issues Raised + +![GitHub issues](https://img.shields.io/github/issues-raw/mampfes/hacs_waste_collection_schedule?color=orange) + +Open-source projects are always a work in progress, and [issues](https://github.com/mampfes/hacs_waste_collection_schedule/issues) arise from time-to-time. If you come across a new issue, please raise it. If you have a solution to an open issue, please raise a pull request with your solution. + +## Join The Home Assistant Community Discussion + +![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) + +The main discussion thread on Home Assistant's Community forum can be found [here](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). diff --git a/doc/faq.md b/doc/faq.md new file mode 100644 index 00000000..134bcf6a --- /dev/null +++ b/doc/faq.md @@ -0,0 +1,287 @@ + + +Waste Collection Schedule logo + +# Frequently Asked Questions, or "How Do I ...?" + +
+How do I format dates? +

+ +Use [strftime](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) in `value_template` or `date_template`: + +```yaml +# returns "20.03.2020" +value_template: '{{value.date.strftime("%d.%m.%Y")}}' +date_template: '{{value.date.strftime("%d.%m.%Y")}}' + +# returns "03/20/2020" +value_template: '{{value.date.strftime("%m/%d/%Y")}}' +date_template: '{{value.date.strftime("%m/%d/%Y")}}' + +# returns "Fri, 03/20/2020" +value_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' +date_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' +``` + +

+
+
+How do I show the number of days to the next collection? +

+ +Set `value_template` within the sensor configuration: + +```yaml +value_template: 'in {{value.daysTo}} days' +``` + +

+
+
+How do I show Today / Tomorrow instead of in 0 / 1 days? +

+ +Set `value_template` within the sensor configuration: + +```yaml +# returns "Today" if value.daysTo == 0 +# returns "Tomorrow" if value.daysTo == 1 +# returns "in X days" if value.daysTo > 1 +value_template: '{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}' +``` + +

+
+ +
+How do I join waste types in a value_template? +

+ +Use the `join` filter: + +```yaml +# returns "Garbage, Recycle" +value_template: '{{value.types|join(", ")}}' + +# returns "Garbage+Recycle" +value_template: '{{value.types|join("+")}}' +``` + +Note: If you don't specify a `value_template`, waste types will be joined using the `separator` configuration variable. +

+
+ +
+How do I setup a sensor which shows only the days to the next collection? +

+ +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.daysTo}}' +``` + +

+
+ +
+How do I setup a sensor which shows only the date of the next collection? +

+ +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.date.strftime("%m/%d/%Y")}}' +``` + +

+
+ +
+How do I configure a sensor which shows only the waste type of the next collection? +

+ +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.types|join(", ")}}' +``` + +

+
+ +
+How do I configure a sensor to show only collections of a specific waste type? + +

+ +Set `types` within the sensor configuration: + +```yaml +sensor: + - platform: waste_collection_schedule + name: next_garbage_collection + types: + - Garbage + + - platform: waste_collection_schedule + name: next_recycle_collection + types: + - Recycle +``` + +Note: If you have set an alias for a waste type, you must use the alias name. +

+
+ +
+How can I rename an waste type? +

+ +Set `alias` in the customize section of a source: + +```yaml +waste_collection_schedule: + sources: + - name: NAME + customize: + - type: Very long garbage name + alias: Garbage + - type: Very long recycle name + alias: Recycle +``` + +

+
+ +
+How can I hide a waste type I don't want to see? +

+ +Set `show` configuration variable to *false* in the customize section of a source: + +```yaml +waste_collection_schedule: + sources: + - name: NAME + customize: + - type: Unwanted Waste Type + show: false +``` + +

+
+ +
+How do I show a coloured Lovelace card depending on the due date? +

+ +You can use [Button Card](https://github.com/custom-cards/button-card) to create a coloured Lovelace cards: + +![Button Card](/images/button-cards.png) + +```yaml +# configuration.yaml +sensor: + - platform: waste_collection_schedule + name: MyButtonCardSensor + value_template: '{{value.types|join(", ")}}|{{value.daysTo}}|{{value.date.strftime("%d.%m.%Y")}}|{{value.date.strftime("%a")}}' +``` + +```yaml +# button-card configuration +type: 'custom:button-card' +entity: sensor.mybuttoncardsensor +layout: icon_name_state2nd +show_label: true +label: | + [[[ + var days_to = entity.state.split("|")[1] + if (days_to == 0) + { return "Today" } + else if (days_to == 1) + { return "Tomorrow" } + else + { return "in " + days_to + " days" } + ]]] +show_name: true +name: | + [[[ + return entity.state.split("|")[0] + ]]] +state: + - color: red + operator: template + value: '[[[ return entity.state.split("|")[1] == 0 ]]]' + - color: orange + operator: template + value: '[[[ return entity.state.split("|")[1] == 1 ]]]' + - value: default +``` + +

+
+ +
+Can I also use the Garbage Collection Card instead? +

+ +Yes, the [Garbage Collection Card](https://github.com/amaximus/garbage-collection-card) can also be used with *Waste Collection Schedule*: + +```yaml +# configuration.yaml +sensor: + - platform: waste_collection_schedule + name: garbage_days + details_format: appointment_types + value_template: "{{ value.daysTo }}" + types: + - Garbage + + - platform: template + sensors: + garbage: + value_template: > + {% if states('sensor.garbage_days')|int > 2 %} + 2 + {% else %} + {{ states('sensor.garbage_days')|int }} + {% endif %} + attribute_templates: + next_date: "{{ state_attr('sensor.garbage_days', 'Garbage') }}" + days: "{{ states('sensor.garbage_days')|int }}" +``` + +```yaml +# garbage-collection-card configuration +entity: sensor.garbage +type: 'custom:garbage-collection-card' +``` + +

+
+ +
+How can I sort waste type specific entities? +

+ +Prerequisites: You already have dedicated sensors per waste type and want to show the sensor with the next collection in a Lovelace card. + +Add `add_days_to: True` to the configuration of all sensors you want to sort. This will add the attribute `daysTo` which can be used by e.g. [auto-entities](https://github.com/thomasloven/lovelace-auto-entities) to sort entities by day of next collection. +

+
diff --git a/doc/installation.md b/doc/installation.md new file mode 100644 index 00000000..0e35427e --- /dev/null +++ b/doc/installation.md @@ -0,0 +1,154 @@ +Waste Collection Schedule logo + +# Installation + +## Automated Installation Using HACS + +![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) +![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) + +The `Waste Collection Schedule` component can be installed via [HACS](https://hacs.xyz/). This allows you to be notified of any updates or new releases of the component. + +After installing HACS: + +1. Visit the HACS `Integrations` panel in Home Assistant. +2. Click `Explore & Download Repositories`. +3. Search for `Waste Collection Schedule`. +4. Click on the `Waste Collection Schedule` entry. +5. Click on `Download` to copy the relevant files to your `config/custom_components/` directory. +6. [Configure your waste collection source(s)](#configuring-waste-collection-schedules). +7. [Configure your waste collection sensor(s)](#configuring-waste-collection-schedules). +8. Restart Home Assistant. + +## Manual Installation + +1. Navigate to the [waste_collection_schedule](https://github.com/mampfes/hacs_waste_collection_schedule/tree/master/custom_components) directory. +2. Copy the `waste_collection_schedule` folder (including all files and subdirectories) to your Home Assistant `config/custom_components/` directory. +3. [Configure your waste collection source(s)](#configuring-waste-collection-schedules). +4. [Configure your waste collection sensor(s)](#configuring-waste-collection-schedules). +5. Restart Home Assistant. + +# Configuring Waste Collection Schedules + +To use Waste Collection Schedules, additional entries need to be made in your `configuration.yaml` file. The required entries are: + +1. Configuring source(s) + + For each service provider, a source has to be added to the configuration. The source takes care of the arguments required to get the correct information from the service provider's web page, e.g. district, city, street, house number, etc. + + If you have to fetch data from multiple service providers, you have to add multiple sources. You can also add the same service provider multiple times. This only makes sense if you use it with different arguments, e.g. you are looking to display waste collection schedules for multiple districts served by the same provider. + +2. Configuring sensor(s) + + Sensors are used to visualize the retrieved information, e.g. waste type, next collection date, or number of days to next collection. The sensor state (which can be shown in a Lovelace/Mushroom cards) can be customized using templates. For example, you can display the collection type only, or the next collection date, or a combination of all available information. + + You can also add multiple sensors per source if you are going to display the information in separate entities. For example, if you want each waste type to have its own entity, you can add one sensor per collection type. + +## Configuring Source(s) + +```yaml +waste_collection_schedule: + sources: + - name: SOURCE + args: + arg1: ARG1 + arg2: ARG2 + arg3: ARG3 + customize: + - type: TYPE + alias: ALIAS + show: SHOW + icon: ICON + picture: PICTURE + use_dedicated_calendar: USE_DEDICATED_CALENDAR + dedicated_calendar_title: DEDICATED_CALENDAR_TITLE + calendar_title: CALENDAR_TITLE + fetch_time: FETCH_TIME + random_fetch_time_offset: RANDOM_FETCH_TIME_OFFSET + day_switch_time: DAY_SWITCH_TIME + separator: SEPARATOR +``` + +| Parameter | Type | Requirement | Description | +|-----|-----|-----|-----| +| sources: | | required | Contains information for the service provider being used | +| name: | string | required | name of the service provider source to use. Should be the same as the source filename, but without the `.py` extension. See the [README](/README.md#supported-service-providers) for supported service providers | +| args: | various | required | source-specific arguments provided to service provider to unambiguously identify the collection schedule to return. Depending on the service provider, some arguments may be mandatory, and some may be optional. See individual sources for more details | +| customize: | | optional | Can be used to customise data retrieved from a source | +| type: | string | required | The identity of the waste type as returned from the source | +| alias: | string | optional | A more readable, or user-friendly, name for the type of waste being collected. Default is `None` | +| show: | boolean | optional | Show (`True`) or hide (`False`) collections of this specific waste type. Default is `True` | +| icon: | string | optional | Icon to use for this specific waste type. Icons from the Home Assistant mdi icon set can be used. Default is `None`. | +| picture: | string | optional | string representation of the path to a picture used to represent this specific waste type. Default is `None` | +| use_dedicated_calendar: | boolean | optional | Creates a calendar dedicated to this specific waste type. Default is `False` | +| dedicated_calendar_title: | string | optional | A more readable, or user-friendly, name for this specific waste calendar object. If nothing is provided, the name returned by the source will be used | +| fetch_time: | time | optional | representation of the time of day in "HH:MM" that Home Assistant polls service provider for latest collection schedule. If no time is provided, the default of "01:00" is used | +| random_fetch_time_offset: | int | optional | randomly offsets the `fetch_time` by up to _int_ minutes. Can be used to distribute Home Assistant fetch commands over a longer time frame to avoid peak loads at service providers | +| day_switch_time: | time | optional | time of the day in "HH:MM" that Home Assistant dismisses the current entry and moves to the next entry. If no time if provided, the default of "10:00" is used. | +| separator: | string | optional | Used to join entries if the multiple values for a single day are returned by the source. If no value is entered, the default of ", " is used | + +## Configuring source sensor(s) + +Add the following lines to your `configuration.yaml` file: + +```yaml +sensor: + - platform: waste_collection_schedule + source_index: SOURCE_INDEX + name: NAME + details_format: DETAILS_FORMAT + count: COUNT + leadtime: LEADTIME + value_template: VALUE_TEMPLATE + date_template: DATE_TEMPLATE + add_days_to: ADD_DAYS_TO + types: + - Waste Type 1 + - Waste Type 2 +``` + +| Parameter | Type | Requirement | Description | +|--|--|--|--| +| platform | | required | waste_collection_schedule | +| source_index | int | optional | Used to assign a sensor to a specific source. Only needed if multiple sources are defined. The first source defined is source_index 0, the second source_index 1, etc. If no value is supplied, the default of 0 is used | +| name | string | required | The name Home Assistant used for this sensor | +| details_format | string | optional | Specifies the format used to display info in Home Assistant's _more info_ pop-up. Valid values are: `"upcoming"`, `"appointment_types"` and `"generic"`. If no value is supplied, the default of "upcoming" is used. See [options for details_format](#options-for-details_format-parameter) for more details | +| count | int | optional | Limits Home Assistant's _more info_ popup to displaying the next _int_ collections | +| leadtime | int | optional | Limits Home Assistant's _more info_ popup to only displaying collections happening within the next _leadtime_ days| +| value_template | string | optional | Uses Home Assistant templating to format the state information of an entity. See [template variables](#template-variables-for-value_template-and-date_template-parameters) for further details | +| date_template | string | optional | Uses Home Assistant templating to format the dates appearing within the _more info_ popup information of an entity. See [template variables](#template-variables-for-value_template-and-date_template-parameters) for further details | +| add_days_to | boolean | optional | Adds a `daysTo` attribute to the source entity state containing the number of days to the next collection | +| types | list of strings | optional | Used to filter waste types. The sensor will only display collections matching these waste types | + +## Options for _details_format_ parameter + +Possible choices: + +| upcoming | appointment_types | generic | +|--|--|--| +| shows a list of upcoming collections |shows a list of waste types and their next collection date | provides all attributes as generic Python data types. | +| ![Upcoming](/images/more-info-upcoming.png) | ![Waste Types](/images/more-info-appointment-types.png) | ![Generic](/images/more-info-generic.png) | + +## Template variables for _value_template_ and _date_template_ parameters + +The following variables can be used within `value_template` and `date_template`: + +| Variable | Description | Type |Comments | +|--|--|--|--| +| `value.date` | Collection date | [datetime.date](https://docs.python.org/3/library/datetime.html#datetime.date) | Use [strftime](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) to format the output | +| `value.daysTo` | Days to collection | int | 0 = today, 1 = tomorrow, etc | +| `value.types` | Waste types | list of strings | Use `join` filter to join types | + +## HomeAssistant Service to manually trigger update + +If you want to trigger a manual update of the sources, you can call the service: + +`waste_collection_schedule.fetch_data` + +Normally the configuration option 'fetch_time' is used to do this periodically. + +## Further Help + +For a full example, see [custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py](/custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py). + +For other examples on how to configure source(s) and sensor(s), see the [FAQ](/doc/faq.md). diff --git a/doc/licence.md b/doc/licence.md new file mode 100644 index 00000000..5e5cb598 --- /dev/null +++ b/doc/licence.md @@ -0,0 +1,23 @@ +Waste Collection Schedule logo + +# MIT License + +Copyright (c) 2020 Steffen Zimmermann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/online.md b/doc/online.md new file mode 100644 index 00000000..beed334a --- /dev/null +++ b/doc/online.md @@ -0,0 +1,25 @@ + + + +Waste Collection Schedule logo + + +# Waste Collection Schedule Online + + +## Community Forum + +![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) + +The main discussion thread on Home Assistant's Community forum can be found [here](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). + +## YouTube + +There are some videos on YouTube: + +- [Bunte Mülltonnenerinnerung mit Home Assistant](https://youtu.be/MzQgARDvRww) (German language) +- [Abfall Kalender in Home Assistant mit Erinnerung in Home Assistant](https://youtu.be/aCKLKGYiA7w) (German language) + +Note: These videos were **not** created by the developer of this component and therefore may be outdated. If you have questions about this component, please create an issue here on GitHub. Do not ask your question in the YouTube comments because any answers may not be correct. diff --git a/doc/page_template.md b/doc/page_template.md new file mode 100644 index 00000000..2965be78 --- /dev/null +++ b/doc/page_template.md @@ -0,0 +1,28 @@ + + + +Waste Collection Schedule logo + + +# PAGE TITLE GOES HERE + + +Asperiores et ratione similique sapiente impedit fuga. Ullam nihil cupiditate ut aliquam non dolorem qui. Rerum ipsa esse amet ipsam omnis et aut laboriosam. Quo dolorem veniam eius doloribus sint aut. Minus placeat quis corporis. Ipsam soluta quidem reprehenderit aspernatur sit sed. + +## SOME SUB-TITLE + +Repudiandae sequi sint tenetur quia perferendis quod amet nulla. Eum sed id quam nobis modi aut nulla libero. Veniam accusantium quia asperiores expedita. + +### A SUB-SUB-TITLE + +Officiis iure et et et. Recusandae quia hic est in in exercitationem est praesentium. Alias dolores adipisci eum. + +### ANOTHER SUB-SUB-TITLE + +Velit unde voluptatem quo nisi voluptatum doloribus beatae. Perferendis voluptatem eos ut et quidem culpa. Accusantium consequatur necessitatibus non. + +## AND ANOTHER SUB-TITLE + +Vitae animi tempora voluptatem doloribus est voluptate qui. Nihil quia numquam voluptates. Aut saepe quia doloribus. Quidem ea non nihil sit cum. Voluptates aperiam quod molestiae non et velit dolorem eos. diff --git a/doc/source/a_region_ch.md b/doc/source/a_region_ch.md index 73d9d3fa..1f0ce6d5 100644 --- a/doc/source/a_region_ch.md +++ b/doc/source/a_region_ch.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**municipality**
+**municipality** *(string) (required)* -**district**
+**district** *(string) (optional)* Some municipalities (like Rorschach) are separated into districts for several waste types (e.g. `Hauskehricht` and `Papier, Karton`). diff --git a/doc/source/abfall_io.md b/doc/source/abfall_io.md index 019c647d..cb89caf2 100644 --- a/doc/source/abfall_io.md +++ b/doc/source/abfall_io.md @@ -22,22 +22,22 @@ waste_collection_schedule: ### Configuration Variables -**key**
+**key** *(hash) (required)* -**f_id_kommune**
+**f_id_kommune** *(integer) (required)* -**f_id_bezirk**
+**f_id_bezirk** *(integer) (optional)* -**f_id_strasse**
+**f_id_strasse** *(integer) (required)* -**f_id_strasse_hnr**
+**f_id_strasse_hnr** *(string) (optional)* -**f_abfallarten**
+**f_abfallarten** *(list of integer) (optional)* ## Example diff --git a/doc/source/abfall_neunkirchen_siegerland_de.md b/doc/source/abfall_neunkirchen_siegerland_de.md index 9c60b2cb..aaca8eac 100644 --- a/doc/source/abfall_neunkirchen_siegerland_de.md +++ b/doc/source/abfall_neunkirchen_siegerland_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**strasse**
+**strasse** *(string) (required)* ## Example diff --git a/doc/source/abfall_zollernalbkreis_de.md b/doc/source/abfall_zollernalbkreis_de.md index c9536fff..4b95eba7 100644 --- a/doc/source/abfall_zollernalbkreis_de.md +++ b/doc/source/abfall_zollernalbkreis_de.md @@ -26,13 +26,13 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(integer) (optional)* -**types**
+**types** *(list of string) (required)* ## Example @@ -67,6 +67,6 @@ Another way get the source arguments is to extract the arguments from the websit 2. Enter your data, but don't click on "ICS Download" so far! 3. Open the Developer Tools (Ctrl + Shift + I) and open the `Network` tab. 4. Now click the "ICS Download" button. -5. You should see (amongst other's) one POST entry to Host https://www.abfallkalender-zak.de/ labeled `/` in the network recording. +5. You should see (amongst other's) one POST entry to Host `https://www.abfallkalender-zak.de/` labeled `/` in the network recording. 6. Select `/` on the left hand side and click on Request on the right hand side. 7. At the `Form Data` you can find the values for `city` and `street` etc.. diff --git a/doc/source/abfallnavi_de.md b/doc/source/abfallnavi_de.md index ddda77da..960cd109 100644 --- a/doc/source/abfallnavi_de.md +++ b/doc/source/abfallnavi_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**service**
+**service** *(string) (required)* -**ort**
+**ort** *(string) (required)* -**strasse**
+**strasse** *(string) (required)* -**hausnummer**
+**hausnummer** *(string) (optional)* ## Example diff --git a/doc/source/abfalltermine_forchheim_de.md b/doc/source/abfalltermine_forchheim_de.md index 71eb3e76..7bab897b 100644 --- a/doc/source/abfalltermine_forchheim_de.md +++ b/doc/source/abfalltermine_forchheim_de.md @@ -15,16 +15,15 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**area**
+**area** *(string) (required)* ### How to get the source arguments The arguments can be found on [abfalltermine-forchheim.de](https://www.abfalltermine-forchheim.de/). Search for your area. Use the part in front of the dash as `city` argument and the part behind it as `area` argument. Do not insert additional spaces. -
**Examples** Forchheim - Bamberger Straße (nördlich der Adenauerallee) @@ -33,10 +32,10 @@ Forchheim - Bamberger Straße (nördlich der Adenauerallee) city: Forchheim area: Bamberger Straße (nördlich der Adenauerallee) ``` -
+ Dormitz - Dormitz ```yaml city: Dormitz area: Dormitz -``` \ No newline at end of file +``` diff --git a/doc/source/alw_wf_de.md b/doc/source/alw_wf_de.md index ef71cb3d..eea1ce21 100644 --- a/doc/source/alw_wf_de.md +++ b/doc/source/alw_wf_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**ort**
+**ort** *(string) (required)* -**strasse**
+**strasse** *(string) (required)* ## Example @@ -34,7 +34,7 @@ waste_collection_schedule: ## How to get the source arguments -1. Go to the calendar at https://www.alw-wf.de/index.php/abfallkalender. +1. Go to the [calendar](https://www.alw-wf.de/index.php/abfallkalender). 2. Select your location in the drop down menus. - Notice: The page reloads after selecting `Ort`, so wait for that before selecting `Straße`. 3. Copy the **exact** values from the 2 drop down menus as `ort` and `strasse` in the source configuration. diff --git a/doc/source/art_trier_de.md b/doc/source/art_trier_de.md index e32706cf..c31274c9 100644 --- a/doc/source/art_trier_de.md +++ b/doc/source/art_trier_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**district**
+**district** _(string) (required)_ -**zip_code**
+**zip_code** _(string) (required)_ ## Example @@ -26,7 +26,7 @@ _(string) (required)_ ```yaml waste_collection_schedule: sources: - - name: cochem_zell_online_de + - name: art_trier_de args: district: "Wittlich, Marktplatz" zip_code: "54516" diff --git a/doc/source/ashfield_gov_uk.md b/doc/source/ashfield_gov_uk.md new file mode 100644 index 00000000..4852e217 --- /dev/null +++ b/doc/source/ashfield_gov_uk.md @@ -0,0 +1,63 @@ +# Ashfield District Council + +Support for schedules provided by [Ashfield District Council](https://www.ashfield.gov.uk/), serving Ashfield district in Nottinghshire, UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: ashfield_gov_uk + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER + post_code: POST_CODE + name: HOUSE_NAME + number: HOUSE_NUMBER +``` + +### Configuration Variables + +### Configuration Variables + +**uprn**
+*(string) (optional)* + +This is required if you do not supply any other options. (Using this removes the need to do an address look up web request) + +**name**
+*(string) (optional)* + +This is required if you supply a Postcode and do not have a house number. + +**number**
+*(string) (optional)* + +This is required if you supply a Postcode and have a house number. + +**post_code**
+*(string) (optional)* + +This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. + +#### How to find your `UPRN` +An easy way to discover your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. +Otherwise you can inspect the web requests the Ashfield District Council website makes when entering in your postcode and then selecting your address. + +## Example using UPRN +```yaml +waste_collection_schedule: + sources: + - name: ashfield_gov_uk + args: + uprn: 100032105121 +``` + +## Example using Address lookup +```yaml +waste_collection_schedule: + sources: + - name: ashfield_gov_uk + args: + post_code: "NG17 8DA" + name: "Ashfield District Council" +``` \ No newline at end of file diff --git a/doc/source/aucklandcouncil_govt_nz.md b/doc/source/aucklandcouncil_govt_nz.md index b5836373..a71cde8f 100644 --- a/doc/source/aucklandcouncil_govt_nz.md +++ b/doc/source/aucklandcouncil_govt_nz.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**area_number**
+**area_number** *(string) (required)* ## Example @@ -31,6 +31,6 @@ waste_collection_schedule: The source argument is the area number from Auckland Council site: -- Open your collection days page by entering your address [on the Auckland Council collection day finder](https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/rubbish-recycling-collection-days.aspx) -- Look for 'an' parameter in your browser URL, e.g. https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/collection-day-detail.aspx?an=12342306525 +- Open your collection days page by entering your address [on the Auckland Council collection day finder](https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/rubbish-recycling-collection-days.aspx) +- Look for 'an' parameter in your browser URL, e.g. `https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/collection-day-detail.aspx?an=12342306525` - In this example the area number is `12342306525`. diff --git a/doc/source/avl_ludwigsburg_de.md b/doc/source/avl_ludwigsburg_de.md deleted file mode 100644 index bbd7a2fc..00000000 --- a/doc/source/avl_ludwigsburg_de.md +++ /dev/null @@ -1,26 +0,0 @@ -# AVL Ludwigsburg - -Support for schedules provided by [avl-ludwigsburg.de](https://www.avl-ludwigsburg.de) located in Baden Württemberg, Germany. - -## Configuration via configuration.yaml - -```yaml -waste_collection_schedule: - sources: - - name: avl_ludwigsburg_de - args: - city: Ludwigsburg - street: Bahnhofstraße -``` - -### Configuration Variables - -**city**
-*(string) (required)* - -**street**
-*(string) (optional - depending on city)* - -## How to get the source arguments - -Check [avl-ludwigsburg.de Abfallkalender](https://www.avl-ludwigsburg.de/privatkunden/termine/abfallkalender/) if you only need the city e.g. Möglingen or if you need an additional street name e.g. in Ludwigsburg. \ No newline at end of file diff --git a/doc/source/aw_harburg_de.md b/doc/source/aw_harburg_de.md index 42b4c654..8f656507 100644 --- a/doc/source/aw_harburg_de.md +++ b/doc/source/aw_harburg_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**level_1**
+**level_1** *(string) (required)* -**level_2**
+**level_2** *(string) (required)* -**level_3**
+**level_3** *(string) (optional - depending on level_2)* ## Example @@ -36,7 +36,6 @@ waste_collection_schedule: level_2: "Evendorf" ``` - ```yaml waste_collection_schedule: sources: diff --git a/doc/source/awb_bad_kreuznach_de.md b/doc/source/awb_bad_kreuznach_de.md index 71d6c44c..538ad3f9 100644 --- a/doc/source/awb_bad_kreuznach_de.md +++ b/doc/source/awb_bad_kreuznach_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**ort**
+**ort** *(string) (required)* -**strasse**
+**strasse** *(string) (required)* -**nummer**
+**nummer** *(integer) (required)* ## Example diff --git a/doc/source/awb_es_de.md b/doc/source/awb_es_de.md index cb8b7d70..afd95786 100644 --- a/doc/source/awb_es_de.md +++ b/doc/source/awb_es_de.md @@ -15,12 +15,12 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## How to get the source arguments -Visit (Abfuhrtermine)[`https://www.awb-es.de/abfuhr/abfuhrtermine/__Abfuhrtermine.html`] and search for your address. The `city` and `street` argument should exactly match the autocomplete result. +Visit [Abfuhrtermine](`https://www.awb-es.de/abfuhr/abfuhrtermine/__Abfuhrtermine.html`) and search for your address. The `city` and `street` argument should exactly match the autocomplete result. diff --git a/doc/source/awb_oldenburg_de.md b/doc/source/awb_oldenburg_de.md index 05759f81..dfc07e34 100644 --- a/doc/source/awb_oldenburg_de.md +++ b/doc/source/awb_oldenburg_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## Example diff --git a/doc/source/awbkoeln_de.md b/doc/source/awbkoeln_de.md index 7c0ed621..e04a0caf 100644 --- a/doc/source/awbkoeln_de.md +++ b/doc/source/awbkoeln_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street_code**
+**street_code** *(string) (required)* -**building_number**
+**building_number** *(string) (required)* ## Example diff --git a/doc/source/awido_de.md b/doc/source/awido_de.md index 5501c077..24db4d3e 100644 --- a/doc/source/awido_de.md +++ b/doc/source/awido_de.md @@ -1,6 +1,6 @@ # AWIDO based services -Cubefour AWIDO is a platform for waste schedules, which has several German cities and districts as customers. The homepage of the company is https://www.awido-online.de/. +Cubefour AWIDO is a platform for waste schedules, which has several German cities and districts as customers. The homepage of the company is [https://www.awido-online.de/](https://www.awido-online.de/). ## Configuration via configuration.yaml @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**customer**
+**customer** *(string) (required)* -**city**
+**city** *(string) (required)* -**street**
+**street** *(integer) (optional, depends on customer)* -**housenumber**
+**housenumber** *(integer) (optional, depends on customer)* ## Example diff --git a/doc/source/awn_de.md b/doc/source/awn_de.md index c4b295a0..475c89ac 100644 --- a/doc/source/awn_de.md +++ b/doc/source/awn_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(integer) (required)* -**address_suffix**
+**address_suffix** *(string) (optional) (default: "")* ## Example @@ -45,4 +45,4 @@ waste_collection_schedule: ## How to get the source arguments These values are the location you want to query for. Make sure, the writing is exactly as it is on [https://www.awn-online.de/abfuhrtermine](https://www.awn-online.de/abfuhrtermine). Typos will result in an parsing error which is printed in the log. As `house_number` expects a numeric input, address suffixes have to be provided via the `address_suffix` argument. -`address_suffix` could be for example a alpha-numeric character "A" or a additional house number like "/1". \ No newline at end of file +`address_suffix` could be for example a alpha-numeric character "A" or a additional house number like "/1". diff --git a/doc/source/awr_de.md b/doc/source/awr_de.md index 4fbeaf19..397633a0 100644 --- a/doc/source/awr_de.md +++ b/doc/source/awr_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/awsh_de.md b/doc/source/awsh_de.md index 10ff6651..48ec014c 100644 --- a/doc/source/awsh_de.md +++ b/doc/source/awsh_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/banyule_vic_gov_au.md b/doc/source/banyule_vic_gov_au.md index 310ebdb7..b3a39435 100644 --- a/doc/source/banyule_vic_gov_au.md +++ b/doc/source/banyule_vic_gov_au.md @@ -1,5 +1,8 @@ # Banyule City Council +**NOTE: Source is not working any more due to anti-scraping features: +** + Support for schedules provided by [Banyule City Council](https://www.banyule.vic.gov.au/binday). This implementation is heavily based upon the Stonnington City Council parser, as both interfaces appear to use the same back-end. ## Configuration via configuration.yaml @@ -14,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (optional)* -**geolocation_id**
+**geolocation_id** *(string) (optional)* At least one argument must be provided. diff --git a/doc/source/belmont_wa_gov_au.md b/doc/source/belmont_wa_gov_au.md index e3fa379e..1f807c08 100644 --- a/doc/source/belmont_wa_gov_au.md +++ b/doc/source/belmont_wa_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source arguments -Visit the [Belmont City Council Waste and Recycling](https://www.belmont.wa.gov.au/live/at-your-place/bins,-waste-and-recycling) page and search for your address. The arguments should exactly match the results of the property. \ No newline at end of file +Visit the [Belmont City Council Waste and Recycling](https://www.belmont.wa.gov.au/live/at-your-place/bins,-waste-and-recycling) page and search for your address. The arguments should exactly match the results of the property. diff --git a/doc/source/berlin_recycling_de.md b/doc/source/berlin_recycling_de.md index 9ab0ae29..d785160b 100644 --- a/doc/source/berlin_recycling_de.md +++ b/doc/source/berlin_recycling_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**username**
+**username** *(string) (required)* -**password**
+**password** *(string) (required)* ## Example @@ -26,7 +26,7 @@ waste_collection_schedule: ```yaml waste_collection_schedule: sources: - - name: bsr_de + - name: berlin_recycling_de args: username: My User Name password: My Password diff --git a/doc/source/bielefeld_de.md b/doc/source/bielefeld_de.md index 9c0b0ef0..16854356 100644 --- a/doc/source/bielefeld_de.md +++ b/doc/source/bielefeld_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(integer) (required)* -**address_suffix**
+**address_suffix** *(string) (optional) (default: "")* ## Example @@ -38,5 +38,5 @@ waste_collection_schedule: ## How to get the source arguments -These values are the location you want to query for. Make sure, the writing is exactly as it is on [https://anwendungen.bielefeld.de/WasteManagementBielefeld/WasteManagementServlet?SubmitAction=wasteDisposalServices](https://anwendungen.bielefeld.de/WasteManagementBielefeld/WasteManagementServlet?SubmitAction=wasteDisposalServices). Typos will result in an parsing error which is printed in the log. As `house_number` expects a numeric input, address suffixes have to be provided via the `address_suffix` argument. -`address_suffix` could be for example a alpha-numeric character "A" or a additional house number like "/1". \ No newline at end of file +These values are the location you want to query for. Make sure, the writing is exactly as it is on `https://anwendungen.bielefeld.de/WasteManagementBielefeld/WasteManagementServlet?SubmitAction=wasteDisposalServices]`. Typos will result in an parsing error which is printed in the log. As `house_number` expects a numeric input, address suffixes have to be provided via the `address_suffix` argument. +`address_suffix` could be for example a alpha-numeric character "A" or a additional house number like "/1". diff --git a/doc/source/bmv_at.md b/doc/source/bmv_at.md index 3e5343cb..025156fa 100644 --- a/doc/source/bmv_at.md +++ b/doc/source/bmv_at.md @@ -20,17 +20,14 @@ Copy the values for `Ort`, `Straße` and `Hausnummer` into the configuration. Do ### Configuration Variables -**ORT**
-*(string) (required)*
-City +**ORT** +*(string) (required)* -**STRASSE**
-*(string) (required)*
-Street +**STRASSE** +*(string) (required)* -**HAUSNUMMER**
-*(string) (required)*
-Housenumber +**HAUSNUMMER** +*(string) (required)* ## Examples diff --git a/doc/source/bracknell_forest_gov_uk.md b/doc/source/bracknell_forest_gov_uk.md index 49ab4142..aa41c9fe 100644 --- a/doc/source/bracknell_forest_gov_uk.md +++ b/doc/source/bracknell_forest_gov_uk.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## Example diff --git a/doc/source/bradford_gov_uk.md b/doc/source/bradford_gov_uk.md index 10a2a003..a5a29a0c 100644 --- a/doc/source/bradford_gov_uk.md +++ b/doc/source/bradford_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/braintree_gov_uk.md b/doc/source/braintree_gov_uk.md index 7854e6de..6560384e 100644 --- a/doc/source/braintree_gov_uk.md +++ b/doc/source/braintree_gov_uk.md @@ -1,4 +1,4 @@ -# Bracknell Forest Council +# Braintree District Council Support for schedules provided by [Braintree District Council](https://www.braintree.gov.uk/xfp/form/554), serving Braintree, UK. @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## Example diff --git a/doc/source/breckland_gov_uk.md b/doc/source/breckland_gov_uk.md new file mode 100644 index 00000000..95c8b2c5 --- /dev/null +++ b/doc/source/breckland_gov_uk.md @@ -0,0 +1,54 @@ +# Breckland Council + +Support for schedules provided by [Breckland Council](https://www.breckland.gov.uk/mybreckland) + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: breckland_gov_uk + args: + postcode: POSTCODE + address: ADDRESS + prn: UPRN +``` + +### Configuration Variables + +**POSTCODE** +*(string) (optional)* + +**ADDRESS** +*(string) (optional)* + +**UPRN** +*(string) (optional)* + +## Examples + +```yaml +waste_collection_schedule: + sources: + - name: breckland_gov_uk + args: + postcode: "IP22 2LJ" + address: "glen travis" +``` + +```yaml +waste_collection_schedule: + sources: + - name: breckland_gov_uk + args: + uprn: "10011977093" +``` + +If uprn is provided, only uprn is used. Otherwise postcode and address are required. + +You can find all relevant information at [Breckland Council](https://www.breckland.gov.uk/mybreckland) homepage. Use the POSTCODE -> click find address. +Choose your address. Please only use the first part of your address. If you got an error, use less characters from address. + +## How to find your UPRN + +An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. diff --git a/doc/source/brisbane_qld_gov_au.md b/doc/source/brisbane_qld_gov_au.md index 84b0f29c..a6958f2e 100644 --- a/doc/source/brisbane_qld_gov_au.md +++ b/doc/source/brisbane_qld_gov_au.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/bsr_de.md b/doc/source/bsr_de.md index 71ab7e97..9c8ac66c 100644 --- a/doc/source/bsr_de.md +++ b/doc/source/bsr_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**abf_strasse**
+**abf_strasse** *(string) (required)* -**abf_hausnr**
+**abf_hausnr** *(string) (required)* ## Example diff --git a/doc/source/buergerportal_de.md b/doc/source/buergerportal_de.md new file mode 100644 index 00000000..340a0fd6 --- /dev/null +++ b/doc/source/buergerportal_de.md @@ -0,0 +1,75 @@ +# Bürgerportal + +Source for waste collection in multiple service areas. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: buergerportal_de + args: + operator: OPERATOR + district: DISTRICT + subdistrict: SUBDISTRICT + street: STREET_NAME + number: HOUSE_NUMBER + show_volume: SHOW_VOLUME +``` + +## Supported Operators + +- `alb_donau`: +- `biedenkopf`: +- `cochem_zell`: + +### Configuration Variables + +**operator**\ +_(string) (required)_ + +**district**\ +_(string) (required)_ + +**street**\ +_(string) (required)_ + +**number**\ +_(string|int) (required)_ + +**subdistrict**\ +_(string) (optional) (default: null)_ + +**show_volume**\ +_(boolean) (optional) (default: false)_ + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: buergerportal_de + args: + operator: cochem_zell + district: Bullay + subdistrict: Bullay + street: Layenweg + number: 3 +``` + +## How to get the source arguments + +1. Open the URL of your operator and click on the menu option `Abfuhrkalender` in the left sidebar. +2. Select your `district` (Ort). _Note_: If your district contains two values separated by a comma, you also have to specify the `subdistrict` (Ortsteil): Enter the first part into the field `district` and the second part into the field `subdistrict`. This is necessary even if your `district` and `subdistrict` have the same value (e.g., `Bullay, Bullay`). Subdistrict may only be left empty if there is no comma in the field value. +3. Select your `street` (Straße). +4. Select your `number` (Hausnummer). + +All parameters are _case-sensitive_. + +## Notes on Container Volumes + +By default, this sources does not differentiate between different container sizes. +If your operator collects large containers (1000 l) on different dates than smaller ones (e.g., 120 l or 240 l), you may set `show_volume: true` in your configuration. +If you do, the volume will be added to the waste type. +For example, the collection `Bio` with a volume of 120 l would then be shown as `Bio (120 l)`. +With this additional information, you can adjust all waste collections to your needs by making use of a source's [`customize` option](../installation.md#configuring-sources). diff --git a/doc/source/c_trace_de.md b/doc/source/c_trace_de.md index 39d23bc3..20c66d44 100644 --- a/doc/source/c_trace_de.md +++ b/doc/source/c_trace_de.md @@ -50,4 +50,17 @@ This source requires the name of a `service` which is specific to your municipal |Municipality|service| |-|-| |Bremen|`bremenabfallkalender`| -|AWB Landkreis Augsburg|`augsburglandkreis`| \ No newline at end of file +|AWB Landkreis Augsburg|`augsburglandkreis`| +|WZV Kreis Segeberg|`segebergwzv-abfallkalender`| + +## Tip + +If your waste-service has an online-tool where you can get an ical or CSV-File, you can extract the needed `service` from the URL of the files. +![image](https://user-images.githubusercontent.com/2480235/210091450-663907b0-6a9c-45b4-b0ae-00110896bb08.png) + + +Link for above image: https://web.c-trace.de/segebergwzv-abfallkalender/(S(ebi2zcbvfeqp0za3ofnepvct))/abfallkalender/cal/2023?Ort=Bad%20Segeberg&Strasse=Am%20Wasserwerk&Hausnr=2&abfall=0|1|2|3|4|5|6|7| + +From this Link you can extract the following parameters: + +web.c-trace.de/`service`/some-id/abfallkalender/cal/year?Ort=`ort`&Strasse=`strasse`&Hausnr=`hausnummer`... diff --git a/doc/source/cambridge_gov_uk.md b/doc/source/cambridge_gov_uk.md index 3fe67289..9c9fa50c 100644 --- a/doc/source/cambridge_gov_uk.md +++ b/doc/source/cambridge_gov_uk.md @@ -16,13 +16,12 @@ waste_collection_schedule: ### Configuration Variables -**POST_CODE**
+**POST_CODE** *(string) (required)* -**NUMBER**
+**NUMBER** *(string) (required)* - ## Example ```yaml diff --git a/doc/source/campbelltown_nsw_gov_au.md b/doc/source/campbelltown_nsw_gov_au.md index e9ce6941..c66d42ec 100644 --- a/doc/source/campbelltown_nsw_gov_au.md +++ b/doc/source/campbelltown_nsw_gov_au.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/canadabay_nsw_gov_au.md b/doc/source/canadabay_nsw_gov_au.md index 036bb409..57db14e1 100644 --- a/doc/source/canadabay_nsw_gov_au.md +++ b/doc/source/canadabay_nsw_gov_au.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/canterbury_gov_uk.md b/doc/source/canterbury_gov_uk.md index 8bae0d02..09e07912 100644 --- a/doc/source/canterbury_gov_uk.md +++ b/doc/source/canterbury_gov_uk.md @@ -16,13 +16,12 @@ waste_collection_schedule: ### Configuration Variables -**POST_CODE**
+**POST_CODE** *(string) (required)* -**NUMBER**
+**NUMBER** *(string) (required)* - ## Examples ```yaml @@ -33,7 +32,7 @@ waste_collection_schedule: post_code: "ct68ru" number: "63" ``` - + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/ccc_govt_nz.md b/doc/source/ccc_govt_nz.md index 324e0f6d..1e53a9e0 100644 --- a/doc/source/ccc_govt_nz.md +++ b/doc/source/ccc_govt_nz.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Bin Names example - Garbage, Recycle & Organic diff --git a/doc/source/cheshire_east_gov_uk.md b/doc/source/cheshire_east_gov_uk.md index 3f2c899c..dbf80b65 100644 --- a/doc/source/cheshire_east_gov_uk.md +++ b/doc/source/cheshire_east_gov_uk.md @@ -23,15 +23,15 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* --- or --- -**postcode**
+**postcode** *(string) (required)* -**name_number**
+**name_number** *(string) (required)* ## Examples diff --git a/doc/source/chesterfield_gov_uk.md b/doc/source/chesterfield_gov_uk.md index eb5e4e30..2f62038e 100644 --- a/doc/source/chesterfield_gov_uk.md +++ b/doc/source/chesterfield_gov_uk.md @@ -14,12 +14,13 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* This is required to unambiguously identify the property. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -30,4 +31,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/cochem_zell_online_de.md b/doc/source/cochem_zell_online_de.md deleted file mode 100644 index 8b2ee087..00000000 --- a/doc/source/cochem_zell_online_de.md +++ /dev/null @@ -1,34 +0,0 @@ -# Cochem-Zell - -Support for schedules provided by . - -## Configuration via configuration.yaml - -```yaml -waste_collection_schedule: - sources: - - name: cochem_zell_online_de - args: - district: DISTRICT -``` - -### Configuration Variables - -**district**
-_(string) (required)_ - -## Example - -```yaml -waste_collection_schedule: - sources: - - name: cochem_zell_online_de - args: - district: "Zell-Stadt" -``` - -## How to get the source arguments - -1. Open . -2. Click on the selection field named `Ortsteil`. -3. Enter the name _with correct capitalization_ in the argument `district`. diff --git a/doc/source/colchester_gov_uk.md b/doc/source/colchester_gov_uk.md index 4e10beb7..ca5682b2 100644 --- a/doc/source/colchester_gov_uk.md +++ b/doc/source/colchester_gov_uk.md @@ -14,10 +14,11 @@ waste_collection_schedule: ### Configuration Variables -**llpgid**
+**llpgid** *(string) (required)* #### How to find your `llpgid` code + The LLPDID code can be found in the URL after entering your postcode and selecting your address on the [Colchester Your recycling calendar page](https://www.colchester.gov.uk/your-recycling-calendar/). The URL in your browser URL bar should look like `https://www.colchester.gov.uk/your-recycling-calendar/?start=true&step=1&llpgid=1197e725-3c27-e711-80fa-5065f38b5681`. ## Example @@ -28,4 +29,4 @@ waste_collection_schedule: - name: colchester_gov_uk args: llpgid: "1197e725-3c27-e711-80fa-5065f38b5681" -``` \ No newline at end of file +``` diff --git a/doc/source/cornwall_gov_uk.md b/doc/source/cornwall_gov_uk.md index 9350dbb4..b1799e4f 100644 --- a/doc/source/cornwall_gov_uk.md +++ b/doc/source/cornwall_gov_uk.md @@ -17,13 +17,13 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** *(string) (optional)* -**hournameornumber**
+**hournameornumber** *(string) (optional)* -**uprn**
+**uprn** *(string) (optional)* Either the postcode and housenameornumber or the UPRN should be supplied in the arguments @@ -42,4 +42,4 @@ waste_collection_schedule: ## How to find your UPRN -An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. +An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. diff --git a/doc/source/data_umweltprofis_at.md b/doc/source/data_umweltprofis_at.md index 15bd4143..3d7f78b3 100644 --- a/doc/source/data_umweltprofis_at.md +++ b/doc/source/data_umweltprofis_at.md @@ -4,8 +4,7 @@ Support for schedules provided by [Umweltprofis.at](https://www.umweltprofis.at) ## Configuration via configuration.yaml -You need to generate your personal XML link before you can start using this source. Go to [https://data.umweltprofis.at/opendata/AppointmentService/index.aspx](https://data.umweltprofis.at/opendata/AppointmentService/index.aspx) and fill out the form. At the end -at step 6 you get a link to a XML file. Copy this link and paste it to configuration.yaml as seen below. +You need to generate your personal XML link before you can start using this source. Go to [https://data.umweltprofis.at/opendata/AppointmentService/index.aspx](https://data.umweltprofis.at/opendata/AppointmentService/index.aspx) and fill out the form. At the end at step 6 you get a link to a XML file. Copy this link and paste it to configuration.yaml as seen below. ```yaml waste_collection_schedule: @@ -17,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**xmlurl**
+**xmlurl** *(URL) (required)* ## Example @@ -28,4 +27,4 @@ waste_collection_schedule: - name: data_umweltprofis_at args: xmlurl: https://data.umweltprofis.at/opendata/AppointmentService/AppointmentService.asmx/GetTermineForLocationSecured?Key=TEMPKeyabvvMKVCic0cMcmsTEMPKey&StreetNr=124972&HouseNr=Alle&intervall=Alle -``` \ No newline at end of file +``` diff --git a/doc/source/derby_gov_uk.md b/doc/source/derby_gov_uk.md index ed502ff2..69cd8763 100644 --- a/doc/source/derby_gov_uk.md +++ b/doc/source/derby_gov_uk.md @@ -6,6 +6,7 @@ city of Derby, UK. ## Configuration via configuration.yaml (recommended) + ```yaml waste_collection_schedule: sources: @@ -27,13 +28,13 @@ waste_collection_schedule: ### Configuration Variables -**premises_id**
+**premises_id** *(int) (required if post_code not provided)* -**post_code**
+**post_code** *(string) (required if premises_id not provided)* -**house_number**
+**house_number** *(int) (required if premises_id not provided)* ## Example diff --git a/doc/source/egn_abfallkalender_de.md b/doc/source/egn_abfallkalender_de.md index 304ed574..85c13ca8 100644 --- a/doc/source/egn_abfallkalender_de.md +++ b/doc/source/egn_abfallkalender_de.md @@ -19,19 +19,19 @@ The arguments can be found above the calendar after generating one [here](https: ### Configuration Variables -**city**
+**city** *(string) (required)* City, extracted from the displayed address. -**district**
+**district** *(string) (required)* District, extracted from the displayed address. -**street**
+**street** *(string) (required)* Street, extracted from the displayed address. -**housenumber**
+**housenumber** *(string) (required)* Housenumber, extracted from the displayed address. diff --git a/doc/source/elmbridge_gov_uk.md b/doc/source/elmbridge_gov_uk.md index d714e020..845cc547 100644 --- a/doc/source/elmbridge_gov_uk.md +++ b/doc/source/elmbridge_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* This is required to unambiguously identify the property. @@ -72,7 +72,7 @@ Trying to convert all this into a schedule of dates for each specific waste col * It assumes the week-commencing dates are for the current year. * This'll cause problems in December as upcoming January collections will have been assigned dates in the past. * Some clunky logic can deal with this: - * If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occured. + * If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occurred. * If a date is more than 1 month in the past, assume it's an incorrectly assigned date and increments the year by 1. * Once that's been done, offset the week-commencing dates to match day of the week indicated for each waste collection type. diff --git a/doc/source/environmentfirst_co_uk.md b/doc/source/environmentfirst_co_uk.md index b6ef2154..35d32f5e 100644 --- a/doc/source/environmentfirst_co_uk.md +++ b/doc/source/environmentfirst_co_uk.md @@ -17,27 +17,28 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional) (preferred method)* This is required if you do not supply any other options. Using a UPRN removes the need to do an address look up using web requests. -**post_code**
+**post_code** *(string) (optional)* This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. -**number**
+**number** *(string) (optional)* This is required if you supply a Postcode and have a house number. -**name**
+**name** *(string) (optional)* This is required if you supply a Postcode and you have a house name rather than a house number. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -47,6 +48,7 @@ waste_collection_schedule: ``` ## Example using Address lookup (Postcode and house number) + ```yaml waste_collection_schedule: sources: @@ -57,6 +59,7 @@ waste_collection_schedule: ``` ## Example using Address lookup (Postcode and house name) + ```yaml waste_collection_schedule: sources: @@ -68,6 +71,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. -Otherwise you can inspect the web requests on the [Environment First](https://www.environmentfirst.co.uk/) having searched using your address details. Your UPRN is the collection of digits at the end of the URL, for example: *https://www.environmentfirst.co.uk/house.php?uprn=`100060091178`* +Otherwise you can inspect the web requests on the [Environment First](https://www.environmentfirst.co.uk/) having searched using your address details. Your UPRN is the collection of digits at the end of the URL, for example: `https://www.environmentfirst.co.uk/house.php?uprn=100060091178` diff --git a/doc/source/erlangen_hoechstadt_de.md b/doc/source/erlangen_hoechstadt_de.md index 49d21c04..909a20f9 100644 --- a/doc/source/erlangen_hoechstadt_de.md +++ b/doc/source/erlangen_hoechstadt_de.md @@ -1,5 +1,6 @@ # Erlangen-Höchstadt -Support for Landkreis [Erlangen-Höchstadt]() located in Bavaria, Germany. + +Support for Landkreis Erlangen-Höchstadt located in Bavaria, Germany. ## Configuration via configuration.yaml @@ -14,12 +15,12 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ### How to get the source arguments -Visit [erlangen-hoechstadt.de](https://www.erlangen-hoechstadt.de/aktuelles/abfallkalender/) and search for your area. Use the value from the "Ort" dropdown as `city` argument and the one from "Ortsteil/Straße" as `street`. `street` is case sensitive! \ No newline at end of file +Visit [erlangen-hoechstadt.de](https://www.erlangen-hoechstadt.de/aktuelles/abfallkalender/) and search for your area. Use the value from the "Ort" dropdown as `city` argument and the one from "Ortsteil/Straße" as `street`. `street` is case sensitive! diff --git a/doc/source/fccenvironment_co_uk.md b/doc/source/fccenvironment_co_uk.md index 99aedccc..04d67c9c 100644 --- a/doc/source/fccenvironment_co_uk.md +++ b/doc/source/fccenvironment_co_uk.md @@ -1,6 +1,9 @@ # FCC Environment -Consolidated support for schedules provided by ~60 local authorities. Currently supports [Harborough District Council](www.harborough.gov.uk) +Consolidated support for schedules provided by ~60 local authorities. Currently supports: + - [Harborough District Council](https://www.harborough.gov.uk/) + - [South Hams](https://southhams.gov.uk/) + - [West Devon](https://www.westdevon.gov.uk/) ## Configuration via configuration.yaml @@ -10,16 +13,27 @@ waste_collection_schedule: - name: fccenvironment_co_uk args: uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER + region: REGION_NAME ``` ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* This is required to unambiguously identify the property. +**region**
+*(string) (optional)* + +Defaults to `harborough`, should be one of: + - `harborough` + - `westdevon` + - `southhams` + + ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -28,6 +42,17 @@ waste_collection_schedule: uprn: 100030493289 ``` +## Example using UPRN and Region + +```yaml +waste_collection_schedule: + sources: + - name: fccenvironment_co_uk + args: + uprn: 10001326041 + region: westdevon +``` + ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/geoport_nwm_de.md b/doc/source/geoport_nwm_de.md new file mode 100644 index 00000000..80d012e3 --- /dev/null +++ b/doc/source/geoport_nwm_de.md @@ -0,0 +1,22 @@ +# Landkreis Nordwestmecklenburg + +Support for Landkreis Nordwestmecklenburg in Mecklenburg-Vorpommern, Germany. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: geoport_nwm_de + args: + district: DISTRICT +``` + +### Configuration Variables + +**district** +*(string) (required)* + +### How to get the source arguments + +Visit [geoport-nwm.de](https://www.geoport-nwm.de/de/abfuhrtermine-geoportal.html) and search for your area. Copy the value from the text input field and use it for the `district` argument. It is case sensitive. \ No newline at end of file diff --git a/doc/source/goldcoast_qld_gov_au.md b/doc/source/goldcoast_qld_gov_au.md new file mode 100644 index 00000000..d4a8f24b --- /dev/null +++ b/doc/source/goldcoast_qld_gov_au.md @@ -0,0 +1,32 @@ +# Gold Coast City Council + +Support for schedules provided by [Gold Coast City Council](https://www.goldcoast.qld.gov.au/Services/Waste-recycling/Find-my-bin-day). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: goldcoast_qld_gov_au + args: + street_address: STREET_ADDRESS +``` + +### Configuration Variables + +**street_address** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: goldcoast_qld_gov_au + args: + street_address: 6/8 Henchman Ave Miami +``` + +## How to get the source arguments + +The Gold Coast API allows for a fuzzy search, so no need to get overly complicated with the address. However, you can visit the [Gold Coast City Council](https://www.goldcoast.qld.gov.au/Services/Waste-recycling/Find-my-bin-day) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. diff --git a/doc/source/grafikai_svara_lt.md b/doc/source/grafikai_svara_lt.md index 3b256b08..40973bae 100644 --- a/doc/source/grafikai_svara_lt.md +++ b/doc/source/grafikai_svara_lt.md @@ -19,19 +19,19 @@ waste_collection_schedule: ### Configuration Variables -**region**
+**region** *(string) (required)* -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* -**district**
+**district** *(string) (optional)* -**waste_object_ids**
+**waste_object_ids** *(list) (optional)* ## Example diff --git a/doc/source/guildford_gov_uk.md b/doc/source/guildford_gov_uk.md index 74b4b39b..83cf428a 100644 --- a/doc/source/guildford_gov_uk.md +++ b/doc/source/guildford_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example @@ -28,4 +28,5 @@ waste_collection_schedule: ``` ## How to get the source argument -# Find the UPRN of your address using https://www.findmyaddress.co.uk/search + +Find the UPRN of your address using [https://www.findmyaddress.co.uk/search](https://www.findmyaddress.co.uk/search). diff --git a/doc/source/horowhenua_govt_nz.md b/doc/source/horowhenua_govt_nz.md index 275a24d1..db7baec8 100644 --- a/doc/source/horowhenua_govt_nz.md +++ b/doc/source/horowhenua_govt_nz.md @@ -17,21 +17,20 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**town**
+**town** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example - ```yaml waste_collection_schedule: sources: diff --git a/doc/source/huntingdonshire_gov_uk.md b/doc/source/huntingdonshire_gov_uk.md index 924520de..4dcfdc01 100644 --- a/doc/source/huntingdonshire_gov_uk.md +++ b/doc/source/huntingdonshire_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/hvcgroep_nl.md b/doc/source/hvcgroep_nl.md index 4419e30b..5d55dae8 100644 --- a/doc/source/hvcgroep_nl.md +++ b/doc/source/hvcgroep_nl.md @@ -1,6 +1,6 @@ # HVCGroep -Support for schedules provided by [hvcgroep.nl](https://www.hvcgroep.nl/). +Support for schedules provided by [hvcgroep.nl](https://www.hvcgroep.nl/) and other Dutch municipalities. ## Configuration via configuration.yaml @@ -11,23 +11,66 @@ waste_collection_schedule: args: postal_code: POSTAL_CODE house_number: HOUSE_NUMBER + service: SERVICE ``` ### Configuration Variables -**postal_code**
+**postal_code** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* +**service** +*(string) (optional, default="hvcgroep")* + +Use one of the following codes as service code: + +- alphenaandenrijn +- cranendonck +- cyclusnv +- dar +- denhaag +- gad +- gemeenteberkelland +- hvcgroep +- lingewaard +- middelburgvlissingen +- mijnblink +- peelenmaas +- prezero +- purmerend +- rmn +- schouwen-duiveland +- spaarnelanden +- stadswerk072 +- sudwestfryslan +- venray +- voorschoten +- waalre +- zrd + ## Example ```yaml +# hvgroep waste_collection_schedule: sources: - name: hvcgroep_nl args: postal_code: 1713VM house_number: 1 + service: hvxgroep +``` + +```yaml +# cyclusnv +waste_collection_schedule: + sources: + - name: hvcgroep_nl + args: + postal_code: 2841ML + house_number: 1090 + service: cyclusnv ``` diff --git a/doc/source/hygea_be.md b/doc/source/hygea_be.md index 13d89b3f..14e74cfb 100644 --- a/doc/source/hygea_be.md +++ b/doc/source/hygea_be.md @@ -17,11 +17,11 @@ The arguments can be found in the URL after visiting the [the calendar](https:// ### Configuration Variables -**streetIndex**
+**streetIndex** *(int)* Street index, extracted from URL. -**cp**
+**cp** *(int)* Postal code, extracted from URL. diff --git a/doc/source/ics.md b/doc/source/ics.md index 51c08d6c..987b3537 100644 --- a/doc/source/ics.md +++ b/doc/source/ics.md @@ -6,7 +6,7 @@ This source has been successfully tested with the following service providers: ### Belgium -- [Limburg.net](https://www.limburg.net/afvalkalender) ([Example](#limburg-net)) +- [Limburg.net](https://www.limburg.net/afvalkalender) ([Example](#limburgnet)) ### Germany @@ -29,8 +29,8 @@ This source has been successfully tested with the following service providers: #### Brandenburg - - [Entsorgungsbetrieb Märkisch-Oderland](https://www.entsorgungsbetrieb-mol.de/de/tourenplaene.html) ([Example](#entsorgungsbetrieb-märkisch-oderland)) - +- [Entsorgungsbetrieb Märkisch-Oderland](https://www.entsorgungsbetrieb-mol.de/de/tourenplaene.html) ([Example](#entsorgungsbetrieb-märkisch-oderland)) + #### Hessen - [Erlensee](https://sperrmuell.erlensee.de/?type=reminder) ([Example](#erlensee)) @@ -47,7 +47,7 @@ This source has been successfully tested with the following service providers: #### Rheinland-Pfalz - [Zweckverband Abfallwirtschaft A.R.T. Trier](https://www.art-trier.de) - - Landkreis Vulkaneifel +- Landkreis Vulkaneifel #### Sachsen @@ -96,7 +96,7 @@ waste_collection_schedule: ### Configuration Variables -**url**
+**url** *(string) (optional)* URL to ICS / iCal file. File will be downloaded using a HTTP GET request. @@ -105,26 +105,42 @@ If the original url contains the current year (4 digits including century), this You have to specify either `url` or `file`! -**file**
+**file** *(string) (optional)* Local ICS / iCal file name. Can be used instead of `url` for local files. You have to specify either `url` or `file`! -**offset**
+Notes: + +- Some users have reported that on their installation, only local files below the folder `config/www` are accessible by the system. Therefore place the ics file there. +- If you are using relative paths (like in the example below), the path depends on which working directory your Home Assistant instance is running on. And this might depend on the installation method (core vs supervisor vs OS vs ...). Therefore check the log output, it tells you the current working directory. + + This example should work for HAOS based installations: + + ```yaml + # file location: config/www/calendar.ics + waste_collection_schedule: + sources: + - name: ics + args: + file: "www/calendar.ics" + ``` + +**offset** *(int) (optional, default: `0`)* Offset in days which will be added to every start time. Can be used if the start time of the events in the ICS file are ahead of the actual date. -**method**
+**method** *(string) (optional, default: `GET`)* Method to send the URL `params`. Need to be `GET` or `POST`. -**params**
+**params** *(dict) (optional, default: None)* Dictionary, list of tuples or bytes to send in the query string for the HTTP request. @@ -136,24 +152,24 @@ This gets Only used if `url` is specified, not used for `file`. -**year_field**
+**year_field** *(string) (optional, default: None)* Field in params dictionary to be replaced with current year (4 digits including century). -**regex**
+**regex** *(string) (optional, default: None)* Regular expression used to remove needless text from collection types. See also example below. -**split_at**
+**split_at** *(string) (optional, default: None)* Delimiter to split event summary into individual collection types. If your service puts multiple collections types which occur at the same day into a single event, this option can be used to separate the collection types again. -**version**
+**version** *(integer) (optional, default: 2)* Selects the underlying ICS file parser: @@ -161,7 +177,7 @@ Selects the underlying ICS file parser: - version: 1 uses `recurring_ical_events` - version: 2 uses `icalevents` -**verify_ssl**
+**verify_ssl** *(boolean) (optional, default: True)* Allows do disable SSL certificate checks in case the HTTPS server of your service provider is misconfigured and therefore doesn't send intermediate certificates. Unlike browsers, python doesn't support automatic fetching of missing intermediates. @@ -190,8 +206,7 @@ waste_collection_schedule: #### Landkreis Vulkaneifel -Go to the website: -[service provider website](https://www.art-trier.de/eo/cms?_bereich=artikel&_aktion=suche_rubrik&idrubrik=1003&_sortierung=info3_asc_info4_asc&info1=54578&info2=) +Go to the website: [art-trier.de](https://www.art-trier.de/eo/cms?_bereich=artikel&_aktion=suche_rubrik&idrubrik=1003&_sortierung=info3_asc_info4_asc&info1=54578&info2=) select your Postal code. @@ -421,12 +436,15 @@ waste_collection_schedule: ### EAW Rheingau Taunus +1. Find your ICS link via the web page +2. Remove the cHash attribute + ```yaml waste_collection_schedule: sources: - name: ics args: - url: "https://www.eaw-rheingau-taunus.de/abfallkalender/calendar.ics?streetid=1429" + url: "https://www.eaw-rheingau-taunus.de/abfallsammlung/abfuhrtermine/feed.ics?tx_vierwdeaw_garbagecalendarics%5Baction%5D=ics&tx_vierwdeaw_garbagecalendarics%5Bcontroller%5D=GarbageCalendar&tx_vierwdeaw_garbagecalendarics%5Bstreet%5D=38" split_at: "," ``` @@ -622,14 +640,20 @@ waste_collection_schedule: ``` You can also compose the URL yourself. You need the following elements for this: -1. the nis-code of your municipality: query the api with the name of your municipality;
example: https://limburg.net/api-proxy/public/afval-kalender/gemeenten/search?query=Peer -```json -[{"nisCode":"72030","naam":"Peer"}] -``` -2. the number of your street: query the api with the nis-code of your municipality and the name of your street;
example: https://limburg.net/api-proxy/public/afval-kalender/gemeente/72030/straten/search?query=Zuidervest -```json -[{"nummer":"66536","naam":"Zuidervest"}] -``` + +1. the nis-code of your municipality: query the api with the name of your municipality; example: + + ```json + [{"nisCode":"72030","naam":"Peer"}] + ``` + +2. the number of your street: query the api with the nis-code of your municipality and the name of your street +example: + + ```json + [{"nummer":"66536","naam":"Zuidervest"}] + ``` + 3. your housenumber ```yaml @@ -691,14 +715,14 @@ To use this you need to idenfify your Unique Property Reference Number (UPRN). T 1. The easiest way to discover your UPRN is by using https://www.findmyaddress.co.uk/ and entering in your address details. -Or + Or 2. By looking at the URLs generated by the South Cambs web site: - * 2.1. Go to [South Cambs Bin Collections](https://www.scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/) - * 2.2 Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. - * 2.3. Your UPRN is the collection of digits at the end of the URL, for example: *scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/#id=`10008079869`* - * 2.4. The iCal collection schedule can then be obtained using: *refusecalendarapi.azurewebsites.net/calendar/ical/`10008079869`* + 1. Go to [South Cambs Bin Collections](https://www.scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/) + 2. Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. + 3. Your UPRN is the collection of digits at the end of the URL, for example: *scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/#id=`10008079869`* + 4. The iCal collection schedule can then be obtained using: *refusecalendarapi.azurewebsites.net/calendar/ical/`10008079869`* ```yaml waste_collection_schedule: @@ -722,15 +746,16 @@ sensor: The Bromley council has a simple way to generate an iCal. All you need is the URL - * Go to [Bromley Bin Collection](https://recyclingservices.bromley.gov.uk/waste) - * Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. - * Your unique code can be found in the URL, eg: *recyclingservices.bromley.gov.uk/waste/`6261994`* - * You can either use the following link and replace your ID, or copy the link address on the "Add to you calendar" link: *https://recyclingservices.bromley.gov.uk/waste/6261994/calendar.ics* +- Go to [Bromley Bin Collection](https://recyclingservices.bromley.gov.uk/waste) +- Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. +- Your unique code can be found in the URL, eg: *recyclingservices.bromley.gov.uk/waste/`6261994`* +- You can either use the following link and replace your ID, or copy the link address on the "Add to you calendar" link: Note: - * This has been designed to break each bin collection into different sensors. - * This was created at a property that has a garden waste subscription. You may need to amit that from the code - * This display number of days until collection. Replace `value_template` with `date_template: '{{value.date.strftime("%A %d %B %Y")}}'` to display date of collection + +- This has been designed to break each bin collection into different sensors. +- This was created at a property that has a garden waste subscription. You may need to amit that from the code +- This display number of days until collection. Replace `value_template` with `date_template: '{{value.date.strftime("%A %d %B %Y")}}'` to display date of collection ```yaml #Waste Collection - London Borough of Bromley @@ -752,54 +777,7 @@ waste_collection_schedule: args: url: YOUR_URL version: 2 - -sensor: - #Food Waste - - platform: waste_collection_schedule - source_index: 0 - name: Bins - Food Waste Collection # Change this to whatever you want the UI to display, sensor name will be similar - types: - - Food Waste - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - - #Garden Waste - - platform: waste_collection_schedule - source_index: 0 - name: Bins - Garden Waste Collection # Change this to whatever you want the UI to display, sensor name will be similar - types: - - Garden Waste - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - - #Mixed Recycling - - platform: waste_collection_schedule - source_index: 0 - name: Bins - Mixed Recycling Collection # Change this to whatever you want the UI to display - types: - - Mixed Recycling - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - - #General Waste - - platform: waste_collection_schedule - source_index: 0 - name: Bins - General Waste Collection # Change this to whatever you want the UI to display - types: - - General Waste - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - - #Paper & Cardboard - - platform: waste_collection_schedule - source_index: 0 - name: Bins - Cardboard Collection # Change this to whatever you want the UI to display - types: - - Cardboard - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - -``` + ``` *** @@ -827,7 +805,7 @@ waste_collection_schedule: args: url: https://www.fes-frankfurt.de/abfallkalender/.ics split_at: " \/ " - regex: "(.*)\s+\|" + regex: "(.*)\\s+\\|" ``` -*** \ No newline at end of file +*** diff --git a/doc/source/infeo_at.md b/doc/source/infeo_at.md index 5a169f2a..b39f8577 100644 --- a/doc/source/infeo_at.md +++ b/doc/source/infeo_at.md @@ -1,6 +1,6 @@ -# INFEO based services +# INFEO based services -INFEO is a platform for waste schedules, which has several German, Austrian and Swiss cities and districts as customers. The homepage of the company is https://www.infeo.at/. +INFEO is a platform for waste schedules, which has several German, Austrian and Swiss cities and districts as customers. The homepage of the company is [https://www.infeo.at/](https://www.infeo.at/). ## Configuration via configuration.yaml @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**customer**
+**customer** *(string) (required)* -**zone**
+**zone** *(string) (required)* ## Example @@ -45,33 +45,39 @@ If your provider is also using infeo.at you can just try to use the name of your ### zone #### Bogenschuetz-Entsorgung.de -- Go to your calendar at `https://www.bogenschuetz-entsorgung.de/images/wastecal/index-zone.html`. + +- Go to your [calendar](https://www.bogenschuetz-entsorgung.de/images/wastecal/index-zone.html). - Browse through all the available years and check the naming of your desired zone and try to find what makes it unique. - Put this unique string into `zone` of your configuration. - It will just be checked if the calendar contains an entry that contains your keyword `zone`. ##### Example 1: Dettenhausen + - For 2022 it is: `Dettenhausen, Tübingen (Bebenhausen; Lustnau)` - For 2023 it is: `Dettenhausen` - Use `Dettenhausen` as zone ##### Example 2: Ofterdingen + - For 2022 it is: `Dußlingen, Ofterdingen` - For 2023 it is: `Ofterdingen` - Use `Ofterdingen` as zone ##### Example 3: Felldorf + - For 2022 it is: `Rottenburg (Bad Niedernau; Bieringen; Eckenweiler; Frommenhausen; Obernau; Schwalldorf), Starzach (Bierlingen; Börstingen; Felldorf; Sulzau; Wachendorf)` - For 2023 it is: `Starzach (Bierlingen; Börstingen; Felldorf; Sulzau; Wachendorf)` - Use `Felldorf` as zone ##### Example 4: Tübingen Innenstadt + - For 2022 it is: `Tübingen (Bezirk 4 - Innenstadt)` - For 2023 it is: `Tübingen (Bezirk 4 - Innenstadt)` - Use `Innenstadt` as zone - Do NOT use `Tübingen` as it is used multiple times! ##### Example 5: Pfäffingen + - For 2022 it is: `Tübingen (Bühl; Hirschau; Kilchberg; Unterjesingen; Weilheim), Rottenburg (Kiebingen; Wurmlingen), Ammerbuch (Pfäffingen)` - For 2023 it is: `Ammerbuch (Pfäffingen)` - Use `Pfäffingen` as zone diff --git a/doc/source/innerwest_nsw_gov_au.md b/doc/source/innerwest_nsw_gov_au.md index c44e13b4..6a5a7bac 100644 --- a/doc/source/innerwest_nsw_gov_au.md +++ b/doc/source/innerwest_nsw_gov_au.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/ipswich_qld_gov_au.md b/doc/source/ipswich_qld_gov_au.md index 198740bc..952ec443 100644 --- a/doc/source/ipswich_qld_gov_au.md +++ b/doc/source/ipswich_qld_gov_au.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**suburb**
+**suburb** *(string) (required)* ## Example diff --git a/doc/source/jumomind_de.md b/doc/source/jumomind_de.md index bff40164..f21bd7ff 100644 --- a/doc/source/jumomind_de.md +++ b/doc/source/jumomind_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**service_id**
+**service_id** *(string) (required)* -**city_id**
+**city_id** *(string) (required)* -**area_id**
+**area_id** *(string) (required)* ## Example diff --git a/doc/source/kaev_niederlausitz_de.md b/doc/source/kaev_niederlausitz_de.md index e28604b3..7c35ac3c 100644 --- a/doc/source/kaev_niederlausitz_de.md +++ b/doc/source/kaev_niederlausitz_de.md @@ -14,10 +14,9 @@ waste_collection_schedule: ### Configuration Variables -**abf_suche**
+**abf_suche** *(string) (required)* - ## Example ```yaml @@ -44,7 +43,6 @@ waste_collection_schedule: abf_suche: "Staakow" ``` - ## How to get the source arguments 1. Go to your calendar at 1. Go to your calendar at [https://www.kaev.de/Info-und-Service/Tourenplan/Tourenplan-Abfalltermine.html](https://www.kaev.de/Info-und-Service/Tourenplan/Tourenplan-Abfalltermine.html). diff --git a/doc/source/kingston_gov_uk.md b/doc/source/kingston_gov_uk.md index ca607f34..c659c73a 100644 --- a/doc/source/kingston_gov_uk.md +++ b/doc/source/kingston_gov_uk.md @@ -1,4 +1,4 @@ -# Thr Royal Borough of Kingston Council +# The Royal Borough of Kingston Council Support for schedules provided by [The Royal Borough of Kingston Council](https://kingston-self.achieveservice.com/service/in_my_area?displaymode=collections). @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example using UPRN @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source argument -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/korneuburg_stadtservice_at.md b/doc/source/korneuburg_stadtservice_at.md index 7c550765..1f23d171 100644 --- a/doc/source/korneuburg_stadtservice_at.md +++ b/doc/source/korneuburg_stadtservice_at.md @@ -16,27 +16,28 @@ waste_collection_schedule: ### Configuration Variables -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* -**teilgebiet**
+**teilgebiet** *(string) (optional)* ### How to get the source arguments + The arguments can be found on [Stadtservice Korneuburg](https://www.korneuburg.gv.at/Rathaus/Buergerservice/Muellabfuhr). Check if your address details are available on the official site. If not use something that is close by or the same region. You can enter your region number (`teilgebiet`) directly to skip the step that determines your region based on your address. Still some values need to be set for `street_name` and `street_number` which are then not used. -
## Example **First Entry** + ```yaml waste_collection_schedule: sources: @@ -45,7 +46,9 @@ waste_collection_schedule: street_name: "Albrecht Dürer-Gasse" street_number: 2 ``` + **Rathaus** + ```yaml waste_collection_schedule: sources: @@ -54,7 +57,9 @@ waste_collection_schedule: street_name: Hauptplatz street_number: 39 ``` + **Rathaus using Teilgebiet** + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/kuringgai_nsw_gov_au.md b/doc/source/kuringgai_nsw_gov_au.md index 86aec35e..6416a108 100644 --- a/doc/source/kuringgai_nsw_gov_au.md +++ b/doc/source/kuringgai_nsw_gov_au.md @@ -17,21 +17,20 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example - ```yaml waste_collection_schedule: sources: diff --git a/doc/source/kwu_de.md b/doc/source/kwu_de.md index a736045a..3fe69599 100644 --- a/doc/source/kwu_de.md +++ b/doc/source/kwu_de.md @@ -16,15 +16,15 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* -**number**
+**number** *(string) (required)* ## How to get the source arguments -Visit (Entsorgungskalender)[`https://www.kwu-entsorgung.de/?page_id=337`] and search for your address. The `city`, `street` and `number` argument should exactly match the autocomplete result. +Visit [Entsorgungskalender](https://www.kwu-entsorgung.de/?page_id=337`) and search for your address. The `city`, `street` and `number` argument should exactly match the autocomplete result. diff --git a/doc/source/landkreis_rhoen_grabfeld.md b/doc/source/landkreis_rhoen_grabfeld.md index 90f7225b..d0ed4c80 100644 --- a/doc/source/landkreis_rhoen_grabfeld.md +++ b/doc/source/landkreis_rhoen_grabfeld.md @@ -22,12 +22,12 @@ waste_collection_schedule: ### Configuration Variables -**district** and **city** can be used independently, they can also be omitted to get the calender for the whole rural district. +**district** and **city** can be used independently, they can also be omitted to get the calendar for the whole rural district. -**district**
+**district** *(string)* -**street**
+**street** *(string)* ## Example diff --git a/doc/source/landkreis_wittmund_de.md b/doc/source/landkreis_wittmund_de.md index 22e1537e..766fa504 100644 --- a/doc/source/landkreis_wittmund_de.md +++ b/doc/source/landkreis_wittmund_de.md @@ -16,10 +16,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (optional)* ## Example diff --git a/doc/source/lerum_se.md b/doc/source/lerum_se.md index a75974bc..859e8e47 100644 --- a/doc/source/lerum_se.md +++ b/doc/source/lerum_se.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/lewisham_gov_uk.md b/doc/source/lewisham_gov_uk.md index 8f65871a..a7a0b377 100644 --- a/doc/source/lewisham_gov_uk.md +++ b/doc/source/lewisham_gov_uk.md @@ -17,31 +17,33 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional)* This is required if you do not supply any other options. (Using this removes the need to do an address look up web request) -**name**
+**name** *(string) (optional)* This is required if you supply a Postcode and do not have a house number. -**number**
+**number** *(string) (optional)* This is required if you supply a Postcode and have a house number. -**post_code**
+**post_code** *(string) (optional)* This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. #### How to find your `UPRN` -An easy way to discover your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. + +An easy way to discover your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Otherwise you can inspect the web requests the Peterborough Council website makes when entering in your postcode and then selecting your address. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -51,6 +53,7 @@ waste_collection_schedule: ``` ## Example using Address lookup + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/lindau_ch.md b/doc/source/lindau_ch.md index c36f5b68..98a6d370 100644 --- a/doc/source/lindau_ch.md +++ b/doc/source/lindau_ch.md @@ -15,7 +15,7 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* Choose one of the following list: diff --git a/doc/source/lrasha_de.md b/doc/source/lrasha_de.md index fdd418de..feb62a77 100644 --- a/doc/source/lrasha_de.md +++ b/doc/source/lrasha_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**location**
+**location** *(string) (required)* ## How to get the source arguments diff --git a/doc/source/manchester_uk.md b/doc/source/manchester_uk.md index 0fdf99b6..840331ac 100644 --- a/doc/source/manchester_uk.md +++ b/doc/source/manchester_uk.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example @@ -33,6 +33,4 @@ waste_collection_schedule: The UPRN code can be found in the page by entering your postcode on the [Manchester City Council Bin Collections page -](https://www.manchester.gov.uk/bincollections/). When on the address list, -View the source code for the page, and look for your address, the uprn will be -shown as the value. +](https://www.manchester.gov.uk/bincollections/). When on the address list, view the source code for the page, and look for your address, the uprn will be shown as the value. diff --git a/doc/source/maroondah_vic_gov_au.md b/doc/source/maroondah_vic_gov_au.md index 2c5d8522..15f33795 100644 --- a/doc/source/maroondah_vic_gov_au.md +++ b/doc/source/maroondah_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example diff --git a/doc/source/meinawb_de.md b/doc/source/meinawb_de.md new file mode 100644 index 00000000..12501305 --- /dev/null +++ b/doc/source/meinawb_de.md @@ -0,0 +1,46 @@ +# Abfallwirtschaftsbetrieb Landkreis Ahrweiler (AWB) + +Support for schedules provided by [Abfallwirtschaftsbetrieb Landkreis Ahrweiler](https://www.meinawb.de/) located in Rhineland Palatinate, Germany. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: meinawb_de + args: + city: CITY + street: STREET + house_number: HNR + address_suffix: HNR_SUFFIX +``` + +### Configuration Variables + +**city** +*(string) (required)* + +**street** +*(string) (required)* + +**house_number** +*(integer) (required)* + +**address_suffix** +*(string) (optional) (default: "")* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: meinawb_de + args: + city: Oberzissen + street: Ackerstrasse + address_suffix: 1 +``` + +## How to get the source arguments + +The arguments are your address. The input validation is a bit petty, so make sure you write it exactly like in the [web form](https://www.meinawb.de/abfuhrtermine). For troubleshooting, have a look in the home assistant logs. diff --git a/doc/source/melton_vic_gov_au.md b/doc/source/melton_vic_gov_au.md index a7b22fca..bfa51fbd 100644 --- a/doc/source/melton_vic_gov_au.md +++ b/doc/source/melton_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source arguments -Visit the [Melton City Council waste and recycling](https://www.melton.vic.gov.au/My-Area) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. \ No newline at end of file +Visit the [Melton City Council waste and recycling](https://www.melton.vic.gov.au/My-Area) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. diff --git a/doc/source/middlesbrough_gov_uk.md b/doc/source/middlesbrough_gov_uk.md index bd7cd23b..6298097a 100644 --- a/doc/source/middlesbrough_gov_uk.md +++ b/doc/source/middlesbrough_gov_uk.md @@ -1,4 +1,4 @@ -# Middlesbrough Council +# Middlesbrough Council Support for schedules provided by [Middlesbrough Council](https://www.middlesbrough.gov.uk/bin-collection-dates). @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example using UPRN @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source argument -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/miljoteknik_se.md b/doc/source/miljoteknik_se.md index 964d439e..60458945 100644 --- a/doc/source/miljoteknik_se.md +++ b/doc/source/miljoteknik_se.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example @@ -40,4 +40,3 @@ The following waste types will be returned: * "Mat, Brännbart, färgat glas, tidningar." * "Plast, pappersförpackningar, ofärgat glas, metall." - diff --git a/doc/source/mrsc_vic_gov_au.md b/doc/source/mrsc_vic_gov_au.md index b01afde5..69bcbc35 100644 --- a/doc/source/mrsc_vic_gov_au.md +++ b/doc/source/mrsc_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/muellmax_de.md b/doc/source/muellmax_de.md index 5cfb9b8d..9622250e 100644 --- a/doc/source/muellmax_de.md +++ b/doc/source/muellmax_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**service**
+**service** *(string) (required)* -**mm_frm_ort_sel**
+**mm_frm_ort_sel** *(string) (optional)* -**mm_frm_str_sel**
+**mm_frm_str_sel** *(string) (optional)* -**mm_frm_hnr_sel**
+**mm_frm_hnr_sel** *(string) (optional)* ## Example diff --git a/doc/source/muenchenstein_ch.md b/doc/source/muenchenstein_ch.md deleted file mode 100644 index 6ecc64f2..00000000 --- a/doc/source/muenchenstein_ch.md +++ /dev/null @@ -1,38 +0,0 @@ -# Abfallsammlung Münchenstein, BL, Switzerland - -Support for schedules provided by [https://www.muenchenstein.ch/abfallsammlung](https://www.muenchenstein.ch/abfallsammlung). - -This source is just a slight modification of [@atrox06](https://github.com/atrox06)'s work for lindau_ch. So kudos to him. - -## Configuration via configuration.yaml - -```yaml -waste_collection_schedule: - sources: - - name: muenchenstein_ch - args: - waste_district: DISTRICT - -``` - -### Configuration Variables - -**waste_district**
-*(string) (required)* - -Valid options for waste_district: -- Abfuhrkreis Ost -- Abfuhrkreis West - -or use one the following IDs: 491 for "Ost", 492 for "West" - -## Example - -```yaml -waste_collection_schedule: - sources: - - name: muenchenstein_ch - args: - waste_district: Abfuhrkreis West - -``` diff --git a/doc/source/nawma_sa_gov_au.md b/doc/source/nawma_sa_gov_au.md index 3e97c82f..d684e119 100644 --- a/doc/source/nawma_sa_gov_au.md +++ b/doc/source/nawma_sa_gov_au.md @@ -17,13 +17,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (optional)* Only required if the street crosses multiple collection areas with different days. diff --git a/doc/source/newcastle_gov_uk.md b/doc/source/newcastle_gov_uk.md index 0e19477f..67eb2245 100644 --- a/doc/source/newcastle_gov_uk.md +++ b/doc/source/newcastle_gov_uk.md @@ -16,12 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* This is required to unambiguously identify the property. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -32,4 +33,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/nillumbik_vic_gov_au.md b/doc/source/nillumbik_vic_gov_au.md new file mode 100644 index 00000000..56876989 --- /dev/null +++ b/doc/source/nillumbik_vic_gov_au.md @@ -0,0 +1,32 @@ +# Nillumbik Shire Council + +Support for schedules provided by [Nillumbik Shire Council](https://www.nillumbik.vic.gov.au). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: nillumbik_vic_gov_au + args: + street_address: STREET_ADDRESS +``` + +### Configuration Variables + +**street_address** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: nillumbik_vic_gov_au + args: + street_address: 30 Crest Road, RESEARCH, 3095 +``` + +## How to get the source arguments + +Visit the [Nillumbik Shire Council waste and recycling](https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. \ No newline at end of file diff --git a/doc/source/nottingham_city_gov_uk.md b/doc/source/nottingham_city_gov_uk.md index ccfd814b..5e7a637a 100644 --- a/doc/source/nottingham_city_gov_uk.md +++ b/doc/source/nottingham_city_gov_uk.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/nsomerset_gov_uk.md b/doc/source/nsomerset_gov_uk.md index 64d5a940..cb13df37 100644 --- a/doc/source/nsomerset_gov_uk.md +++ b/doc/source/nsomerset_gov_uk.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** *(string) (required)* -**uprn**
+**uprn** *(string) (required)* ## Examples diff --git a/doc/source/nuernberger_land_de.md b/doc/source/nuernberger_land_de.md new file mode 100644 index 00000000..7179f30f --- /dev/null +++ b/doc/source/nuernberger_land_de.md @@ -0,0 +1,35 @@ +# Abfuhrkalender Nürnberger Land + +Support for schedules provided by . + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: nuernberger_land_de + args: + id: ID +``` + +### Configuration Variables + +**id** +_(integer) (required)_ : The unique 8-digit identifier of your street section + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: nuernberger_land_de + args: + id: 14579001 +``` + +## How to get the source arguments + +1. Open . +2. Fill out the filter fields on the page. +3. Right click the button "Termine in den Kalender importieren" and select "Copy link address". You should get something like this `https://abfuhrkalender.nuernberger-land.de/waste_calendar/ical?id=14579001` +4. Copy the id number at the end of the link to your configuration file. diff --git a/doc/source/peterborough_gov_uk.md b/doc/source/peterborough_gov_uk.md index 69c0fd06..a5827b18 100644 --- a/doc/source/peterborough_gov_uk.md +++ b/doc/source/peterborough_gov_uk.md @@ -17,31 +17,33 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional)* This is required if you do not supply any other options. (Using this removes the need to do an address look up web request) -**name**
+**name** *(string) (optional)* This is required if you supply a Postcode and do not have a house number. -**number**
+**number** *(string) (optional)* This is required if you supply a Postcode and have a house number. -**post_code**
+**post_code** *(string) (optional)* This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. #### How to find your `UPRN` -An easy way to discover your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. + +An easy way to discover your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Otherwise you can inspect the web requests the Peterborough Council website makes when entering in your postcode and then selecting your address. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -51,6 +53,7 @@ waste_collection_schedule: ``` ## Example using Address lookup + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/pgh_st.md b/doc/source/pgh_st.md index cd84b904..a78fb362 100644 --- a/doc/source/pgh_st.md +++ b/doc/source/pgh_st.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**house_number**
+**house_number** *(integer) (required)* -**street_name**
+**street_name** *(string) (required)* -**zipcode**
+**zipcode** *(integer) (required)* ## Example diff --git a/doc/source/recycleapp_be.md b/doc/source/recycleapp_be.md index 3d7155ba..419a649d 100644 --- a/doc/source/recycleapp_be.md +++ b/doc/source/recycleapp_be.md @@ -19,19 +19,19 @@ The source arguments are simply the values of the form elements on the homepage. ### Configuration Variables -**postcode**
+**postcode** *(int)* Postal Code. -**street**
+**street** *(string)* Street name. -**house_number**
+**house_number** *(int)* House number -**add_events**
+**add_events** *(boolean)* Add events (e.g. Repair Cafe) in addition to waste collections. diff --git a/doc/source/recyclesmart_com.md b/doc/source/recyclesmart_com.md index 5141928d..b5ec3ce9 100644 --- a/doc/source/recyclesmart_com.md +++ b/doc/source/recyclesmart_com.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**email**
+**email** *(string) (required)* -**password**
+**password** *(string) (required)* ## Example diff --git a/doc/source/regioentsorgung_de.md b/doc/source/regioentsorgung_de.md index 3feb0496..cfb5af59 100644 --- a/doc/source/regioentsorgung_de.md +++ b/doc/source/regioentsorgung_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string | number) (required)* ## Example diff --git a/doc/source/republicservices_com.md b/doc/source/republicservices_com.md index 5feca1c2..7653f06b 100644 --- a/doc/source/republicservices_com.md +++ b/doc/source/republicservices_com.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to check the street address -The street address can be tested [here](https://republicservices.com). \ No newline at end of file +The street address can be tested [here](https://republicservices.com). diff --git a/doc/source/rh_entsorgung_de.md b/doc/source/rh_entsorgung_de.md index 7242e9b0..f7433805 100644 --- a/doc/source/rh_entsorgung_de.md +++ b/doc/source/rh_entsorgung_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(integer) (required)* -**address_suffix**
+**address_suffix** *(string) (optional) (default: "")* ## Example diff --git a/doc/source/richmondshire_gov_uk.md b/doc/source/richmondshire_gov_uk.md index d1424739..f0425ad8 100644 --- a/doc/source/richmondshire_gov_uk.md +++ b/doc/source/richmondshire_gov_uk.md @@ -15,7 +15,7 @@ waste_collection_schedule: ### Configuration Variables -**UPRN**
+**UPRN** *(integer) (required)* ## Example @@ -30,4 +30,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. Or you can visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Or you can visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value. diff --git a/doc/source/rushmoor_gov_uk.md b/doc/source/rushmoor_gov_uk.md index b223aabe..95a8142d 100644 --- a/doc/source/rushmoor_gov_uk.md +++ b/doc/source/rushmoor_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example @@ -28,4 +28,5 @@ waste_collection_schedule: ``` ## How to get the source argument -# Find the UPRN of your address using https://www.findmyaddress.co.uk/search + +Find the UPRN of your address using [https://www.findmyaddress.co.uk/search](https://www.findmyaddress.co.uk/search). diff --git a/doc/source/salford_gov_uk.md b/doc/source/salford_gov_uk.md new file mode 100644 index 00000000..046bb370 --- /dev/null +++ b/doc/source/salford_gov_uk.md @@ -0,0 +1,36 @@ +# Salford City Council + +Support for schedules provided by [Salford City +Council](https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/), serving the +city of Salford, UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: salford_gov_uk + args: + uprn: UPRN_CODE +``` + +### Configuration Variables + +**uprn** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: salford_gov_uk + args: + uprn: "100011404886" +``` + +## How to get the source argument + +The UPRN code can be found by entering your postcode on the +[Salford City Council bin collections days page +](https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/). After selecting your address from the list and waiting for the new page to load, the uprn will be shown in the address bar. diff --git a/doc/source/sbazv_de.md b/doc/source/sbazv_de.md index d959be9d..0e73a456 100644 --- a/doc/source/sbazv_de.md +++ b/doc/source/sbazv_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**district**
+**district** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/scambs_gov_uk.md b/doc/source/scambs_gov_uk.md index 4c009038..7e991acc 100644 --- a/doc/source/scambs_gov_uk.md +++ b/doc/source/scambs_gov_uk.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**POST_CODE**
+**POST_CODE** *(string) (required)* -**NUMBER**
+**NUMBER** *(string) (required)* ## Example diff --git a/doc/source/seattle_gov.md b/doc/source/seattle_gov.md index 17169349..4ee26744 100644 --- a/doc/source/seattle_gov.md +++ b/doc/source/seattle_gov.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* -**prem_code**
+**prem_code** *(string) (optional)* ## Example diff --git a/doc/source/sector27_de.md b/doc/source/sector27_de.md index e8831298..aeac17bf 100644 --- a/doc/source/sector27_de.md +++ b/doc/source/sector27_de.md @@ -19,10 +19,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/sheffield_gov_uk.md b/doc/source/sheffield_gov_uk.md index 82aa8800..9bf39af3 100644 --- a/doc/source/sheffield_gov_uk.md +++ b/doc/source/sheffield_gov_uk.md @@ -14,12 +14,13 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional) (preferred method)* This is required if you do not supply any other options. Using a UPRN removes the need to do an address look up using web requests. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -30,6 +31,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. -Otherwise you can inspect the URL on [Sheffield City Council's Waste Services](https://wasteservices.sheffield.gov.uk/) site having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL (before /calendar), for example: *https://wasteservices.sheffield.gov.uk/property/`100050938234`* or *https://wasteservices.sheffield.gov.uk/property/`100050938234`/calendar* \ No newline at end of file +Otherwise you can inspect the URL on [Sheffield City Council's Waste Services](https://wasteservices.sheffield.gov.uk/) site having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL (before /calendar), for example: `https://wasteservices.sheffield.gov.uk/property/100050938234` or `https://wasteservices.sheffield.gov.uk/property/100050938234/calendar` diff --git a/doc/source/srvatervinning_se.md b/doc/source/srvatervinning_se.md new file mode 100644 index 00000000..05c5c476 --- /dev/null +++ b/doc/source/srvatervinning_se.md @@ -0,0 +1,35 @@ +# SRV Återvinning + +Support for schedules provided by [SRV återvinning AB](https://www.srvatervinning.se/), Sweden. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: srvatervinning_se + args: + address: address +``` + +### Configuration Variables + +**address** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: srvatervinning_se + args: + address: "Skansvägen" + +``` + +## How to get the source arguments + +1. Go to your calendar at [SRV återvinning AB](https://www.srvatervinning.se/sophamtning/privat/hamtinformation-och-driftstorningar) +2. Enter your address. +3. Copy the exact values from the textboxes in the source configuration. diff --git a/doc/source/ssam_se.md b/doc/source/ssam_se.md index a3f1e88f..448031fb 100644 --- a/doc/source/ssam_se.md +++ b/doc/source/ssam_se.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/stadt_willich_de.md b/doc/source/stadt_willich_de.md index ac2ff7c8..6aaf3e8e 100644 --- a/doc/source/stadt_willich_de.md +++ b/doc/source/stadt_willich_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* ## How to get the source arguments diff --git a/doc/source/stadtreinigung_dresden_de.md b/doc/source/stadtreinigung_dresden_de.md index a0e8f7f7..70276151 100644 --- a/doc/source/stadtreinigung_dresden_de.md +++ b/doc/source/stadtreinigung_dresden_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**standort**
+**standort** *(string) (required)* ## Example diff --git a/doc/source/stadtreinigung_hamburg.md b/doc/source/stadtreinigung_hamburg.md index 2f1b83a3..7614fd5c 100644 --- a/doc/source/stadtreinigung_hamburg.md +++ b/doc/source/stadtreinigung_hamburg.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**hnId**
+**hnId** *(string) (required)* ## Example diff --git a/doc/source/stadtreinigung_leipzig_de.md b/doc/source/stadtreinigung_leipzig_de.md index d5bac15a..92b40c61 100644 --- a/doc/source/stadtreinigung_leipzig_de.md +++ b/doc/source/stadtreinigung_leipzig_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## Example diff --git a/doc/source/stadtservice_bruehl_de.md b/doc/source/stadtservice_bruehl_de.md index c3abeece..a17c7b17 100644 --- a/doc/source/stadtservice_bruehl_de.md +++ b/doc/source/stadtservice_bruehl_de.md @@ -15,9 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**strasse**
+**strasse** *(string) (required)* -**hnr**
+ +**hnr** *(string) (required)* ## Example diff --git a/doc/source/staedteservice_de.md b/doc/source/staedteservice_de.md index 33f00cd1..1f943bf3 100644 --- a/doc/source/staedteservice_de.md +++ b/doc/source/staedteservice_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/static.md b/doc/source/static.md index 61fc38f9..557f4bb2 100644 --- a/doc/source/static.md +++ b/doc/source/static.md @@ -20,40 +20,40 @@ waste_collection_schedule: ### Configuration Variables -**TYPE**
+**TYPE** *(string) (required)* The type of this source. -**DATES**
+**DATES** *(list) (optional)* A list of dates in format "YYYY-MM-DD" which should be added to the source. Dates defined in this list will be added in addition to calculated dates from the recurrence and will not be affected by the exclude-list. -**FREQUENCY**
+**FREQUENCY** *(string) (optional)* Defines the frequency of the recurrence. Must be one of "DAILY", "WEEKLY", "MONTHLY" or "YEARLY". -**INTERVAL**
+**INTERVAL** *(int) (optional, default: ```1```)* Defines the interval of the recurrence. -**START**
+**START** *(string) (optional)* Defines the start of the recurrence in the format "YYYY-MM-DD". Required if *FREQUENCY* is set. -**UNTIL**
+**UNTIL** *(string) (optional)* Defines the end of the recurrence in the format "YYYY-MM-DD". Required if *FREQUENCY* is set. -**EXCLUDES**
+**EXCLUDES** *(list) (optional)* A list of dates in format "YYYY-MM-DD" which should be excluded from the recurrence. @@ -80,4 +80,4 @@ waste_collection_schedule: dates: # Manually define dates that are not part of the recurrence - '2022-07-28' - '2022-09-22' -``` \ No newline at end of file +``` diff --git a/doc/source/stevenage_gov_uk.md b/doc/source/stevenage_gov_uk.md index 2bfccac3..106b94fa 100644 --- a/doc/source/stevenage_gov_uk.md +++ b/doc/source/stevenage_gov_uk.md @@ -1,4 +1,4 @@ -# Stevenage Borough Council +# Stevenage Borough Council Support for schedules provided by [Stevenage Borough Council](https://www.stevenage.gov.uk/waste-and-recycling/your-bin-collections). @@ -15,17 +15,18 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** *(string) (required)* Postcode of property. This is required. Stevenage Borough Council API does not support UKPRN. Single space between 1st and 2nd part of postcode is optional. -**road**
+**road** *(string) (required)* Name of road property is in. This is required. ## Example + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/stonnington_vic_gov_au.md b/doc/source/stonnington_vic_gov_au.md index 8b80441d..1e9f1be5 100644 --- a/doc/source/stonnington_vic_gov_au.md +++ b/doc/source/stonnington_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/stuttgart_de.md b/doc/source/stuttgart_de.md index 5e55194a..ad34b8fc 100644 --- a/doc/source/stuttgart_de.md +++ b/doc/source/stuttgart_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**streetnr**
+**streetnr** *(string) (required)* ## Example diff --git a/doc/source/sysav_se.md b/doc/source/sysav_se.md index 4a00ae8b..65258a27 100644 --- a/doc/source/sysav_se.md +++ b/doc/source/sysav_se.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/tewkesbury_gov_uk.md b/doc/source/tewkesbury_gov_uk.md index cccea756..71ae38c1 100644 --- a/doc/source/tewkesbury_gov_uk.md +++ b/doc/source/tewkesbury_gov_uk.md @@ -14,10 +14,9 @@ waste_collection_schedule: ### Configuration Variables -**POSTCODE**
+**POSTCODE** *(string) (required)* - ## Example ```yaml diff --git a/doc/source/thehills_nsw_gov_au.md b/doc/source/thehills_nsw_gov_au.md index 35e517fe..59252d09 100644 --- a/doc/source/thehills_nsw_gov_au.md +++ b/doc/source/thehills_nsw_gov_au.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street**
+**street** *(string) (required)* -**houseNo**
+**houseNo** *(string) (required)* ## Example diff --git a/doc/source/toronto_ca.md b/doc/source/toronto_ca.md index 97f57f4e..7e4321b5 100644 --- a/doc/source/toronto_ca.md +++ b/doc/source/toronto_ca.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/vasyd_se.md b/doc/source/vasyd_se.md index 751f44a9..b6bd3d45 100644 --- a/doc/source/vasyd_se.md +++ b/doc/source/vasyd_se.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/waipa_nz.md b/doc/source/waipa_nz.md index c8fa6583..487e65b6 100644 --- a/doc/source/waipa_nz.md +++ b/doc/source/waipa_nz.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example diff --git a/doc/source/walsall_gov_uk.md b/doc/source/walsall_gov_uk.md index 698cf4fa..64dfe128 100644 --- a/doc/source/walsall_gov_uk.md +++ b/doc/source/walsall_gov_uk.md @@ -14,12 +14,13 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional) (preferred method)* This is required if you do not supply any other options. Using a UPRN removes the need to do an address look up using web requests. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -30,6 +31,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. -Otherwise you can inspect the web requests on [Walsall Council](https://www.environmentfirst.co.uk/) having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL, for example: *https://cag.walsall.gov.uk/BinCollections/GetBins?uprn=`100071103746`* +Otherwise you can inspect the web requests on [Walsall Council](https://www.environmentfirst.co.uk/) having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL, for example: `https://cag.walsall.gov.uk/BinCollections/GetBins?uprn=100071103746` diff --git a/doc/source/warszawa19115_pl.md b/doc/source/warszawa19115_pl.md index 94d5a435..8f0d079c 100644 --- a/doc/source/warszawa19115_pl.md +++ b/doc/source/warszawa19115_pl.md @@ -14,10 +14,10 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (optional)* -**geolocation_id**
+**geolocation_id** *(string) (optional)* At least one argument must be provided. diff --git a/doc/source/was_wolfsburg_de.md b/doc/source/was_wolfsburg_de.md index 6269f31d..38c7abf5 100644 --- a/doc/source/was_wolfsburg_de.md +++ b/doc/source/was_wolfsburg_de.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/wastenet_org_nz.md b/doc/source/wastenet_org_nz.md index 1cc01052..018c15b7 100644 --- a/doc/source/wastenet_org_nz.md +++ b/doc/source/wastenet_org_nz.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example diff --git a/doc/source/wellington_govt_nz.md b/doc/source/wellington_govt_nz.md index b26ebe62..2d4b50d3 100644 --- a/doc/source/wellington_govt_nz.md +++ b/doc/source/wellington_govt_nz.md @@ -15,11 +15,11 @@ waste_collection_schedule: ### Configuration Variables -**streetName**
-*(string)*
+**streetName** +*(string)* -**streetId**
-*(string)*
+**streetId** +*(string)* *One of the above is required* diff --git a/doc/source/wermelskirchen_de.md b/doc/source/wermelskirchen_de.md index a6aab9f6..dd28229c 100644 --- a/doc/source/wermelskirchen_de.md +++ b/doc/source/wermelskirchen_de.md @@ -29,10 +29,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## How to get the source arguments diff --git a/doc/source/westberks_gov_uk.md b/doc/source/westberks_gov_uk.md index 3e39be4c..c38cac29 100644 --- a/doc/source/westberks_gov_uk.md +++ b/doc/source/westberks_gov_uk.md @@ -17,13 +17,13 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** _(string) (optional)_ -**hournameornumber**
+**hournameornumber** _(string) (optional)_ -**uprn**
+**uprn** _(string) (optional)_ Either the postcode _and_ housenameornumber or the UPRN should be supplied in the arguments diff --git a/doc/source/wiltshire_gov_uk.md b/doc/source/wiltshire_gov_uk.md index c0fffdd8..7222fc9c 100644 --- a/doc/source/wiltshire_gov_uk.md +++ b/doc/source/wiltshire_gov_uk.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** *(string) (required)* -**uprn**
+**uprn** *(string) (required)* Both the postcode and the UPRN should be supplied in the arguments. @@ -38,4 +38,4 @@ waste_collection_schedule: ## How to find your UPRN -An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. \ No newline at end of file +An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. diff --git a/doc/source/wsz_moosburg_at.md b/doc/source/wsz_moosburg_at.md index a76e4302..da8200f8 100644 --- a/doc/source/wsz_moosburg_at.md +++ b/doc/source/wsz_moosburg_at.md @@ -2,11 +2,11 @@ Support for schedules provided by [wsz-moosburg.at](https://wsz-moosburg.at). -## Configuration via configuration.yaml +## Configuration Variables There are two options to configure this source. -### Using the Address ID +### 1. Using the Address ID ```yaml waste_collection_schedule: @@ -16,12 +16,10 @@ waste_collection_schedule: address_id: ID ``` -#### Configuration Variables - -**address_id**
+**address_id** *(integer) (required)* See the next section on how to obtain it. -#### How to get the Address ID +### How to get the Address ID For this you will have to use a (desktop) browser with developer tools, e.g. Google Chrome: @@ -33,7 +31,7 @@ For this you will have to use a (desktop) browser with developer tools, e.g. Goo 6. Select the last entry in the `Network` tab's list, it should be a number followed by `?include-public-holidays`, e.g. `69980?include-public-holidays`. 7. This number (e.g. `69980`) is what needs to be used as `address_id` in the configuration. -### Using the full Address +### 2. Using the full Address ```yaml waste_collection_schedule: @@ -45,17 +43,15 @@ waste_collection_schedule: street: Straße ``` -#### Configuration Variables - Please note that exact spelling and casing matters. -**municipal**
+**municipal** *(string) (required)* -**address**
+**address** *(string) (required)* -**street**
+**street** *(string) (required)* #### How to get the correct Address @@ -65,6 +61,6 @@ In any web browser: 1. Open [https://wsz-moosburg.at/calendar](https://wsz-moosburg.at/calendar). 2. Select your `Gemeinde` from the list. This is the value for `municipal`. 3. Select your `Addresse` from the list. This is the value for `address`. -4. There might be another step to select your `Straße`, but this depends on the address. +4. There might be another step to select your `Straße`, but this depends on the address. - If it's prompted to you, select that as well. This is the value for `street`. - If it is not prompted, use the same value for `address` also for `street`. diff --git a/doc/source/wuerzburg_de.md b/doc/source/wuerzburg_de.md index 5a212007..d07e853d 100644 --- a/doc/source/wuerzburg_de.md +++ b/doc/source/wuerzburg_de.md @@ -17,10 +17,10 @@ waste_collection_schedule: **district** and **street** can be used independently, only **one** is required. If set, priority will be given to **street**. -**district**
+**district** *(string) (required)* - if *street* is empty -**street**
+**street** *(string) (required)* - if *district* is empty ## Example diff --git a/doc/source/wyndham_vic_gov_au.md b/doc/source/wyndham_vic_gov_au.md index 578f80ec..5f920229 100644 --- a/doc/source/wyndham_vic_gov_au.md +++ b/doc/source/wyndham_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source arguments -Visit the [Wyndham City Council waste and recycling](https://digital.wyndham.vic.gov.au/myWyndham/) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. \ No newline at end of file +Visit the [Wyndham City Council waste and recycling](https://digital.wyndham.vic.gov.au/myWyndham/) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. diff --git a/doc/source/ximmio_nl.md b/doc/source/ximmio_nl.md index 4de6bf18..960068f8 100644 --- a/doc/source/ximmio_nl.md +++ b/doc/source/ximmio_nl.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**company**
+**company** *(string) (required)* Use one of the following codes as company code: @@ -24,6 +24,7 @@ Use one of the following codes as company code: - acv - almere - areareiniging +- avalex - avri - bar - hellendoorn @@ -33,12 +34,13 @@ Use one of the following codes as company code: - reinis - twentemilieu - waardlanden +- westland - ximmio -**post_code**
+**post_code** *(string) (required)* -**house_number**
+**house_number** *(integer) (required)* ## Example diff --git a/doc/source/york_gov_uk.md b/doc/source/york_gov_uk.md index 16042cd8..f644dcd0 100644 --- a/doc/source/york_gov_uk.md +++ b/doc/source/york_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/zva_wmk_de.md b/doc/source/zva_wmk_de.md new file mode 100644 index 00000000..1429c4e2 --- /dev/null +++ b/doc/source/zva_wmk_de.md @@ -0,0 +1,26 @@ +# Zweckverband Abfallwirtschaft Werra-Meißner-Kreis + +Support für Werra-Meißner-Kreis located in Hesse, Germany + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: zva_wmk_de + args: + city: CITY + street: STREET +``` + +### Configuration Variables + +**city** +*(string) (required)* + +**street** +*(street) (required)* + +### How to get the source arguments + +Visit [zva-wmk.de](https://www.zva-wmk.de/termine/schnellsuche-2023) and search for your locality. Use the value from the "Ort" dropdown as `city` argument and the one from "Ortsteil/Straße" as `street` as shown. diff --git a/doc/button-cards.png b/images/button-cards.png similarity index 100% rename from doc/button-cards.png rename to images/button-cards.png diff --git a/doc/calendar.png b/images/calendar.png similarity index 100% rename from doc/calendar.png rename to images/calendar.png diff --git a/doc/date-of-next-collections.png b/images/date-of-next-collections.png similarity index 100% rename from doc/date-of-next-collections.png rename to images/date-of-next-collections.png diff --git a/doc/days-to-next-collections.png b/images/days-to-next-collections.png similarity index 100% rename from doc/days-to-next-collections.png rename to images/days-to-next-collections.png diff --git a/doc/default-entity.png b/images/default-entity.png similarity index 100% rename from doc/default-entity.png rename to images/default-entity.png diff --git a/doc/more-info-appointment-types.png b/images/more-info-appointment-types.png similarity index 100% rename from doc/more-info-appointment-types.png rename to images/more-info-appointment-types.png diff --git a/doc/more-info-generic.png b/images/more-info-generic.png similarity index 100% rename from doc/more-info-generic.png rename to images/more-info-generic.png diff --git a/doc/more-info-upcoming.png b/images/more-info-upcoming.png similarity index 100% rename from doc/more-info-upcoming.png rename to images/more-info-upcoming.png diff --git a/doc/next-collection-type.png b/images/next-collection-type.png similarity index 100% rename from doc/next-collection-type.png rename to images/next-collection-type.png diff --git a/doc/next-collections-date-and-days.png b/images/next-collections-date-and-days.png similarity index 100% rename from doc/next-collections-date-and-days.png rename to images/next-collections-date-and-days.png diff --git a/doc/upcoming_details.png b/images/upcoming_details.png similarity index 100% rename from doc/upcoming_details.png rename to images/upcoming_details.png diff --git a/images/wcs_animated.gif b/images/wcs_animated.gif new file mode 100644 index 00000000..21aa2080 Binary files /dev/null and b/images/wcs_animated.gif differ diff --git a/images/wcs_code_btn.png b/images/wcs_code_btn.png new file mode 100644 index 00000000..07afbddb Binary files /dev/null and b/images/wcs_code_btn.png differ diff --git a/images/wcs_fork_btn.png b/images/wcs_fork_btn.png new file mode 100644 index 00000000..dc10a2fd Binary files /dev/null and b/images/wcs_fork_btn.png differ diff --git a/info.md b/info.md index 14bd1295..cebebd15 100644 --- a/info.md +++ b/info.md @@ -1,212 +1,35 @@ +Waste Collection Schedule logo + # Waste Collection Schedule -Waste Collection Schedule provides schedules from waste collection service providers to Home Assistant. Additionally, it supports schedules from generic ICS files which can be stored locally or fetched from a web site. There is a high flexibility in providing the information to be displayed. +![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) ![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) [![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange)](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492) -## Examples +**A custom component for Home Assistant that retrieves waste collection schedules from a wide range of service providers.** -Per default (without further configuration), the time to the next collection will be shown in an [entity card](https://www.home-assistant.io/lovelace/entity/): +Waste Collection Schedule animation -![Default Lovelace Card](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/default-entity.png) - -You can also setup dedicated entities per waste type and show the schedule in various formats: - -![Days to next collections](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/days-to-next-collections.png) -![Date of next collections](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/date-of-next-collections.png) -![Date and days to next collections](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/next-collections-date-and-days.png) - -The information in the more-info popup can be displayed in different formats: - -1. List of upcoming collections: - - ![More info: upcoming](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/more-info-upcoming.png) - -2. List of waste types and collection date: - - ![More info: waste types](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/more-info-appointment-types.png) - -[Button Card](https://github.com/custom-cards/button-card) can be used to create individual Lovelace cards: - -![Button Card](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/button-cards.png) - -## Documentation - -- [Full Documentation](https://github.com/mampfes/hacs_waste_collection_schedule) +Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. ## Supported Service Providers -Currently the following service providers are supported: +| Country | Service Providers | +|--|--| +| Generic | ICS / iCal files | +| Static | User-defined dates or repeating date patterns | +| Australia | Banyule City Council, Belmont City Council, Brisbane City Council, Campbelltown City Council, City of Canada Bay Council, Gold Coast City Council, Inner West Council (NSW), Ipswich City Council, Ku-ring-gai Council, Macedon Ranges Shire Council, Maroondah City Council, Melton City Council, Nillumbik Shire Council, North Adelaide Waste Management Authority, RecycleSmart, Stonnington City Council, The Hills Shire Council, Sydney, Wyndham City Council, Melbourne | +| Austria | Burgenländischer Müllverband, infeo, Stadtservice Korneuburg, Umweltprofis, WSZ Moosburg | +| Belgium | Hygea, Recycle! | +| Canada | City of Toronto | +| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Nürnberger Land, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, Abfallwirtschaftsbetrieb Landkreis Ahrweiler, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Landkreis Augsburg, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bremener Stadreinigung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung, WZV Kreis Segeberg | +| Lithuania | Kauno švara | +| Netherlands | ACV Group, Alpen an den Rijn, Area Afval, Avalex, Avri, Bar Afvalbeheer, Cyclus NV, Dar, Den Haag, GAD, Gemeente Almere, Gemeente Berkelland, Gemeente Cranendonck, Gemeente Hellendoorn, Gemeente Lingewaard, Gemeente Meppel, Gemeente Middelburg + Vlissingen, Gemeente Peel en Maas, Gemeente Schouwen-Duiveland, Gemeente Sudwest-Fryslan, Gemeente Venray, Gemeente Voorschoten, Gemeente Wallre, Gemeente Westland, HVC Groep, Meerlanden, Mijn Blink, PreZero, Purmerend, RAD BV, Reinigingsbedrijf Midden Nederland, Reinis, Spaarne Landen, Stadswerk 072, Twente Milieu, Waardlanden, Ximmio, ZRD | +| New Zealand | Auckland Council, Christchurch City Council, Gore, Invercargill & Southland, Horowhenua District Council, Waipa District Council, Wellington City Council | +| Norway | Min Renovasjon, Oslo Kommune | +| Poland | Ecoharmonogram, Warsaw | +| Sweden | Lerum Vatten och Avlopp, Ronneby Miljöteknik, SRV Återvinning, SSAM, Sysav Sophämntning, VA Syd Sophämntning | +| Switzerland | A-Region, Andwil, Appenzell, Berg, Bühler, Eggersriet, Gais, Gaiserwald, Goldach, Grub, Heiden, Herisau, Horn, Hundwil, Häggenschwil, Lindau, Lutzenberg, Muolen, Mörschwil, Rehetobel, Rorschach, Rorschacherberg, Schwellbrunn, Schönengrund, Speicher, Stein, Steinach, Teufen, Thal, Trogen, Tübach, Untereggen, Urnäsch, Wald, Waldkirch, Waldstatt, Wittenbach, Wolfhalden | +| United Kingdom | Ashfield District Council, Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Breckland Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Salford City Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | +| United States of America | City of Pittsburgh, Republic Services, Seattle Public Utilities | + -- [Generic ICS / iCal File](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ics.md) -- [Static source](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/static.md) - -### Australia - -- [Banyule City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/banyule_vic_gov_au.md) -- [Belmont City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/belmont_wa_gov_au.md) -- [Brisbane City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/brisbane_qld_gov_au.md) -- [Campbelltown City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/campbelltown_nsw_gov_au.md) -- [City of Canada Bay Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/canadabay_nsw_gov_au.md) -- [Inner West Council (NSW)](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/innerwest_nsw_gov_au.md) -- [Ku-ring-gai Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kuringgai_nsw_gov_au.md) -- [Macedon Ranges Shire Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/mrsc_vic_gov_au.md) -- [Maroondah City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/maroondah_vic_gov_au.md) -- [Melton City Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/melton_vic_gov_au.md) -- [North Adelaide Waste Management Authority, South Australia](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nawma_sa_gov_au.md) -- [RecycleSmart](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/recyclesmart.md) -- [Stonnington City Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stonnington_vic_gov_au.md) -- [The Hills Council, Sydney](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/thehills_nsw_gov_au.md) -- [Wyndham City Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/sourcewyndham_vic_gov_au.md) - -### Austria - -- [BMV.at](https://github.com/mampfes/hacs_waste_collection_schedule/doc/source/bmv_at.md) -- [Data.Umweltprofis](https://github.com/mampfes/hacs_waste_collection_schedule/doc/source/data_umweltprofis_at.md) -- [Korneuburg Stadtservice](https://github.com/mampfes/hacs_waste_collection_schedule/doc/source/korneuburg_stadtservice_at.md) -- [WSZ-Moosburg.at](https://github.com/mampfes/hacs_waste_collection_schedule/doc/source/wsz_moosburg_at.md) - -### Belgium - -- [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) -- [AbfallNavi.de (RegioIT.de)](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfallnavi_de.md) -- [Abfallkalender Würzburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wuerzburg_de.md) -- [Abfalltermine Forchheim](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfalltermine_forchheim_de.md) -- [Abfallwirtschaft Bremen](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/c_trace_de.md) -- [Abfallwirtschaft Landkreis Harburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/aw_harburg_de.md) -- [Abfallwirtschaft Landkreis Wolfenbüttel](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/alw_wf_de.md) -- [Abfallwirtschaft Neckar-Odenwald-Kreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awn_de.md) -- [Abfallwirtschaft Rendsburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awr_de.md) -- [Abfallwirtschaft Stuttgart](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stuttgart_de.md) -- [Abfallwirtschaft Südholstein](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awsh_de.md) -- [Abfallwirtschaft Zollernalbkreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfall_zollernalbkreis_de.md) -- [ART Trier](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/art_trier_de.md) -- [AVL Ludwigsburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/avl_ludwigsburg_de.md) -- [AWB Bad Kreuznach](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_bad_kreuznach_de.md) -- [AWB Esslingen](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_es_de.md) -- [AWB Limburg-Weilburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_lm_de.md) -- [AWB Oldenburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_oldenburg_de.md) -- [AWBKoeln.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awbkoeln_de.md) -- [AWIDO-online.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awido_de.md) -- [Berlin-Recycling.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/berlin_recycling_de.md) -- [Bogenschuetz-Entsorgung.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/infeo_at.md) -- [BSR.de / Berliner Stadtreinigungsbetriebe](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bsr_de.md) -- [C-Trace.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/c_trace_de.md) -- [Cochem-Zell](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/cochem_zell_online_de.md) -- [EGN-Abfallkalender.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/egn_abfallkalender_de.md) -- [Erlangen-Höchstadt](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/erlangen_hoechstadt_de.md) -- [Jumomind.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/jumomind_de.md) -- [KWB-Goslar.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kwb_goslar_de.md) -- [KWU-Entsorgung.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kwu_de.md) -- [KAEV Niederlausitz](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kaev_niederlausitz_de.md) -- [Landkreis-Wittmund.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/landkreis_wittmund_de.md) -- [Landkreis Rhön Grabfeld](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/landkreis_rhoen_grabfeld.md) -- [Landkreis Schwäbisch Hall](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lrasha_de.md) -- [Muellmax.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/muellmax_de.md) -- [MyMuell App](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/jumomind_de.md) -- [Neunkirchen Siegerland](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfall_neunkirchen_siegerland_de.md) -- [RegioEntsorgung](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/regioentsorgung_de.md) -- [Rhein-Hunsrück Entsorgung (RHE)](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/rh_entsorgung_de.md) -- [Sector27.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sector27_de.md) -- [Stadtreinigung Dresden](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadtreinigung_dresden_de.md) -- [Stadtreinigung.Hamburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadtreinigung_hamburg.md) -- [Stadtreinigung-Leipzig.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadtreinigung_leipzig_de.md) -- [Stadt-Willich.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadt_willich_de.md) -- [Stadtservice Brühl](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadtservice_bruehl_de.md) -- [Städteservice Raunheim Rüsselsheim](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/staedteservice_de.md) -- [Südbrandenburgischer Abfallzweckverband](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sbazv_de.md) -- [Umweltbetrieb Stadt Bielefeld](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bielefeld_de.md) -- [WAS Wolfsburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/was_wolfsburg_de.md) -- [Wermelskirchen](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wermelskirchen_de.md) - -### Lithuania - -- [Kauno švara](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/grafikai_svara_lt.md) - -### Netherlands - -- [Ximmio](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ximmio_nl.md) -- [HVCGroep](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/hvcgroep_nl.md) - -### New Zealand - -- [Auckland](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/aucklandcouncil_govt_nz.md) -- [Christchurch](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ccc_govt_nz.md) -- [Gore, Invercargill & Southland](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wastenet_org_nz.md) -- [Horowhenua District](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/horowhenua_govt_nz.md) -- [Waipa District](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/waipa_nz.md) -- [Wellington](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wellington_govt_nz.md) - -### Norway - -- [Min Renovasjon](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/minrenovasjon_no.md) -- [Oslo Kommune](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/oslokommune_no.md) - -### Poland - -- [Warsaw](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/warszawa19115_pl.md) -- [Multiple communities - ecoharmonogram](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ecoharmonogram_pl.md) - -### Sweden - -- [Lerum.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lerum_se.md) -- [Ronneby Miljöteknik](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/miljoteknik_se.md) -- [SSAM.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ssam_se.md) -- [Sysav.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sysav_se.md) -- [Vasyd.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/vasyd_se.md) - -### Switzerland - -- [A-Region.ch](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/a_region_ch.md) -- [Lindau.ch](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lindau_ch.md) -- [Münchenstein](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/muenchenstein_ch.md) - -### United States of America - -- [PGH.ST](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/pgh_st.md) -- [Republic Services](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/republicservices_com.md) -- [Seattle Public Utilities](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/seattle_gov.md) - -### United Kingdom - -- [Bracknell Forest Council - bracknell-forest.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bracknell_forest_gov_uk.md) -- [Bradford Metropolitan District Council - bradford.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bradford_gov_uk.md) -- [Braintree District Council - bracknell-forest.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/braintree_gov_uk.md) -- [Cambridge City Council - cambridge.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/cambridge_gov_uk.md) -- [Canterbury City Council - canterbury.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/canterbury_gov_uk.md) -- [Cheshire East Council - cheshireeast.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/cheshire_east_gov_uk.md) -- [Chesterfield Borough Council - chesterfield.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/chesterfield_gov_uk.md) -- [Colchester Borough Council - colchester.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/colchester_gov_uk.md) -- [Cornwall Council - cornwall.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/cornwall_gov_uk.md) -- [Derby City Council - derby.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/derby_gov_uk.md) -- [Eastbourne Borough Council - lewes-eastbourne.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/environmentfirst_co_uk.md) -- [Elmbridge Borough Council - elmbridge.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/elmbridge_gov_uk.md) -- [Guildford Borough Council - guildford.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/guildford_gov_uk.md) -- [Harborough District Council - www.harborough.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/fccenvironment_co_uk.md) -- [Huntingdonshire District Council - huntingdonshire.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/huntingdonshire_gov_uk.md) -- [The Royal Borough of Kingston Council - kingston.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kingston_gov_uk.md) -- [Lewes District Council - lewes-eastbourne.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/environmentfirst_co_uk.md) -- [London Borough of Lewisham - lewisham.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lewisham_gov_uk.md) -- [Manchester City Council - manchester.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/manchester_uk.md) -- [Middlesbrough Council - middlesbrough.gov.uk](https://www.middlesbrough.gov.uk/bin-collection-dates) -- [Newcastle City Council - newcastle.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/newcastle_gov_uk.md) -- [North Somerset Council - n-somerset.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nsomerset_gov_uk.md) -- [Nottingham City Council - nottinghamcity.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nottingham_city_gov_uk.md) -- [Peterborough City Council - peterborough.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/peterborough_gov_uk.md) -- [Richmondshire District Council - richmondshire.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/richmondshire_gov_uk.md) -- [Rushmoor Borough Council - rushmoor.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/rushmoor_gov_uk.md) -- [Sheffield City Council - Sheffield.gov.uk]((https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sheffield_gov_uk.md) -- [South Cambridgeshire District Council - scambs.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/scambs_gov_uk.md) -- [South Norfolk and Broadland Council - southnorfolkandbroadland.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/south_norfolk_and_broadland_gov_uk.md) -- [Stevenage Borough Council - stevenage.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stevenage_gov_uk.md) -- [Tewkesbury Borough Council](./doc/source/tewkesbury_gov_uk.md) -- [City of York Council - york.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/york_gov_uk.md) -- [Walsall Council - walsall.gov.uk](./doc/source/walsall_gov_uk.md) -- [West Berkshire Council - westberks.gov.uk](./doc/source/westberks_gov_uk.md) -- [Wiltshire Council - wiltshire.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wiltshire_gov_uk.md) +For full details on supported service providers, and more project details, visit us on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule). diff --git a/LICENSE b/md_archive/LICENSE similarity index 100% rename from LICENSE rename to md_archive/LICENSE diff --git a/md_archive/README.md b/md_archive/README.md new file mode 100644 index 00000000..f8215148 --- /dev/null +++ b/md_archive/README.md @@ -0,0 +1,834 @@ +# Waste Collection Schedule + +Waste Collection Schedule provides schedules from waste collection service providers to Home Assistant. Additionally, it supports schedules from generic ICS files which can be stored locally or fetched from a web site. There is a high flexibility in providing the information to be displayed. + +*For developers:* This framework can be easily enhanced to support further waste collection service providers or other services which provide schedules. + +If you like this component, please give it a star on [github](https://github.com/mampfes/hacs_waste_collection_schedule). + +## Table of Contents + +- [Examples](#examples) +- [Supported Service Providers](#supported-service-providers) +- [Installation](#installation) +- [Configuration](#configuration) +- [FAQ](#faq) +- *For developers*: [How to add new sources](#how-to-add-new-sources) + +## Examples + +A complete example can be found [here](./doc/configuration.yaml). + +Per default (without further configuration), the time to the next collection will be shown in an [entity card](https://www.home-assistant.io/lovelace/entity/): + +![Default Lovelace Card](./doc/default-entity.png) + +You can also setup dedicated entities per waste type and show the schedule in various formats: + +![Days to next collections](./doc/days-to-next-collections.png) +![Date of next collections](./doc/date-of-next-collections.png) +![Date and days to next collections](./doc/next-collections-date-and-days.png) + +The information in the more-info popup can be displayed in different formats: + +1. List of upcoming collections: + + ![More info: upcoming](./doc/more-info-upcoming.png) + +2. List of waste types and collection date: + + ![More info: waste types](./doc/more-info-appointment-types.png) + +[Button Card](https://github.com/custom-cards/button-card) can be used to create individual Lovelace cards: + +![Button Card](./doc/button-cards.png) + +The collection schedule will be automatically integrated into the Home Assistant calendar: +![Calendar](./doc/calendar.png) + +## Supported Service Providers + +Currently the following service providers are supported: + +- [Generic ICS / iCal File](./doc/source/ics.md) +- [Static source](./doc/source/static.md) + +### Australia + +- [Banyule City Council](./doc/source/banyule_vic_gov_au.md) +- [Belmont City Council](./doc/source/belmont_wa_gov_au.md) +- [Brisbane City Council](./doc/source/brisbane_qld_gov_au.md) +- [Campbelltown City Council](./doc/source/campbelltown_nsw_gov_au.md) +- [City of Canada Bay Council](./doc/source/canadabay_nsw_gov_au.md) +- [Inner West Council (NSW)](./doc/source/innerwest_nsw_gov_au.md) +- [Ku-ring-gai Council](./doc/source/kuringgai_nsw_gov_au.md) +- [Macedon Ranges Shire Council, Melbourne](./doc/source/mrsc_vic_gov_au.md) +- [Maroondah City Council](./doc/source/maroondah_vic_gov_au.md) +- [Melton City Council, Melbourne](./doc/source/melton_vic_gov_au.md) +- [Nillumbik Shire Council](./doc/source/nillumbik_vic_gov_au.md) +- [North Adelaide Waste Management Authority, South Australia](./doc/source/nawma_sa_gov_au.md) +- [RecycleSmart](./doc/source/recyclesmart_com.md) +- [Stonnington City Council, Melbourne](./doc/source/stonnington_vic_gov_au.md) +- [The Hills Council, Sydney](./doc/source/thehills_nsw_gov_au.md) +- [Wyndham City Council, Melbourne](./doc/source/wyndham_vic_gov_au.md) + +### Austria + +- [BMV.at](./doc/source/bmv_at.md) +- [Data.Umweltprofis](./doc/source/data_umweltprofis_at.md) +- [Korneuburg Stadtservice](./doc/source/korneuburg_stadtservice_at.md) +- [WSZ-Moosburg.at](./doc/source/wsz_moosburg_at.md) + +### Belgium + +- [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) +- [AbfallNavi.de (RegioIT.de)](./doc/source/abfallnavi_de.md) +- [Abfallkalender Würzburg](./doc/source/wuerzburg_de.md) +- [Abfalltermine Forchheim](./doc/source/abfalltermine_forchheim_de.md) +- [Abfallwirtschaft Bremen](./doc/source/c_trace_de.md) +- [Abfallwirtschaft Landkreis Harburg](./doc/source/aw_harburg_de.md) +- [Abfallwirtschaft Landkreis Wolfenbüttel](./doc/source/alw_wf_de.md) +- [Abfallwirtschaft Neckar-Odenwald-Kreis](./doc/source/awn_de.md) +- [Abfallwirtschaft Rendsburg](./doc/source/awr_de.md) +- [Abfallwirtschaft Stuttgart](./doc/source/stuttgart_de.md) +- [Abfallwirtschaft Südholstein](./doc/source/awsh_de.md) +- [Abfallwirtschaft Zollernalbkreis](./doc/source/abfall_zollernalbkreis_de.md) +- [Alb-Donau-Kreis](./doc/source/buergerportal_de.md) +- [ART Trier](./doc/source/art_trier_de.md) +- [AWB Bad Kreuznach](./doc/source/awb_bad_kreuznach_de.md) +- [AWB Esslingen](./doc/source/awb_es_de.md) +- [AWB Landkreis Augsburg](./doc/source/c_trace_de.md) +- [AWB Limburg-Weilburg](./doc/source/awb_lm_de.md) +- [AWB Oldenburg](./doc/source/awb_oldenburg_de.md) +- [AWBKoeln.de](./doc/source/awbkoeln_de.md) +- [AWIDO-online.de](./doc/source/awido_de.md) +- [Berlin-Recycling.de](./doc/source/berlin_recycling_de.md) +- [Bogenschuetz-Entsorgung.de](./doc/source/infeo_at.md) +- [Biedenkopf MZF](./doc/source/buergerportal_de.md) +- [BSR.de / Berliner Stadtreinigungsbetriebe](./doc/source/bsr_de.md) +- [C-Trace.de](./doc/source/c_trace_de.md) +- [Cochem-Zell](./doc/source/buergerportal_de.md) +- [EGN-Abfallkalender.de](./doc/source/egn_abfallkalender_de.md) +- [Erlangen-Höchstadt](./doc/source/erlangen_hoechstadt_de.md) +- [Jumomind.de](./doc/source/jumomind_de.md) +- [KAEV Niederlausitz](./doc/source/kaev_niederlausitz_de.md) +- [KWB-Goslar.de](./doc/source/kwb_goslar_de.md) +- [KWU-Entsorgung](./doc/source/kwu_de.md) +- [Landkreis-Wittmund.de](./doc/source/landkreis_wittmund_de.md) +- [Landkreis Rhön Grabfeld](./doc/source/landkreis_rhoen_grabfeld.md) +- [Landkreis Schwäbisch Hall](./doc/source/lrasha_de.md) +- [Muellmax.de](./doc/source/muellmax_de.md) +- [MyMuell App](./doc/source/jumomind_de.md) +- [Neunkirchen Siegerland](./doc/source/abfall_neunkirchen_siegerland_de.md) +- [RegioEntsorgung](./doc/source/regioentsorgung_de.md) +- [Rhein-Hunsrück Entsorgung (RHE)](./doc/source/rh_entsorgung_de.md) +- [Sector27.de](./doc/source/sector27_de.md) +- [Stadtreinigung Dresden](./doc/source/stadtreinigung_dresden_de.md) +- [Stadtreinigung.Hamburg](./doc/source/stadtreinigung_hamburg.md) +- [Stadtreinigung-Leipzig.de](./doc/source/stadtreinigung_leipzig_de.md) +- [Stadt-Willich.de](.doc/source/stadt_willich_de.md) +- [StadtService Brühl](.doc/source/stadtservice_bruehl_de.md) +- [Städteservice Raunheim Rüsselsheim](./doc/source/staedteservice_de.md) +- [Südbrandenburgischer Abfallzweckverband](./doc/source/sbazv_de.md) +- [Umweltbetrieb Stadt Bielefeld](./doc/source/bielefeld_de.md) +- [WAS Wolfsburg](./doc/source/was_wolfsburg_de.md) +- [Wermelskirchen](./doc/source/wermelskirchen_de.md) +- [Zweckverband Abfallwirtschaft Werra-Meißner-Kreis](./doc/source/zva_wmk_de.md) + +### Lithuania + +- [Kauno švara](./doc/source/grafikai_svara_lt.md) + +### Netherlands + +- [HVCGroep and others](./doc/source/hvcgroep_nl.md) +- [Ximmio](./doc/source/ximmio_nl.md) + +### New Zealand + +- [Auckland](./doc/source/aucklandcouncil_govt_nz.md) +- [Christchurch](./doc/source/ccc_govt_nz.md) +- [Gore, Invercargill & Southland](./doc/source/wastenet_org_nz.md) +- [Horowhenua District](./doc/source/horowhenua_govt_nz.md) +- [Waipa District](./doc/source/waipa_nz.md) +- [Wellington](./doc/source/wellington_govt_nz.md) + +### Norway + +- [Min Renovasjon](./doc/source/minrenovasjon_no.md) +- [Oslo Kommune](./doc/source/oslokommune_no.md) + +### Poland + +- [Warsaw](./doc/source/warszawa19115_pl.md) +- [Multiple communities - ecoharmonogram](./doc/source/ecoharmonogram_pl.md) + +### Sweden + +- [Lerum.se](./doc/source/lerum_se.md) +- [Ronneby Miljöteknik](./doc/source/miljoteknik_se.md) +- [SSAM.se](./doc/source/ssam_se.md) +- [srvatervinning.se](./doc/source/srvatervinning_se.md) +- [Sysav.se](./doc/source/sysav_se.md) +- [Vasyd.se](./doc/source/vasyd_se.md) + +### Switzerland + +- [A-Region.ch](./doc/source/a_region_ch.md) +- [Lindau.ch](./doc/source/lindau_ch.md) + +### United States of America + +- [PGH.ST](./doc/source/pgh_st.md) +- [Republic Services](./doc/source/republicservices_com.md) +- [Seattle Public Utilities](./doc/source/seattle_gov.md) + +### United Kingdom + +- [Bracknell Forest Council - bracknell-forest.gov.uk](./doc/source/bracknell_forest_gov_uk.md) +- [Bradford Metropolitan District Council - bradford.gov.uk](./doc/source/bradford_gov_uk.md) +- [Braintree District Council - bracknell-forest.gov.uk](./doc/source/braintree_gov_uk.md) +- [Cambridge City Council - cambridge.gov.uk](./doc/source/cambridge_gov_uk.md) +- [Canterbury City Council - canterbury.gov.uk](./doc/source/canterbury_gov_uk.md) +- [Cheshire East Council - cheshireeast.gov.uk](./doc/source/cheshire_east_gov_uk.md) +- [Chesterfield Borough Council - chesterfield.gov.uk](./doc/source/chesterfield_gov_uk.md) +- [Colchester Borough Council - colchester.gov.uk](./doc/source/colchester_gov_uk.md) +- [Cornwall Council - cornwall.gov.uk](./doc/source/cornwall_gov_uk.md) +- [Derby City Council](./doc/source/derby_gov_uk.md) +- [Eastbourne Borough Council - lewes-eastbourne.gov.uk](./doc/source/environmentfirst_co_uk.md) +- [Elmbridge Borough Council - elmbridge_gov_uk](./doc/source/elmbridge_gov_uk.md) +- [Guildford Borough Council - guildford.gov.uk](./doc/source/guildford_gov_uk.md) +- [Harborough District Council - www.harborough.gov.uk](./doc/source/fccenvironment_co_uk.md) +- [Huntingdonshire District Council - huntingdonshire.gov.uk](./doc/source/huntingdonshire_gov_uk.md) +- [The Royal Borough of Kingston - kingston.gov.uk](./doc/source/kingston_gov_uk.md) +- [Lewes District Council - lewes-eastbourne.gov.uk](./doc/source/environmentfirst_co_uk.md) +- [London Borough of Lewisham - lewisham.gov.uk](.doc/source/lewisham_gov_uk.md) +- [Manchester City Council - manchester.gov.uk](./doc/source/manchester_uk.md) +- [Middlesbrough Countil - middlesbrough.gov.uk](./doc/source/middlesbrough_gov_uk.md) +- [Newcastle City Council - newcastle.gov.uk](./doc/source/newcastle_gov_uk.md) +- [North Somerset Council - n-somerset.gov.uk](./doc/source/nsomerset_gov_uk.md) +- [Nottingham City Council - nottinghamcity.gov.uk](./doc/source/nottingham_city_gov_uk.md) +- [Peterborough City Council - peterborough.gov.uk](./doc/source/peterborough_gov_uk.md) +- [Richmondshire District Council - richmondshire.gov.uk](./doc/source/richmondshire_gov_uk.md) +- [Rushmoor Borough Council - rushmoor.gov.uk](./doc/source/rushmoor_gov_uk.md) +- [Sheffield City Council - sheffield.gov.uk](./doc/source/sheffield_gov_uk.md) +- [South Cambridgeshire District Council - scambs.gov.uk](./doc/source/scambs_gov_uk.md) +- [South Norfolk and Broadland Council - southnorfolkandbroadland.gov.uk](./doc/source/south_norfolk_and_broadland_gov_uk.md) +- [Stevenage Borough Council - stevenage.gov.uk](./doc/source/stevenage_gov_uk.md) +- [Tewkesbury Borough Council](./doc/source/tewkesbury_gov_uk.md) +- [City of York Council - york.gov.uk](./doc/source/york_gov_uk.md) +- [Walsall Council - walsall.gov.uk](./doc/source/walsall_gov_uk.md) +- [West Berkshire Council - westberks.gov.uk](./doc/source/westberks_gov_uk.md) +- [Wiltshire Council - wiltshire.gov.uk](./doc/source/wiltshire_gov_uk.md) + +## Installation + +1. Ensure that [HACS](https://github.com/hacs/integration) is installed. +2. Install the "Waste Collection Schedule" integration. +3. [Configure the integration](#configuration). +4. Restart Home Assistant. + +In case you would like to install manually: + +1. Copy the folder `waste_collection_schedule` to `custom_components` in your Home Assistant `config` folder. +2. [Configure the integration](#configuration). +3. Restart Home Assistant. + +## Configuration + +The configuration consists of two entries in the file `configuration.yaml`: + +1. Source configuration + + For each service provider, a source has to be added to the configuration. The source takes care of the arguments which are required to get the correct information from the service provider's web page, e.g. district, city, street, house number, etc. + + If you have to fetch data from multiple service providers, you have to add multiple sources. You can also add the same service provider multiple times (which only makes sense if you use this with different arguments), e.g. if you are looking for displaying the waste collection schedules for multiple districts. + +2. Sensor configuration + + A sensor is used to visualize the retrieved information, e.g. waste type, next collection date or number of days to next collection. The sensor state (which is shown in a Lovelace card) can be customized using templates. As an example, you may display the collection type only or the next collection date or a combination of all available information. + + You can also add multiple sensors per source if you are going to display the information in separate entities like the available collection types or the next collection date. + + If you are looking for displaying one entity per collection type, you just have to add one sensor per collection type. + +## 1. Configure the source(s) + +```yaml +waste_collection_schedule: + sources: + - name: SOURCE + args: + SOURCE_SPECIFIC_ARGUMENTS + customize: + - type: TYPE + alias: ALIAS + show: SHOW + icon: ICON + picture: PICTURE + use_dedicated_calendar: USE_DEDICATED_CALENDAR + dedicated_calendar_title: DEDICATED_CALENDAR_TITLE + calendar_title: CALENDAR_TITLE + fetch_time: FETCH_TIME + random_fetch_time_offset: RANDOM_FETCH_TIME_OFFSET + day_switch_time: DAY_SWITCH_TIME + separator: SEPARATOR +``` + +### Configuration Variables + +**sources** + +*(list) (required)* + +List of service providers (waste collectors). See [Source Configuration Variables](#source-configuration-variables) for a list of available configuration variables. + +**fetch_time** + +*(time) (optional, default: ```"01:00"```)* + +Time of day when to fetch new data from the source. Data will be fetched once per day. + +**random_fetch_time_offset** + +*(int) (optional, default: ```60```)* + +Random offset to the `fetch_time` in minutes. Used to distribute the fetch commands of all Home Assistant instances over a larger period of time to avoid peak loads at the service providers. + +**day_switch_time** + +*(time) (optional, default: ```"10:00"```)* + +Time of day when today's collection is going to expire and hence will not be displayed anymore. + +How it works: If you set the ```day_switch_time``` to 10:00 the sensor will display today's collections until 10:00. After 10:00, today's collections will not be displayed anymore. + +**separator** + +*(string) (optional, default: ```", "```)* + +Used to join entries if there are multiple entries for one day (n/a if value_templates are used). + +### Source Configuration Variables + +**name** + +*(string) (required)* + +Name of the source (service provider). Equates to the file name (without ```.py```) of the source. See [Supported Service Providers](#supported-service-providers) for a list of available sources. + +**args** + +*(dict) (optional)* + +Source (service provider) specific arguments, e.g. district, city, street, waste type, etc. See [Supported Service Providers](#supported-service-providers) for details. + +**customize** + +*(dict) (optional)* + +Used to customize the retrieved data from a source (service provider). See [Customize Source](#customize-source) for a list of available configuration variables. + +**calendar_title** + +*(string) (optional)* + +Alternative title for source in Home Assistant calendar. + +### Customize Source + +Used to customize the retrieved data from a source (service provider). + +**type** + +*(dict) (required)* + +Type of waste as it has been retrieved by the source (service provider). + +**alias** + +*(string) (optional, default: ```None```)* + +Optional, usually better readable name for type of waste to be collected. + +**show** + +*(boolean) (optional, default: ```True```)* + +Show or hide collections with the given waste type. + +**icon** + +*(string) (optional, default: ```None```)* + +Alternative icon for waste type. + +**picture** + +*(string) (optional, default: ```None```)* + +Optional picture for waste type. + +**use_dedicated_calendar** + +*(boolean) (optional, default: ```False```)* + +Create a dedicated calendar for this type. + +**dedicated_calendar_title** + +*(string) (optional, default: ```None```)* + +Optional title of the dedicated calendar. If not set, the waste type will be used. + +## 2. Add sensor(s) to a source + +Add the following lines to your `configuration.yaml` file: + +```yaml +sensor: + - platform: waste_collection_schedule + source_index: SOURCE_INDEX + name: NAME + details_format: DETAILS_FORMAT + count: COUNT + leadtime: LEADTIME + value_template: VALUE_TEMPLATE + date_template: DATE_TEMPLATE + add_days_to: ADD_DAYS_TO + types: + - Waste Type 1 + - Waste Type 2 +``` + +### Configuration Variables + +**source_index** + +*(integer or list of integers) (optional, default: ```0```)* + +Reference to source (service provider). Used to assign a sensor to a specific source. Only required if you defined more than one source. The first defined source in `configuration.yaml` has the source_index 0, the second source 1, ... +If you want to have a sensor which combines the data from multiple sources, just add a list of sources here. +Example: +```yaml + source_index: [0, 1] +#or + source_index: + - 0 + - 1 +``` + +**name** + +*(string) (required)* + +Name of the sensor. + +**details_format** + +*(string) (optional, default: ```"upcoming"```)* + +Used to specify the format of the information displayed in the more-info popup of a Lovelace card. + +Possible choices: + +- ```upcoming``` shows a list of upcoming collections. + + ![Upcoming](./doc/more-info-upcoming.png) + +- ```appointment_types``` shows a list of waste types and their next collection date. + + ![Waste Types](/doc/more-info-appointment-types.png) + +- ```generic``` provides all attributes as generic Python data types. This can be used by a specialized Lovelace card (which doesn't exist so far). + + ![Generic](./doc/more-info-generic.png) + +**count** + +*(integer) (optional, default = infinite)* + +Used to limit the number of collections displayed in the more-info popup of a Lovelace card by ```count```. + +**leadtime** + +*(integer) (optional, default = infinite)* + +Used to limit the number of collections displayed in the more-info popup of a Lovelace card. Only collections within the next ```leadtime``` days will be displayed. + +**value_template** + +*(string) (optional)* + +Template string used to format the state of an entity. + +See [Template Variables](#template-variables) for a list of available variables. + +**date_template** + +*(string) (optional)* + +Template string used to format collection dates within the more-info popup. + +See [Template Variables](#template-variables) for a list of available variables. + +**add_days_to** + +*(boolean) (optional, default: ```False```)* + +Adds an attribute with the label `daysTo` and the number of days to the next collection to the entity state of the source. + +**types** + +*(list of strings) (optional)* + +Used to filter waste types. The sensor will only display collections with these type(s). + +## Template Variables + +The following variables can be used within `value_template` and `date_template`: + +| Variable | Description | Type | Comments | +|--------------------|--------------------|--------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| +| ```value.date``` | Collection date | [datetime.date](https://docs.python.org/3/library/datetime.html#datetime.date) | Use [strftime](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) to format the output. | +| ```value.daysTo``` | Days to collection | int | 0 = today, 1 = tomorrow, ... | +| ```value.types``` | Waste types | list of strings | Use `join` filter to join types. | + +## FAQ + +### 1. My Service Provider isn't supported. What can I do? + +1. A lot of service providers provide ICS/iCal data as downloads or persistent links. This can be used together with the generic [iCS/iCal](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ics.md) source. + +2. In case your schedule follows a static schema, you can use the [static](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/static.md) source. + +3. Implement a new [source](https://github.com/mampfes/hacs_waste_collection_schedule#how-to-add-new-sources) and create a PR. + +4. Raise an [issue](https://github.com/mampfes/hacs_waste_collection_schedule/issues). + +### 2. How do I format dates? + +Use [strftime](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) in `value_template` or `date_template`: + +```yaml +# returns "20.03.2020" +value_template: '{{value.date.strftime("%d.%m.%Y")}}' +date_template: '{{value.date.strftime("%d.%m.%Y")}}' + +# returns "03/20/2020" +value_template: '{{value.date.strftime("%m/%d/%Y")}}' +date_template: '{{value.date.strftime("%m/%d/%Y")}}' + +# returns "Fri, 03/20/2020" +value_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' +date_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' +``` + +### 3. How do I show the number of days to the next collection? + +Set `value_template` within the sensor configuration: + +```yaml +value_template: 'in {{value.daysTo}} days' +``` + +### 4. How do I show *Today* / *Tomorrow* instead of *in 0/1 days*? + +Set `value_template` within the sensor configuration: + +```yaml +# returns "Today" if value.daysTo == 0 +# returns "Tomorrow" if value.daysTo == 1 +# returns "in X days" if value.daysTo > 1 +value_template: '{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}' +``` + +### 5. How do I join waste types in a `value_template`? + +Use the `join` filter: + +```yaml +# returns "Garbage, Recycle" +value_template: '{{value.types|join(", ")}}' + +# returns "Garbage+Recycle" +value_template: '{{value.types|join("+")}}' +``` + +Note: If you don't specify a `value_template`, waste types will be joined using the `separator` configuration variable. + +### 6. How do I setup a sensor which shows only the days to the next collection? + +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.daysTo}}' +``` + +### 7. How do I setup a sensor which shows only the date of the next collection? + +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.date.strftime("%m/%d/%Y")}}' +``` + +### 8. How do I configure a sensor which shows only the waste type of the next collection? + +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.types|join(", ")}}' +``` + +### 9. How do I configure a sensor to show only collections of a specific waste type? + +Set `types` within the sensor configuration: + +```yaml +sensor: + - platform: waste_collection_schedule + name: next_garbage_collection + types: + - Garbage + + - platform: waste_collection_schedule + name: next_recycle_collection + types: + - Recycle +``` + +Note: If you have set an alias for a waste type, you must use the alias name. + +### 10. How can I rename an waste type? + +Set `alias` in the customize section of a source: + +```yaml +waste_collection_schedule: + sources: + - name: NAME + customize: + - type: Very long garbage name + alias: Garbage + - type: Very long recycle name + alias: Recycle +``` + +### 11. How can I hide inappropriate waste types? + +Set `show` configuration variable to *false* in the customize section of a source: + +```yaml +waste_collection_schedule: + sources: + - name: NAME + customize: + - type: Inappropriate Waste Type + show: false +``` + +### 12. How do I show a colored Lovelace card depending on the due date? + +You can use [Button Card](https://github.com/custom-cards/button-card) to create a colored Lovelace cards: + +![Button Card](./doc/button-cards.png) + +```yaml +# configuration.yaml +sensor: + - platform: waste_collection_schedule + name: MyButtonCardSensor + value_template: '{{value.types|join(", ")}}|{{value.daysTo}}|{{value.date.strftime("%d.%m.%Y")}}|{{value.date.strftime("%a")}}' +``` + +```yaml +# button-card configuration +type: 'custom:button-card' +entity: sensor.mybuttoncardsensor +layout: icon_name_state2nd +show_label: true +label: | + [[[ + var days_to = entity.state.split("|")[1] + if (days_to == 0) + { return "Today" } + else if (days_to == 1) + { return "Tomorrow" } + else + { return "in " + days_to + " days" } + ]]] +show_name: true +name: | + [[[ + return entity.state.split("|")[0] + ]]] +state: + - color: red + operator: template + value: '[[[ return entity.state.split("|")[1] == 0 ]]]' + - color: orange + operator: template + value: '[[[ return entity.state.split("|")[1] == 1 ]]]' + - value: default +``` + +### 13. Can I also use the **Garbage Collection Card** instead? + +Yes, the [Garbage Collection Card](https://github.com/amaximus/garbage-collection-card) can also be used with *Waste Collection Schedule*: + +```yaml +# configuration.yaml +sensor: + - platform: waste_collection_schedule + name: garbage_days + details_format: appointment_types + value_template: "{{ value.daysTo }}" + types: + - Garbage + + - platform: template + sensors: + garbage: + value_template: > + {% if states('sensor.garbage_days')|int > 2 %} + 2 + {% else %} + {{ states('sensor.garbage_days')|int }} + {% endif %} + attribute_templates: + next_date: "{{ state_attr('sensor.garbage_days', 'Garbage') }}" + days: "{{ states('sensor.garbage_days')|int }}" +``` + +```yaml +# garbage-collection-card configuration +entity: sensor.garbage +type: 'custom:garbage-collection-card' +``` + +### 14. How can I sort waste type specific entities? + +Prerequisites: You already have dedicated sensors per waste type and want to show the sensor with the next collection in a Lovelace card. + +Add `add_days_to: True` to the configuration of all sensors you want to sort. This will add the attribute `daysTo` which can be used by e.g. [auto-entities](https://github.com/thomasloven/lovelace-auto-entities) to sort entities by day of next collection. + +### 15. How can I disable the calendar? + +If you don't like the calendar provided by Waste Collection Schedule or you have configured some dedicated calendars per waste type and therefore don't need the global calendar any more, you can disable it so that it doesn't show up in the Calendar Dashboard any more: + +Go to `Settings` --> `Entities` and select the calendar entity provided by Waste Collection Schedule. Now disable it using the menu items. + +[![entities](https://my.home-assistant.io/badges/entities.svg)](https://my.home-assistant.io/redirect/entities/) + +### 16. I have configured multiple sources, but the sensors show only *UNAVAILABLE* + +You probably missed to add `source_index` to the sensor configuration. + +## How to add new sources + +1. Create a new source in folder `custom_components/waste_collection_schedule/waste_collection_schedule/source` with the lower case url of your service provider (e.g. `abc_com.py` for `http://www.abc.com`). +2. Don't forget to add test cases (= sample data to query the service api). +3. Run `test_sources.py` script to ensure that your source works. +4. Add documentation in folder `docs/source` and add a link to your new source on `README.md` and `info.md`. + +### Guidelines + +- A source shall return data for all available waste types. A source shall **not** provide a configuration option to limit the returned waste types. +- A source shall return data for the entire available period (including past). A source shall **not** provide a configuration option to limit the requested period. + +Filtering of data for waste types or time periods is a functionality of the framework and shall not be done by a source. + +### Source Code Example + +Example for `abc_com.py`: + +```py +import datetime +from waste_collection_schedule import Collection + + +DESCRIPTION = "Example source for abc.com" # Describe your source +URL = "abc.com" # Insert url to service homepage +TEST_CASES = { # Insert arguments for test cases using test_sources.py script + "TestName": {"arg1": 100, "arg2": "street"} +} + + +class Source: + def __init__(self, arg1, arg2): # argX correspond to the args dict in the source configuration + self._arg1 = arg1 + self._arg2 = arg2 + + def fetch(self): + entries = [] + + entries.append( + Collection( + datetime.datetime(2020, 4, 11), + "Waste Type", + ) + ) + + return entries +``` + +See also: [custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py](./custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py) + +### Debugging + +Debugging a source within Home Assistant is not recommended because startup of HA is far too slow for fast debugging cycles. + +Instead, there is a test fixture which allows to run a source from the command line. The fixture is a Python script which is located here: + +`custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py`. + +The script uses the test cases defined in the source file and runs the source with the arguments of every test case. + +By default (without additional arguments), the script tests every source file in the `source` folder and prints the number of found entries for every test case. + +Example output for `abfall_io`: + +```text +Testing source abfall_io ... + found 269 entries for Waldenbuch + found 55 entries for Landshut + found 101 entries for Schoenmackers + found 139 entries for Freudenstadt + found 190 entries for Ludwigshafen am Rhein +``` + +The script supports the following options: + +| Option | Argument | Description | +|--------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| `-s` | SOURCE | [Source name](https://github.com/mampfes/hacs_waste_collection_schedule#source-configuration-variables) (source file name without ending `.py`) | +| `-l` | - | List all found dates | +| `-i` | - | Add icon name to output. Only effective together with `-l`. | + +For debugging purposes of a single source, it is recommended to use the `-s SOURCE` option. + +Example for `abc_com.py`: + +```bash +test_sources.py -s abc_com -l -i +``` + +## Videos + +There are some videos on YouTube: + +### German + +- [Bunte Mülltonnenerinnerung mit Home Assistant](https://youtu.be/MzQgARDvRww) +- [Abfall Kalender in Home Assistant mit Erinnerung in Home Assistant](https://youtu.be/aCKLKGYiA7w) + +Please note that all these videos are **not** created by the developer of this component and therefore may be outdated, point in the wrong direction or contain errors. If you have questions, please create an issue here on GitHub. Do not ask your question in the YouTube comments because you may get wrong answers there. diff --git a/md_archive/info.md b/md_archive/info.md new file mode 100644 index 00000000..49f6817f --- /dev/null +++ b/md_archive/info.md @@ -0,0 +1,215 @@ +# Waste Collection Schedule + +Waste Collection Schedule provides schedules from waste collection service providers to Home Assistant. Additionally, it supports schedules from generic ICS files which can be stored locally or fetched from a web site. There is a high flexibility in providing the information to be displayed. + +## Examples + +Per default (without further configuration), the time to the next collection will be shown in an [entity card](https://www.home-assistant.io/lovelace/entity/): + +![Default Lovelace Card](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/default-entity.png) + +You can also setup dedicated entities per waste type and show the schedule in various formats: + +![Days to next collections](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/days-to-next-collections.png) +![Date of next collections](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/date-of-next-collections.png) +![Date and days to next collections](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/next-collections-date-and-days.png) + +The information in the more-info popup can be displayed in different formats: + +1. List of upcoming collections: + + ![More info: upcoming](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/more-info-upcoming.png) + +2. List of waste types and collection date: + + ![More info: waste types](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/more-info-appointment-types.png) + +[Button Card](https://github.com/custom-cards/button-card) can be used to create individual Lovelace cards: + +![Button Card](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/button-cards.png) + +## Documentation + +- [Full Documentation](https://github.com/mampfes/hacs_waste_collection_schedule) + +## Supported Service Providers + +Currently the following service providers are supported: + +- [Generic ICS / iCal File](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ics.md) +- [Static source](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/static.md) + +### Australia + +- [Banyule City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/banyule_vic_gov_au.md) +- [Belmont City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/belmont_wa_gov_au.md) +- [Brisbane City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/brisbane_qld_gov_au.md) +- [Campbelltown City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/campbelltown_nsw_gov_au.md) +- [City of Canada Bay Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/canadabay_nsw_gov_au.md) +- [Inner West Council (NSW)](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/innerwest_nsw_gov_au.md) +- [Ku-ring-gai Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kuringgai_nsw_gov_au.md) +- [Macedon Ranges Shire Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/mrsc_vic_gov_au.md) +- [Maroondah City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/maroondah_vic_gov_au.md) +- [Melton City Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/melton_vic_gov_au.md) +- [Nillumbik Shire Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nillumbik_vic_gov_au.md) +- [North Adelaide Waste Management Authority, South Australia](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nawma_sa_gov_au.md) +- [RecycleSmart](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/recyclesmart.md) +- [Stonnington City Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stonnington_vic_gov_au.md) +- [The Hills Council, Sydney](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/thehills_nsw_gov_au.md) +- [Wyndham City Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/sourcewyndham_vic_gov_au.md) + +### Austria + +- [BMV.at](https://github.com/mampfes/hacs_waste_collection_schedule/doc/source/bmv_at.md) +- [Data.Umweltprofis](https://github.com/mampfes/hacs_waste_collection_schedule/doc/source/data_umweltprofis_at.md) +- [Korneuburg Stadtservice](https://github.com/mampfes/hacs_waste_collection_schedule/doc/source/korneuburg_stadtservice_at.md) +- [WSZ-Moosburg.at](https://github.com/mampfes/hacs_waste_collection_schedule/doc/source/wsz_moosburg_at.md) + +### Belgium + +- [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) +- [AbfallNavi.de (RegioIT.de)](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfallnavi_de.md) +- [Abfallkalender Würzburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wuerzburg_de.md) +- [Abfalltermine Forchheim](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfalltermine_forchheim_de.md) +- [Abfallwirtschaft Bremen](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/c_trace_de.md) +- [Abfallwirtschaft Landkreis Harburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/aw_harburg_de.md) +- [Abfallwirtschaft Landkreis Wolfenbüttel](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/alw_wf_de.md) +- [Abfallwirtschaft Neckar-Odenwald-Kreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awn_de.md) +- [Abfallwirtschaft Rendsburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awr_de.md) +- [Abfallwirtschaft Stuttgart](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stuttgart_de.md) +- [Abfallwirtschaft Südholstein](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awsh_de.md) +- [Abfallwirtschaft Zollernalbkreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfall_zollernalbkreis_de.md) +- [Alb-Donau-Kreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/buergerportal_de.md) +- [ART Trier](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/art_trier_de.md) +- [AWB Bad Kreuznach](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_bad_kreuznach_de.md) +- [AWB Esslingen](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_es_de.md) +- [AWB Limburg-Weilburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_lm_de.md) +- [AWB Oldenburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_oldenburg_de.md) +- [AWBKoeln.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awbkoeln_de.md) +- [AWIDO-online.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awido_de.md) +- [Berlin-Recycling.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/berlin_recycling_de.md) +- [Biedenkopf MZV](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/buergerportal_de.md) +- [Bogenschuetz-Entsorgung.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/infeo_at.md) +- [BSR.de / Berliner Stadtreinigungsbetriebe](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bsr_de.md) +- [C-Trace.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/c_trace_de.md) +- [Cochem-Zell](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/buergerportal_de.md) +- [EGN-Abfallkalender.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/egn_abfallkalender_de.md) +- [Erlangen-Höchstadt](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/erlangen_hoechstadt_de.md) +- [Jumomind.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/jumomind_de.md) +- [KWB-Goslar.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kwb_goslar_de.md) +- [KWU-Entsorgung.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kwu_de.md) +- [KAEV Niederlausitz](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kaev_niederlausitz_de.md) +- [Landkreis-Wittmund.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/landkreis_wittmund_de.md) +- [Landkreis Rhön Grabfeld](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/landkreis_rhoen_grabfeld.md) +- [Landkreis Schwäbisch Hall](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lrasha_de.md) +- [Muellmax.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/muellmax_de.md) +- [MyMuell App](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/jumomind_de.md) +- [Neunkirchen Siegerland](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfall_neunkirchen_siegerland_de.md) +- [RegioEntsorgung](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/regioentsorgung_de.md) +- [Rhein-Hunsrück Entsorgung (RHE)](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/rh_entsorgung_de.md) +- [Sector27.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sector27_de.md) +- [Stadtreinigung Dresden](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadtreinigung_dresden_de.md) +- [Stadtreinigung.Hamburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadtreinigung_hamburg.md) +- [Stadtreinigung-Leipzig.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadtreinigung_leipzig_de.md) +- [Stadt-Willich.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadt_willich_de.md) +- [Stadtservice Brühl](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stadtservice_bruehl_de.md) +- [Städteservice Raunheim Rüsselsheim](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/staedteservice_de.md) +- [Südbrandenburgischer Abfallzweckverband](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sbazv_de.md) +- [Umweltbetrieb Stadt Bielefeld](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bielefeld_de.md) +- [WAS Wolfsburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/was_wolfsburg_de.md) +- [Wermelskirchen](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wermelskirchen_de.md) +- [Zweckverband Abfallwirtschaft Werra-Meißner-Kreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/zva_wmk_de.md) + +### Lithuania + +- [Kauno švara](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/grafikai_svara_lt.md) + +### Netherlands + +- [HVCGroep and others](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/hvcgroep_nl.md) +- [Ximmio](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ximmio_nl.md) + +### New Zealand + +- [Auckland](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/aucklandcouncil_govt_nz.md) +- [Christchurch](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ccc_govt_nz.md) +- [Gore, Invercargill & Southland](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wastenet_org_nz.md) +- [Horowhenua District](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/horowhenua_govt_nz.md) +- [Waipa District](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/waipa_nz.md) +- [Wellington](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wellington_govt_nz.md) + +### Norway + +- [Min Renovasjon](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/minrenovasjon_no.md) +- [Oslo Kommune](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/oslokommune_no.md) + +### Poland + +- [Warsaw](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/warszawa19115_pl.md) +- [Multiple communities - ecoharmonogram](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ecoharmonogram_pl.md) + +### Sweden + +- [Lerum.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lerum_se.md) +- [Ronneby Miljöteknik](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/miljoteknik_se.md) +- [srvatervinning.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/srvatervinning_se.md) +- [SSAM.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ssam_se.md) +- [Sysav.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sysav_se.md) +- [Vasyd.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/vasyd_se.md) + +### Switzerland + +- [A-Region.ch](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/a_region_ch.md) +- [Lindau.ch](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lindau_ch.md) + +### United States of America + +- [PGH.ST](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/pgh_st.md) +- [Republic Services](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/republicservices_com.md) +- [Seattle Public Utilities](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/seattle_gov.md) + +### United Kingdom + +- [Bracknell Forest Council - bracknell-forest.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bracknell_forest_gov_uk.md) +- [Bradford Metropolitan District Council - bradford.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bradford_gov_uk.md) +- [Braintree District Council - bracknell-forest.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/braintree_gov_uk.md) +- [Cambridge City Council - cambridge.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/cambridge_gov_uk.md) +- [Canterbury City Council - canterbury.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/canterbury_gov_uk.md) +- [Cheshire East Council - cheshireeast.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/cheshire_east_gov_uk.md) +- [Chesterfield Borough Council - chesterfield.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/chesterfield_gov_uk.md) +- [Colchester Borough Council - colchester.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/colchester_gov_uk.md) +- [Cornwall Council - cornwall.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/cornwall_gov_uk.md) +- [Derby City Council - derby.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/derby_gov_uk.md) +- [Eastbourne Borough Council - lewes-eastbourne.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/environmentfirst_co_uk.md) +- [Elmbridge Borough Council - elmbridge.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/elmbridge_gov_uk.md) +- [Guildford Borough Council - guildford.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/guildford_gov_uk.md) +- [Harborough District Council - www.harborough.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/fccenvironment_co_uk.md) +- [Huntingdonshire District Council - huntingdonshire.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/huntingdonshire_gov_uk.md) +- [The Royal Borough of Kingston Council - kingston.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kingston_gov_uk.md) +- [Lewes District Council - lewes-eastbourne.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/environmentfirst_co_uk.md) +- [London Borough of Lewisham - lewisham.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lewisham_gov_uk.md) +- [Manchester City Council - manchester.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/manchester_uk.md) +- [Middlesbrough Council - middlesbrough.gov.uk](https://www.middlesbrough.gov.uk/bin-collection-dates) +- [Newcastle City Council - newcastle.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/newcastle_gov_uk.md) +- [North Somerset Council - n-somerset.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nsomerset_gov_uk.md) +- [Nottingham City Council - nottinghamcity.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nottingham_city_gov_uk.md) +- [Peterborough City Council - peterborough.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/peterborough_gov_uk.md) +- [Richmondshire District Council - richmondshire.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/richmondshire_gov_uk.md) +- [Rushmoor Borough Council - rushmoor.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/rushmoor_gov_uk.md) +- [Sheffield City Council - Sheffield.gov.uk]((https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sheffield_gov_uk.md) +- [South Cambridgeshire District Council - scambs.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/scambs_gov_uk.md) +- [South Norfolk and Broadland Council - southnorfolkandbroadland.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/south_norfolk_and_broadland_gov_uk.md) +- [Stevenage Borough Council - stevenage.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stevenage_gov_uk.md) +- [Tewkesbury Borough Council](./doc/source/tewkesbury_gov_uk.md) +- [City of York Council - york.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/york_gov_uk.md) +- [Walsall Council - walsall.gov.uk](./doc/source/walsall_gov_uk.md) +- [West Berkshire Council - westberks.gov.uk](./doc/source/westberks_gov_uk.md) +- [Wiltshire Council - wiltshire.gov.uk](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wiltshire_gov_uk.md) diff --git a/update_docu_links.py b/update_docu_links.py new file mode 100755 index 00000000..551083c4 --- /dev/null +++ b/update_docu_links.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 + +import argparse +import importlib +import re +import site +from pathlib import Path + +import yaml + +SECRET_FILENAME = "secrets.yaml" +SECRET_REGEX = re.compile(r"!secret\s(\w+)") + +BLACK_LIST = {"ics", "static", "example"} + +START_COUNTRY_SECTION = "" +END_COUNTRY_SECTION = "" + + +def main(): + parser = argparse.ArgumentParser(description="Test sources.") + args = parser.parse_args() + + # read secrets.yaml + secrets = {} + try: + with open(SECRET_FILENAME) as stream: + try: + secrets = yaml.safe_load(stream) + except yaml.YAMLError as exc: + print(exc) + except FileNotFoundError: + # ignore missing secrets.yaml + pass + + package_dir = ( + Path(__file__).resolve().parents[0] + / "custom_components" + / "waste_collection_schedule" + ) + source_dir = package_dir / "waste_collection_schedule" / "source" + print(source_dir) + + # add module directory to path + site.addsitedir(str(package_dir)) + + files = filter( + lambda x: x != "__init__", + map(lambda x: x.stem, source_dir.glob("*.py")), + ) + + sources = [] + + # retrieve all data from sources + for f in files: + # iterate through all *.py files in waste_collection_schedule/source + module = importlib.import_module(f"waste_collection_schedule.source.{f}") + + title = module.TITLE + url = module.URL + country = getattr(module, "COUNTRY", f.split("_")[-1]) + + if title is not None: + sources.append( + SourceInfo(filename=f, title=title, url=url, country=country) + ) + + extra_info = getattr(module, "EXTRA_INFO", []) + if callable(extra_info): + extra_info = extra_info() + for e in extra_info: + sources.append( + SourceInfo( + filename=f, + title=e.get("title", title), + url=e.get("url", url), + country=e.get("country", country), + ) + ) + + # sort into countries + country_code_map = make_country_code_map() + countries = {} + zombies = [] + for s in sources: + if s.filename in BLACK_LIST: + continue # skip + + # extract country code + code = s.country + if code in country_code_map: + countries.setdefault(country_code_map[code]["name"], []).append(s) + else: + zombies.append(s) + + update_readme_md(countries) + update_info_md(countries) + + print("Zombies =========================") + for z in zombies: + print(z) + + +def beautify_url(url): + url = url.removesuffix("/") + url = url.removeprefix("http://") + url = url.removeprefix("https://") + url = url.removeprefix("www.") + return url + + +def update_readme_md(countries): + # generate country list + str = "" + for country in sorted(countries): + str += "
\n" + str += f"{country}\n" + str += "\n" + + for e in sorted(countries[country], key=lambda e: e.title.lower()): + # print(f" {e.title} - {beautify_url(e.url)}") + str += ( + f"- [{e.title}](/doc/source/{e.filename}.md) / {beautify_url(e.url)}\n" + ) + + str += "
\n" + str += "\n" + + # read entire file + with open("README.md") as f: + md = f.read() + + # find beginning and end of country section + start_pos = md.index(START_COUNTRY_SECTION) + len(START_COUNTRY_SECTION) + 1 + end_pos = md.index(END_COUNTRY_SECTION) + + md = md[:start_pos] + str + md[end_pos:] + + # write entire file + with open("README.md", "w") as f: + f.write(md) + + +def update_info_md(countries): + # generate country list + str = "" + for country in sorted(countries): + str += f"| {country} | " + str += ", ".join( + [e.title for e in sorted(countries[country], key=lambda e: e.title.lower())] + ) + str += " |\n" + + # read entire file + with open("info.md") as f: + md = f.read() + + # find beginning and end of country section + start_pos = md.index(START_COUNTRY_SECTION) + len(START_COUNTRY_SECTION) + 1 + end_pos = md.index(END_COUNTRY_SECTION) + + md = md[:start_pos] + str + md[end_pos:] + + # write entire file + with open("info.md", "w") as f: + f.write(md) + + +class SourceInfo: + def __init__(self, filename, title, url, country): + self._filename = filename + self._title = title + self._url = url + self._country = country + + def __repr__(self): + return f"filename:{self._filename}, title:{self._title}, url:{self._url}, country:{self._country}" + + @property + def filename(self): + return self._filename + + @property + def title(self): + return self._title + + @property + def url(self): + return self._url + + @property + def country(self): + return self._country + + +def make_country_code_map(): + return {x["code"]: x for x in COUNTRYCODES} + + +COUNTRYCODES = [ + { + "code": "au", + "name": "Australia", + }, + { + "code": "at", + "name": "Austria", + }, + { + "code": "be", + "name": "Belgium", + }, + { + "code": "ca", + "name": "Canada", + }, + { + "code": "de", + "name": "Germany", + }, + { + "code": "hamburg", + "name": "Germany", + }, + { + "code": "lt", + "name": "Lithuania", + }, + { + "code": "nl", + "name": "Netherlands", + }, + { + "code": "nz", + "name": "New Zealand", + }, + { + "code": "no", + "name": "Norway", + }, + { + "code": "pl", + "name": "Poland", + }, + { + "code": "se", + "name": "Sweden", + }, + { + "code": "ch", + "name": "Switzerland", + }, + { + "code": "us", + "name": "United States of America", + }, + { + "code": "uk", + "name": "United Kingdom", + }, +] + +if __name__ == "__main__": + main()