-
-
Notifications
You must be signed in to change notification settings - Fork 36.5k
Add Homevolt battery integration #160416
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Add Homevolt battery integration #160416
Changes from all commits
335994a
d9c1f48
21d0bd3
a741f21
90ae81f
14a67c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| """The Homevolt integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from homevolt import Homevolt, HomevoltConnectionError | ||
|
|
||
| from homeassistant.const import CONF_HOST, CONF_PASSWORD, Platform | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import ConfigEntryNotReady | ||
| from homeassistant.helpers import config_validation as cv | ||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
|
||
| from .const import DOMAIN, HomevoltConfigEntry | ||
| from .coordinator import HomevoltDataUpdateCoordinator | ||
|
|
||
| PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
|
||
| CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) | ||
|
|
||
|
|
||
| async def async_setup_entry(hass: HomeAssistant, entry: HomevoltConfigEntry) -> bool: | ||
| """Set up Homevolt from a config entry.""" | ||
| host: str = entry.data[CONF_HOST] | ||
| password: str | None = entry.data.get(CONF_PASSWORD) | ||
|
|
||
| websession = async_get_clientsession(hass) | ||
| client = Homevolt(host, password, websession=websession) | ||
|
|
||
| try: | ||
| await client.update_info() | ||
| except HomevoltConnectionError as err: | ||
| raise ConfigEntryNotReady( | ||
| f"Unable to connect to Homevolt battery: {err}" | ||
| ) from err | ||
Danielhiversen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| coordinator = HomevoltDataUpdateCoordinator(hass, entry, client) | ||
| await coordinator.async_config_entry_first_refresh() | ||
|
|
||
| entry.runtime_data = coordinator | ||
|
|
||
| await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
|
||
| return True | ||
|
Comment on lines
+21
to
+43
|
||
|
|
||
|
|
||
| async def async_unload_entry(hass: HomeAssistant, entry: HomevoltConfigEntry) -> bool: | ||
| """Unload a config entry.""" | ||
| if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): | ||
| if entry.runtime_data: | ||
Danielhiversen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| await entry.runtime_data.client.close_connection() | ||
Danielhiversen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return unload_ok | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| """Config flow for the Homevolt integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import Any | ||
|
|
||
| from homevolt import Homevolt, HomevoltAuthenticationError, HomevoltConnectionError | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.config_entries import ConfigFlow, ConfigFlowResult | ||
| from homeassistant.const import CONF_HOST, CONF_PASSWORD | ||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
|
||
| from .const import DOMAIN | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| STEP_USER_DATA_SCHEMA = vol.Schema( | ||
| { | ||
| vol.Required(CONF_HOST): str, | ||
| vol.Optional(CONF_PASSWORD): str, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do we use the password for? When does a device have one? |
||
| } | ||
| ) | ||
|
|
||
|
|
||
| class HomevoltConfigFlow(ConfigFlow, domain=DOMAIN): | ||
| """Handle a config flow for Homevolt.""" | ||
|
|
||
| VERSION = 1 | ||
|
|
||
| async def async_step_user( | ||
| self, user_input: dict[str, Any] | None = None | ||
| ) -> ConfigFlowResult: | ||
| """Handle the initial step.""" | ||
| errors: dict[str, str] = {} | ||
| if user_input is not None: | ||
| host = user_input[CONF_HOST] | ||
| password = user_input.get(CONF_PASSWORD) | ||
| websession = async_get_clientsession(self.hass) | ||
| try: | ||
| await Homevolt(host, password, websession=websession).update_info() | ||
| except HomevoltAuthenticationError: | ||
| errors["base"] = "invalid_auth" | ||
| except HomevoltConnectionError: | ||
| errors["base"] = "cannot_connect" | ||
| except Exception: # noqa: BLE001 | ||
| errors["base"] = "unknown" | ||
Danielhiversen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| else: | ||
| await self.async_set_unique_id(host) | ||
| self._abort_if_unique_id_configured() | ||
Danielhiversen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return self.async_create_entry( | ||
| title="Homevolt Local", | ||
| data={ | ||
| CONF_HOST: host, | ||
| CONF_PASSWORD: password, | ||
| }, | ||
| ) | ||
|
|
||
| return self.async_show_form( | ||
| step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| """Constants for the Homevolt integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from datetime import timedelta | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from homeassistant.config_entries import ConfigEntry | ||
|
|
||
| if TYPE_CHECKING: | ||
| from .coordinator import HomevoltDataUpdateCoordinator | ||
|
|
||
| DOMAIN = "homevolt" | ||
| MANUFACTURER = "Homevolt" | ||
| SCAN_INTERVAL = timedelta(seconds=15) | ||
| type HomevoltConfigEntry = ConfigEntry["HomevoltDataUpdateCoordinator"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| """Data update coordinator for Homevolt integration.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
|
|
||
| from homevolt import ( | ||
| Device, | ||
| Homevolt, | ||
| HomevoltAuthenticationError, | ||
| HomevoltConnectionError, | ||
| HomevoltError, | ||
| ) | ||
|
|
||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import ConfigEntryAuthFailed | ||
| from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
|
|
||
| from .const import DOMAIN, SCAN_INTERVAL, HomevoltConfigEntry | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class HomevoltDataUpdateCoordinator(DataUpdateCoordinator[Device]): | ||
| """Class to manage fetching Homevolt data.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| hass: HomeAssistant, | ||
| entry: HomevoltConfigEntry, | ||
| client: Homevolt, | ||
| ) -> None: | ||
| """Initialize the Homevolt coordinator.""" | ||
| self.client = client | ||
| super().__init__( | ||
| hass, | ||
| _LOGGER, | ||
| name=DOMAIN, | ||
| update_interval=SCAN_INTERVAL, | ||
| config_entry=entry, | ||
| ) | ||
|
|
||
| async def _async_update_data(self) -> Device: | ||
| """Fetch data from the Homevolt API.""" | ||
| try: | ||
| await self.client.update_info() | ||
| return self.client.get_device() | ||
| except HomevoltAuthenticationError as err: | ||
| raise ConfigEntryAuthFailed from err | ||
| except (HomevoltConnectionError, HomevoltError) as err: | ||
| raise UpdateFailed(f"Error communicating with device: {err}") from err |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "domain": "homevolt", | ||
| "name": "Homevolt", | ||
| "codeowners": ["@danielhiversen"], | ||
| "config_flow": true, | ||
| "dependencies": [], | ||
| "documentation": "https://www.home-assistant.io/integrations/homevolt", | ||
| "homekit": {}, | ||
| "integration_type": "device", | ||
| "iot_class": "local_polling", | ||
| "quality_scale": "bronze", | ||
| "requirements": ["homevolt==0.2.3"], | ||
| "ssdp": [], | ||
| "zeroconf": [] | ||
Danielhiversen marked this conversation as resolved.
Show resolved
Hide resolved
Danielhiversen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| rules: | ||
| # Bronze | ||
| action-setup: | ||
| status: exempt | ||
| comment: Integration does not register custom actions. | ||
| appropriate-polling: done | ||
| brands: done | ||
| common-modules: done | ||
| config-flow-test-coverage: done | ||
| config-flow: done | ||
| dependency-transparency: done | ||
| docs-actions: | ||
| status: exempt | ||
| comment: Integration does not register custom actions. | ||
| docs-high-level-description: done | ||
| docs-installation-instructions: done | ||
| docs-removal-instructions: done | ||
| entity-event-setup: | ||
| status: exempt | ||
| comment: Local_polling without events | ||
| entity-unique-id: done | ||
| has-entity-name: done | ||
| runtime-data: done | ||
| test-before-configure: done | ||
| test-before-setup: done | ||
| unique-config-entry: done | ||
|
|
||
| # Silver | ||
| action-exceptions: | ||
| status: exempt | ||
| comment: Integration does not register custom actions. | ||
| config-entry-unloading: done | ||
| docs-configuration-parameters: | ||
| status: exempt | ||
| comment: Integration does not have an options flow. | ||
| docs-installation-parameters: todo | ||
| entity-unavailable: done | ||
| integration-owner: done | ||
| log-when-unavailable: done | ||
| parallel-updates: | ||
| status: exempt | ||
| comment: Coordinator handles updates, no explicit parallel updates needed. | ||
|
Comment on lines
+40
to
+42
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can still mention it explicitly |
||
| reauthentication-flow: todo | ||
| test-coverage: todo | ||
|
|
||
| # Gold | ||
| devices: done | ||
| diagnostics: todo | ||
| discovery-update-info: todo | ||
| discovery: todo | ||
| docs-data-update: todo | ||
| docs-examples: todo | ||
| docs-known-limitations: todo | ||
| docs-supported-devices: todo | ||
| docs-supported-functions: todo | ||
| docs-troubleshooting: todo | ||
| docs-use-cases: todo | ||
| dynamic-devices: todo | ||
| entity-category: todo | ||
| entity-device-class: done | ||
| entity-disabled-by-default: todo | ||
| entity-translations: todo | ||
| exception-translations: todo | ||
| icon-translations: todo | ||
| reconfiguration-flow: todo | ||
| repair-issues: todo | ||
| stale-devices: todo | ||
|
|
||
| # Platinum | ||
| async-dependency: done | ||
| inject-websession: done | ||
| strict-typing: todo | ||
Uh oh!
There was an error while loading. Please reload this page.