From dbac414bd4590d0e501ed102ce45526ca449a22d Mon Sep 17 00:00:00 2001 From: Dan Raper Date: Mon, 15 Jan 2024 20:28:31 +0100 Subject: [PATCH] Tolerate certain API failures, allow config migration, charge graph reading tweak, allow credential changes (#40) * Tolerate certain coordinator setup failures * Version bump * Updated issue templates * Fix no end time in the last charge slot * Deduplicate entity types * Added config migration support * Allow credential changes --- .github/ISSUE_TEMPLATE/bug_report.md | 6 +-- .github/ISSUE_TEMPLATE/enquiry.md | 8 ++++ custom_components/ohme/__init__.py | 46 +++++++++++++++++++-- custom_components/ohme/config_flow.py | 41 +++++++++++++++++- custom_components/ohme/const.py | 4 +- custom_components/ohme/translations/en.json | 4 +- custom_components/ohme/utils.py | 4 +- 7 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/enquiry.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2319356..fc79209 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,7 +7,7 @@ assignees: dan-r --- -**Describe the bug** +**What happened?** A clear and concise description of what the bug is. **To Reproduce** @@ -16,8 +16,8 @@ Steps to reproduce the behavior. **Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots** -If applicable, add screenshots to help explain your problem. +**Log Output** +Any relevant logs from Home Assistant. **Home Assistant Version** Find this in Settings > About. diff --git a/.github/ISSUE_TEMPLATE/enquiry.md b/.github/ISSUE_TEMPLATE/enquiry.md new file mode 100644 index 0000000..aecfb67 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enquiry.md @@ -0,0 +1,8 @@ +--- +name: Enquiry +about: Ask a question +title: '' +labels: enquiry +assignees: dan-r + +--- diff --git a/custom_components/ohme/__init__.py b/custom_components/ohme/__init__.py index fe82310..ca7110f 100644 --- a/custom_components/ohme/__init__.py +++ b/custom_components/ohme/__init__.py @@ -1,8 +1,11 @@ +import logging from homeassistant import core from .const import * from .api_client import OhmeApiClient from .coordinator import OhmeChargeSessionsCoordinator, OhmeStatisticsCoordinator, OhmeAccountInfoCoordinator, OhmeAdvancedSettingsCoordinator, OhmeChargeSchedulesCoordinator +from homeassistant.exceptions import ConfigEntryNotReady +_LOGGER = logging.getLogger(__name__) async def async_setup(hass: core.HomeAssistant, config: dict) -> bool: """Set up the Ohme EV Charger component.""" @@ -39,14 +42,30 @@ async def async_setup_entry(hass, entry): OhmeChargeSchedulesCoordinator(hass=hass) # COORDINATOR_SCHEDULES ] + # We can function without these so setup can continue + coordinators_optional = [ + OhmeStatisticsCoordinator, + OhmeAdvancedSettingsCoordinator + ] + for coordinator in coordinators: - await coordinator.async_config_entry_first_refresh() + # Catch failures if this is an 'optional' coordinator + try: + await coordinator.async_config_entry_first_refresh() + except ConfigEntryNotReady as ex: + allow_failure = False + for optional in coordinators_optional: + allow_failure = True if isinstance(coordinator, optional) else allow_failure + + if allow_failure: + _LOGGER.error(f"{coordinator.__class__.__name__} failed to setup. This coordinator is optional so the integration will still function, but please raise an issue if this persists.") + else: + raise ex hass.data[DOMAIN][DATA_COORDINATORS] = coordinators # Create tasks for each entity type - entity_types = ["sensor", "binary_sensor", "switch", "button", "number", "time"] - for entity_type in entity_types: + for entity_type in ENTITY_TYPES: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, entity_type) ) @@ -57,4 +76,23 @@ async def async_setup_entry(hass, entry): async def async_unload_entry(hass, entry): """Unload a config entry.""" - return await hass.config_entries.async_unload_platforms(entry, ['binary_sensor', 'sensor', 'switch']) + return await hass.config_entries.async_unload_platforms(entry, ENTITY_TYPES) + +async def async_migrate_entry(hass: core.HomeAssistant, config_entry) -> bool: + """Migrate old entry.""" + # Version number has gone backwards + if CONFIG_VERSION < config_entry.version: + _LOGGER.error("Backwards migration not possible. Please update the integration.") + return False + + # Version number has gone up + if config_entry.version < CONFIG_VERSION: + _LOGGER.debug("Migrating from version %s", config_entry.version) + new_data = config_entry.data + + config_entry.version = CONFIG_VERSION + hass.config_entries.async_update_entry(config_entry, data=new_data) + + _LOGGER.debug("Migration to version %s successful", config_entry.version) + + return True diff --git a/custom_components/ohme/config_flow.py b/custom_components/ohme/config_flow.py index 1d7279b..ec566a7 100644 --- a/custom_components/ohme/config_flow.py +++ b/custom_components/ohme/config_flow.py @@ -1,6 +1,6 @@ import voluptuous as vol from homeassistant.config_entries import (ConfigFlow, OptionsFlow) -from .const import DOMAIN +from .const import DOMAIN, CONFIG_VERSION from .api_client import OhmeApiClient @@ -9,8 +9,10 @@ vol.Required("password"): str }) + class OhmeConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow.""" + VERSION = CONFIG_VERSION async def async_step_user(self, info): errors = {} @@ -30,3 +32,40 @@ async def async_step_user(self, info): return self.async_show_form( step_id="user", data_schema=USER_SCHEMA, errors=errors ) + + def async_get_options_flow(entry): + return OhmeOptionsFlow(entry) + + +class OhmeOptionsFlow(OptionsFlow): + """Options flow.""" + + def __init__(self, entry) -> None: + self._config_entry = entry + + async def async_step_init(self, info): + errors = {} + if info is not None: + instance = OhmeApiClient(info['email'], info['password']) + if await instance.async_refresh_session() is None: + errors["base"] = "auth_error" + else: + self.hass.config_entries.async_update_entry( + self._config_entry, data=info + ) + return self.async_create_entry( + title="", + data={} + ) + + return self.async_show_form( + step_id="init", data_schema=vol.Schema( + { + vol.Required( + "email", default=self._config_entry.data['email'] + ): str, + vol.Required( + "password" + ): str + }), errors=errors + ) diff --git a/custom_components/ohme/const.py b/custom_components/ohme/const.py index 4f03218..ae0b25f 100644 --- a/custom_components/ohme/const.py +++ b/custom_components/ohme/const.py @@ -1,7 +1,9 @@ """Component constants""" DOMAIN = "ohme" USER_AGENT = "dan-r-homeassistant-ohme" -INTEGRATION_VERSION = "0.3.1" +INTEGRATION_VERSION = "0.3.2" +CONFIG_VERSION = 1 +ENTITY_TYPES = ["sensor", "binary_sensor", "switch", "button", "number", "time"] DATA_CLIENT = "client" DATA_COORDINATORS = "coordinators" diff --git a/custom_components/ohme/translations/en.json b/custom_components/ohme/translations/en.json index 14a1a76..c1c83e5 100644 --- a/custom_components/ohme/translations/en.json +++ b/custom_components/ohme/translations/en.json @@ -17,9 +17,9 @@ }, "options": { "step": { - "user": { + "init": { "title": "Update Account Info", - "description": "Update your basic account information.", + "description": "Update your Ohme account information.", "data": { "email": "Email address", "password": "Password" diff --git a/custom_components/ohme/utils.py b/custom_components/ohme/utils.py index 8053374..9d0a744 100644 --- a/custom_components/ohme/utils.py +++ b/custom_components/ohme/utils.py @@ -18,8 +18,8 @@ def charge_graph_next_slot(charge_start, points, skip_format=False): # Filter to points from now onwards data = [x for x in data if x["t"] > now] - # Give up if we have less than 3 points - if len(data) < 3: + # Give up if we have less than 2 points + if len(data) < 2: return {"start": None, "end": None} start_ts = None