Add new source AWM München (#1768)

* Add new source AWM München

* fstring + typehint + comment fix

---------

Co-authored-by: 5ila5 <5ila5@users.noreply.github.com>
This commit is contained in:
Rene Nulsch
2024-02-05 18:10:29 +01:00
committed by GitHub
parent 93d2ee8548
commit 204f7b3a0d
7 changed files with 257 additions and 38 deletions

View File

@@ -563,7 +563,6 @@ Waste collection schedules in the following formats and countries are supported.
- [Abfallwirtschaftsbetrieb Landkreis Aurich](/doc/source/c_trace_de.md) / mkw-grossefehn.de
- [Abfallwirtschaftsbetrieb Landkreis Karlsruhe](/doc/ics/awb_landkreis_karlsruhe_de.md) / awb-landkreis-karlsruhe.de
- [Abfallwirtschaftsbetrieb LK Mainz-Bingen](/doc/source/awb_mainz_bingen_de.md) / awb-mainz-bingen.de
- [Abfallwirtschaftsbetrieb München](/doc/ics/awm_muenchen_de.md) / awm-muenchen.de
- [Abfallwirtschaftsbetriebe Münster](/doc/source/muellmax_de.md) / stadt-muenster.de
- [Abfallwirtschaftsgesellschaft Landkreis Schaumburg](/doc/ics/aws_shg_de.md) / aws-shg.de
- [Abfallwirtschaftsverband Kreis Groß-Gerau](/doc/source/c_trace_de.md) / awv-gg.de
@@ -602,6 +601,7 @@ Waste collection schedules in the following formats and countries are supported.
- [AWISTA Düsseldorf](/doc/source/muellmax_de.md) / awista.de
- [Awista Starnberg](/doc/ics/awista_starnberg_de.md) / awista-starnberg.de
- [AWL Neuss](/doc/source/awlneuss_de.md) / buergerportal.awl-neuss.de
- [AWM München](/doc/source/awm_muenchen_de.md) / awm-muenchen.de
- [Bad Arolsen (MyMuell App)](/doc/source/jumomind_de.md) / mymuell.de
- [Bad Homburg vdH](/doc/source/jumomind_de.md) / bad-homburg.de
- [Bad Kissingen](/doc/source/app_abfallplus_de.md) / Abfall+ App: abfallappbk

View File

@@ -0,0 +1,170 @@
import urllib.parse
from html.parser import HTMLParser
from typing import Tuple
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 = "AWM München"
DESCRIPTION = "Source for AWM München."
URL = "https://www.awm-muenchen.de"
TEST_CASES = {
"Waltenbergerstr. 1": {
"street": "Waltenbergerstr.",
"house_number": "1",
},
"Geretsrieder Str. 10a": {
"street": "Geretsrieder Str.",
"house_number": "10a",
},
"Neureutherstr. 8": {
"street": "Neureutherstr.",
"house_number": "8",
"r_collect_cycle": "1/2;G",
},
}
ICON_MAP = {
"Restmülltonne": "mdi:delete",
"Biotonne": "mdi:leaf",
"Papiertonne": "mdi:newspaper",
"Wertstofftonne": "mdi:recycle",
}
BASE_URL = "https://www.awm-muenchen.de"
# Parser for HTML input (hidden) text
class HiddenInputParser(HTMLParser):
def __init__(self):
super().__init__()
self._args = {}
@property
def args(self):
return self._args
def handle_starttag(self, tag, attrs):
if tag == "input":
d = dict(attrs)
if str(d["type"]).lower() == "hidden":
self._args[d["name"]] = d["value"] if "value" in d else ""
class Source:
def __init__(
self,
street: str,
house_number: str,
r_collect_cycle="",
b_collect_cycle="",
p_collect_cycle="",
):
self._street = street
self._hnr = house_number
self._ics = ICS()
self._r_collect_cycle = r_collect_cycle
self._b_collect_cycle = b_collect_cycle
self._p_collect_cycle = p_collect_cycle
def fetch(self):
s = requests.session()
# special request header is required, server backend checks for Origin
headers = {
"Origin": "https://www.awm-muenchen.de",
}
s.headers.update(headers)
# request default page
r = s.get(f"{BASE_URL}/entsorgen/abfuhrkalender")
r.raise_for_status()
r.encoding = "utf-8"
step1_action_url, args = self._get_html_form_infos(r.text, "abfuhrkalender")
# add the address information
args["tx_awmabfuhrkalender_abfuhrkalender[strasse]"] = self._street
args["tx_awmabfuhrkalender_abfuhrkalender[hausnummer]"] = self._hnr
args["tx_awmabfuhrkalender_abfuhrkalender[section]"] = "address"
args["tx_awmabfuhrkalender_abfuhrkalender[submitAbfuhrkalender]"] = "true"
# ready for step 1 - we post the address
r = s.post(
step1_action_url,
data=args,
)
r.raise_for_status()
# result is the result page or the collection cycle select page
ics_action_url = ""
page_soup = BeautifulSoup(r.text, "html.parser")
if download_link := page_soup.find("a", {"class": "downloadics"}):
ics_action_url = download_link.get("href")
else:
action_url, args = self._get_html_form_infos(r.text, "abfuhrkalender")
error_message = ""
for key in ("B", "P", "R"):
if (
f"tx_awmabfuhrkalender_abfuhrkalender[leerungszyklus][{key}]"
not in args
):
if self.__getattribute__(f"_{key.lower()}_collect_cycle") == "":
cycle_options = {}
cycle_options = page_soup.find(
"form", id="abfuhrkalender"
).find_all("option")
error_message += f"Optional parameter {key.lower()}_collect_cycle required. Possible values: "
for option in cycle_options:
error_message += f"{option.get('value')} ({option.text}) "
else:
args[
f"tx_awmabfuhrkalender_abfuhrkalender[leerungszyklus][{key}]"
] = self.__getattribute__(f"_{key.lower()}_collect_cycle")
if error_message:
raise ValueError(error_message)
r = s.post(
action_url,
data=args,
)
r.raise_for_status()
page_soup = BeautifulSoup(r.text, "html.parser")
if download_link := page_soup.find("a", {"class": "downloadics"}):
ics_action_url = download_link.get("href")
else:
raise ValueError("Unknown error getting ics link with cycle options.")
# Download the ics.file
r = s.get(f"{URL}{urllib.parse.unquote(ics_action_url)}")
r.raise_for_status()
dates = self._ics.convert(r.text)
entries = []
for d in dates:
bin_type = d[1].split(",")[0].strip()
entries.append(Collection(d[0], bin_type, ICON_MAP.get(bin_type)))
return entries
def _get_html_form_infos(self, html: str, form_name: str) -> Tuple[str, dict]:
"""Return a tuple with form action url and hidden form fields."""
# collect the url where we post to
page_soup = BeautifulSoup(html, "html.parser")
form_soup = page_soup.find("form", id=form_name)
action_url = f"{URL}{urllib.parse.unquote(form_soup.get('action'))}"
# collect the hidden input fields
parser = HiddenInputParser()
parser.feed(page_soup.find("form", id=form_name).decode_contents())
return action_url, parser.args

View File

@@ -1,24 +0,0 @@
# Abfallwirtschaftsbetrieb München
Abfallwirtschaftsbetrieb München is supported by the generic [ICS](/doc/source/ics.md) source. For all available configuration options, please refer to the source description.
## How to get the configuration arguments
- Goto <https://www.awm-muenchen.de/entsorgen/abfuhrkalender> and select your location.
- Right-click on `Download ICS-Datei 20xx für Ihren Kalender` link and copy link address.
- Replace the `url` in the example configuration with this link.
- Replace the year in the url with `{%Y}`.
## Examples
### Adolaweg 1
```yaml
waste_collection_schedule:
sources:
- name: ics
args:
regex: (.*), .*
url: https://www.awm-muenchen.de/entsorgen/abfuhrkalender?tx_awmabfuhrkalender_abfuhrkalender%5Bhausnummer%5D=1&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BB%5D=1%2F2%3BG&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BP%5D=1%2F2%3BU&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BR%5D=001%3BG&tx_awmabfuhrkalender_abfuhrkalender%5Bsection%5D=ics&tx_awmabfuhrkalender_abfuhrkalender%5Bsinglestandplatz%5D=false&tx_awmabfuhrkalender_abfuhrkalender%5Bstandplatzwahl%5D=true&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bbio%5D=70082516&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bpapier%5D=70082516&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Brestmuell%5D=70082516&tx_awmabfuhrkalender_abfuhrkalender%5Bstrasse%5D=Adaloweg&tx_awmabfuhrkalender_abfuhrkalender%5Byear%5D={%Y}&cHash=e346bec0e7fdb173ae2d0e8650ecd980
```

View File

@@ -1,11 +0,0 @@
title: Abfallwirtschaftsbetrieb München
url: https://www.awm-muenchen.de
howto: |
- Goto <https://www.awm-muenchen.de/entsorgen/abfuhrkalender> and select your location.
- Right-click on `Download ICS-Datei 20xx für Ihren Kalender` link and copy link address.
- Replace the `url` in the example configuration with this link.
- Replace the year in the url with `{%Y}`.
test_cases:
Adolaweg 1:
url: "https://www.awm-muenchen.de/entsorgen/abfuhrkalender?tx_awmabfuhrkalender_abfuhrkalender%5Bhausnummer%5D=1&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BB%5D=1%2F2%3BG&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BP%5D=1%2F2%3BU&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BR%5D=001%3BG&tx_awmabfuhrkalender_abfuhrkalender%5Bsection%5D=ics&tx_awmabfuhrkalender_abfuhrkalender%5Bsinglestandplatz%5D=false&tx_awmabfuhrkalender_abfuhrkalender%5Bstandplatzwahl%5D=true&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bbio%5D=70082516&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bpapier%5D=70082516&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Brestmuell%5D=70082516&tx_awmabfuhrkalender_abfuhrkalender%5Bstrasse%5D=Adaloweg&tx_awmabfuhrkalender_abfuhrkalender%5Byear%5D={%Y}&cHash=e346bec0e7fdb173ae2d0e8650ecd980"
regex: "(.*), .*"

View File

@@ -0,0 +1,85 @@
# Abfallwirtschaftsbetrieb München
Support for schedules provided by [Abfallwirtschaftsbetrieb München](https://www.awm-muenchen.de/), Germany.
## Configuration via configuration.yaml
```yaml
waste_collection_schedule:
sources:
- name: awm_muenchen_de
args:
street: STREET
house_number: HNR
b_collect_cycle: COLLECTION CYCLE ID
p_collect_cycle: COLLECTION CYCLE ID
r_collect_cycle: COLLECTION CYCLE ID
```
### Configuration Variables
**street**
*(string) (required)*
**house_number**
*(string) (required)*
**b_collect_cycle**
*(string) (optional) (default: "")*
**p_collect_cycle**
*(string) (optional) (default: "")*
**r_collect_cycle**
*(string) (optional) (default: "")*
## Example
```yaml
waste_collection_schedule:
sources:
- name: awm_muenchen_de
args:
street: "Waltenbergerstr."
house_number: "1"
- name: awm_muenchen_de
args:
street: "Neureutherstr."
house_number: "8"
r_collect_cycle: "1/2;G"
```
Some addresses have different bin collection cycles (ex: weekly, bi-weekly). For these addresses the optional parameters are required.
## How to get the optional configuration arguments
- Setup the component without the optional parameter and restart Home Assistant
- Check the Home Assistant log for entries from this component.
- The available options are listed in the error message.
- Adjust the configuration and restart Home Assistant.
## Examples
### Waltenbergerstr. 1
```yaml
waste_collection_schedule:
sources:
- name: awm_muenchen_de
args:
street: "Waltenbergerstr."
house_number: "1"
```
### Neureutherstr. 8 with an collection cycle option
```yaml
waste_collection_schedule:
sources:
- name: awm_muenchen_de
args:
street: "Neureutherstr."
house_number: "8"
r_collect_cycle: "1/2;G"
```

View File

@@ -159,7 +159,6 @@ This source has been successfully tested with the following service providers:
- [Abfallwirtschaft Sonneberg](/doc/ics/abfallwirtschaft_sonneberg_de.md) / abfallwirtschaft-sonneberg.de
- [Abfallwirtschaftsbetrieb Ilm-Kreis](/doc/ics/ilm_kreis_de.md) / ilm-kreis.de
- [Abfallwirtschaftsbetrieb Landkreis Karlsruhe](/doc/ics/awb_landkreis_karlsruhe_de.md) / awb-landkreis-karlsruhe.de
- [Abfallwirtschaftsbetrieb München](/doc/ics/awm_muenchen_de.md) / awm-muenchen.de
- [Abfallwirtschaftsgesellschaft Landkreis Schaumburg](/doc/ics/aws_shg_de.md) / aws-shg.de
- [ALBA Braunschweig](/doc/ics/alba_bs_de.md) / alba-bs.de
- [Altmarkkreis Salzwedel](/doc/ics/abfall_app_net.md) / altmarkkreis-salzwedel.de

File diff suppressed because one or more lines are too long