diff --git a/README.md b/README.md
index 7ec765ee..c2bb12a3 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@ Currently the following service providers are supported:
- [AbfallNavi / RegioIT.de](./doc/source/regioit_de.md)
- [AbfallPlus.de / Abfall.IO](./doc/source/abfall_io.md)
- [AWBKoeln.de](./doc/source/awbkoeln_de.md)
+- [BSR.de / Berliner Stadtreinigungsbetriebe](./doc/source/bsr_de.md)
- [Generic ICS File](./doc/source/ics.md)
- [Jumomind.de](./doc/source/jumomind_de.md)
- [Stadtreinigung.Hamburg](./doc/source/stadtreinigung_hamburg.md)
diff --git a/custom_components/waste_collection_schedule/package/source/bsr_de.py b/custom_components/waste_collection_schedule/package/source/bsr_de.py
new file mode 100644
index 00000000..945ad8e8
--- /dev/null
+++ b/custom_components/waste_collection_schedule/package/source/bsr_de.py
@@ -0,0 +1,111 @@
+import requests
+import datetime
+import icalendar
+from collections import OrderedDict
+import urllib.parse
+
+from ..helpers import CollectionAppointment
+
+DESCRIPTION = "Source for Berliner Stadtreinigungsbetriebe"
+URL = "bsr.de"
+TEST_CASES = OrderedDict(
+ [
+ (
+ "Bahnhofstr., 12159 Berlin (Tempelhof-Schöneberg)",
+ {
+ "abf_strasse": "Bahnhofstr., 12159 Berlin (Tempelhof-Schöneberg)",
+ "abf_hausnr": 1,
+ },
+ ),
+ (
+ "Am Ried, 13467 Berlin (Reinickendorf)",
+ {
+ "abf_strasse": "Am Ried, 13467 Berlin (Reinickendorf)",
+ "abf_hausnr": "11G",
+ },
+ )
+ ]
+)
+
+
+def myquote(s):
+ # bsr uses strange quoting
+ return urllib.parse.quote(s, safe=",()")
+
+
+class Source:
+ def __init__(self, abf_strasse, abf_hausnr):
+ self._abf_strasse = abf_strasse
+ self._abf_hausnr = abf_hausnr
+
+ def fetch(self):
+ # get cookie
+ r = requests.get("https://www.bsr.de/abfuhrkalender-20520.php")
+ cookies = r.cookies
+
+ # get street name only (without PLZ)
+ street = self._abf_strasse.split(",")[0]
+
+ # start search using string name (without PLZ)
+ args = {"script": "dynamic_search", "step": 1, "q": street}
+ r = requests.get(
+ "https://www.bsr.de/abfuhrkalender_ajax.php", params=args, cookies=cookies
+ )
+
+ # retrieve house number list
+ args = {"script": "dynamic_search", "step": 2, "q": self._abf_strasse}
+ r = requests.get(
+ "https://www.bsr.de/abfuhrkalender_ajax.php", params=args, cookies=cookies
+ )
+
+ args = {
+ "abf_strasse": street,
+ "abf_hausnr": self._abf_hausnr,
+ "tab_control": "Jahr",
+ "abf_config_weihnachtsbaeume": "",
+ "abf_config_restmuell": "on",
+ "abf_config_biogut": "on",
+ "abf_config_wertstoffe": "on",
+ "abf_config_laubtonne": "on",
+# "abf_selectmonth": "5 2020",
+# "abf_datepicker": "28.04.2020",
+# "listitems":7,
+ }
+ r = requests.post(
+ "https://www.bsr.de/abfuhrkalender_ajax.php?script=dynamic_kalender_ajax", data=args, cookies=cookies
+ )
+
+ args = {
+ "script": "dynamic_iCal_ajax",
+ "abf_strasse": self._abf_strasse,
+ "abf_hausnr": self._abf_hausnr,
+ "tab_control": "Jahr",
+ "abf_config_weihnachtsbaeume": "",
+ "abf_config_restmuell": "on",
+ "abf_config_biogut": "on",
+ "abf_config_wertstoffe": "on",
+ "abf_config_laubtonne": "on",
+# "abf_selectmonth": "5 2020",
+# "listitems":7,
+ }
+
+ # create url using private url encoding
+ encoded = map(lambda key: f"{key}={myquote(str(args[key]))}", args.keys())
+ url = "https://www.bsr.de/abfuhrkalender_ajax.php?" + "&".join(encoded)
+ r = requests.get(url, cookies=cookies)
+
+ # parse ics file
+ calender = icalendar.Calendar.from_ical(r.text)
+
+ entries = []
+ for e in calender.walk():
+ if e.name == "VEVENT":
+ dtstart = None
+ if type(e.get("dtstart").dt) == datetime.date:
+ dtstart = e.get("dtstart").dt
+ elif type(e.get("dtstart").dt) == datetime.datetime:
+ dtstart = e.get("dtstart").dt.date()
+ summary = str(e.get("summary"))
+ entries.append(CollectionAppointment(dtstart, summary))
+
+ return entries
diff --git a/custom_components/waste_collection_schedule/package/wizard/bsr_de.py b/custom_components/waste_collection_schedule/package/wizard/bsr_de.py
new file mode 100755
index 00000000..ad9ae86f
--- /dev/null
+++ b/custom_components/waste_collection_schedule/package/wizard/bsr_de.py
@@ -0,0 +1,72 @@
+#!/usr/bin/python3
+
+import inquirer
+import requests
+import json
+
+
+def main():
+ # get cookies
+ r = requests.get("https://www.bsr.de/abfuhrkalender-20520.php")
+ cookies = r.cookies
+
+ while True:
+ questions = [inquirer.Text("q", message="Enter search string for street")]
+ answers = inquirer.prompt(questions)
+
+ args = {"script": "dynamic_search", "step": 1, "q": answers["q"]}
+
+ r = requests.get(
+ "https://www.bsr.de/abfuhrkalender_ajax.php", params=args, cookies=cookies
+ )
+
+ data = json.loads(r.text)
+ if (
+ len(data) == 1 and data[0]["value"] == "Keine Adresse gefunden"
+ ): # {'value': 'Keine Adresse gefunden'}
+ print("Search returned no result. Please try again.")
+ else:
+ break
+
+ street_choices = []
+ for d in data:
+ street_choices.append(d["value"])
+
+ # select street
+ questions = [
+ inquirer.List("abf_strasse", choices=street_choices, message="Select street")
+ ]
+ answers = inquirer.prompt(questions)
+
+ # retrieve house number list
+ args = {"script": "dynamic_search", "step": 2, "q": answers["abf_strasse"]}
+
+ r = requests.get(
+ "https://www.bsr.de/abfuhrkalender_ajax.php", params=args, cookies=cookies
+ )
+
+ # select house number
+ data = json.loads(r.text)
+ house_number_choices = []
+ for d in data.values():
+ house_number_choices.append((d["FullStreet"], d["HouseNo"]))
+
+ questions = [
+ inquirer.List(
+ "abf_hausnr", choices=house_number_choices, message="Select house number"
+ )
+ ]
+ answers.update(inquirer.prompt(questions))
+
+ print("Copy the following statements into your configuration.yaml:\n")
+ print("# waste_collection_schedule source configuration")
+ print("waste_collection_schedule:")
+ print(" sources:")
+ print(" - name: bsr_de")
+ print(" args:")
+ for key, value in answers.items():
+ print(f" {key}: {value}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/doc/source/bsr_de.md b/doc/source/bsr_de.md
new file mode 100644
index 00000000..5ec9eaf4
--- /dev/null
+++ b/doc/source/bsr_de.md
@@ -0,0 +1,41 @@
+# Stadtreinigung.Hamburg
+
+Add support for schedules provided by `Berliner Stadtreinigungsbetriebe`.
+
+## Configuration via configuration.yaml
+
+```yaml
+waste_collection_schedule:
+ sources:
+ - name: bsr_de
+ args:
+ abf_strasse: STRASSE
+ abf_hausnr: HAUSNR
+```
+
+### Configuration Variables
+
+**abf_strasse**
+*(string) (required)*
+
+**abf_hausnr**
+*(string) (required)*
+
+## Example
+
+```yaml
+waste_collection_schedule:
+ sources:
+ - name: bsr_de
+ args:
+ abf_strasse: "Bahnhofstr., 12159 Berlin (Tempelhof-Schöneberg)"
+ abf_hausnr: 1
+```
+
+## How to get the source arguments
+
+There is a script with an interactive command line interface which generates the required source configuration:
+
+[https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/custom_components/waste_collection_schedule/package/wizard/bsr_de.py](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/custom_components/waste_collection_schedule/package/wizard/bsr_de.py).
+
+Just run this script from a shell and answer the questions.
diff --git a/info.md b/info.md
index f60fa5f9..21366c63 100644
--- a/info.md
+++ b/info.md
@@ -28,10 +28,11 @@ Alternative details view showing the list of appointment types and their next ev
Currently the following service providers are supported:
-- Abfall_Kreis_Tuebingen.de
-- AbfallNavi / RegioIT.de
-- AbfallPlus.de / Abfall.IO
-- AWBKoeln.de
+- [Abfall_Kreis_Tuebingen.de]([Abfall_Kreis_Tuebingen.de)
+- [AbfallNavi by RegioIT.de](RegioIT.de)
+- [AbfallPlus.de](AbfallPlus.de) / [Abfall.IO](Abfall.IO)
+- [AWBKoeln.de](AWBKoeln.de)
+- [BSR.de / Berliner Stadtreinigungsbetriebe](bsr.de)
- Generic ICS File
-- Jumomind.de
-- Stadtreinigung.Hamburg
+- [Jumomind.de](Jumomind.de)
+- [Stadtreinigung.Hamburg](Stadtreinigung.Hamburg)