add source multiple to combine multiple sources to one Entity (#1810)

* add source multiple to combine multiple sources to one Entity

* typo

---------

Co-authored-by: 5ila5 <5ila5@users.noreply.github.com>
This commit is contained in:
5ila5
2024-03-03 00:13:01 +01:00
committed by GitHub
parent 935bfc55d2
commit 429df282c8
5 changed files with 296 additions and 1 deletions

View File

@@ -19,6 +19,8 @@ Waste collection schedules in the following formats and countries are supported.
- [Generic ICS / iCal File](/doc/source/ics.md)
- [User Specified](/doc/source/static.md)
- [Multiple Sources Wrapper](/doc/source/multiple.md)
</details>
<!--Begin of country section-->

View File

@@ -0,0 +1,139 @@
import importlib
import logging
URL = None
TITLE = "Multiple Sources"
DESCRIPTION = "Source wrapper for multiple waste collection schedules."
TEST_CASES = {
"two static": {
"static": [
{"type": "Dates only", "dates": ["2022-01-01", "2022-01-01"]},
{
"type": "First day of month",
"frequency": "MONTHLY",
"interval": 1,
"start": "2022-01-01",
"until": "2022-12-31",
},
],
},
"multiple ics": {
"ics": [
{
"url": "https://servicebetrieb.koblenz.de/abfallwirtschaft/entsorgungstermine-digital/entsorgungstermine-2023-digital/altstadt-2023.ics?cid=2ui7"
},
{
"url": "https://recollect.a.ssl.fastly.net/api/places/BCCDF30E-578B-11E4-AD38-5839C200407A/services/208/events.en.ics",
"split_at": "\\, (?:and )?|(?: and )",
},
]
},
"static and ics": {
"static": {"type": "Dates only", "dates": ["2022-01-01", "2022-01-01"]},
"ics": {
"url": "https://sperrmuell.erlensee.de/?type=reminder",
"method": "POST",
"params": {
"street": 8,
"eventType[]": [27, 23, 19, 20, 21, 24, 22, 25, 26],
"timeframe": 23,
"download": "ical",
},
},
},
"multiple different sources": {
"lund_se": {"street_address": "Lokföraregatan 7, LUND (19120)"},
"meinawb_de": {
"city": "Oberzissen",
"street": "Lindenstrasse",
"house_number": "1",
},
"jumomind_de": {
"service_id": "mymuell",
"city": "Bad Wünnenberg-Bleiwäsche",
},
},
"multiple different with two static": {
"lund_se": {"street_address": "Lokföraregatan 7, LUND (19120)"},
"nawma_sa_gov_au": {
"street_number": "128",
"street_name": "Bridge Road",
"suburb": "Pooraka",
},
"static": [
{"type": "Dates only", "dates": ["2024-01-01", "2024-01-24"]},
{
"type": "First day of month",
"frequency": "MONTHLY",
"interval": 1,
"start": "2022-01-01",
"until": "2022-12-31",
},
],
},
}
LOGGER = logging.getLogger(__name__)
def get_source(source: str, args: dict | list[dict]) -> list:
if isinstance(args, list):
return [
getattr(
importlib.import_module(f"waste_collection_schedule.source.{source}"),
"Source",
)(**arg)
for arg in args
]
return [
getattr(
importlib.import_module(f"waste_collection_schedule.source.{source}"),
"Source",
)(**args)
]
def check_source_type(data):
"""Check if the type of 'data' matches either 'dict[str, dict]' or 'dict[str, list[dict]]'."""
if isinstance(data, dict):
# Check if all keys are strings
if all(isinstance(key, str) for key in data.keys()):
# Check if all values are either dictionaries or lists of dictionaries
if all(
isinstance(value, dict)
or (
isinstance(value, list)
and all(isinstance(value2, dict) for value2 in value)
)
for value in data.values()
):
return True
return False
class Source:
def __init__(self, **sources: dict[str, dict] | dict[str, list[dict]]):
# test for correct source format
if not check_source_type(sources):
raise ValueError(
f"Invalid source format provided should be a list of dictionaries or a list of list of dictionaries but is {type(sources)}, please take a look at the examples"
)
self._sources: list = []
for source, args in sources.items():
self._sources += get_source(source, args)
def fetch(self):
dates = []
fails = 0
for source in self._sources:
try:
dates.extend(source.fetch())
except Exception as e:
fails += 1
LOGGER.error(f"Error fetching dates from source {source}: {e}")
if fails == len(self._sources):
raise RuntimeError("Failed to fetch dates from all sources")
return dates

View File

@@ -38,6 +38,8 @@ To use Waste Collection Schedules, additional entries need to be made in your `c
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.
If you prefer to combine the data into one calendar entity, you can use the [multiple](/doc/source/multiple.md) wrapper source.
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.
@@ -162,6 +164,8 @@ Examples:
## Combine Data from multiple Sources
### Combine Sensor Data
To combine data from multiple sources into one sensor, just add the source indexes like that:
```yaml
@@ -172,6 +176,10 @@ To combine data from multiple sources into one sensor, just add the source index
- 1
```
### Combine Source Data
If you prefer to combine the data into one calendar entity, you can use the [multiple](/doc/source/multiple.md) wrapper source.
## HomeAssistant Service to manually trigger update
If you want to trigger a manual update of the sources, you can call the service:

141
doc/source/multiple.md Normal file
View File

@@ -0,0 +1,141 @@
# Multiple Source Wrapper
Wrapper Source to include multiple sources in one calendar.
This wrapper is meant for configurations where you want to include multiple sources in one calendar entity. If you want to have multiple calendars, you should simply pass multiple sources to the `sources` parameter in the configuration.
This is just a wrapper class for other sources for further information see the documentation of the sources you want to include.
## Configuration via configuration.yaml
```yaml
waste_collection_schedule:
sources:
- name: multiple
args:
- SOURCE_NAME: SOURCE_ARGS
- SOURCE_NAME: SOURCE_ARGS
...
# or (can be mixed)
- SOURCE_NAME:
- SOURCE_ARGS
- SOURCE_ARGS
...
```
### Configuration Variables
**SOURCE_NAME**
*(string) (required)*
The name of the source to include.
**SOURCE_ARGS**
*(dict) (required)*
A dictionary of arguments for the source.
## Examples
### Two static sources
```yaml
waste_collection_schedule:
sources:
- name: multiple
args:
- static:
- type: "Dates only"
dates:
- "2022-01-01"
- "2022-01-01"
- type: "First day of month"
frequency: "MONTHLY"
interval: 1
start: "2022-01-01"
until: "2022-12-31"
```
### Two ics sources
```yaml
waste_collection_schedule:
sources:
- name: multiple
args:
- ics:
- url: "https://servicebetrieb.koblenz.de/abfallwirtschaft/entsorgungstermine-digital/entsorgungstermine-2023-digital/altstadt-2023.ics?cid=2ui7"
- url: "https://recollect.a.ssl.fastly.net/api/places/BCCDF30E-578B-11E4-AD38-5839C200407A/services/208/events.en.ics"
split_at: "\\, (?:and )?|(?: and )"
```
### One Static and ics source each
```yaml
waste_collection_schedule:
sources:
- name: multiple
- static:
type: "Dates only"
dates:
- "2022-01-01"
- "2022-01-01"
- ics:
url: "https://sperrmuell.erlensee.de/?type=reminder"
method: "POST"
params:
street: 8
eventType[]:
- 27
- 23
- 19
- 20
- 21
- 24
- 22
- 25
- 26
timeframe: 23
download: "ical"
```
### Three "normal" sources
```yaml
waste_collection_schedule:
sources:
- name: multiple
- lund_se:
street_address: "Lokföraregatan 7, LUND (19120)"
- meinawb_de:
city: "Oberzissen"
street: "Lindenstrasse"
house_number: "1"
- jumomind_de:
service_id: "mymuell"
city: "Bad Wünnenberg-Bleiwäsche"
```
### Two "normal" sources with two static sources
```yaml
waste_collection_schedule:
sources:
- name: multiple
- lund_se:
street_address: "Lokföraregatan 7, LUND (19120)"
- nawma_sa_gov_au:
street_number: "128"
street_name: "Bridge Road"
suburb: "Pooraka"
- static:
- type: "Dates only"
dates:
- "2024-01-01"
- "2024-01-24"
- type: "First day of month"
frequency: "MONTHLY"
interval: 1
start: "2022-01-01"
until: "2022-12-31"
```

View File

@@ -11,7 +11,12 @@ import yaml
SECRET_FILENAME = "secrets.yaml"
SECRET_REGEX = re.compile(r"!secret\s(\w+)")
BLACK_LIST = {"/doc/source/ics.md", "/doc/source/static.md", "/doc/source/example.md"}
BLACK_LIST = {
"/doc/source/ics.md",
"/doc/source/static.md",
"/doc/source/multiple.md",
"/doc/source/example.md",
}
START_COUNTRY_SECTION = "<!--Begin of country section-->"
END_COUNTRY_SECTION = "<!--End of country section-->"