Skip to content

Commit

Permalink
feat: remove integration services
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Remove integration services, use generic enity services in automations and scripts
  • Loading branch information
bj00rn committed Dec 3, 2024
1 parent f3909b2 commit a9bbe75
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 391 deletions.
135 changes: 8 additions & 127 deletions custom_components/saleryd_hrv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@

import async_timeout
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_DEVICE, CONF_NAME
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.const import CONF_NAME
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.loader import async_get_loaded_integration
from homeassistant.util import slugify
from pysaleryd.client import Client
from pysaleryd.const import DataKeyEnum

from .const import (
CONF_ENABLE_INSTALLER_SETTINGS,
CONF_INSTALLER_PASSWORD,
CONF_VALUE,
CONF_WEBSOCKET_IP,
CONF_WEBSOCKET_PORT,
CONFIG_VERSION,
Expand All @@ -32,21 +29,14 @@
DOMAIN,
LOGGER,
PLATFORMS,
SERVICE_SET_COOLING_MODE,
SERVICE_SET_FIREPLACE_MODE,
SERVICE_SET_SYSTEM_ACTIVE_MODE,
SERVICE_SET_TARGET_TEMPERATURE_COOL,
SERVICE_SET_TARGET_TEMPERATURE_ECONOMY,
SERVICE_SET_TARGET_TEMPERATURE_NORMAL,
SERVICE_SET_TEMPERATURE_MODE,
SERVICE_SET_VENTILATION_MODE,
STARTUP_MESSAGE,
)

if TYPE_CHECKING:
from .data import SalerydLokeConfigEntry
from homeassistant.core import HomeAssistant, ServiceCall

from .bridge import SalerydLokeBridge
from .coordinator import SalerydLokeDataUpdateCoordinator
from .data import SalerydLokeData

