From f4fc6e23537c34b25bc870f5a4dc18d4fd559a99 Mon Sep 17 00:00:00 2001 From: Jon Kristian Nilsen Date: Sat, 24 Jun 2023 14:43:10 +0200 Subject: [PATCH] Rewritten integration to work with api v2. --- .devcontainer.json | 36 ++++ .gitattributes | 1 + .github/workflows/validate.yml | 29 --- .gitignore | 28 ++- .pre-commit-config.yaml | 58 ------ CONTRIBUTING.md | 61 ++++++ LICENSE | 21 ++ README.md | 59 +++--- custom_components/wasteplan_trv/__init__.py | 104 +++++++++- custom_components/wasteplan_trv/api.py | 69 +++++++ custom_components/wasteplan_trv/calendar.py | 73 +++++++ .../wasteplan_trv/config_flow.py | 112 +++++++++++ custom_components/wasteplan_trv/const.py | 63 +----- .../wasteplan_trv/icons/farligavfall.png | Bin 1865 -> 0 bytes .../wasteplan_trv/icons/farligavfall.svg | 14 -- .../wasteplan_trv/icons/glassogmetall.png | Bin 1969 -> 0 bytes .../wasteplan_trv/icons/glassogmetall.svg | 21 -- .../wasteplan_trv/icons/pappogpapir.png | Bin 2295 -> 0 bytes .../wasteplan_trv/icons/pappogpapir.svg | 23 --- .../wasteplan_trv/icons/plastemballasje.png | Bin 2128 -> 0 bytes .../wasteplan_trv/icons/plastemballasje.svg | 14 -- .../wasteplan_trv/icons/restavfall.png | Bin 1692 -> 0 bytes .../wasteplan_trv/icons/restavfall.svg | 13 -- custom_components/wasteplan_trv/manifest.json | 9 +- custom_components/wasteplan_trv/sensor.py | 186 ------------------ custom_components/wasteplan_trv/strings.json | 24 +++ .../wasteplan_trv/translations/en.json | 24 +++ .../wasteplan_trv/translations/nb.json | 24 +++ example.png | Bin 0 -> 7683 bytes hacs.json | 2 +- requirements.txt | 3 + scripts/develop | 8 + scripts/setup | 7 + setup.cfg | 2 +- 34 files changed, 638 insertions(+), 450 deletions(-) create mode 100644 .devcontainer.json create mode 100644 .gitattributes delete mode 100644 .github/workflows/validate.yml delete mode 100644 .pre-commit-config.yaml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 custom_components/wasteplan_trv/api.py create mode 100644 custom_components/wasteplan_trv/calendar.py create mode 100644 custom_components/wasteplan_trv/config_flow.py delete mode 100644 custom_components/wasteplan_trv/icons/farligavfall.png delete mode 100644 custom_components/wasteplan_trv/icons/farligavfall.svg delete mode 100644 custom_components/wasteplan_trv/icons/glassogmetall.png delete mode 100644 custom_components/wasteplan_trv/icons/glassogmetall.svg delete mode 100644 custom_components/wasteplan_trv/icons/pappogpapir.png delete mode 100644 custom_components/wasteplan_trv/icons/pappogpapir.svg delete mode 100644 custom_components/wasteplan_trv/icons/plastemballasje.png delete mode 100644 custom_components/wasteplan_trv/icons/plastemballasje.svg delete mode 100644 custom_components/wasteplan_trv/icons/restavfall.png delete mode 100644 custom_components/wasteplan_trv/icons/restavfall.svg delete mode 100644 custom_components/wasteplan_trv/sensor.py create mode 100644 custom_components/wasteplan_trv/strings.json create mode 100644 custom_components/wasteplan_trv/translations/en.json create mode 100644 custom_components/wasteplan_trv/translations/nb.json create mode 100644 example.png create mode 100644 requirements.txt create mode 100755 scripts/develop create mode 100755 scripts/setup diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..2872ab9 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,36 @@ +{ + "name": "ludeeus/integration_blueprint", + "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.10-bullseye", + "postCreateCommand": "scripts/setup", + "forwardPorts": [ + 8123 + ], + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "github.vscode-pull-request-github", + "ryanluker.vscode-coverage-gutters", + "ms-python.vscode-pylance" + ], + "settings": { + "files.eol": "\n", + "editor.tabSize": 4, + "python.pythonPath": "/usr/bin/python3", + "python.analysis.autoSearchPaths": false, + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "python.formatting.provider": "black", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true + } + } + }, + "remoteUser": "vscode", + "features": { + "rust": "latest" + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml deleted file mode 100644 index 921e613..0000000 --- a/.github/workflows/validate.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Validate - -on: - push: - branches: - - master - pull_request: - schedule: - - cron: "0 0 * * *" - -jobs: - hacs: - runs-on: "ubuntu-latest" - steps: - - uses: "actions/checkout@v2" - - name: HACS validation - uses: "hacs/action@main" - with: - category: "integration" - - hassfest: - runs-on: "ubuntu-latest" - name: Hassfest - steps: - - name: Check out the repository - uses: "actions/checkout@v2.3.4" - - - name: Hassfest validation - uses: "home-assistant/actions/hassfest@master" \ No newline at end of file diff --git a/.gitignore b/.gitignore index d901320..5bd46d1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,31 @@ eggs *.orig .idea -# vscode +# artifacts +__pycache__ +.pytest* +*.egg-info +*/build/* +*/dist/* + + +# misc +.coverage .vscode +coverage.xml + + +# Home Assistant configuration +.cloud +.HA_VERSION +.storage +automations.yaml +blueprints +configuration.yaml +deps +home-assistant_v2* +home-assistant.log* +tts +scenes.yaml +scripts.yaml +secrets.yaml \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index ff94dd2..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,58 +0,0 @@ -repos: - - repo: https://github.com/asottile/pyupgrade - rev: v2.3.0 - hooks: - - id: pyupgrade - args: [--py37-plus] - - repo: https://github.com/psf/black - rev: 19.10b0 - hooks: - - id: black - args: - - --safe - - --quiet - files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$ - - repo: https://github.com/codespell-project/codespell - rev: v1.16.0 - hooks: - - id: codespell - args: - - --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing - - --skip="./.*,*.csv,*.json" - - --quiet-level=2 - exclude_types: [csv, json] - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.1 - hooks: - - id: flake8 - additional_dependencies: - - flake8-docstrings==1.5.0 - - pydocstyle==5.0.2 - files: ^(homeassistant|script|tests)/.+\.py$ - - repo: https://github.com/PyCQA/bandit - rev: 1.6.2 - hooks: - - id: bandit - args: - - --quiet - - --format=custom - - --configfile=tests/bandit.yaml - files: ^(homeassistant|script|tests)/.+\.py$ - - repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 - hooks: - - id: isort - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 - hooks: - - id: check-executables-have-shebangs - stages: [manual] - - id: check-json - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.770 - hooks: - - id: mypy - args: - - --pretty - - --show-error-codes - - --show-error-context \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..fd953bd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Contribution guidelines + +Contributing to this project should be as easy and transparent as possible, whether it's: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features + +## Github is used for everything + +Github is used to host code, to track issues and feature requests, as well as accept pull requests. + +Pull requests are the best way to propose changes to the codebase. + +1. Fork the repo and create your branch from `master`. +2. If you've changed something, update the documentation. +3. Make sure your code lints (using black). +4. Test you contribution. +5. Issue that pull request! + +## Any contributions you make will be under the MIT Software License + +In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using Github's [issues](../../issues) + +GitHub issues are used to track public bugs. +Report a bug by [opening a new issue](../../issues/new/choose); it's that easy! + +## Write bug reports with detail, background, and sample code + +**Great Bug Reports** tend to have: + +- A quick summary and/or background +- Steps to reproduce + - Be specific! + - Give sample code if you can. +- What you expected would happen +- What actually happens +- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) + +People *love* thorough bug reports. I'm not even kidding. + +## Use a Consistent Coding Style + +Use [black](https://github.com/ambv/black) to make sure the code follows the style. + +## Test your code modification + +This custom component is based on [integration_blueprint template](https://github.com/ludeeus/integration_blueprint). + +It comes with development environment in a container, easy to launch +if you use Visual Studio Code. With this container you will have a stand alone +Home Assistant instance running and already configured with the included +[`configuration.yaml`](./configuration.yaml) +file. + +## License + +By contributing, you agree that your contributions will be licensed under its MIT License. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..64cebfd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Jon Kristian Nilsen + +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/README.md b/README.md index b70bdbf..781852f 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,46 @@ # Wasteplan TRV -# NOTE -Unfortunately trv api v1 doesn't seem to work any longer, and it seems that they don't plan on giving access to v2, so this component will no longer work. [Ticket with response from trv](/issues/13). -[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) +[![GitHub Release][releases-shield]][releases] +[![License][license-shield]](LICENSE) +[![hacs][hacsbadge]][hacs] +![Project Maintenance][maintenance-shield] +[![BuyMeCoffee][buymecoffeebadge]][buymecoffee] Wasteplan component for Trondheim Renholdsverk (TRV). -This component provides sensors for your bins or containers and gives you status about pickup. - -[![image-1.png](https://i.postimg.cc/hGs0gPr7/image-1.png)](https://postimg.cc/f33dfs1w) +This component creates a calendar of pickup events. ## Installation -### Manual installation -Download or clone and copy the folder `custom/components/wasteplan_trv` into your `custom_components/` - -### Installation via Home Assistant Community Store (HACS) 1. Ensure [HACS](http://hacs.xyz/) is installed. 2. Search for and install the "Wasteplan TRV" integration -3. Configure the sensor -4. Restart Home Assistant +3. Install and restart Home Assistant +4. Install Wasteplan TRV from the integrations screen. -## Finding your ID +## Attributions +- This component uses the excellent [integration_blueprint] from [ludeeus]. -To locate your ID, append your address to the end of one of the URLs below, either bin or container. -- Bins: https://trv.no/wp-json/wasteplan/v1/bins/?s= -- Containers: https://trv.no/wp-json/wasteplan/v1/containers/?s= +## Contributions are welcome! -### Configuration variables -| Variable | Required | Type | Description | -| -------- | ---------- | ----------- | ----------- | -| `id` | yes | integer | Bin/Container ID. | -| `pickup_day` | no | integer | Pickup day of the week. Defaults to 0 (Monday). | +If you want to contribute to this please read the [Contribution guidelines](CONTRIBUTING.md) -## Example -```yaml -sensor: - - platform: wasteplan_trv - id: 774 - pickup_day: 0 -``` +*** ⭐️ this repository if you found it useful ❤️ -Buy Me A Coffee +[![BuyMeCoffee][buymecoffebadge2]][buymecoffee] + +[wasteplan_trv]: https://github.com/jonkristian/wasteplan_trv +[buymecoffee]: https://www.buymeacoffee.com/jonkristian +[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge +[buymecoffebadge2]: https://bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/white_img.png +[hacs]: https://github.com/hacs/integration +[hacsbadge]: https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge +[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge +[forum]: https://community.home-assistant.io/ +[license-shield]: https://img.shields.io/github/license/jonkristian/wasteplan_trv.svg?style=for-the-badge +[maintenance-shield]: https://img.shields.io/badge/maintainer-Jon%20Kristian%20sNilsen%20%40jonkristian-blue.svg?style=for-the-badge +[releases-shield]: https://img.shields.io/github/release/jonkristian/wasteplan_trv.svg?style=for-the-badge +[releases]: https://github.com/jonkristian/wasteplan_trv/releases +[exampleimg]: example.png +[integration_blueprint]: https://github.com/ludeeus/integration_blueprint +[ludeeus]: https://github.com/ludeeus/ diff --git a/custom_components/wasteplan_trv/__init__.py b/custom_components/wasteplan_trv/__init__.py index 9f483b2..6095d5e 100644 --- a/custom_components/wasteplan_trv/__init__.py +++ b/custom_components/wasteplan_trv/__init__.py @@ -1 +1,103 @@ -"""Component for integrating wasteplan_trv.""" +"""Wasteplan TRV integration.""" +from __future__ import annotations + +from datetime import timedelta + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .api import ( + TRVApiClient, + TRVApiClientError, +) +from .const import DOMAIN, LOCATION_ID, LOGGER + +PLATFORMS: list[Platform] = [ + Platform.CALENDAR, +] + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up this integration using UI.""" + hass.data.setdefault(DOMAIN, {}) + + hass.data[DOMAIN][entry.entry_id] = coordinator = TRVDataUpdateCoordinator( + hass=hass, + client=TRVApiClient( + location_id=entry.data[LOCATION_ID], + address="", + session=async_get_clientsession(hass), + ), + ) + + await coordinator.async_config_entry_first_refresh() + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + entry.async_on_unload(entry.add_update_listener(async_reload_entry)) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Handle removal of an entry.""" + if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + return unloaded + + +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Reload config entry.""" + await async_unload_entry(hass, entry) + await async_setup_entry(hass, entry) + + +class TRVDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching data from the API.""" + + def __init__( + self, + hass: HomeAssistant, + client: TRVApiClient, + ) -> None: + """Initialize.""" + self.client = client + super().__init__( + hass=hass, + logger=LOGGER, + name=DOMAIN, + update_interval=timedelta(hours=5), + ) + self.entities: list[TRVEntity] = [] + + async def _async_update_data(self): + """Update data via library.""" + try: + return await self.client.async_get_pickups() + except TRVApiClientError as exception: + raise UpdateFailed(exception) from exception + + +class TRVEntity(CoordinatorEntity): + """Representation of a Wasteplan entity.""" + def __init__( + self, + coordinator: TRVDataUpdateCoordinator, + entry: ConfigEntry, + ) -> None: + """Initialize Wasteplan entity.""" + super().__init__(coordinator=coordinator) + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, str(entry.data[LOCATION_ID]))}, + entry_type=DeviceEntryType.SERVICE, + configuration_url="https://github.com/jonkristian/wasteplan_trv", + manufacturer="Trondheim Renholdsverk", + name="Trondheim Renholdsverk", + ) diff --git a/custom_components/wasteplan_trv/api.py b/custom_components/wasteplan_trv/api.py new file mode 100644 index 0000000..ec6d841 --- /dev/null +++ b/custom_components/wasteplan_trv/api.py @@ -0,0 +1,69 @@ +"""TRV API Client.""" +from __future__ import annotations + +import asyncio +import socket + +import aiohttp +import async_timeout + + +class TRVApiClientError(Exception): + """Exception to indicate a general API error.""" + + +class TRVApiClientCommunicationError(TRVApiClientError): + """Exception to indicate a communication error.""" + + +class TRVApiClient: + """TRV API Client.""" + def __init__( + self, + address: str, + location_id: str, + session: aiohttp.ClientSession, + ) -> None: + self._location_id = location_id + self._address = address + self._session = session + + async def async_lookup_address(self) -> any: + """Get address locations from TRV.""" + return await self._api_wrapper( + method="get", + url="https://trv.no/wp-json/wasteplan/v2/adress?s=" + self._address, + ) + + async def async_get_pickups(self) -> any: + """Get pickup base data from TRV.""" + return await self._api_wrapper( + method="get", + url="https://trv.no/wp-json/wasteplan/v2/calendar/" + self._location_id, + ) + + async def _api_wrapper( + self, + method: str, + url: str, + data: dict | None = None, + headers: dict | None = None, + ) -> any: + """Fetch the information from the API.""" + try: + async with async_timeout.timeout(10): + response = await self._session.request( + method=method, + url=url, + headers=headers, + json=data, + ) + response.raise_for_status() + return await response.json() + + except asyncio.TimeoutError as exception: + raise TRVApiClientCommunicationError("Timeout error fetching information") from exception + except (aiohttp.ClientError, socket.gaierror) as exception: + raise TRVApiClientCommunicationError("Error fetching information") from exception + except Exception as exception: # pylint: disable=broad-except + raise TRVApiClientError("Something really wrong happened!") from exception diff --git a/custom_components/wasteplan_trv/calendar.py b/custom_components/wasteplan_trv/calendar.py new file mode 100644 index 0000000..50012ba --- /dev/null +++ b/custom_components/wasteplan_trv/calendar.py @@ -0,0 +1,73 @@ +"""Support for Wasteplan TRV calendar.""" +from __future__ import annotations +import logging +from datetime import date, datetime, timedelta +from homeassistant.util import dt + +from homeassistant.components.calendar import CalendarEntity, CalendarEvent +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import TRVEntity +from .const import DOMAIN, CALENDAR_NAME, LOCATION_ID + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up Wasteplan calendars based on a config entry.""" + coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities([TRVCalendar(coordinator, entry)]) + + +class TRVCalendar(TRVEntity, CalendarEntity): + """Define a Wasteplan calendar.""" + def __init__( + self, + coordinator: DataUpdateCoordinator, + entry: ConfigEntry, + ) -> None: + """Initialize the Wasteplan entity.""" + super().__init__(coordinator, entry) + self._attr_unique_id = str(entry.data[LOCATION_ID]) + self._attr_name = str(entry.data[CALENDAR_NAME]) + self._event: CalendarEvent | None = None + + @property + def event(self) -> CalendarEvent | None: + """Return the next upcoming event.""" + return self._event + + async def async_get_events( + self, + hass: HomeAssistant, + start_date: datetime, + end_date: datetime, + ) -> list[CalendarEvent]: + """Return calendar events within a datetime range.""" + events: list[CalendarEvent] = [] + + # start = start_date.strftime("%Y-%m-%d") + # end = end_date.strftime("%Y-%m-%d") + + for waste in self.coordinator.data["calendar"]: + waste_date = datetime.strptime(waste["dato"], "%Y-%m-%dT%H:%M:%S") + start = dt.start_of_local_day(waste_date) + + event = CalendarEvent( + summary=str(waste["fraksjon"]), + start=start, + end=start + timedelta(days=1), + ) + + if event is not None: + events.append(event) + + return events + + async def get_calendar_item(self, calendar_item) -> object | None: + """Return formatted calendar entry.""" \ No newline at end of file diff --git a/custom_components/wasteplan_trv/config_flow.py b/custom_components/wasteplan_trv/config_flow.py new file mode 100644 index 0000000..bbcd9ac --- /dev/null +++ b/custom_components/wasteplan_trv/config_flow.py @@ -0,0 +1,112 @@ +"""config flow for the Wasteplan TRV integration.""" +from __future__ import annotations + +import voluptuous as vol +from homeassistant import config_entries +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.aiohttp_client import async_create_clientsession + +from .api import ( + TRVApiClient, + TRVApiClientCommunicationError, + TRVApiClientError, +) +from .const import DOMAIN, CALENDAR_NAME, LOCATION_NAME, LOCATION_ID, LOGGER + + +class TRVConfigFLow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle config flow for Wasteplan TRV.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self) -> None: + """Initialize the config flow.""" + self._errors: dict[str, str] = {} + self._locations: list | None = None + self._location_id: str | None = None + + async def async_step_user( + self, + user_input: dict | None = None, + ) -> FlowResult: + """Handle a flow initialized by the user.""" + self._errors = {} + self._locations = None + + if user_input is not None: + address = user_input[LOCATION_NAME] + self._locations = await self._id_from_address( + address=address, + ) + + if self._locations is not None: + return await self.async_step_location() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema({vol.Required(LOCATION_NAME): str}), + errors=self._errors, + ) + + async def async_step_location( + self, + user_input: dict | None = None, + ) -> FlowResult: + """Handle location select""" + self._errors = {} + + assert self._locations is not None + + if user_input is not None: + address = user_input[LOCATION_NAME] + locations = [ + location + for location in self._locations + if location["adresse"] == address + ] + location = locations[0] + location_id = location["id"] + calendar_name = user_input.get(CALENDAR_NAME) + + return self.async_create_entry( + title=address, + data={ + CALENDAR_NAME: calendar_name, + LOCATION_ID: location_id, + }, + ) + + return self.async_show_form( + step_id="location", + data_schema=vol.Schema( + { + vol.Required(CALENDAR_NAME): str, + vol.Required(LOCATION_NAME): vol.In( + [address["adresse"] for address in self._locations] + ), + } + ), + errors=self._errors, + ) + + async def _id_from_address(self, address: str) -> None: + """Validate location.""" + client = TRVApiClient( + address=address, + location_id="", + session=async_create_clientsession(self.hass), + ) + + try: + locations = await client.async_lookup_address() + if len(locations) == 0: + self._errors["base"] = "no_location" + return None + return locations + except TRVApiClientCommunicationError as exception: + LOGGER.error(exception) + self._errors["base"] = "connection" + except TRVApiClientError as exception: + LOGGER.exception(exception) + self._errors["base"] = "unknown" diff --git a/custom_components/wasteplan_trv/const.py b/custom_components/wasteplan_trv/const.py index 9cfc401..328c0ee 100644 --- a/custom_components/wasteplan_trv/const.py +++ b/custom_components/wasteplan_trv/const.py @@ -1,57 +1,12 @@ -ATTRIBUTION = "Data provided by https://trv.no" +"""Constants for Wasteplan TRV integration.""" +from logging import Logger, getLogger -CONF_PICKUP_ID = "id" -CONF_PICKUP_DAY = "pickup_day" +LOGGER: Logger = getLogger(__package__) -URL = "https://trv.no/wp-json/wasteplan/v1/calendar/" +NAME = "Wasteplan TRV" +DOMAIN = "wasteplan_trv" +ATTRIBUTION = "Data provided by Trondheim Renholdsverk" -WASTE_TYPES = { - "Restavfall": [ - "mdi:recycle", # Default - "mdi:delete-alert", # Today - "mdi:delete-alert-outline", # Tomorrow - "mdi:delete-clock-outline", # This week - "mdi:delete-empty-outline", # Emptied - "mdi:delete-restore", # Next week - ], - "Papir": [ - "mdi:file", # Default - "mdi:delete-alert", # Today - "mdi:delete-alert-outline", # Tomorrow - "mdi:delete-clock-outline", # This week - "mdi:delete-empty-outline", # Emptied - "mdi:delete-restore", # Next week - ], - "Plastemballasje": [ - "mdi:bottle-soda", # Default - "mdi:delete-alert", # Today - "mdi:delete-alert-outline", # Tomorrow - "mdi:delete-clock-outline", # This week - "mdi:delete-empty-outline", # Emptied - "mdi:delete-restore", # Next week - ], - "Hageavfall": [ - "mdi:apple", # Default - "mdi:delete-alert", # Today - "mdi:delete-alert-outline", # Tomorrow - "mdi:delete-clock-outline", # This week - "mdi:delete-empty-outline", # Emptied - "mdi:delete-restore", # Next week - ], - "Tømmefri uke": [ - "mdi:delete-forever-outline", # Default - "mdi:delete-forever-outline", # Today - "mdi:delete-forever-outline", # Tomorrow - "mdi:delete-forever-outline", # This week - "mdi:delete-forever-outline", # Emptied - "mdi:delete-forever-outline", # Next week - ], - "Farlig avfall": [ - "mdi:skull-crossbones", # Default - "mdi:skull-scan", # Today - "mdi:skull-scan-outline", # Tomorrow - "mdi:skull-scan-outline", # This week - "mdi:skull-outline", # Emptied - "mdi:skull-crossbones-outline", # Next week - ], -} +CALENDAR_NAME = "calendar_name" +LOCATION_NAME = "address" +LOCATION_ID = "location" diff --git a/custom_components/wasteplan_trv/icons/farligavfall.png b/custom_components/wasteplan_trv/icons/farligavfall.png deleted file mode 100644 index 11362cda4bf3227eb6c583fac7f3d5530fe758fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1865 zcmV-P2e$Z$P)yN8(>-+ugR)-Q`%o6O9OpM^qr(a>*fw z+#*3@Ac_PDpt!3@*eECniqQxHBFBo{vfK#BX;4@Ml-+iEPw#oY_qCnwEM5Awo$l_J zyllJeH=W=2-uu04bUF=}-6*uN=`EA+?^3%#*o{(yBBDf5B$PUoGd6?pligrlXwzH! zxC=_})wPr*>P_wN2Fp>dqCgfBMex<4K^X5e8t-Wy5Jqz+n_k$MC4k1nYu8&A*$V`H zlYnp<1dH8h-J~&yh9aT1W`iKykrxo4)7^qUG8M(2XCO(@3uhe##qBi(Vi$zF(55#u z83?ub5!7f+fLu43?nfX;n+b>_=${L627(tl-`qgzNkuNEb+iQmiD}3v%*fc;5y-lh z1|%-B&0y)puay{U<^029yGOv;`8jaC+=~glwilIX0m$w!nF`YasnuJATLy_H7i1&1im>_d?;rN1*o4jr$TGNI>dE{@b~-`q@L2ACskaVpEc;D=0aGbR6`!UA<6rWb+IM>O2hX_4XmS%7 z0f;4K%4g(Dj*d@h;5*hssTi!BXS09VatLkT3DUv}kVS`b;^F-V!TCaW?wimK3<}m! z4Uj}u1Rl#=x38Wsa{=rFkY z4N|dOaMKoW_3U?ppV3O}%L5q9s;wR4&TrYn4QAhd)W*;lm?Ou))q6may$}QhgCP4P z#T0$B=ERup%dhgO8B9Pe!xBtZ!yPOS98#ip`1cbxXo1X*QWgk@!M}VZKqLae%~j0Q z@(Bk0=Dh?MM<$Wq)pua*^@)=#rMV$D!@wA=t796Wj6k%u%`=-Z`6vM<%njzqQE>H% zgTXWW^(n|jM5ppDbM&f)FxGP8GLi*ssC47t1>gO^@EJ+z0+r8n{ZB# zX(oftWa!r&;2tq18E4{TP`|otxQ9%Rn9nnMyg~-itJj!s5zEmbNFOg|rstbIH(N97 z8TT3MLf%cmEmiEM5;RSIk!}xx@y?vZQX$e@^vcy(6_A zPXLqUTNxM{E5yeRX*UMe{0QC|?GP$2J1UogCXW3K!LUEn7@go?UG4pr>?B#p>8Mn zOa?JVau$}DZr(J<(Hl*yv<5^zMoA_WbQCBPi$Dg~Z&X*Td=Y;p=LFeYmJf4N_uoo# z$&hzpki3n55`d$(mO0X}TqEDkeBgxrs1^psEL&O=%ny%DE3 zr)g0yT*D_agdUWgHu($_ojofbNw;Y9hCi^iiGWB%zZtPsR(=$#fBwu9h$uyNM^R}} zlRXGTCq{!FnvZCu2~jVs5)GEN&H9*@t - - - Slice 1 - Created with Sketch. - - - - - - - - - \ No newline at end of file diff --git a/custom_components/wasteplan_trv/icons/glassogmetall.png b/custom_components/wasteplan_trv/icons/glassogmetall.png deleted file mode 100644 index 98d0526d7935be84ac5c4a7e2d0ea076cf96f5a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1969 zcmV;i2Tu5jP)~QBxJrDxfF=0t#gl1;q{75k(LLL_ij0Jw4~bkQtrj&CHw8 zzT{-yGVi|o-E+@(&vNbTBs?7D{CAEj!B?i_56YBWG5)rqJVlvA8F5tdJ@{KIQ*oIx z6(8-S;1ee5em8dV5ek?g2$U z$q)jIb5?Sn*#w01QsFC{BH0pycv(hdDk0q2+3!7T;dl$D!W#V0b1MVUlsZS1=TFjV zL+SSc8t*w9g3y%b4l03@1jt-D_X##wB}K=aTs@hX^R9!6kFppDGF5}6AY50khpOrt zXnEMqdRrXy0fZ7{>w`3$M%ePT)~ zG_*W`{mG|bf6_^4xOX3prkypny(wh6MWT_+u$E*-ym40?+^VUEn8c$Hd*~Qkzf;4s zV?mp}^AsMR8+S=m9$!mMzXYbRykhn`ulTa1U`XbFMYk z3TuJEfp4BOC;VOAT2bHS)36%UP$hf8HwX0LOL%CxB35^+%Sm9HB9 zv>R^~FxdsgjGook2}%FJrP3>swNG)B_PSzzJfc^ATj9;LKvcZo4{xpe*KXg1g0ic4 z-^X$=3NBJBLX;Ya%6~mH-EW06xtCy`+sap-JG*&8dQLvHw6?+WfDlW+cUgnQI3IHw ziP*Crl1^tr;MN$Jx7iR~Y zV`^#&atccrDE0G)k2+xy=V!E?`8WvOPkJq%4X(m3YDz9K_2q>ge$Y4Y49?{jvT1U$ zw49CbV&0!U?|9s8Q!tJy&P@%b)%2GID66c3h(8mwyyJ}A0(L$&J`PL0*V;}q3NB0y zrq7%PPUl>NWVD&tspk~{&jyDeWal0jM=)~VK%0R}L_+pRc1X4( zHYX4s9{%=pehkYBGi3)+nhDw#GwV$Hgl@%-Y-Z~P-6NYZ24-k@glS@YVuB3<*;+di z#{e?f;P5c>>u%T9iwC3N(_e}*v8*8CMOsJnIgrbI0-s7vV!KFjVN7u8$Z8LnBDESzX`6+!yLNofFFu3rPWn5`}jgnnZvRlJ%vTV#RRR?8>M&_3ct=&;z!(n~$Uby+1_lRVa&n604U*4f>c}kwFz;XzeK*Z0 zRzo_-woTupfcYq?QcnbQ+ulUj5`O^P{5QbayaEP@{IN!?-BjMHX4;sm?GJ?KeAl30HW}gjpB|82CSzJ2eO5f0h00000NkvXXu0mjf Dw57Lk diff --git a/custom_components/wasteplan_trv/icons/glassogmetall.svg b/custom_components/wasteplan_trv/icons/glassogmetall.svg deleted file mode 100644 index 1c23114..0000000 --- a/custom_components/wasteplan_trv/icons/glassogmetall.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - Slice 1 - Created with Sketch. - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/custom_components/wasteplan_trv/icons/pappogpapir.png b/custom_components/wasteplan_trv/icons/pappogpapir.png deleted file mode 100644 index a472ca792f51323ebe5d157a465bd9c7594d2837..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2295 zcmVU1ho-b2kwE6cJ|wwrC%>6oW9P2U^9 zeYZ51Tye=vbIA>1W*#DfY~qfJf(t4tpeT}v3of823T}v^c)Pz3=A8#Jyg_+@bIy0( z_h!Ct=DYX)?(g3F&A7Q$^I`6?;2C$T?R~RVvARp@K(i!|bhj$wOjdcQyG7|?mTX_v zm;B6bZuF2_T{~L#@_Wo=QQPrieOZ8XlT{&u?E;UFG+E@n^}KD*)v6GUx+oqjz${)= zZWRR3Yen2vd06E(H7bOuvs&LIskYJuP_;KnNnn_M=2|Iyd>%Gil)?PK%PtY3Y`eqH z3UUeja8r{-iLL=5$~1SYdd_T-zp6Gt+`9%eXWz)GX+a#?ZH8XI`=?n?6xoO;=hP`dG-8kx&ng8$HtEhfD2UbyJtXCAV}4?h zr`2IWH2+C&=oRpU0p@a1FsyEYB-y9(=x$Zw9|S?{aAV)D7+##sRyNr~QmOUuNE+RrcaO6VTyKR=Gf zFG--2=dMuj=5%_aPtbiYt~uXhLhvs7VZth-b8aA4EGvjbX#(1-Y$Am4Ws8(Q->hdK z1#bF}t`yv)q>LO=*B_+83%AhMqoNqsci*RxmyHH2*h~x7?5E(kLlhC8L2Gs&r+vrH zi+kL;L-g^`a7IA{jb5~a#suzkMh1VB^%aXc(U}%n^be-oOV{a(k;^K)-mF(3dCu5C z(;|{+)vjZ7?Cd4V%DpOJsg3z)Xc!$jl}FnTW{YtnY}-W}Y4rk?&ZNoul);V0>@l*8%e=|CY{t8PJ8vS?0KH(?YwZ8LWh40La+AI2R_ALvw z_6(QY%h$z1JF#z_XJugVde8cH>~cywdC~qqASR8*1tmCz8xuh@A;=3>-{_|xPybmrnUdbP(Q@gIW0`Sr%bho*$> zq4x$a760KM5C#y#|IbH6()y$mqRn5OA~fHDpMh&VW^WYdwR-`o7;k_GLN$nuaW;54 zL+PK$WYLGF;H=s;;2ajzn+wv==Jw>pZrXQ}J{%HO;aI>j>Bza#pr}yc&D-=!_eDm8 z*l`vaI-e_b3@Y|#Gp&OD$A1Xx!qRLTJso0mDvzMce%d^o;(e!@>p345v4|#pQ+#1S5lI zEK8=jD^o=O?}$W^1?cBZB@0I%^o)g}k6`<&-xk4)dh?2I6$_;x+c=V)Z&(Zl# zg%b{)6;=l8p&`<1_essXAcRBALBEQYG3GOf8@FzkKmc$k=Dee#bj2}az;8xYK7BQ+ zBy0G+)Y&VW2SrHJ*9*(-J$l}-FkIg}4PgR}3~nf#;)V^6hjRyYG}>Ff1Gp0#$dOTEb9^ zHl~T}T4Ot~7Xl+#4s?#T&LL2O&5%6zS@2c7RZoMUiHR}B%uG>`(Hp0X@AG#<4Z&79dg>z!4;nm|@H^4J7|p9Evk_yt4@*Tus6=k;k17)-7V^st4!VUy41Vw1DXbHYqbn4(2D&|yN*DKY#KBU#gs2ADrjkC z>KUfBhR!O@(;UgcXywnt4^Uho`9yXdy7jPt2Q-WWdjxoV z69(Fa7Lkul4NnyL70w~&Lgd$_bIt~!b^LmGmIO`YXJ9F0;YcL$3&e$fYn1vHDi!==PX_8rtraCqcnAPlf4 z{1Fz`3zYS}Z6xN>QDFm47bkr@CwlXb)I;!P6;EaL+;~Mpz>ZdeY9}@Z_LVX!`{o$(95qW-z>8h z1u5ccxLdWoK5yL7? - - - papir - Created with Sketch. - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/custom_components/wasteplan_trv/icons/plastemballasje.png b/custom_components/wasteplan_trv/icons/plastemballasje.png deleted file mode 100644 index c183273ee82630b409f7b905ab55cf73a5b08eeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2128 zcmV-W2(R~vP)YSlQer(H(NPwy&>XM@|p26>hV4MG64SgSJ5U+L!a)QEssqY)?TR6@ca z0Z7SpCZc}4>A7J6p>;LA%wCu@#2{>isieEws9!Y3^wnfn-3I-t<>r? zrU^r97h_CMum>L;MiAC=Q>QY{8xkO^U4>_~YT?KT0kMd?O7OUEAk0(Mk2DY~K!!o9 z)@WFf{-RYIS2=C$?8dm!2Bc4VSWKFs-2L`?EgHpgX^qP3d3p8H7{XSzO`}#+v zRwY{w%mMdIE;x=|olf!ktO5IB1!}XK?AniKoTRWt31s|uFPWwVTUM^$jMM%lNp#^< z7X|-*K!*B=vnANC{L*4ZBmPR_$3gRADQ9Qtz+iSArk=Y!TrI8J3j)fyYSpAQa5ZZ> zbcR}vp0&Gw_s`MPQFnn7)@RF&sce8%;WSXZh4ylK)-{_i_0oKBawaa*wN3uE6oD~zwi;mB$(xEBTvV{bpvNf%FF z=Bm_K)I#M^)l?E%$@|6WGC0`i6{+0jVUTSO;zUAi(Zg_~Jw=+->{P z9G?>Iip}S60x4VbDZ^Mqv1`(4A9EaV$&TI`{97Cy3aL;+bBe5jq;Y}V4VY!*KvEqY-z>-GISG zE=`ua9=$S^(svZvxaKU+L;M&nIM2U~1A#3S2Un0`>Ta$^xe^WpngF{A`ZZ2zAf043 z{(QO4&}-q-Aj#{nAvgo`Ic(`vS!-YL^k>Fg&jA4hYyy1|bfpH1c-GTH+^Z>L1j0|| z_|hWR`6L6g*3IOwPjgw@y_t0txyO^T3tZpunz!V$Dh{0B-VFAoj7^Jc&0sFebw)(C z<7i)p@maPGh)@snIlLFB&*ry$ohFlQHRmX1RT}q-il~iR*lq@MTds3;a=l$!Wqd6k z?Di>v{JgcQ!!Fu46rASjAg$=pcJhPtJRso$QCIuu>Nf@Q1CRo6z3q5*4JS( zpe;%VWG!IVML|cYDzT2!9t9O0?>=uPbSkl#{ss>!f{2~^Pd;z!YQ1992b2Y1irJu? zrT75Qe*8SQ8HMh7iy2IvT<4aGcAIl}B@jgLGjo*gY`kRW&XM;FCILgPgy)0qfEg%I zvt_+t6jUbRO3gGLvhfWc-iAnynMWIDvVQ~zO&JWBsAVZUP9cF)!~)df^gf$0Zj*Y}Y&$)E#?w#{eQwt~YvhUFNW-`pu{w54HH zHf@*AbUP%IQj*D%z-+HWl(->>;@4%#^f%76EibYC5l1&vCDh7-@%wf^ry!7eGhhr;_t2Gd*LeCZvobeVyyWqu4H#_n^zDxfcoRGp*sr4j1kQ=#m6!hQJ3b2J zaSDMKq*Ey%RBaH5%{D0^Ap=)uSS?rTrj4)raORB6QHmfU`AXWHD@p0W2R7HE8)tAExBxiT^Vz<=M~N4S{3S@LP-YT7*=u0~&}}4O+dm9+ zINQinQ5OJlML;7_5xn$=xXie*Dx`@--OGoo*3o02&fXYa?1}|k17pvf8_E| zAGl8p%$cBApV!PQFmS`jD#g!&oZ4N}j`e)eAsB4E@g1wtU2BG{R<;Gs^w5#Ijksux zxwBj%A5p%i&$ix1DVTQHw$mFd|iH@-c%|Gj^<^~Qbr zNU9dAg%8>Gxx2Tolyf-Jh?F5L6j6TI6AHjDAE~D*- zYCMYA?iz6pzDz=;kqy?5o%`UE5vD>*v5$|O-ID4K(eHmY9$1p4Sq}*S0000 - - - Slice 1 - Created with Sketch. - - - - - - - - - \ No newline at end of file diff --git a/custom_components/wasteplan_trv/icons/restavfall.png b/custom_components/wasteplan_trv/icons/restavfall.png deleted file mode 100644 index 0334f2f4d88a5467a8dd122a1af5d0353d50b68b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1692 zcma)7X*ASp82^{ChFlT~GfY{NGFN1YSqx*`vCjxuOO6(iea(m_WJ^VMMhX|VK`1qr z3@T&^iAzSVX&4+r~H zqz6O-!2WSF6DtEY1z<41S{c9`1JGiCz9^tAzl#ke0bRBN1&}g;E*Q{1x{Fb=073@P zhX80WfRfy`;CmDS2H?^FQVKvw?*RyafBZI(AtC#bG8`ybkO7nv4dp@~<2F3bV|rG z@MDz(Oq34(apE`AlftH|hb`2^EMXGX>PM{49K~x$5j3T3&VtWtL2R{U?R25`=j0vX z3QhJ zeC*JE_D22=m;gu9(2HiFmn_0BTSs`{iB|~3E4ESB?W1ovkh~p9-cEOKI>-39#QM6$ z2VRa3bWaGnc0c@j%B>p@BD@|Febb`-((VLh!~|!?hGyRj&q=tIn-Gzg7@41Zn|wdI z-~p-dVa(Ii*k@_+#Tf}DnaQQu_seop%5xt*C#O;h(kKOwE1qOjJ_5%+nRen(0bcj`#L{-?(P`u{W#RuJ>1{(b)a|T^C$XXAN|X~ z=)f18>ZoBlpN^J8LW^2glt)cou;V|I3Ner}O5zqG(u zUR+pNT3lIPT3udVTUl9OU0q*W-`v<>Zf-J}%q`{?Ym2qb+TPyY+1aTb0rJ@I+}g(i zX9;j}aUb9p5EK?YEG8i-B@KqisGL+ir3O>C#uLul+BrBnIR^!YgocGDC8s=koSBoG zmtXj-sJMjkqPnK`<*T}8T1)Sz{(-@v>Djq?#`?x4dk%+jyE}WpJ{PPZYW2&Yu`>X0 zfG{YymAJ&cdVl>mFG=o^GN`iss$9A~x(aINb;oCoK6Pj+v948mAU#erRtSQBer1Qa9LxWmjCJ&|2V9$%1s&;LW|C+%|=vd{o+! zCH=v=WOZM8jOa`F!>avv6{qSIJvq;|NSC1qzu< z@t|g~3F;9rHMx~pG!*0^lmxc1R1@K%mJ)gznT^yb6d9yEvw`P{I;IDUC?I<{rCxW? zphhg<`E0Vt#G$bsZMu-er7Tdi*hYifM8Xi-NHg@fxSz+={^H>T5s2cq33&g;eJ*@4 zOctWjr6YRgf+Fy${NPcDyrTLcysiT9O2rcap)ChWl>{YnWiUFf$awDP+V~cn94Xlm z=LaJGg&5=g;mLu7s0ysn2A^{v@fPjR!GAPAdH;P2e&80MR`?BJRCCc@5)@eBdJr`sxqwE5SvMCq5-l$4i}Og7|P5z zS56|~Hnu}Nt-$>~@;JE4Oi;pw2aIp;7Sz)P9Yd0RT}Q>sD>z96K|#5uOl{#8F44S# zN=f%Lwqs*vyepf+-SZ*QcEQy>?e{>76}JyuTTJGN(#WtFm;H;+wxuftirJ|1NhGf+ z8hOVsK?qb|9{<@yueB)<9>vYOxyxa^LE4$cn;@soLAnp}vOtyMGUWF~Ffp I5l+Ov0eevCkpKVy diff --git a/custom_components/wasteplan_trv/icons/restavfall.svg b/custom_components/wasteplan_trv/icons/restavfall.svg deleted file mode 100644 index 776dbea..0000000 --- a/custom_components/wasteplan_trv/icons/restavfall.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - Slice 1 - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/custom_components/wasteplan_trv/manifest.json b/custom_components/wasteplan_trv/manifest.json index e1c84bd..7385bbd 100644 --- a/custom_components/wasteplan_trv/manifest.json +++ b/custom_components/wasteplan_trv/manifest.json @@ -1,13 +1,12 @@ { "domain": "wasteplan_trv", "name": "Wasteplan TRV", - "documentation": "https://github.com/jonkristian/wasteplan_trv", - "issue_tracker": "https://github.com/jonkristian/wasteplan_trv/issues", - "dependencies": [], "codeowners": [ "@jonkristian" ], - "requirements": [], + "config_flow": true, + "documentation": "https://github.com/jonkristian/wasteplan_trv", "iot_class": "cloud_polling", - "version": "1.0.1" + "issue_tracker": "https://github.com/jonkristian/wasteplan_trv/issues", + "version": "2.0.0" } \ No newline at end of file diff --git a/custom_components/wasteplan_trv/sensor.py b/custom_components/wasteplan_trv/sensor.py deleted file mode 100644 index 0ba1486..0000000 --- a/custom_components/wasteplan_trv/sensor.py +++ /dev/null @@ -1,186 +0,0 @@ -"""Platform for sensor integration.""" -from datetime import datetime as date, timedelta -import logging - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle -import homeassistant.util.dt as dt_util -import requests -import voluptuous as vol - -from .const import ( - ATTRIBUTION, - CONF_PICKUP_ID, - CONF_PICKUP_DAY, - WASTE_TYPES, - URL, -) - -_LOGGER = logging.getLogger(__name__) - -SCAN_INTERVAL = timedelta(hours=5) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Required(CONF_PICKUP_ID): cv.string, vol.Optional(CONF_PICKUP_DAY): cv.string} -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the TRV sensor.""" - id = config.get(CONF_PICKUP_ID) - - pickup_day = 0 - if config.get(CONF_PICKUP_DAY): - pickup_day = int(config.get(CONF_PICKUP_DAY)) - data = TRVData(id) - data.update() - - sensors = [] - for wastetype in WASTE_TYPES: - sensors.append(TRVSensor(wastetype, data, pickup_day)) - - add_entities(sensors, True) - - -class TRVData: - """Get the latest data for all authorities.""" - - def __init__(self, id): - """Initialize the object.""" - self.data = None - self.id = id - - # Update only once in scan interval. - @Throttle(SCAN_INTERVAL) - def update(self): - """Get the latest data from TRV.""" - response = requests.get(URL + self.id, timeout=10) - if response.status_code != 200: - _LOGGER.warning("Invalid response from TRV API") - else: - self.data = response.json() - - -class TRVSensor(Entity): - """Single authority wasteplan sensor.""" - - def __init__(self, name, data, pickup_day): - """Initialize the sensor.""" - self._data = data - self._name = name - self._icon = WASTE_TYPES[self._name][0] - self._state = "Ikke bestemt" - self._year = None - self._pickup_this_week = False - self._next_pickup_week = None - self._date_week_start = None - self._date_week_end = None - self._description = None - self._pickup_day = pickup_day - self.attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} - - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def icon(self): - """Return the icon.""" - return self._icon - - @property - def extra_state_attributes(self): - """Return other details about the sensor state.""" - self.attrs["next_pickup_week"] = self._next_pickup_week - self.attrs["pickup_this_week"] = self._pickup_this_week - self.attrs["description"] = self._description - self.attrs["date_week_start"] = self._date_week_start - self.attrs["date_week_end"] = self._date_week_end - - return self.attrs - - def update(self): - """Update the sensor.""" - self._data.update() - - containers = [] - today = dt_util.now().weekday() - this_week = dt_util.now().isocalendar()[1] - - # Iterate and stop on match. - for entry in self._data.data["calendar"]: - - # Containers? Make a list. - if "," in entry["wastetype"]: - containers = map(str.strip, entry["wastetype"].split(",")) - - # Do we have a match? - if self._name == entry["wastetype"] or self._name in containers: - self._year = entry["year"] - self._next_pickup_week = entry["week"] - self._date_week_start = entry["date_week_start"] - self._date_week_end = entry["date_week_end"] - - self._state, self._icon = self.pickup_state() - - if entry["week"] == this_week: - self._pickup_this_week = True - if today <= self._pickup_day: - break - - elif entry["week"] > this_week: - break - - def pickup_state(self): - state = None - icon = None - - year = dt_util.now().year - today = dt_util.now().weekday() - tomorrow = (dt_util.now() + timedelta(1)).weekday() - weeks_until = 0 - this_week = dt_util.now().isocalendar()[1] - - weeks_until = self._next_pickup_week - this_week - - if 0 == weeks_until: - - if self._name.startswith("Tømmefri"): - state = "Denne uken" - icon = WASTE_TYPES[self._name][3] - elif today == self._pickup_day: - state = "I dag" - icon = WASTE_TYPES[self._name][1] - elif tomorrow == self._pickup_day: - state = "I morgen" - icon = WASTE_TYPES[self._name][2] - elif today < self._pickup_day or self._pickup_this_week: - state = "Denne uken" - icon = WASTE_TYPES[self._name][3] - else: - state = "Tømt" - icon = WASTE_TYPES[self._name][4] - - elif 1 == weeks_until: - - state = "Neste uke" - icon = WASTE_TYPES[self._name][4] - - elif year < self._year: - state = "Uke " + str(self._next_pickup_week) + " (" + str(self._year) + ")" - icon = WASTE_TYPES[self._name][5] - - else: - state = "Uke " + str(self._next_pickup_week) - icon = WASTE_TYPES[self._name][5] - - return state, icon diff --git a/custom_components/wasteplan_trv/strings.json b/custom_components/wasteplan_trv/strings.json new file mode 100644 index 0000000..eb484e9 --- /dev/null +++ b/custom_components/wasteplan_trv/strings.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "user": { + "data": { + "location_name": "Address" + }, + "description": "Fill out your address" + }, + "location": { + "data": { + "calendar_name": "Calendar name", + "location_id": "Location ID" + }, + "description": "Pick your location" + } + }, + "error": { + "no_location": "Unable to find location based on your address", + "connection": "Unable to connect to the server.", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + } +} \ No newline at end of file diff --git a/custom_components/wasteplan_trv/translations/en.json b/custom_components/wasteplan_trv/translations/en.json new file mode 100644 index 0000000..eb484e9 --- /dev/null +++ b/custom_components/wasteplan_trv/translations/en.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "user": { + "data": { + "location_name": "Address" + }, + "description": "Fill out your address" + }, + "location": { + "data": { + "calendar_name": "Calendar name", + "location_id": "Location ID" + }, + "description": "Pick your location" + } + }, + "error": { + "no_location": "Unable to find location based on your address", + "connection": "Unable to connect to the server.", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + } +} \ No newline at end of file diff --git a/custom_components/wasteplan_trv/translations/nb.json b/custom_components/wasteplan_trv/translations/nb.json new file mode 100644 index 0000000..4ec85b0 --- /dev/null +++ b/custom_components/wasteplan_trv/translations/nb.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "user": { + "data": { + "location_name": "Adresse" + }, + "description": "Skriv inn din adresse" + }, + "location": { + "data": { + "calendar_name": "Kalendernavn", + "location_id": "Lokasjons ID" + }, + "description": "Velg din lokasjon" + } + }, + "error": { + "no_location": "Fant ingen lokasjoner fra denne adressen.", + "connection": "Kunne ikke kontakte til server.", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + } +} \ No newline at end of file diff --git a/example.png b/example.png new file mode 100644 index 0000000000000000000000000000000000000000..c22e3deeda69a13bd8969aad34556069e665d67a GIT binary patch literal 7683 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 literal 0 HcmV?d00001 diff --git a/hacs.json b/hacs.json index dd7f144..df73baf 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,6 @@ { "name": "Wasteplan TRV", "country": "NO", - "domains": "sensor", + "domains": "calendar", "render_readme": true } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..17dfa6b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pip>=21.0,<23.1 +colorlog +homeassistant diff --git a/scripts/develop b/scripts/develop new file mode 100755 index 0000000..49de63b --- /dev/null +++ b/scripts/develop @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +# Start Home Assistant +hass -c . --debug diff --git a/scripts/setup b/scripts/setup new file mode 100755 index 0000000..141d19f --- /dev/null +++ b/scripts/setup @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +python3 -m pip install --requirement requirements.txt diff --git a/setup.cfg b/setup.cfg index 17c1143..dc6564a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ include_trailing_comma=True force_grid_wrap=0 use_parentheses=True line_length=88 -indent = " " +indent = " " # by default isort don't check module indexes not_skip = __init__.py # will group `import x` and `from x import` of the same module.