197 lines
6.4 KiB
Python
197 lines
6.4 KiB
Python
""" Some usefull commons class """
|
|
|
|
# pylint: disable=line-too-long
|
|
|
|
import logging
|
|
from vtherm_api.log_collector import get_vtherm_logger
|
|
import warnings
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.helpers import entity_registry as er
|
|
|
|
from .const import ServiceConfigurationError, DOMAIN
|
|
|
|
_LOGGER = get_vtherm_logger(__name__)
|
|
|
|
|
|
def round_to_nearest(n: float, x: float) -> float:
|
|
"""Round a number to the nearest x (which should be decimal but not null)
|
|
Example:
|
|
nombre1 = 3.2
|
|
nombre2 = 4.7
|
|
x = 0.3
|
|
|
|
nombre_arrondi1 = round_to_nearest(nombre1, x)
|
|
nombre_arrondi2 = round_to_nearest(nombre2, x)
|
|
|
|
print(nombre_arrondi1) # Output: 3.3
|
|
print(nombre_arrondi2) # Output: 4.6
|
|
"""
|
|
assert x > 0
|
|
return round(n * (1 / x)) / (1 / x)
|
|
|
|
|
|
def check_and_extract_service_configuration(service_config) -> dict:
|
|
"""Raise a ServiceConfigurationError. In return you have a dict formatted like follows.
|
|
Example if you call with 'climate.central_boiler/climate.set_temperature/temperature:10':
|
|
{
|
|
"service_domain": "climate",
|
|
"service_name": "set_temperature",
|
|
"entity_id": "climate.central_boiler",
|
|
"entity_domain": "climate",
|
|
"entity_name": "central_boiler",
|
|
"data": {
|
|
"temperature": "10"
|
|
},
|
|
"attribute_name": "temperature",
|
|
"attribute_value: "10"
|
|
}
|
|
|
|
For this example 'switch.central_boiler/switch.turn_off' you will have this:
|
|
{
|
|
"service_domain": "switch",
|
|
"service_name": "turn_off",
|
|
"entity_id": "switch.central_boiler",
|
|
"entity_domain": "switch",
|
|
"entity_name": "central_boiler",
|
|
"data": { },
|
|
}
|
|
|
|
All values are striped (white space are removed) and are string
|
|
"""
|
|
|
|
ret = {}
|
|
|
|
if service_config is None:
|
|
return ret
|
|
|
|
parties = service_config.split("/")
|
|
if len(parties) < 2:
|
|
raise ServiceConfigurationError(
|
|
f"Incorrect service configuration. Service {service_config} should be formatted with: 'entity_name/service_name[/data]'. See README for more information."
|
|
)
|
|
entity_id = parties[0]
|
|
service_name = parties[1]
|
|
|
|
service_infos = service_name.split(".")
|
|
if len(service_infos) != 2:
|
|
raise ServiceConfigurationError(
|
|
f"Incorrect service configuration. The service {service_config} should be formatted like: 'domain.service_name' (ex: 'switch.turn_on'). See README for more information."
|
|
)
|
|
|
|
ret.update(
|
|
{
|
|
"service_domain": service_infos[0].strip(),
|
|
"service_name": service_infos[1].strip(),
|
|
}
|
|
)
|
|
|
|
entity_infos = entity_id.split(".")
|
|
if len(entity_infos) != 2:
|
|
raise ServiceConfigurationError(
|
|
f"Incorrect service configuration. The entity_id {entity_id} should be formatted like: 'domain.entity_name' (ex: 'switch.central_boiler_switch'). See README for more information."
|
|
)
|
|
|
|
ret.update(
|
|
{
|
|
"entity_domain": entity_infos[0].strip(),
|
|
"entity_name": entity_infos[1].strip(),
|
|
"entity_id": entity_id.strip(),
|
|
}
|
|
)
|
|
|
|
if len(parties) == 3:
|
|
data = parties[2]
|
|
if len(data) > 0:
|
|
data_infos = None
|
|
data_infos = data.split(":")
|
|
if (
|
|
len(data_infos) != 2
|
|
or len(data_infos[0]) <= 0
|
|
or len(data_infos[1]) <= 0
|
|
):
|
|
raise ServiceConfigurationError(
|
|
f"Incorrect service configuration. The data {data} should be formatted like: 'attribute:value' (ex: 'value:25'). See README for more information."
|
|
)
|
|
|
|
ret.update(
|
|
{
|
|
"attribute_name": data_infos[0].strip(),
|
|
"attribute_value": data_infos[1].strip(),
|
|
"data": {data_infos[0].strip(): data_infos[1].strip()},
|
|
}
|
|
)
|
|
else:
|
|
raise ServiceConfigurationError(
|
|
f"Incorrect service configuration. The data {data} should be formatted like: 'attribute:value' (ex: 'value:25'). See README for more information."
|
|
)
|
|
else:
|
|
ret.update({"data": {}})
|
|
|
|
_LOGGER.debug(
|
|
"check_and_extract_service_configuration(%s) gives '%s'", service_config, ret
|
|
)
|
|
return ret
|
|
|
|
|
|
def deprecated(message):
|
|
"""A decorator to indicate that the method/attribut is deprecated"""
|
|
|
|
def decorator(func):
|
|
def wrapper(*args, **kwargs):
|
|
warnings.warn(
|
|
f"{func.__name__} is deprecated: {message}",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
return func(*args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
def write_event_log(logger: logging.Logger, vtherm: "BaseThermostat", message: str):
|
|
"""Write an event log entry for the thermostat."""
|
|
logger.info("%s - ---------------------> NEW EVENT: %s --------------------------------------------------------------", vtherm, message)
|
|
|
|
|
|
async def cleanup_orphan_entity(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
domain: str,
|
|
device_name: str,
|
|
unique_id_suffix: str,
|
|
) -> None:
|
|
"""Remove an orphan entity from entity registry if it exists but is no longer needed.
|
|
|
|
This generic function can be used for any entity type that needs to be
|
|
conditionally created/removed based on configuration changes.
|
|
|
|
Args:
|
|
hass: The Home Assistant instance.
|
|
entry: The config entry for the thermostat.
|
|
domain: The entity domain (e.g., "sensor", "binary_sensor", "switch").
|
|
device_name: The device name used to build the unique_id.
|
|
unique_id_suffix: The suffix appended to the device name (e.g., "auto_tpi_learning").
|
|
"""
|
|
registry = er.async_get(hass)
|
|
# Build the expected unique_id for the entity
|
|
expected_unique_id = f"{device_name}_{unique_id_suffix}"
|
|
|
|
# Find entity by unique_id within this config entry
|
|
entity_id = registry.async_get_entity_id(
|
|
domain, DOMAIN, expected_unique_id
|
|
)
|
|
|
|
if entity_id:
|
|
entity_entry = registry.async_get(entity_id)
|
|
if entity_entry and entity_entry.config_entry_id == entry.entry_id:
|
|
_LOGGER.debug(
|
|
"Removing orphan %s entity %s from registry (feature disabled)",
|
|
domain,
|
|
entity_id
|
|
)
|
|
registry.async_remove(entity_id)
|