-
-
Notifications
You must be signed in to change notification settings - Fork 32k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
1,125 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
"""Set up ohme integration.""" | ||
|
||
from dataclasses import dataclass | ||
|
||
from ohme import ApiException, AuthException, OhmeApiClient | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady | ||
|
||
from .const import DOMAIN, PLATFORMS | ||
from .coordinator import OhmeAdvancedSettingsCoordinator, OhmeChargeSessionCoordinator | ||
|
||
type OhmeConfigEntry = ConfigEntry[OhmeRuntimeData] | ||
|
||
|
||
@dataclass() | ||
class OhmeRuntimeData: | ||
"""Dataclass to hold ohme coordinators.""" | ||
|
||
charge_session_coordinator: OhmeChargeSessionCoordinator | ||
advanced_settings_coordinator: OhmeAdvancedSettingsCoordinator | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: OhmeConfigEntry) -> bool: | ||
"""Set up Ohme from a config entry.""" | ||
|
||
client = OhmeApiClient(entry.data[CONF_EMAIL], entry.data[CONF_PASSWORD]) | ||
|
||
try: | ||
await client.async_login() | ||
|
||
if not await client.async_update_device_info(): | ||
raise ConfigEntryNotReady( | ||
translation_key="device_info_failed", translation_domain=DOMAIN | ||
) | ||
except AuthException as e: | ||
raise ConfigEntryError( | ||
translation_key="auth_failed", translation_domain=DOMAIN | ||
) from e | ||
except ApiException as e: | ||
raise ConfigEntryNotReady( | ||
translation_key="api_failed", translation_domain=DOMAIN | ||
) from e | ||
|
||
coordinators = ( | ||
OhmeChargeSessionCoordinator(hass, client), | ||
OhmeAdvancedSettingsCoordinator(hass, client), | ||
) | ||
|
||
for coordinator in coordinators: | ||
await coordinator.async_config_entry_first_refresh() | ||
|
||
entry.runtime_data = OhmeRuntimeData(*coordinators) | ||
|
||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: OhmeConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
|
||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
"""Config flow for ohme integration.""" | ||
|
||
from typing import Any | ||
|
||
from ohme import ApiException, AuthException, OhmeApiClient | ||
import voluptuous as vol | ||
|
||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult | ||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD | ||
from homeassistant.helpers.selector import ( | ||
TextSelector, | ||
TextSelectorConfig, | ||
TextSelectorType, | ||
) | ||
|
||
from .const import DOMAIN | ||
|
||
USER_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_EMAIL): TextSelector( | ||
TextSelectorConfig( | ||
type=TextSelectorType.EMAIL, | ||
autocomplete="email", | ||
), | ||
), | ||
vol.Required(CONF_PASSWORD): TextSelector( | ||
TextSelectorConfig( | ||
type=TextSelectorType.PASSWORD, | ||
autocomplete="current-password", | ||
), | ||
), | ||
} | ||
) | ||
|
||
|
||
class OhmeConfigFlow(ConfigFlow, domain=DOMAIN): | ||
"""Config flow.""" | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""First config step.""" | ||
|
||
errors: dict[str, str] = {} | ||
|
||
if user_input is not None: | ||
self._async_abort_entries_match({CONF_EMAIL: user_input[CONF_EMAIL]}) | ||
|
||
instance = OhmeApiClient(user_input[CONF_EMAIL], user_input[CONF_PASSWORD]) | ||
try: | ||
await instance.async_login() | ||
except AuthException: | ||
errors["base"] = "invalid_auth" | ||
except ApiException: | ||
errors["base"] = "unknown" | ||
|
||
if not errors: | ||
return self.async_create_entry( | ||
title=user_input[CONF_EMAIL], data=user_input | ||
) | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=USER_SCHEMA, errors=errors | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"""Component constants.""" | ||
|
||
from homeassistant.const import Platform | ||
|
||
DOMAIN = "ohme" | ||
PLATFORMS = [Platform.SENSOR] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
"""Ohme coordinators.""" | ||
|
||
from abc import abstractmethod | ||
from datetime import timedelta | ||
import logging | ||
|
||
from ohme import ApiException, OhmeApiClient | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
|
||
from .const import DOMAIN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class OhmeBaseCoordinator(DataUpdateCoordinator[None]): | ||
"""Base for all Ohme coordinators.""" | ||
|
||
client: OhmeApiClient | ||
_default_update_interval: timedelta | None = timedelta(minutes=1) | ||
coordinator_name: str = "" | ||
|
||
def __init__(self, hass: HomeAssistant, client: OhmeApiClient) -> None: | ||
"""Initialise coordinator.""" | ||
super().__init__( | ||
hass, | ||
_LOGGER, | ||
name="", | ||
update_interval=self._default_update_interval, | ||
) | ||
|
||
self.name = f"Ohme {self.coordinator_name}" | ||
self.client = client | ||
|
||
async def _async_update_data(self) -> None: | ||
"""Fetch data from API endpoint.""" | ||
try: | ||
await self._internal_update_data() | ||
except ApiException as e: | ||
raise UpdateFailed( | ||
translation_key="api_failed", translation_domain=DOMAIN | ||
) from e | ||
|
||
@abstractmethod | ||
async def _internal_update_data(self) -> None: | ||
"""Update coordinator data.""" | ||
|
||
|
||
class OhmeChargeSessionCoordinator(OhmeBaseCoordinator): | ||
"""Coordinator to pull all updates from the API.""" | ||
|
||
coordinator_name = "Charge Sessions" | ||
_default_update_interval = timedelta(seconds=30) | ||
|
||
async def _internal_update_data(self): | ||
"""Fetch data from API endpoint.""" | ||
await self.client.async_get_charge_session() | ||
|
||
|
||
class OhmeAdvancedSettingsCoordinator(OhmeBaseCoordinator): | ||
"""Coordinator to pull settings and charger state from the API.""" | ||
|
||
coordinator_name = "Advanced Settings" | ||
|
||
async def _internal_update_data(self): | ||
"""Fetch data from API endpoint.""" | ||
await self.client.async_get_advanced_settings() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
"""Base class for entities.""" | ||
|
||
from homeassistant.helpers.device_registry import DeviceInfo | ||
from homeassistant.helpers.entity import EntityDescription | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from .const import DOMAIN | ||
from .coordinator import OhmeBaseCoordinator | ||
|
||
|
||
class OhmeEntity(CoordinatorEntity[OhmeBaseCoordinator]): | ||
"""Base class for all Ohme entities.""" | ||
|
||
_attr_has_entity_name = True | ||
|
||
def __init__( | ||
self, | ||
coordinator: OhmeBaseCoordinator, | ||
entity_description: EntityDescription, | ||
) -> None: | ||
"""Initialize the entity.""" | ||
super().__init__(coordinator) | ||
|
||
self.entity_description = entity_description | ||
|
||
client = coordinator.client | ||
self._attr_unique_id = f"{client.serial}_{entity_description.key}" | ||
|
||
device_info = client.device_info | ||
self._attr_device_info = DeviceInfo( | ||
identifiers={(DOMAIN, client.serial)}, | ||
name=device_info["name"], | ||
manufacturer="Ohme", | ||
model=device_info["model"], | ||
sw_version=device_info["sw_version"], | ||
serial_number=client.serial, | ||
) | ||
|
||
@property | ||
def available(self) -> bool: | ||
"""Return if charger reporting as online.""" | ||
return super().available and self.coordinator.client.available |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"entity": { | ||
"sensor": { | ||
"status": { | ||
"default": "mdi:car", | ||
"state": { | ||
"unplugged": "mdi:power-plug-off", | ||
"plugged_in": "mdi:power-plug", | ||
"charging": "mdi:battery-charging-100", | ||
"pending_approval": "mdi:alert-decagram" | ||
} | ||
}, | ||
"ct_current": { | ||
"default": "mdi:gauge" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"domain": "ohme", | ||
"name": "Ohme", | ||
"codeowners": ["@dan-r"], | ||
"config_flow": true, | ||
"documentation": "https://www.home-assistant.io/integrations/ohme/", | ||
"integration_type": "device", | ||
"iot_class": "cloud_polling", | ||
"quality_scale": "bronze", | ||
"requirements": ["ohme==1.1.1"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
rules: | ||
# Bronze | ||
action-setup: | ||
status: exempt | ||
comment: | | ||
This integration has no 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: | | ||
This integration has no custom actions. | ||
docs-high-level-description: done | ||
docs-installation-instructions: done | ||
docs-removal-instructions: done | ||
entity-event-setup: | ||
status: exempt | ||
comment: | | ||
This integration has no explicit subscriptions to 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: | | ||
This integration has no custom actions and read-only platform only. | ||
config-entry-unloading: done | ||
docs-configuration-parameters: | ||
status: exempt | ||
comment: | | ||
This integration has no options flow. | ||
docs-installation-parameters: done | ||
entity-unavailable: done | ||
integration-owner: done | ||
log-when-unavailable: done | ||
parallel-updates: done | ||
reauthentication-flow: todo | ||
test-coverage: done | ||
|
||
# Gold | ||
devices: done | ||
diagnostics: todo | ||
discovery: | ||
status: exempt | ||
comment: | | ||
All supported devices are cloud connected over mobile data. Discovery is not possible. | ||
discovery-update-info: | ||
status: exempt | ||
comment: | | ||
All supported devices are cloud connected over mobile data. Discovery is not possible. | ||
docs-data-update: todo | ||
docs-examples: todo | ||
docs-known-limitations: todo | ||
docs-supported-devices: done | ||
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: done | ||
exception-translations: done | ||
icon-translations: done | ||
reconfiguration-flow: todo | ||
repair-issues: | ||
status: exempt | ||
comment: | | ||
This integration currently has no repairs. | ||
stale-devices: todo | ||
# Platinum | ||
async-dependency: todo | ||
inject-websession: todo | ||
strict-typing: todo |
Oops, something went wrong.