Files

702 lines
23 KiB
Python

"""Support for Agua IOT heating devices."""
import logging
import re
import copy
import numbers
from homeassistant.helpers import entity_platform
from homeassistant.util import dt
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
HVACAction,
HVACMode,
ClimateEntityFeature,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
UnitOfTemperature,
PRECISION_HALVES,
)
from .const import (
DOMAIN,
AIR_VARIANTS,
WATER_VARIANTS,
CLIMATE_CANALIZATIONS,
MODE_PELLETS,
MODE_WOOD,
STATUS_OFF,
STATUS_IDLE,
)
from .aguaiot import AguaIOTError
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
coordinator = config_entry.runtime_data
agua = coordinator.agua
entities = []
for device in agua.devices:
stove = AguaIOTAirDevice(coordinator, device)
entities.append(stove)
if any(f"temp_{variant}_set" in device.registers for variant in WATER_VARIANTS):
entities.append(AguaIOTWaterDevice(coordinator, device, stove))
for canalization in CLIMATE_CANALIZATIONS:
for c_found in [
m
for i in device.registers
for m in [re.match(canalization.key, i.lower())]
if m
]:
if (
(
canalization.key_enable
and device.get_register_enabled(
canalization.key_enable.format(id=c_found.group(1))
)
)
or (
canalization.key2_enable
and device.get_register_enabled(
canalization.key2_enable.format(id=c_found.group(1))
)
)
or (
not canalization.key_enable
and device.get_register_enabled(c_found.group(0))
)
):
c_copy = copy.deepcopy(canalization)
c_copy.key = c_found.group(0)
for key in [
"name",
"key_temp_set",
"key_temp_get",
"key_temp2_get",
"key_vent_set",
]:
if getattr(c_copy, key):
setattr(
c_copy,
key,
getattr(c_copy, key).format_map(c_found.groupdict()),
)
entities.append(
AguaIOTCanalizationDevice(coordinator, device, c_copy, stove)
)
async_add_entities(entities, True)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
"sync_clock",
{},
"sync_clock",
)
class AguaIOTClimateDevice(CoordinatorEntity, ClimateEntity):
@property
def device_info(self):
"""Return the device info."""
return DeviceInfo(
identifiers={(DOMAIN, self._device.id_device)},
name=self._device.name,
manufacturer="Micronova",
model=self._device.name_product,
)
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return UnitOfTemperature.CELSIUS
@property
def precision(self):
"""Return the precision of the system."""
return PRECISION_HALVES
class AguaIOTAirDevice(AguaIOTClimateDevice):
"""Representation of an Agua IOT heating device."""
_attr_has_entity_name = True
_attr_name = None
def __init__(self, coordinator, device):
"""Initialize the thermostat."""
super().__init__(coordinator)
self._enable_turn_on_off_backwards_compatibility = False
self._device = device
self._hybrid = "power_wood_set" in device.registers
self._temperature_get_key = None
for variant in AIR_VARIANTS:
if (
f"temp_{variant}_get" in self._device.registers
and self._device.get_register_enabled(f"temp_{variant}_get")
and self._device.get_register_value(f"temp_{variant}_get")
):
self._temperature_get_key = f"temp_{variant}_get"
break
self._temperature_set_key = None
for variant in AIR_VARIANTS:
if (
f"temp_{variant}_set" in self._device.registers
and self._device.get_register_enabled(f"temp_{variant}_set")
and self._device.get_register_value(f"temp_{variant}_set")
):
self._temperature_set_key = f"temp_{variant}_set"
break
@property
def unique_id(self):
"""Return a unique ID."""
return self._device.id_device
@property
def supported_features(self):
"""Return the list of supported features."""
features = (
ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.FAN_MODE
)
if self._hybrid:
features = features | ClimateEntityFeature.PRESET_MODE
return features
@property
def hvac_action(self):
"""Return the current running hvac operation."""
if self._device.get_register_value("status_get") is not None:
if (
str(
self._device.get_register_value_description(
key="status_get", language="ENG"
)
).upper()
in STATUS_IDLE
):
return HVACAction.IDLE
elif (
self._device.get_register_value("status_get") == 0
or str(
self._device.get_register_value_description(
key="status_get", language="ENG"
)
).upper()
in STATUS_OFF
):
return HVACAction.OFF
return HVACAction.HEATING
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes."""
return [HVACMode.HEAT, HVACMode.OFF]
@property
def hvac_mode(self):
"""Return hvac operation ie. heat, cool mode."""
if self._device.get_register_value("status_get") is not None:
if (
self._device.get_register_value("status_get") == 0
or str(
self._device.get_register_value_description(
key="status_get", language="ENG"
)
).upper()
in STATUS_OFF
):
return HVACMode.OFF
return HVACMode.HEAT
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVACMode.OFF:
await self.async_turn_off()
elif hvac_mode == HVACMode.HEAT:
await self.async_turn_on()
@property
def hybrid_mode(self):
return (
MODE_WOOD
if self._hybrid and self._device.get_register_enabled("real_power_wood_get")
else MODE_PELLETS
)
@property
def fan_mode(self):
"""Return fan mode."""
power_register = (
"power_wood_set" if self.hybrid_mode == MODE_WOOD else "power_set"
)
return str(self._device.get_register_value_description(power_register))
@property
def fan_modes(self):
"""Return the list of available fan modes."""
fan_modes = []
power_register = (
"power_wood_set" if self.hybrid_mode == MODE_WOOD else "power_set"
)
for x in range(
self._device.get_register_value_min(power_register),
(self._device.get_register_value_max(power_register) + 1),
):
fan_modes.append(
str(self._device.get_register_value_options(power_register).get(x, x))
)
return fan_modes
@property
def preset_modes(self):
return [self.hybrid_mode]
@property
def preset_mode(self):
return self.hybrid_mode
async def async_set_preset_mode(self, preset_mode):
# The stove will pick the correct mode.
pass
async def async_turn_off(self):
"""Turn device off."""
try:
await self._device.set_register_value_description(
key="status_managed_get",
value_description="OFF",
value_fallback=170,
language="ENG",
)
await self.coordinator.async_request_refresh()
except AguaIOTError as err:
_LOGGER.error("Failed to turn off device, error: %s", err)
async def async_turn_on(self):
"""Turn device on."""
try:
await self._device.set_register_value_description(
key="status_managed_get",
value_description="ON",
value_fallback=85,
language="ENG",
)
await self.coordinator.async_request_refresh()
except AguaIOTError as err:
_LOGGER.error("Failed to turn on device, error: %s", err)
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
power_register = (
"power_wood_set" if self.hybrid_mode == MODE_WOOD else "power_set"
)
try:
await self._device.set_register_value_description(power_register, fan_mode)
await self.coordinator.async_request_refresh()
except AguaIOTError as err:
_LOGGER.error("Failed to set fan mode, error: %s", err)
@property
def min_temp(self):
"""Return the minimum temperature to set."""
return self._device.get_register_value_min(self._temperature_set_key)
@property
def max_temp(self):
"""Return the maximum temperature to set."""
return self._device.get_register_value_max(self._temperature_set_key)
@property
def current_temperature(self):
"""Return the current temperature."""
if self._temperature_get_key:
value = self._device.get_register_value_description(
self._temperature_get_key
)
if isinstance(value, numbers.Number):
return value
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.current_temperature:
return self._device.get_register_value(self._temperature_set_key)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
try:
await self._device.set_register_value(
self._temperature_set_key, temperature
)
await self.coordinator.async_request_refresh()
except (ValueError, AguaIOTError) as err:
_LOGGER.error("Failed to set temperature, error: %s", err)
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self._device.get_register(self._temperature_set_key).get("step", 1)
async def sync_clock(self):
dt_now = dt.now()
try:
await self._device.set_register_values(
{
"clock_hour_set": dt_now.hour,
"clock_minute_set": dt_now.minute,
"calendar_day_set": dt_now.day,
"calendar_month_set": dt_now.month,
"calendar_year_set": dt_now.year,
},
limit_value_raw=True,
)
except (ValueError, AguaIOTError) as err:
_LOGGER.error("Failed to set value, error: %s", err)
class AguaIOTWaterDevice(AguaIOTClimateDevice):
"""Representation of an Agua IOT heating device."""
_attr_has_entity_name = True
_attr_name = "Water"
_attr_icon = "mdi:water"
def __init__(self, coordinator, device, parent):
"""Initialize the thermostat."""
super().__init__(coordinator)
self._enable_turn_on_off_backwards_compatibility = False
self._device = device
self._parent = parent
self._temperature_get_key = None
for variant in WATER_VARIANTS:
if (
f"temp_{variant}_get" in self._device.registers
and self._device.get_register_enabled(f"temp_{variant}_get")
and self._device.get_register_value(f"temp_{variant}_get")
):
self._temperature_get_key = f"temp_{variant}_get"
break
self._temperature_set_key = None
for variant in WATER_VARIANTS:
if (
f"temp_{variant}_set" in self._device.registers
and self._device.get_register_enabled(f"temp_{variant}_set")
and self._device.get_register_value(f"temp_{variant}_set")
):
self._temperature_set_key = f"temp_{variant}_set"
break
@property
def unique_id(self):
return f"{self._device.id_device}_water"
@property
def supported_features(self):
"""Return the list of supported features."""
features = (
ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TARGET_TEMPERATURE
)
return features
@property
def hvac_action(self):
return self._parent.hvac_action
@property
def hvac_modes(self):
return self._parent.hvac_modes
@property
def hvac_mode(self):
return self._parent.hvac_mode
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if hvac_mode == HVACMode.OFF:
await self.async_turn_off()
elif hvac_mode == HVACMode.HEAT:
await self.async_turn_on()
async def async_turn_off(self):
"""Turn device off."""
try:
await self._device.set_register_value_description(
key="status_managed_get",
value_description="OFF",
value_fallback=170,
language="ENG",
)
await self.coordinator.async_request_refresh()
except AguaIOTError as err:
_LOGGER.error("Failed to turn off device, error: %s", err)
async def async_turn_on(self):
"""Turn device on."""
try:
await self._device.set_register_value_description(
key="status_managed_get",
value_description="ON",
value_fallback=85,
language="ENG",
)
await self.coordinator.async_request_refresh()
except AguaIOTError as err:
_LOGGER.error("Failed to turn on device, error: %s", err)
@property
def min_temp(self):
"""Return the minimum temperature to set."""
return self._device.get_register_value_min(self._temperature_set_key)
@property
def max_temp(self):
"""Return the maximum temperature to set."""
return self._device.get_register_value_max(self._temperature_set_key)
@property
def current_temperature(self):
"""Return the current temperature."""
if self._temperature_get_key:
value = self._device.get_register_value_description(
self._temperature_get_key
)
if isinstance(value, numbers.Number):
return value
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.current_temperature:
return self._device.get_register_value(self._temperature_set_key)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
try:
await self._device.set_register_value(
self._temperature_set_key, temperature
)
await self.coordinator.async_request_refresh()
except (ValueError, AguaIOTError) as err:
_LOGGER.error("Failed to set temperature, error: %s", err)
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self._device.get_register(self._temperature_set_key).get("step", 1)
class AguaIOTCanalizationDevice(AguaIOTClimateDevice):
"""Canalization device"""
_attr_has_entity_name = True
def __init__(self, coordinator, device, description, parent):
super().__init__(coordinator)
self._enable_turn_on_off_backwards_compatibility = False
self._device = device
self._parent = parent
self.entity_description = description
self._fan_register = self.entity_description.key
if (
self.entity_description.key_vent_set
and self.entity_description.key_vent_set in self._device.registers
):
self._fan_register = self.entity_description.key_vent_set
@property
def unique_id(self):
return f"{self._device.id_device}_{self.entity_description.key}"
@property
def name(self):
return self.entity_description.name
@property
def supported_features(self):
features = ClimateEntityFeature.FAN_MODE
if (
self.entity_description.key_temp_set
and self.entity_description.key_temp_set in self._device.registers
and self._device.get_register_enabled(self.entity_description.key_temp_set)
):
features |= ClimateEntityFeature.TARGET_TEMPERATURE
if (
self.entity_description.key_vent_set
and self.entity_description.key_vent_set in self._device.registers
):
features |= ClimateEntityFeature.PRESET_MODE
return features
@property
def fan_mode(self):
"""Return fan mode."""
return str(self._device.get_register_value_description(self._fan_register))
@property
def fan_modes(self):
"""Return the list of available fan modes."""
fan_modes = []
for x in range(
self._device.get_register_value_min(self._fan_register),
(self._device.get_register_value_max(self._fan_register) + 1),
):
fan_modes.append(
str(
self._device.get_register_value_options(self._fan_register).get(
x, x
)
)
)
return fan_modes
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
try:
await self._device.set_register_value_description(
self._fan_register, fan_mode
)
await self.coordinator.async_request_refresh()
except AguaIOTError as err:
_LOGGER.error("Failed to set fan mode, error: %s", err)
@property
def preset_modes(self):
return list(
self._device.get_register_value_options(
self.entity_description.key
).values()
)
@property
def preset_mode(self):
return self._device.get_register_value_description(self.entity_description.key)
async def async_set_preset_mode(self, preset_mode):
"""Set new target preset mode."""
try:
await self._device.set_register_value_description(
self.entity_description.key, preset_mode
)
await self.coordinator.async_request_refresh()
except AguaIOTError as err:
_LOGGER.error("Failed to set preset mode, error: %s", err)
@property
def hvac_action(self):
if self._device.get_register_value(self.entity_description.key):
return self._parent.hvac_action
return HVACAction.OFF
@property
def hvac_modes(self):
if self._device.get_register_value(self.entity_description.key):
return [self._parent.hvac_mode]
return [HVACMode.OFF]
@property
def hvac_mode(self):
if self._device.get_register_value(self.entity_description.key):
return self._parent.hvac_mode
return HVACMode.OFF
async def async_set_hvac_mode(self, hvac_mode):
pass
@property
def min_temp(self):
"""Return the minimum temperature to set."""
if self.entity_description.key_temp_set in self._device.registers:
return self._device.get_register_value_min(
self.entity_description.key_temp_set
)
@property
def max_temp(self):
"""Return the maximum temperature to set."""
if self.entity_description.key_temp_set in self._device.registers:
return self._device.get_register_value_max(
self.entity_description.key_temp_set
)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.entity_description.key_temp_set in self._device.registers:
if self.current_temperature:
return self._device.get_register_value(
self.entity_description.key_temp_set
)
@property
def current_temperature(self):
"""Return the current temperature."""
if (
self.entity_description.key_temp_get in self._device.registers
and self._device.get_register_enabled(self.entity_description.key_temp_get)
):
value = self._device.get_register_value_description(
self.entity_description.key_temp_get
)
if isinstance(value, numbers.Number):
return value
elif (
self.entity_description.key_temp2_get
and self.entity_description.key_temp2_get in self._device.registers
and self._device.get_register_enabled(self.entity_description.key_temp2_get)
):
value = self._device.get_register_value_description(
self.entity_description.key_temp2_get
)
if isinstance(value, numbers.Number):
return value
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
try:
await self._device.set_register_value(
self.entity_description.key_temp_set, temperature
)
await self.coordinator.async_request_refresh()
except (ValueError, AguaIOTError) as err:
_LOGGER.error("Failed to set temperature, error: %s", err)
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
if self.entity_description.key_temp_set in self._device.registers:
return self._device.get_register(self.entity_description.key_temp_set).get(
"step", 1
)