Expand Down Expand Up @@ -105,108 +95,10 @@ async def async_migrate_entry(
return True


def _get_entry_from_service_data(
hass: HomeAssistant, call: ServiceCall
) -> "SalerydLokeConfigEntry":
"""Return coordinator for entry id."""
device_id = call.get(CONF_DEVICE)
device_registry = dr.async_get(hass)
device = device_registry.async_get(device_id)
if device is None:
raise HomeAssistantError(f"Cannot find device {device_id} is not found")
if device.disabled:
raise HomeAssistantError(f"Device {device_id} is disabled")

entry = hass.config_entries.async_get_entry(device.primary_config_entry)
if entry is None or entry.state is not ConfigEntryState.LOADED:
raise HomeAssistantError(
f"Config entry for device {device_id} is not found or not loaded"
)
return entry


def setup_hass_services(hass: HomeAssistant) -> None:
"""Register ingegration services."""

async def control_request(call: ServiceCall, key, installer_setting=False):
"""Helper for system control calls"""
entry = _get_entry_from_service_data(hass, call.data)
value = call.data.get(CONF_VALUE)
device = call.data.get(CONF_DEVICE)
client = entry.runtime_data.client
if installer_setting:
installer_settings_enabled = entry.data.get(CONF_ENABLE_INSTALLER_SETTINGS)
if not installer_settings_enabled:
raise HomeAssistantError(
f"Installer settings not enabled for device {device}"
)
installer_password = entry.data.get(CONF_INSTALLER_PASSWORD)
LOGGER.debug("Sending unlock installer settings control request")
await client.send_command(
DataKeyEnum.INSTALLER_PASSWORD, installer_password
)

LOGGER.debug("Sending control request %s with payload %s", key, value)
await client.send_command(key, value)

async def set_fireplace_mode(call: ServiceCall):
"""Set fireplace mode"""
await control_request(call, DataKeyEnum.FIREPLACE_MODE)

async def set_ventilation_mode(call: ServiceCall):
"""Set ventilation mode"""
await control_request(call, DataKeyEnum.MODE_FAN)

async def set_temperature_mode(call: ServiceCall):
"""Set temperature mode"""
await control_request(call, DataKeyEnum.MODE_TEMPERATURE)

async def set_cooling_mode(call: ServiceCall):
"""Set cooling mode"""
await control_request(call, DataKeyEnum.COOLING_MODE)

async def set_system_active_mode(call: ServiceCall):
"""Set system active mode"""
await control_request(call, DataKeyEnum.CONTROL_SYSTEM_STATE, True)

async def set_target_temperature_cool(call: ServiceCall):
"""Set target temperature for Cool temperature mode"""
await control_request(call, DataKeyEnum.TARGET_TEMPERATURE_COOL, True)

async def set_target_temperature_normal(call: ServiceCall):
"""Set target temperature for Normal temperature mode"""
await control_request(call, DataKeyEnum.TARGET_TEMPERATURE_NORMAL, True)

async def set_target_temperature_economy(call: ServiceCall):
"""Set target temperature for Economy temperature mode"""
await control_request(call, DataKeyEnum.TARGET_TEMPERATURE_ECONOMY, True)

services = {
SERVICE_SET_FIREPLACE_MODE: set_fireplace_mode,
SERVICE_SET_COOLING_MODE: set_cooling_mode,
SERVICE_SET_VENTILATION_MODE: set_ventilation_mode,
SERVICE_SET_TEMPERATURE_MODE: set_temperature_mode,
}

installer_services = {
SERVICE_SET_SYSTEM_ACTIVE_MODE: set_system_active_mode,
SERVICE_SET_TARGET_TEMPERATURE_COOL: set_target_temperature_cool,
SERVICE_SET_TARGET_TEMPERATURE_ECONOMY: set_target_temperature_economy,
SERVICE_SET_TARGET_TEMPERATURE_NORMAL: set_target_temperature_normal,
}

# register services
for key, fn in (services | installer_services).items():
if hass.services.has_service(DOMAIN, key):
continue
hass.services.async_register(DOMAIN, key, fn)


async def async_setup_entry(hass: HomeAssistant, entry: "SalerydLokeConfigEntry"):
"""Set up the integration from ConfigEntry."""
integration = async_get_loaded_integration(hass, entry.domain)
LOGGER.info(STARTUP_MESSAGE, integration.name, integration.version)
setup_hass_services(hass)

url = entry.data.get(CONF_WEBSOCKET_IP)
port = entry.data.get(CONF_WEBSOCKET_PORT)
Expand All @@ -221,10 +113,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: "SalerydLokeConfigEntry"
raise ConfigEntryNotReady(f"Timeout while connecting to {url}:{port}") from ex
else:
coordinator = SalerydLokeDataUpdateCoordinator(hass, LOGGER)
bridge = SalerydLokeBridge(client, coordinator, LOGGER)
entry.runtime_data = SalerydLokeData(
client=client, coordinator=coordinator, integration=integration
client=client,
coordinator=coordinator,
integration=integration,
bridge=bridge,
)
client.add_handler(coordinator.async_set_updated_data)
await coordinator.async_config_entry_first_refresh()

# Setup platforms
Expand All @@ -246,20 +141,6 @@ async def async_unload_entry(
client: SalerydLokeDataUpdateCoordinator = entry.runtime_data.client
client.disconnect()

# remove services

loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state is ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
# If this is the last loaded instance, deregister any services
# defined during integration setup:

for key in hass.services.async_services().get(DOMAIN, dict()):
hass.services.async_remove(DOMAIN, key)

return unload_ok


Expand Down
48 changes: 48 additions & 0 deletions custom_components/saleryd_hrv/bridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import TYPE_CHECKING

from pysaleryd.const import DataKeyEnum

from .const import CONF_INSTALLER_PASSWORD, KEY_CLIENT_STATE, KEY_TARGET_TEMPERATURE

if TYPE_CHECKING:
from pysaleryd.client import Client

from .coordinator import SalerydLokeDataUpdateCoordinator
from .data import SalerydLokeConfigEntry


class SalerydLokeBridge:
"""Representation of bridge between client and coordinator"""

def __init__(
self, client: "Client", coordinator: "SalerydLokeDataUpdateCoordinator", logger
):
self.client = client
self.coordinator = coordinator
self.logger = logger

self.client.add_handler(self.update_data_callback)

def update_data_callback(self, data):
"""Update coordindator data"""
self.logger.debug("Received data")
_data = data.copy()
self.__inject_virtual_keys(_data)
self.coordinator.async_set_updated_data(data)

def __inject_virtual_keys(self, data):
"""Inject additional keys for virtual sensors not present in the data set"""
data[KEY_CLIENT_STATE] = self.client.state.name
data[KEY_TARGET_TEMPERATURE] = None

async def send_command(self, key: DataKeyEnum, data: str | int, auth: bool = False):
"""Send command to client"""

async def send(key, data):
self.logger.debug("Sending control request %s with payload %s", key, data)
await self.client.send_command(key, data)

if auth:
installer_password = self._entry.data.get(CONF_INSTALLER_PASSWORD)
await send(DataKeyEnum.INSTALLER_PASSWORD, installer_password)
await send(key, data)
12 changes: 1 addition & 11 deletions custom_components/saleryd_hrv/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
-------------------------------------------------------------------
%s %s
This is a custom integration!
If you have any issues with this you need to open an issue here:
If encounter any problems open an issue here:
{ISSUE_URL}
-------------------------------------------------------------------
"""
Expand Down Expand Up @@ -85,16 +85,6 @@ class ModeEnum(IntEnum):
Off = 0


# Services
SERVICE_SET_FIREPLACE_MODE = "set_fireplace_mode"
SERVICE_SET_COOLING_MODE = "set_cooling_mode"
SERVICE_SET_VENTILATION_MODE = "set_ventilation_mode"
SERVICE_SET_TEMPERATURE_MODE = "set_temperature_mode"
SERVICE_SET_SYSTEM_ACTIVE_MODE = "set_system_active_mode"
SERVICE_SET_TARGET_TEMPERATURE_COOL = "set_target_temperature_cool"
SERVICE_SET_TARGET_TEMPERATURE_NORMAL = "set_target_temperature_normal"
SERVICE_SET_TARGET_TEMPERATURE_ECONOMY = "set_target_temperature_economy"

LOGGER: Logger = getLogger(__package__)

# Deprecated constants kept for migrations
Expand Down
13 changes: 1 addition & 12 deletions custom_components/saleryd_hrv/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from pysaleryd.const import DataKeyEnum

from .const import DOMAIN, KEY_CLIENT_STATE, KEY_TARGET_TEMPERATURE

Expand All @@ -17,22 +18,10 @@ class SalerydLokeDataUpdateCoordinator(DataUpdateCoordinator):

config_entry: "SalerydLokeConfigEntry"

def inject_virtual_keys(self, data):
"""Inject additional keys for virtual sensors not present in the data set"""
data[KEY_CLIENT_STATE] = self.config_entry.runtime_data.client.state.name
data[KEY_TARGET_TEMPERATURE] = None

def __init__(self, hass: HomeAssistant, logger: Logger) -> None:
"""Initialize."""
self.platforms = []
super().__init__(hass, logger, name=DOMAIN)

async def _async_update_data(self):
"""Fetch the latest data from the source."""
return self.data or dict()

def async_set_updated_data(self, data) -> None:
self.logger.debug("Received data")
_data = data.copy()
self.inject_virtual_keys(_data)
return super().async_set_updated_data(_data)
5 changes: 3 additions & 2 deletions custom_components/saleryd_hrv/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING

from pysaleryd.client import Client

if TYPE_CHECKING:
from homeassistant.config_entries import ConfigEntry
from homeassistant.loader import Integration
from pysaleryd.client import Client

from .bridge import SalerydLokeBridge
from .coordinator import SalerydLokeDataUpdateCoordinator


Expand All @@ -24,3 +24,4 @@ class SalerydLokeData:
client: Client
coordinator: SalerydLokeDataUpdateCoordinator
integration: Integration
bridge: SalerydLokeBridge
2 changes: 1 addition & 1 deletion custom_components/saleryd_hrv/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def native_value(self):
return self._get_native_value(system_property)

async def async_set_native_value(self, value):
await self._entry.runtime_data.client.send_command(
await self._entry.runtime_data.bridge.send_command(
self.entity_description.key, int(value)
)

Expand Down
Loading

0 comments on commit a9bbe75

Please sign in to comment.