diff --git a/.github/workflows/combined_ci.yml b/.github/workflows/combined_ci.yml
index 342d70e..2f97cc2 100644
--- a/.github/workflows/combined_ci.yml
+++ b/.github/workflows/combined_ci.yml
@@ -8,6 +8,11 @@ on:
pull_request:
schedule:
- cron: "0 0 * * *"
+ workflow_dispatch:
+ inputs:
+ trigger-build:
+ description: 'Trigger a manual build and push'
+ default: 'true'
jobs:
hassfest:
diff --git a/.gitignore b/.gitignore
index 6d8abe5..3abbd73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
+# Custom directories not needed.
+.vscode
+
+
@eaDir
.DS_Store
# __pycache__
@@ -19,6 +23,7 @@ __pycache__/
# C extensions
*.so
+
# Distribution / packaging
.Python
build/
diff --git a/.strings.json b/.strings.json
new file mode 100644
index 0000000..95084d3
--- /dev/null
+++ b/.strings.json
@@ -0,0 +1,7 @@
+{
+ "translations": {
+ "en": "translations/en.json",
+ "nl": "translations/nl.json"
+ }
+ }
+
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 27adb82..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- // Example of attaching to local debug server
- "name": "Python: Attach Local",
- "type": "python",
- "request": "attach",
- "port": 5678,
- "host": "localhost",
- "pathMappings": [
- {
- "localRoot": "${workspaceFolder}",
- "remoteRoot": "."
- }
- ]
- },
- {
- // Example of attaching to my production server
- "name": "Python: Attach Remote",
- "type": "python",
- "request": "attach",
- "port": 5678,
- "host": "homeassistant.local",
- "pathMappings": [
- {
- "localRoot": "${workspaceFolder}",
- "remoteRoot": "/usr/src/homeassistant"
- }
- ]
- }
- ]
-}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index a3d535d..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "python.linting.pylintEnabled": true,
- "python.linting.enabled": true,
- "python.pythonPath": "/usr/local/bin/python",
- "files.associations": {
- "*.yaml": "home-assistant"
- }
-}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
deleted file mode 100644
index 829a78f..0000000
--- a/.vscode/tasks.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "version": "2.0.0",
- "tasks": [
- {
- "label": "Run Home Assistant on port 9123",
- "type": "shell",
- "command": "scripts/develop",
- "problemMatcher": []
- },
- {
- "label": "Upgrade Home Assistant to latest (beta)",
- "type": "shell",
- "command": "scripts/upgrade",
- "problemMatcher": []
- },
- {
- "label": "Load Home Assistant from github - dev branch",
- "type": "shell",
- "command": "scripts/dev-branch",
- "problemMatcher": []
- },
- {
- "label": "Load specific version of Home Assistant",
- "type": "shell",
- "command": "scripts/specific-version",
- "problemMatcher": []
- },
- {
- "label": "Lint with ruff",
- "type": "shell",
- "command": "scripts/lint",
- "problemMatcher": []
- }
- ]
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index 8adf2e1..e0b45f3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# Afvalwijzer
+
[![custom_updater][customupdaterbadge]][customupdater]
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration)
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/hacs/integration)
@@ -8,11 +9,10 @@
[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/xirixiz)
-
_Component to integrate with the following providers._
| Provider |
-| ---------------------------------|
+| -------------------------------- |
| acv |
| afvalstoffendienstkalender (all) |
| alkmaar |
@@ -35,7 +35,6 @@ _Component to integrate with the following providers._
| hellendoorn |
| hvc |
| lingewaard |
-| meppel |
| meerlanden |
| middelburg |
| mijnafvalwijzer |
@@ -44,11 +43,13 @@ _Component to integrate with the following providers._
| peelenmaas |
| prezero |
| purmerend |
+| saver |
| rad |
| reinis |
| rd4 |
| rova |
| rmn |
+| rwm |
| schouwenand |
| spaarnelanden |
| sudwestfryslan |
@@ -64,7 +65,7 @@ _Component to integrate with the following providers._
| ximmio |
| zrd |
-This custom component dynamically creates sensor.afvalwijzer_* items. For me personally the items created are gft, restafval, papier, pmd and kerstbomen. Look in the states overview in the developer tools in Home Assistant what the sensor names for your region are and modify where necessary.
+This custom component dynamically creates sensor.afvalwijzer\_\* items. For me personally the items created are gft, restafval, papier, pmd and kerstbomen. Look in the states overview in the developer tools in Home Assistant what the sensor names for your region are and modify where necessary.
**This component will set up the following platform(s).**
@@ -75,69 +76,100 @@ This custom component dynamically creates sensor.afvalwijzer_* items. For me per
![example][exampleimg1]
The second row sorts the waste items by date using the following lovelace code
+
```yaml
- - type: custom:auto-entities
- card:
- type: glance
- filter:
- exclude:
- - entity_id: sensor.afvalwijzer_*next*
- - entity_id: sensor.afvalwijzer_day_after_tomorrow*
- - entity_id: sensor.afvalwijzer_today*
- - entity_id: sensor.afvalwijzer_tomorrow*
- - entity_id: sensor.afvalwijzer_kerstbomen*
- - entity_id: sensor.afvalwijzer_*orgen
- - entity_id: sensor.afvalwijzer_van*
- include:
- - entity_id: sensor.afvalwijzer_*
- options:
- format: date
- sort:
- method: state
- - entities:
- - style:
- background: '#62717b'
- height: 1px
- margin-left: auto
- margin-right: auto
- type: divider
- type: entities
- - type: markdown
- content: >-
-
De volgende leging is {{ states('sensor.afvalwijzer_next_type')
- }}. Dat is over {{ states('sensor.afvalwijzer_next_in_days') }} {% if
- is_state('sensor.afvalwijzer_next_in_days', '1') %}dag{% else %}dagen{%
- endif %}.
+- type: custom:auto-entities
+ card:
+ type: glance
+ filter:
+ exclude:
+ - entity_id: sensor.afvalwijzer_*next*
+ - entity_id: sensor.afvalwijzer_day_after_tomorrow*
+ - entity_id: sensor.afvalwijzer_today*
+ - entity_id: sensor.afvalwijzer_tomorrow*
+ - entity_id: sensor.afvalwijzer_kerstbomen*
+ - entity_id: sensor.afvalwijzer_*orgen
+ - entity_id: sensor.afvalwijzer_van*
+ include:
+ - entity_id: sensor.afvalwijzer_*
+ options:
+ format: date
+ sort:
+ method: state
+- entities:
+ - style:
+ background: '#62717b'
+ height: 1px
+ margin-left: auto
+ margin-right: auto
+ type: divider
+ type: entities
+- type: markdown
+ content: >-
+ De volgende leging is {{ states('sensor.afvalwijzer_next_type')
+ }}. Dat is over {{ states('sensor.afvalwijzer_next_in_days') }} {% if
+ is_state('sensor.afvalwijzer_next_in_days', '1') %}dag{% else %}dagen{%
+ endif %}.
```
More information on the reminders (ios in this case):
+
- https://github.com/xirixiz/my-hass-config/blob/master/packages/waste.yaml
- https://github.com/xirixiz/my-hass-config/blob/05d8755a737676b60faac98dc0cce91d06277939/configuration.yaml#L73
## Installation
-1. Using your tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`).
-2. If you do not have a `custom_components` directory (folder) there, you need to create it.
-3. In the `custom_components` directory (folder) create a new folder called `afvalwijzer`.
-4. Download _all_ the files from the `custom_components/afvalwijzer/` directory (folder) in this repository.
-5. Place the files you downloaded in the new directory (folder) you created.
-6. Restart Home Assistant before further configuration.
-7. Look at the `Example Configuration` section for further configuration.
-8. Restart Home Assistant again when configuration is done to activate the configuration.
-
-Using your HA configuration directory (folder) as a starting point you should now also have this:
-
-```text
-custom_components/afvalwijzer/__init__.py
-custom_components/afvalwijzer/manifest.json
-custom_components/afvalwijzer/sensor.py
+### Manual Installation
+
+1. Navigate to your Home Assistant configuration directory (where your `configuration.yaml` is located).
+2. Create a folder named `custom_components` if it doesn't exist.
+3. Inside the `custom_components` folder, create another folder named `afvalwijzer`.
+4. Clone this repository or download the source code and copy all files from the `custom_components/afvalwijzer/` directory to the newly created `afvalwijzer` folder.
+5. Restart Home Assistant to load the custom component.
+
+After following these steps, your directory structure should look like this:
+
+```markdown
+custom_components/
+afvalwijzer/
+**init**.py
+manifest.json
+sensor.py
+config_flow.py
+...
```
+### Installation via HACS
+
+1. Ensure HACS is installed in your Home Assistant setup. If not, follow the [HACS installation guide](https://hacs.xyz/docs/setup/download).
+2. Open the HACS panel in Home Assistant.
+3. Click on the `Frontend` or `Integrations` tab.
+4. Click the `+` button and search for `Afvalwijzer`.
+5. Click `Install` to add the component to your Home Assistant setup.
+6. Restart Home Assistant after the installation completes.
+
+---
+
+## Configuration
+
+### Add Integration
+
+1. Go to the **Settings** → **Devices & Services** page in Home Assistant.
+2. Click **Add Integration** and search for `Afvalwijzer`.
+3. Follow the on-screen instructions to complete the setup.
+ - Provide your postal code, street number, and any other required details.
+
+After completing the config flow, the integration will dynamically create sensors for waste collection dates based on your chosen provider.
+
+---
+
##### CUSTOM COMPONENT USAGE
+
https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_sensor
##### LOGLEVEL
-In order to extend the log level, modify the following (configuration.yaml probably)
+
+To enable debug logging for troubleshooting, add the following lines to your `configuration.yaml`:
```yaml
logger:
@@ -150,22 +182,23 @@ logger:
Here's an example of my own Home Asisstant config: https://github.com/xirixiz/home-assistant
-
###### SENSOR - CONFIGURATION.YAML
+
```yaml
- sensor:
- - platform: afvalwijzer
- provider: mijnafvalwijzer # (required, default = mijnafvalwijzer) either choose mijnafvalwijzer, afvalstoffendienstkalender or rova
- postal_code: 1234AB # (required, default = '')
- street_number: 5 # (required, default = '')
- suffix: '' # (optional, default = '')
- exclude_pickup_today: true # (optional, default = true) to take or not to take Today into account in the next pickup.
- default_label: geen # (optional, default = geen) label if no date found
- id: '' # (optional, default = '') use if you'd like to have multiple waste pickup locations in HASS
- exclude_list: '' # (optional, default = '') comma separated list of waste types (case ignored). F.e. "papier, gft"
+sensor:
+ - platform: afvalwijzer
+ provider: mijnafvalwijzer # (required, default = mijnafvalwijzer) either choose mijnafvalwijzer, afvalstoffendienstkalender or rova
+ postal_code: 1234AB # (required, default = '')
+ street_number: 5 # (required, default = '')
+ suffix: '' # (optional, default = '')
+ exclude_pickup_today: true # (optional, default = true) to take or not to take Today into account in the next pickup.
+ default_label: geen # (optional, default = geen) label if no date found
+ id: '' # (optional, default = '') use if you'd like to have multiple waste pickup locations in HASS
+ exclude_list: '' # (optional, default = '') comma separated list of waste types (case ignored). F.e. "papier, gft"
```
###### INPUT BOOLEAN (FOR AUTOMATION)
+
```yaml
input_boolean:
waste_moved:
@@ -178,6 +211,7 @@ input_boolean:
```
###### AUTOMATION
+
```yaml
automation:
- alias: Reset waste notification
@@ -206,7 +240,7 @@ automation:
- alias: Waste has not been moved
trigger:
platform: time_pattern
- hours: "/1"
+ hours: '/1'
condition:
condition: and
conditions:
@@ -224,17 +258,17 @@ automation:
action:
- service: notify.family
data:
- title: "Afval"
+ title: 'Afval'
message: 'Het is vandaag - {{ now().strftime("%d-%m-%Y") }}. Afvaltype(n): {{ states("sensor.afvalwijzer_tomorrow") }} wordt opgehaald op: {{ (now() + timedelta(days=1)).strftime("%d-%m-%Y") }}!'
data:
actions:
- - action: "MARK_WASTE_MOVED" # The key you are sending for the event
- title: "Afval buiten gezet" # The button title
+ - action: 'MARK_WASTE_MOVED' # The key you are sending for the event
+ title: 'Afval buiten gezet' # The button title
```
-***
+---
-[exampleimg1]: afvalwijzer-lovelace.png
-[exampleimg2]: afvalwijzer_lovelace.png
+[exampleimg1]: images/afvalwijzer-lovelace_2.png
+[exampleimg2]: images/afvalwijzer_lovelace_1.png
[customupdater]: https://github.com/custom-components/custom_updater
[customupdaterbadge]: https://img.shields.io/badge/custom__updater-true-success.svg
diff --git a/custom_components/afvalwijzer/__init__.py b/custom_components/afvalwijzer/__init__.py
old mode 100755
new mode 100644
index e69de29..496b7de
--- a/custom_components/afvalwijzer/__init__.py
+++ b/custom_components/afvalwijzer/__init__.py
@@ -0,0 +1,31 @@
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.typing import ConfigType
+from .const.const import DOMAIN
+
+
+async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
+ """Set up the Afvalwijzer integration."""
+ hass.data.setdefault(DOMAIN, {})
+ return True
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+ """Set up Afvalwijzer from a config entry."""
+ # Store config entry data
+ hass.data[DOMAIN][entry.entry_id] = entry.data
+
+ # Forward the setup to the sensor platform
+ await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])
+ return True
+
+
+async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
+ """Unload a config entry."""
+ # Remove stored data
+ if entry.entry_id in hass.data[DOMAIN]:
+ hass.data[DOMAIN].pop(entry.entry_id)
+
+ # Unload the sensor platform
+ await hass.config_entries.async_forward_entry_unload(entry, "sensor")
+ return True
diff --git a/custom_components/afvalwijzer/collector/main_collector.py b/custom_components/afvalwijzer/collector/main_collector.py
index c9a4b08..5f1ea96 100644
--- a/custom_components/afvalwijzer/collector/main_collector.py
+++ b/custom_components/afvalwijzer/collector/main_collector.py
@@ -8,11 +8,11 @@
SENSOR_COLLECTORS_ICALENDAR,
SENSOR_COLLECTORS_OPZET,
SENSOR_COLLECTORS_RD4,
- SENSOR_COLLECTORS_XIMMIO,
+ SENSOR_COLLECTORS_XIMMIO
)
try:
- from . import burgerportaal, circulus, deafvalapp, icalendar, mijnafvalwijzer, opzet, rd4, ximmio
+ from . import burgerportaal, circulus, deafvalapp, icalendar, mijnafvalwijzer, opzet, rd4, rwm, ximmio
except ImportError as err:
_LOGGER.error(f"Import error {err.args}")
@@ -29,15 +29,21 @@ def __init__(
exclude_list,
default_label,
):
- self.provider = provider.strip().lower()
- self.postal_code = postal_code.strip().upper()
- self.street_number = street_number.strip()
- self.suffix = suffix.strip().lower()
- self.exclude_pickup_today = exclude_pickup_today.strip()
- self.date_isoformat = date_isoformat.strip()
- self.exclude_list = exclude_list.strip().lower()
- self.default_label = default_label.strip()
+ # Ensure provider and address fields are strings
+ self.provider = str(provider).strip().lower()
+ self.postal_code = str(postal_code).strip().upper()
+ self.street_number = str(street_number).strip()
+ self.suffix = str(suffix).strip().lower()
+ # Handle boolean and string parameters correctly
+ self.exclude_pickup_today = str(exclude_pickup_today).lower() if isinstance(
+ exclude_pickup_today, bool) else str(exclude_pickup_today).strip().lower()
+ self.date_isoformat = str(date_isoformat).lower() if isinstance(
+ date_isoformat, bool) else str(date_isoformat).strip().lower()
+ self.exclude_list = str(exclude_list).strip().lower()
+ self.default_label = str(default_label).strip()
+
+ # Validate and process the provider
try:
if provider in SENSOR_COLLECTORS_AFVALWIJZER:
waste_data_raw = mijnafvalwijzer.get_waste_data_raw(
@@ -95,12 +101,20 @@ def __init__(
self.street_number,
self.suffix,
)
+ elif provider == "rwm":
+ waste_data_raw = rwm.get_waste_data_raw(
+ self.provider,
+ self.postal_code,
+ self.street_number,
+ self.suffix,
+ )
else:
_LOGGER.error(f"Unknown provider: {provider}")
- return False
+ raise ValueError(f"Unknown provider: {provider}")
except ValueError as err:
- _LOGGER.error(f"Check afvalwijzer platform settings {err.args}")
+ _LOGGER.error(f"Check afvalwijzer platform settings: {err}")
+ raise
##########################################################################
# COMMON CODE
diff --git a/custom_components/afvalwijzer/collector/rwm.py b/custom_components/afvalwijzer/collector/rwm.py
new file mode 100644
index 0000000..1e12040
--- /dev/null
+++ b/custom_components/afvalwijzer/collector/rwm.py
@@ -0,0 +1,56 @@
+from ..const.const import _LOGGER, SENSOR_COLLECTORS_RWM
+from ..common.main_functions import _waste_type_rename
+from datetime import datetime
+import requests
+from urllib3.exceptions import InsecureRequestWarning
+
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+
+
+def get_waste_data_raw(provider, postal_code, street_number, suffix):
+ try:
+ if provider != "rwm":
+ raise ValueError(f"Invalid provider: {provider}, please verify")
+
+ ##########################################################################
+ # First request: get bag id
+ ##########################################################################
+ url = SENSOR_COLLECTORS_RWM["getAddress"].format(postal_code, street_number)
+
+ response = requests.get(url=url, timeout=60).json()
+
+ if not response:
+ _LOGGER.error("Address not found!")
+ return []
+
+ bagId = response[0]["bagid"]
+
+ ##########################################################################
+ # Second request: get the dates
+ ##########################################################################
+ url = SENSOR_COLLECTORS_RWM["getSchedule"].format(bagId)
+
+ response = requests.get(url=url, timeout=60).json()
+
+ if not response:
+ _LOGGER.error("Could not retrieve trash schedule!")
+ return []
+
+ waste_data_raw = []
+
+ for item in response:
+ if item["ophaaldatum"] is not None:
+ data = {
+ "type": _waste_type_rename(item["title"].strip().lower()),
+ "date": datetime.strptime(item["ophaaldatum"], "%Y-%m-%d").strftime("%Y-%m-%d"),
+ }
+ waste_data_raw.append(data)
+
+ except requests.exceptions.RequestException as err:
+ raise ValueError(err) from err
+
+ return waste_data_raw
+
+
+if __name__ == "__main__":
+ print("Yell something at a mountain!")
diff --git a/custom_components/afvalwijzer/common/main_functions.py b/custom_components/afvalwijzer/common/main_functions.py
index 0640a90..868bb18 100644
--- a/custom_components/afvalwijzer/common/main_functions.py
+++ b/custom_components/afvalwijzer/common/main_functions.py
@@ -8,12 +8,15 @@ def _waste_type_rename(item_name):
"chemokar": "chemisch",
"christmas_trees": "kerstbomen",
"gemengde plastics": "plastic",
+ "gft & etensresten": "gft",
"glass": "glas",
+ "gft afval": "gft",
"green": "gft",
"groene container": "gft",
"groente": "gft",
"groente-, fruit en tuinafval": "gft",
"groente, fruit- en tuinafval": "gft",
+ "grof tuinafval": "takken",
"grey": "restafval",
"grijze container": "restafval",
"kca": "chemisch",
diff --git a/custom_components/afvalwijzer/config_flow.py b/custom_components/afvalwijzer/config_flow.py
new file mode 100644
index 0000000..792173c
--- /dev/null
+++ b/custom_components/afvalwijzer/config_flow.py
@@ -0,0 +1,103 @@
+import voluptuous as vol
+from homeassistant import config_entries
+from homeassistant.core import callback
+from homeassistant.helpers import config_validation as cv
+
+from .const.const import (
+ DOMAIN,
+ CONF_COLLECTOR,
+ CONF_POSTAL_CODE,
+ CONF_STREET_NUMBER,
+ CONF_SUFFIX,
+ CONF_EXCLUDE_PICKUP_TODAY,
+ CONF_DATE_ISOFORMAT,
+ CONF_DEFAULT_LABEL,
+ CONF_EXCLUDE_LIST,
+)
+
+DATA_SCHEMA = vol.Schema({
+ vol.Required(CONF_COLLECTOR): cv.string,
+ vol.Required(CONF_POSTAL_CODE): cv.string,
+ vol.Required(CONF_STREET_NUMBER): cv.string,
+ vol.Optional(CONF_SUFFIX, default=""): cv.string,
+ vol.Optional(CONF_EXCLUDE_PICKUP_TODAY, default=True): cv.boolean,
+ vol.Optional(CONF_DATE_ISOFORMAT, default=False): cv.boolean,
+ vol.Optional(CONF_DEFAULT_LABEL, default="geen"): cv.string,
+ vol.Optional(CONF_EXCLUDE_LIST, default=""): cv.string,
+})
+
+
+class AfvalwijzerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
+ """Handle a config flow for Afvalwijzer."""
+ VERSION = 1
+
+ async def async_step_user(self, user_input=None):
+ """Handle the initial step."""
+ errors = {}
+
+ if user_input is not None:
+ # Perform validation
+ if not self._validate_postal_code(user_input.get(CONF_POSTAL_CODE)):
+ errors["postal_code"] = "config.error.invalid_postal_code"
+ elif not self._validate_street_number(user_input.get(CONF_STREET_NUMBER)):
+ errors["street_number"] = "config.error.invalid_street_number"
+ else:
+ # Validation passed, create the entry
+ return self.async_create_entry(title="Afvalwijzer", data=user_input)
+
+ return self.async_show_form(
+ step_id="user",
+ data_schema=DATA_SCHEMA,
+ errors=errors,
+ description_placeholders={
+ "provider": "e.g., mijnafvalwijzer",
+ "postal_code": "e.g., 1234AB",
+ },
+ )
+
+ @staticmethod
+ @callback
+ def async_get_options_flow(config_entry):
+ return AfvalwijzerOptionsFlow()
+
+ def _validate_postal_code(self, postal_code):
+ """Validate the postal code format."""
+ return (
+ isinstance(postal_code, str)
+ and len(postal_code) == 6
+ and postal_code[:4].isdigit()
+ and postal_code[4:].isalpha()
+ )
+
+ def _validate_street_number(self, street_number):
+ """Validate the street number."""
+ return street_number.isdigit()
+
+
+class AfvalwijzerOptionsFlow(config_entries.OptionsFlow):
+ """Handle options for Afvalwijzer."""
+
+ def __init__(self):
+ """Initialize the options flow."""
+
+ async def async_step_init(self, user_input=None):
+ """Handle options configuration."""
+ if user_input is not None:
+ return self.async_create_entry(title="", data=user_input)
+
+ options_schema = vol.Schema({
+ vol.Optional(CONF_EXCLUDE_PICKUP_TODAY, default=True): cv.boolean,
+ vol.Optional(CONF_DATE_ISOFORMAT, default=False): cv.boolean,
+ vol.Optional(CONF_DEFAULT_LABEL, default="geen"): cv.string,
+ vol.Optional(CONF_EXCLUDE_LIST, default=""): cv.string,
+ })
+
+ return self.async_show_form(
+ step_id="init",
+ data_schema=options_schema,
+ description_placeholders={
+ "exclude_pickup_today": "Exclude today's pickup",
+ "date_isoformat": "Use ISO date format",
+ },
+ )
+
diff --git a/custom_components/afvalwijzer/const/const.py b/custom_components/afvalwijzer/const/const.py
old mode 100755
new mode 100644
index 9ec161a..f973e4b
--- a/custom_components/afvalwijzer/const/const.py
+++ b/custom_components/afvalwijzer/const/const.py
@@ -5,7 +5,7 @@
API = "api"
NAME = "afvalwijzer"
-VERSION = "2024.06.02"
+VERSION = "2024.12.01"
ISSUE_URL = "https://github.com/xirixiz/homeassistant-afvalwijzer/issues"
@@ -35,7 +35,7 @@
"berkelland": "https://afvalkalender.gemeenteberkelland.nl",
"blink": "https://mijnblink.nl",
"cranendonck": "https://afvalkalender.cranendonck.nl",
- "cyclus": "https://afvalkalender.cyclusnv.nl",
+ "cyclus": "https://cyclusnv.nl",
"dar": "https://afvalkalender.dar.nl",
"denhaag": "https://huisvuilkalender.denhaag.nl",
"gad": "https://inzamelkalender.gad.nl",
@@ -46,6 +46,7 @@
"peelenmaas": "https://afvalkalender.peelenmaas.nl",
"prezero": "https://inzamelwijzer.prezero.nl",
"purmerend": "https://afvalkalender.purmerend.nl",
+ "saver": "https://saver.nl",
"schouwen-duiveland": "https://afvalkalender.schouwen-duiveland.nl",
"spaarnelanden": "https://afvalwijzer.spaarnelanden.nl",
"sudwestfryslan": "https://afvalkalender.sudwestfryslan.nl",
@@ -105,6 +106,11 @@
"deafvalapp": "https://dataservice.deafvalapp.nl/dataservice/DataServiceServlet?service=OPHAALSCHEMA&land=NL&postcode={0}&straatId=0&huisnr={1}&huisnrtoev={2}",
}
+SENSOR_COLLECTORS_RWM = {
+ "getAddress": "https://rwm.nl/adressen/{0}:{1}",
+ "getSchedule": "https://rwm.nl/rest/adressen/{0}/afvalstromen"
+}
+
CONF_COLLECTOR = "provider"
CONF_API_TOKEN = "api_token"
CONF_POSTAL_CODE = "postal_code"
@@ -126,9 +132,7 @@
ATTR_IS_COLLECTION_DATE_DAY_AFTER_TOMORROW = "is_collection_date_day_after_tomorrow"
ATTR_DAYS_UNTIL_COLLECTION_DATE = "days_until_collection_date"
-MIN_TIME_BETWEEN_UPDATES = timedelta(hours=1)
-PARALLEL_UPDATES = 1
-SCAN_INTERVAL = timedelta(seconds=30)
+SCAN_INTERVAL = timedelta(hours=4)
DOMAIN = "afvalwijzer"
DOMAIN_DATA = "afvalwijzer_data"
diff --git a/custom_components/afvalwijzer/manifest.json b/custom_components/afvalwijzer/manifest.json
index 343d0bb..376a484 100644
--- a/custom_components/afvalwijzer/manifest.json
+++ b/custom_components/afvalwijzer/manifest.json
@@ -1,14 +1,12 @@
{
"domain": "afvalwijzer",
"name": "Afvalwijzer",
- "codeowners": [
- "@xirixiz"
- ],
- "config_flow": false,
+ "codeowners": ["@xirixiz"],
+ "config_flow": true,
"dependencies": [],
"documentation": "https://github.com/xirixiz/homeassistant-afvalwijzer/blob/main/README.md",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/xirixiz/homeassistant-afvalwijzer/issues",
"requirements": [],
- "version": "2024.06.02"
-}
\ No newline at end of file
+ "version": "2024.12.01"
+}
diff --git a/custom_components/afvalwijzer/sensor.py b/custom_components/afvalwijzer/sensor.py
old mode 100755
new mode 100644
index 6842c07..25e5252
--- a/custom_components/afvalwijzer/sensor.py
+++ b/custom_components/afvalwijzer/sensor.py
@@ -1,15 +1,13 @@
-#!/usr/bin/env python3
"""
Sensor component Afvalwijzer
Author: Bram van Dartel - xirixiz
"""
-from functools import partial
-
+from homeassistant.helpers.event import async_track_time_interval
+from datetime import timedelta
from homeassistant.components.sensor import PLATFORM_SCHEMA
-import homeassistant.helpers.config_validation as cv
-from homeassistant.util import Throttle
import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
from .collector.main_collector import MainCollector
from .const.const import (
@@ -23,91 +21,106 @@
CONF_POSTAL_CODE,
CONF_STREET_NUMBER,
CONF_SUFFIX,
- MIN_TIME_BETWEEN_UPDATES,
- PARALLEL_UPDATES,
SCAN_INTERVAL,
- STARTUP_MESSAGE,
)
from .sensor_custom import CustomSensor
from .sensor_provider import ProviderSensor
+# Define the platform schema for validation
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
- vol.Optional(
- CONF_COLLECTOR, default="mijnafvalwijzer"
- ): cv.string,
- vol.Required(CONF_POSTAL_CODE, default="1234AB"): cv.string,
- vol.Required(CONF_STREET_NUMBER, default="5"): cv.string,
+ vol.Optional(CONF_COLLECTOR, default="mijnafvalwijzer"): cv.string,
+ vol.Required(CONF_POSTAL_CODE): cv.string,
+ vol.Required(CONF_STREET_NUMBER): cv.string,
vol.Optional(CONF_SUFFIX, default=""): cv.string,
- vol.Optional(CONF_EXCLUDE_PICKUP_TODAY, default="true"): cv.string,
- vol.Optional(CONF_DATE_ISOFORMAT, default="false"): cv.string,
+ vol.Optional(CONF_EXCLUDE_PICKUP_TODAY, default=True): cv.boolean,
+ vol.Optional(CONF_DATE_ISOFORMAT, default=False): cv.boolean,
vol.Optional(CONF_EXCLUDE_LIST, default=""): cv.string,
vol.Optional(CONF_DEFAULT_LABEL, default="geen"): cv.string,
- vol.Optional(CONF_ID.strip().lower(), default=""): cv.string,
+ vol.Optional(CONF_ID, default=""): cv.string,
}
)
-_LOGGER.info(STARTUP_MESSAGE)
-
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
+ """Set up sensors using the platform schema."""
+ if not discovery_info:
+ _LOGGER.error(
+ "No discovery information provided; sensors cannot be created.")
+ return
+
+ await _setup_sensors(hass, discovery_info, async_add_entities)
+
+
+async def async_setup_entry(hass, entry, async_add_entities):
+ """Set up sensors from a config entry."""
+ await _setup_sensors(hass, entry.data, async_add_entities)
+
+
+async def _setup_sensors(hass, config, async_add_entities):
+ """Common setup logic for platform and config entry."""
provider = config.get(CONF_COLLECTOR)
postal_code = config.get(CONF_POSTAL_CODE)
street_number = config.get(CONF_STREET_NUMBER)
- suffix = config.get(CONF_SUFFIX)
- exclude_pickup_today = config.get(CONF_EXCLUDE_PICKUP_TODAY)
- date_isoformat = config.get(CONF_DATE_ISOFORMAT)
- exclude_list = config.get(CONF_EXCLUDE_LIST)
- default_label = config.get(CONF_DEFAULT_LABEL)
+ suffix = config.get(CONF_SUFFIX, "")
+ exclude_pickup_today = config.get(CONF_EXCLUDE_PICKUP_TODAY, True)
+ date_isoformat = config.get(CONF_DATE_ISOFORMAT, False)
+ exclude_list = config.get(CONF_EXCLUDE_LIST, "")
+ default_label = config.get(CONF_DEFAULT_LABEL, "geen")
- _LOGGER.debug(f"Afvalwijzer provider = {provider}")
- _LOGGER.debug(f"Afvalwijzer zipcode = {postal_code}")
- _LOGGER.debug(f"Afvalwijzer street_number = {street_number}")
+ _LOGGER.debug(f"Setting up Afvalwijzer sensors for provider: {provider}.")
+
+ # Initialize data handler
+ data = AfvalwijzerData(hass, config)
+
+ # Perform an initial update at startup
+ await hass.async_add_executor_job(data.update)
+
+ # Schedule periodic updates every 4 hours
+ update_interval = timedelta(hours=4)
+ def schedule_update(_):
+ """Safely schedule the update."""
+ hass.loop.call_soon_threadsafe(hass.async_add_executor_job, data.update)
+
+ async_track_time_interval(hass, schedule_update, update_interval)
- try:
- collector = await hass.async_add_executor_job(
- partial(
- MainCollector,
- provider,
- postal_code,
- street_number,
- suffix,
- exclude_pickup_today,
- date_isoformat,
- exclude_list,
- default_label,
- )
- )
- except ValueError as err:
- _LOGGER.error(f"Check afvalwijzer platform settings {err.args}")
- fetch_data = AfvalwijzerData(hass, config)
+ # Fetch waste types
+ try:
+ waste_types_provider = data.waste_data_with_today.keys()
+ waste_types_custom = data.waste_data_custom.keys()
+ except Exception as err:
+ _LOGGER.error(f"Failed to fetch waste types: {err}")
+ return
- waste_types_provider = collector.waste_types_provider
- _LOGGER.debug(f"Generating waste_types_provider list = {waste_types_provider}")
- waste_types_custom = collector.waste_types_custom
- _LOGGER.debug(f"Generating waste_types_custom list = {waste_types_custom}")
+ # Create entities
+ entities = [
+ ProviderSensor(hass, waste_type, data, config) for waste_type in waste_types_provider
+ ] + [
+ CustomSensor(hass, waste_type, data, config) for waste_type in waste_types_custom
+ ]
- entities = []
+ if not entities:
+ _LOGGER.error(
+ "No entities created; check configuration or collector output.")
+ return
- for waste_type in waste_types_provider:
- _LOGGER.debug(f"Adding sensor provider: {waste_type}")
- entities.append(ProviderSensor(hass, waste_type, fetch_data, config))
- for waste_type in waste_types_custom:
- _LOGGER.debug(f"Adding sensor custom: {waste_type}")
- entities.append(CustomSensor(hass, waste_type, fetch_data, config))
+ _LOGGER.info(f"Adding {len(entities)} sensors for Afvalwijzer.")
+ async_add_entities(entities, True)
- _LOGGER.debug(f"Entities appended = {entities}")
- async_add_entities(entities)
+class AfvalwijzerData:
+ """Class to handle fetching and storing Afvalwijzer data."""
-class AfvalwijzerData(object):
def __init__(self, hass, config):
- self._hass = hass
+ self.hass = hass
self.config = config
+ self.waste_data_with_today = None
+ self.waste_data_without_today = None
+ self.waste_data_custom = None
- @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
+ """Fetch the latest waste data."""
provider = self.config.get(CONF_COLLECTOR)
postal_code = self.config.get(CONF_POSTAL_CODE)
street_number = self.config.get(CONF_STREET_NUMBER)
@@ -129,25 +142,14 @@ def update(self):
default_label,
)
except ValueError as err:
- _LOGGER.error(f"Check afvalwijzer platform settings {err.args}")
+ _LOGGER.error(f"Collector initialization failed: {err}")
+ return
- # waste data provider update - with today
try:
self.waste_data_with_today = collector.waste_data_with_today
- except ValueError as err:
- _LOGGER.error(f"Check waste_data_provider {err.args}")
- self.waste_data_with_today = default_label
-
- # waste data provider update - without today
- try:
self.waste_data_without_today = collector.waste_data_without_today
- except ValueError as err:
- _LOGGER.error(f"Check waste_data_provider {err.args}")
- self.waste_data_without_today = default_label
-
- # waste data custom update
- try:
self.waste_data_custom = collector.waste_data_custom
+ _LOGGER.debug("Waste data updated successfully.")
except ValueError as err:
- _LOGGER.error(f"Check waste_data_custom {err.args}")
- self.waste_data_custom = default_label
+ _LOGGER.error(f"Failed to fetch waste data: {err}")
+ self.waste_data_with_today = self.waste_data_without_today = self.waste_data_custom = default_label
diff --git a/custom_components/afvalwijzer/sensor_custom.py b/custom_components/afvalwijzer/sensor_custom.py
old mode 100755
new mode 100644
index 41d2faa..3100ed2
--- a/custom_components/afvalwijzer/sensor_custom.py
+++ b/custom_components/afvalwijzer/sensor_custom.py
@@ -1,6 +1,5 @@
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.components.sensor import SensorEntity, SensorDeviceClass
-from homeassistant.util import Throttle
from datetime import datetime, date
import hashlib
@@ -15,30 +14,32 @@
CONF_STREET_NUMBER,
CONF_SUFFIX,
CONF_DATE_ISOFORMAT,
- MIN_TIME_BETWEEN_UPDATES,
SENSOR_ICON,
SENSOR_PREFIX,
)
class CustomSensor(RestoreEntity, SensorEntity):
+ """Representation of a custom-based waste sensor."""
+
def __init__(self, hass, waste_type, fetch_data, config):
+ """Initialize the sensor."""
self.hass = hass
self.waste_type = waste_type
- self.fetch_data = fetch_data
+ self.fetch_data = fetch_data # Should be an instance of AfvalwijzerData
self.config = config
self._id_name = config.get(CONF_ID)
self._default_label = config.get(CONF_DEFAULT_LABEL)
+ self._date_isoformat = str(config.get(CONF_DATE_ISOFORMAT)).lower()
self._last_update = None
self._days_until_collection_date = None
- self._date_isoformat = config.get(CONF_DATE_ISOFORMAT)
self._name = (
SENSOR_PREFIX + (f"{self._id_name} " if self._id_name else "")
) + waste_type
- self._state = config.get(CONF_DEFAULT_LABEL)
+ self._state = self._default_label
self._icon = SENSOR_ICON
self._unique_id = hashlib.sha1(
- f"{waste_type}{config.get(CONF_ID)}{config.get(CONF_POSTAL_CODE)}{config.get(CONF_STREET_NUMBER)}{config.get(CONF_SUFFIX,'')}".encode(
+ f"{waste_type}{config.get(CONF_ID)}{config.get(CONF_POSTAL_CODE)}{config.get(CONF_STREET_NUMBER)}{config.get(CONF_SUFFIX, '')}".encode(
"utf-8"
)
).hexdigest()
@@ -46,26 +47,32 @@ def __init__(self, hass, waste_type, fetch_data, config):
@property
def name(self):
+ """Return the name of the sensor."""
return self._name
@property
def unique_id(self):
+ """Return a unique ID for the sensor."""
return self._unique_id
@property
def icon(self):
+ """Return the icon of the sensor."""
return self._icon
@property
def state(self):
+ """Return the state of the sensor."""
return self._state
@property
def device_class(self):
+ """Return the device class of the sensor."""
return self._device_class
@property
def state_attributes(self):
+ """Return the attributes of the sensor."""
attrs = {
ATTR_LAST_UPDATE: self._last_update,
}
@@ -75,43 +82,52 @@ def state_attributes(self):
attrs["device_class"] = self._device_class
return attrs
- @Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
- await self.hass.async_add_executor_job(self.fetch_data.update)
-
- waste_data_custom = self.fetch_data.waste_data_custom
+ """Fetch the latest data and update the state."""
+ _LOGGER.debug(f"Updating custom sensor: {self.name}")
try:
- self._last_update = datetime.now().isoformat()
+ # Call update method from fetch_data
+ await self.hass.async_add_executor_job(self.fetch_data.update)
+
+ # Get waste data for custom sensors
+ waste_data_custom = self.fetch_data.waste_data_custom
+
+ if not waste_data_custom or self.waste_type not in waste_data_custom:
+ raise ValueError(f"No data for waste type: {self.waste_type}")
- if isinstance(waste_data_custom[self.waste_type], datetime):
- self._update_attributes_date(waste_data_custom[self.waste_type])
+ # Update attributes and state based on waste data
+ collection_date = waste_data_custom[self.waste_type]
+ if isinstance(collection_date, datetime):
+ self._update_attributes_date(collection_date)
else:
- self._update_attributes_non_date(waste_data_custom[self.waste_type])
- except ValueError:
- _LOGGER.debug("ValueError AfvalwijzerCustomSensor - unable to set value!")
+ self._update_attributes_non_date(collection_date)
+
+ except Exception as err:
+ _LOGGER.error(f"Error updating custom sensor {self.name}: {err}")
self._handle_value_error()
def _update_attributes_date(self, collection_date):
- if self._date_isoformat.casefold() in ("true", "yes"):
- collection_date_object = collection_date.isoformat()
- else:
- collection_date_object = collection_date.date()
-
+ """Update attributes for a datetime value."""
+ collection_date_object = (
+ collection_date.isoformat() if self._date_isoformat in (
+ "true", "yes") else collection_date.date()
+ )
collection_date_delta = collection_date.date()
delta = collection_date_delta - date.today()
- self._days_until_collection_date = delta.days
+ self._days_until_collection_date = delta.days
self._device_class = SensorDeviceClass.TIMESTAMP
-
self._state = collection_date_object
def _update_attributes_non_date(self, value):
+ """Update attributes for a non-datetime value."""
self._state = str(value)
self._days_until_collection_date = None
self._device_class = None
def _handle_value_error(self):
+ """Handle errors in fetching data."""
self._state = self._default_label
self._days_until_collection_date = None
self._device_class = None
diff --git a/custom_components/afvalwijzer/sensor_provider.py b/custom_components/afvalwijzer/sensor_provider.py
old mode 100755
new mode 100644
index 2711116..3cf16cf
--- a/custom_components/afvalwijzer/sensor_provider.py
+++ b/custom_components/afvalwijzer/sensor_provider.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-from homeassistant.util import Throttle
from homeassistant.components.sensor import SensorEntity, SensorDeviceClass
from homeassistant.helpers.restore_state import RestoreEntity
from datetime import datetime, date, timedelta
@@ -19,22 +18,24 @@
CONF_STREET_NUMBER,
CONF_SUFFIX,
CONF_DATE_ISOFORMAT,
- MIN_TIME_BETWEEN_UPDATES,
- PARALLEL_UPDATES,
SENSOR_ICON,
SENSOR_PREFIX,
)
class ProviderSensor(RestoreEntity, SensorEntity):
+ """Representation of a provider-based waste sensor."""
+
def __init__(self, hass, waste_type, fetch_data, config):
+ """Initialize the sensor."""
self.hass = hass
self.waste_type = waste_type
- self.fetch_data = fetch_data
+ self.fetch_data = fetch_data # This should be an instance of AfvalwijzerData
self.config = config
self._id_name = config.get(CONF_ID)
self._default_label = config.get(CONF_DEFAULT_LABEL)
- self._exclude_pickup_today = config.get(CONF_EXCLUDE_PICKUP_TODAY)
+ self._exclude_pickup_today = str(
+ config.get(CONF_EXCLUDE_PICKUP_TODAY)).lower()
self._name = (
SENSOR_PREFIX + (f"{self._id_name} " if self._id_name else "")
) + waste_type
@@ -43,11 +44,11 @@ def __init__(self, hass, waste_type, fetch_data, config):
self._is_collection_date_today = False
self._is_collection_date_tomorrow = False
self._is_collection_date_day_after_tomorrow = False
- self._date_isoformat = config.get(CONF_DATE_ISOFORMAT)
- self._state = config.get(CONF_DEFAULT_LABEL)
+ self._date_isoformat = str(config.get(CONF_DATE_ISOFORMAT)).lower()
+ self._state = self._default_label
self._icon = SENSOR_ICON
self._unique_id = hashlib.sha1(
- f"{waste_type}{config.get(CONF_ID)}{config.get(CONF_POSTAL_CODE)}{config.get(CONF_STREET_NUMBER)}{config.get(CONF_SUFFIX,'')}".encode(
+ f"{waste_type}{config.get(CONF_ID)}{config.get(CONF_POSTAL_CODE)}{config.get(CONF_STREET_NUMBER)}{config.get(CONF_SUFFIX, '')}".encode(
"utf-8"
)
).hexdigest()
@@ -55,86 +56,100 @@ def __init__(self, hass, waste_type, fetch_data, config):
@property
def name(self):
+ """Return the name of the sensor."""
return self._name
@property
def unique_id(self):
+ """Return a unique ID for the sensor."""
return self._unique_id
@property
def icon(self):
+ """Return the icon of the sensor."""
return self._icon
@property
def state(self):
+ """Return the state of the sensor."""
return self._state
@property
def device_class(self):
+ """Return the device class of the sensor."""
return self._device_class
@property
def state_attributes(self):
- attrs = {
+ """Return the attributes of the sensor."""
+ return {
ATTR_LAST_UPDATE: self._last_update,
ATTR_DAYS_UNTIL_COLLECTION_DATE: self._days_until_collection_date,
ATTR_IS_COLLECTION_DATE_TODAY: self._is_collection_date_today,
ATTR_IS_COLLECTION_DATE_TOMORROW: self._is_collection_date_tomorrow,
ATTR_IS_COLLECTION_DATE_DAY_AFTER_TOMORROW: self._is_collection_date_day_after_tomorrow,
}
- if isinstance(self._state, datetime):
- attrs["device_class"] = self._device_class
- return attrs
- @Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
- await self.hass.async_add_executor_job(self.fetch_data.update)
-
- waste_data_provider = (
- self.fetch_data.waste_data_with_today
- if self._exclude_pickup_today.casefold() in ("false", "no")
- else self.fetch_data.waste_data_without_today
- )
+ """Fetch the latest data and update the state."""
+ _LOGGER.debug(f"Updating sensor: {self.name}")
try:
+ # Call update method from fetch_data
+ await self.hass.async_add_executor_job(self.fetch_data.update)
+
+ # Select the correct waste data based on exclude_pickup_today
+ waste_data_provider = (
+ self.fetch_data.waste_data_with_today
+ if self._exclude_pickup_today in ("false", "no")
+ else self.fetch_data.waste_data_without_today
+ )
+
if not waste_data_provider or self.waste_type not in waste_data_provider:
- raise ValueError
- self._last_update = datetime.now().isoformat()
+ raise ValueError(f"No data for waste type: {self.waste_type}")
- if isinstance(waste_data_provider[self.waste_type], datetime):
- self._update_attributes_date(waste_data_provider[self.waste_type])
+ # Update attributes and state based on the waste data
+ collection_date = waste_data_provider[self.waste_type]
+ if isinstance(collection_date, datetime):
+ self._update_attributes_date(collection_date)
else:
- self._update_attributes_non_date(waste_data_provider[self.waste_type])
- except ValueError:
+ self._update_attributes_non_date(collection_date)
+
+ except Exception as err:
+ _LOGGER.error(f"Error updating sensor {self.name}: {err}")
self._handle_value_error()
def _update_attributes_date(self, collection_date):
- if self._date_isoformat.casefold() in ("true", "yes"):
- collection_date_object = collection_date.isoformat()
- else:
- collection_date_object = collection_date.date()
-
+ """Update attributes for a datetime value."""
+ collection_date_object = (
+ collection_date.isoformat() if self._date_isoformat in (
+ "true", "yes") else collection_date.date()
+ )
collection_date_delta = collection_date.date()
delta = collection_date_delta - date.today()
- self._days_until_collection_date = delta.days
+ self._days_until_collection_date = delta.days
self._update_collection_date_flags(collection_date_delta)
self._device_class = SensorDeviceClass.TIMESTAMP
-
self._state = collection_date_object
def _update_attributes_non_date(self, value):
+ """Update attributes for a non-datetime value."""
self._state = str(value)
self._days_until_collection_date = None
self._device_class = None
def _update_collection_date_flags(self, collection_date_delta):
+ """Update flags for collection date."""
today = date.today()
self._is_collection_date_today = collection_date_delta == today
- self._is_collection_date_tomorrow = collection_date_delta == today + timedelta(days=1)
- self._is_collection_date_day_after_tomorrow = collection_date_delta == today + timedelta(days=2)
+ self._is_collection_date_tomorrow = collection_date_delta == today + \
+ timedelta(days=1)
+ self._is_collection_date_day_after_tomorrow = collection_date_delta == today + \
+ timedelta(days=2)
def _handle_value_error(self):
+ """Handle errors in fetching data."""
self._state = self._default_label
self._is_collection_date_today = None
self._is_collection_date_tomorrow = None
diff --git a/custom_components/afvalwijzer/tests/test_module.py b/custom_components/afvalwijzer/tests/test_module.py
index a251495..432076e 100644
--- a/custom_components/afvalwijzer/tests/test_module.py
+++ b/custom_components/afvalwijzer/tests/test_module.py
@@ -7,11 +7,11 @@
from afvalwijzer.collector.mijnafvalwijzer import AfvalWijzer
AfvalWijzer().get_data('','','')
-python3 -m afvalwijzer.tests.test_module
+- Comment out __init__.py
+- Run python3 -m afvalwijzer.tests.test_module
"""
-
from ..collector.main_collector import MainCollector
# provider = "afvalstoffendienstkalender"
@@ -39,9 +39,9 @@
# street_number = "2"
# Afvalwijzer
-provider = "mijnafvalwijzer"
-postal_code = "5146eg"
-street_number = "1"
+# provider = "mijnafvalwijzer"
+# postal_code = "5146eg"
+# street_number = "1"
# iCalendar file
# provider = "veldhoven"
@@ -87,9 +87,9 @@
# street_number = "24"
# suffix = "C"
-# provider = "rova"
-# postal_code = "8021CC"
-# street_number = "1"
+provider = "rova"
+postal_code = "7671BL"
+street_number = "2"
# provider = "rmn"
# postal_code = "3402TA"
@@ -123,11 +123,11 @@
street_number,
suffix,
exclude_pickup_today,
+ date_isoformat,
exclude_list,
default_label,
)
-
# MainCollector(
# provider,
# postal_code,
diff --git a/custom_components/afvalwijzer/translations/en.json b/custom_components/afvalwijzer/translations/en.json
new file mode 100644
index 0000000..1cfb78a
--- /dev/null
+++ b/custom_components/afvalwijzer/translations/en.json
@@ -0,0 +1,41 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "title": "Configure Afvalwijzer",
+ "description": "Set up your afvalwijzer integration.",
+ "data": {
+ "provider": "Provider (e.g., mijnafvalwijzer). See README.md.",
+ "postal_code": "Postal code (e.g., 1234AB)",
+ "street_number": "Street number",
+ "suffix": "Address suffix",
+ "exclude_pickup_today": "Exclude today's pickup",
+ "date_isoformat": "Use ISO date format",
+ "default_label": "Default label when no data is available",
+ "exclude_list": "Waste type exclude list"
+ }
+ }
+ },
+ "error": {
+ "invalid_postal_code": "Invalid postal code format",
+ "invalid_street_number": "Street number must be numeric"
+ },
+ "abort": {
+ "already_configured": "Afvalwijzer is already configured"
+ }
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Afvalwijzer Options",
+ "description": "Configure options for afvalwijzer integration.",
+ "data": {
+ "exclude_pickup_today": "Exclude today's pickup",
+ "date_isoformat": "Use ISO date format",
+ "default_label": "Default label when no data is available",
+ "exclude_list": "Waste type exclude list"
+ }
+ }
+ }
+ }
+}
diff --git a/custom_components/afvalwijzer/translations/nl.json b/custom_components/afvalwijzer/translations/nl.json
new file mode 100644
index 0000000..d6de13a
--- /dev/null
+++ b/custom_components/afvalwijzer/translations/nl.json
@@ -0,0 +1,41 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "title": "Configureer Afvalwijzer",
+ "description": "Stel je Afvalwijzer-integratie in.",
+ "data": {
+ "provider": "Provider (bijv. mijnafvalwijzer). Lees README.md.",
+ "postal_code": "Postcode (bijv. 1234AB)",
+ "street_number": "Huisnummer",
+ "suffix": "Huisnummer toevoeging",
+ "exclude_pickup_today": "Sluit ophalen van vandaag uit",
+ "date_isoformat": "Gebruik ISO-datumformaat",
+ "default_label": "Standaard label bij geen datum bekend",
+ "exclude_list": "Afvaltype uitsluiten"
+ }
+ }
+ },
+ "error": {
+ "invalid_postal_code": "Ongeldig postcodeformaat",
+ "invalid_street_number": "Huisnummer moet numeriek zijn"
+ },
+ "abort": {
+ "already_configured": "Afvalwijzer is al geconfigureerd"
+ }
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Afvalwijzer Opties",
+ "description": "Pas extra opties aan voor de Afvalwijzer-integratie.",
+ "data": {
+ "exclude_pickup_today": "Sluit ophalen van vandaag uit",
+ "date_isoformat": "Gebruik ISO-datumformaat",
+ "default_label": "Standaard label bij geen datum bekend",
+ "exclude_list": "Afvaltype uitsluiten"
+ }
+ }
+ }
+ }
+}
diff --git a/requirements.txt b/requirements.txt
index 4ed3a76..379f981 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
colorlog==6.8.2
homeassistant
pip>=21.0,<24.2
-ruff==0.4.10
+ruff==0.67
pre-commit==3.8.0
bumpver==2023.1129
urllib3>=1.26.5,<3