diff --git a/custom_components/uponor/__init__.py b/custom_components/uponor/__init__.py index 3008540..bd08837 100644 --- a/custom_components/uponor/__init__.py +++ b/custom_components/uponor/__init__.py @@ -1,24 +1,17 @@ -from datetime import timedelta import math -import ipaddress -import requests -import voluptuous as vol import logging from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.const import CONF_HOST -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import async_load_platform -from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.storage import Store from UponorJnap import UponorJnap from .const import ( DOMAIN, - SIGNAL_UPONOR_STATE_UPDATE, SCAN_INTERVAL, STORAGE_KEY, STORAGE_VERSION, @@ -48,10 +41,10 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): host = config_entry.data[CONF_HOST] - store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) + store = Store(hass, STORAGE_VERSION, STORAGE_KEY) state_proxy = await hass.async_add_executor_job(lambda: UponorStateProxy(hass, host, store)) - await state_proxy.async_update(0) + await state_proxy.async_update() thermostats = state_proxy.get_active_thermostats() hass.data[DOMAIN] = { @@ -66,13 +59,25 @@ def handle_set_variable(call): hass.services.async_register(DOMAIN, "set_variable", handle_set_variable) - hass.async_create_task(hass.config_entries.async_forward_entry_setup(config_entry, "climate")) - hass.async_create_task(hass.config_entries.async_forward_entry_setup(config_entry, "switch")) + await hass.config_entries.async_forward_entry_setup(config_entry, Platform.CLIMATE) + await hass.config_entries.async_forward_entry_setup(config_entry, Platform.SWITCH) - async_track_time_interval(hass, state_proxy.async_update, SCAN_INTERVAL) + # async_track_time_interval(hass, state_proxy.async_update, SCAN_INTERVAL) + + config_entry.async_on_unload(config_entry.add_update_listener(async_update_options)) return True +async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Update options.""" + _LOGGER.debug("Update setup entry: %s, data: %s, options: %s", entry.entry_id, entry.data, entry.options) + await hass.config_entries.async_reload(entry.entry_id) + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Unload a config entry.""" + _LOGGER.debug("Unloading setup entry: %s, data: %s, options: %s", config_entry.entry_id, config_entry.data, config_entry.options) + unload_ok = await hass.config_entries.async_unload_platforms(config_entry, [Platform.SWITCH, Platform.CLIMATE]) + return unload_ok class UponorStateProxy: def __init__(self, hass, host, store): @@ -148,13 +153,6 @@ def get_setpoint(self, thermostat): temp = math.floor((int(self._data[var]) - 320) / 1.8) / 10 return math.floor((int(self._data[var]) - self.get_active_setback(thermostat, temp) - 320) / 1.8) / 10 - def set_setpoint(self, thermostat, temp): - var = thermostat + '_setpoint' - setpoint = int(temp * 18 + self.get_active_setback(thermostat, temp) + 320) - self._client.send_data({var: setpoint}) - self._data[var] = setpoint - async_dispatcher_send(self._hass, SIGNAL_UPONOR_STATE_UPDATE) - def get_active_setback(self, thermostat, temp): if temp == self.get_min_limit(thermostat) or temp == self.get_max_limit(thermostat): return 0 @@ -219,28 +217,24 @@ def get_status(self, thermostat): async def async_switch_to_cooling(self): for thermostat in self._hass.data[DOMAIN]['thermostats']: if self.get_setpoint(thermostat) == self.get_min_limit(thermostat): - await self._hass.async_add_executor_job( - lambda: self.set_setpoint(thermostat, self.get_max_limit(thermostat))) - + await self.set_setpoint(thermostat, self.get_max_limit(thermostat)) + await self._hass.async_add_executor_job(lambda: self._client.send_data({'sys_heat_cool_mode': '1'})) self._data['sys_heat_cool_mode'] = '1' - async_dispatcher_send(self._hass, SIGNAL_UPONOR_STATE_UPDATE) async def async_switch_to_heating(self): for thermostat in self._hass.data[DOMAIN]['thermostats']: if self.get_setpoint(thermostat) == self.get_max_limit(thermostat): - await self._hass.async_add_executor_job( - lambda: self.set_setpoint(thermostat, self.get_min_limit(thermostat))) + await self.set_setpoint(thermostat, self.get_min_limit(thermostat)) await self._hass.async_add_executor_job(lambda: self._client.send_data({'sys_heat_cool_mode': '0'})) self._data['sys_heat_cool_mode'] = '0' - async_dispatcher_send(self._hass, SIGNAL_UPONOR_STATE_UPDATE) async def async_turn_on(self, thermostat): data = await self._store.async_load() self._storage_data = {} if data is None else data last_temp = self._storage_data[thermostat] if thermostat in self._storage_data else DEFAULT_TEMP - await self._hass.async_add_executor_job(lambda: self.set_setpoint(thermostat, last_temp)) + await self.set_setpoint(thermostat, last_temp) async def async_turn_off(self, thermostat): data = await self._store.async_load() @@ -248,7 +242,7 @@ async def async_turn_off(self, thermostat): self._storage_data[thermostat] = self.get_setpoint(thermostat) await self._store.async_save(self._storage_data) off_temp = self.get_max_limit(thermostat) if self.is_cool_enabled() else self.get_min_limit(thermostat) - await self._hass.async_add_executor_job(lambda: self.set_setpoint(thermostat, off_temp)) + await self.set_setpoint(thermostat, off_temp) # Cooling @@ -273,7 +267,6 @@ async def async_set_away(self, is_away): data = "1" if is_away else "0" await self._hass.async_add_executor_job(lambda: self._client.send_data({var: data})) self._data[var] = data - async_dispatcher_send(self._hass, SIGNAL_UPONOR_STATE_UPDATE) def is_eco(self, thermostat): if self.get_eco_setback(thermostat) == 0: @@ -290,11 +283,16 @@ def get_eco_setback(self, thermostat): # Rest - async def async_update(self, event_time): + async def async_update(self): self._data = await self._hass.async_add_executor_job(lambda: self._client.get_data()) - async_dispatcher_send(self._hass, SIGNAL_UPONOR_STATE_UPDATE) def set_variable(self, var_name, var_value): + _LOGGER.debug("Called set variable: name: %s, value: %s, data: %s", var_name, var_value, self._data) self._client.send_data({var_name: var_value}) self._data[var_name] = var_value - async_dispatcher_send(self._hass, SIGNAL_UPONOR_STATE_UPDATE) + + async def set_setpoint(self, thermostat, temp): + var = thermostat + '_setpoint' + setpoint = int(temp * 18 + self.get_active_setback(thermostat, temp) + 320) + await self._hass.async_add_executor_job(lambda: self._client.send_data({var: setpoint})) + self._data[var] = setpoint diff --git a/custom_components/uponor/climate.py b/custom_components/uponor/climate.py index bd36d06..1428c65 100644 --- a/custom_components/uponor/climate.py +++ b/custom_components/uponor/climate.py @@ -1,31 +1,22 @@ import logging from homeassistant.components.climate import ClimateEntity -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.const import ( ATTR_TEMPERATURE, - TEMP_CELSIUS + UnitOfTemperature ) from homeassistant.components.climate.const import ( - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_OFF, - CURRENT_HVAC_OFF, - CURRENT_HVAC_HEAT, - CURRENT_HVAC_COOL, - CURRENT_HVAC_IDLE, + HVACMode, + HVACAction, PRESET_AWAY, PRESET_ECO, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_PRESET_MODE + ClimateEntityFeature ) from .const import ( DOMAIN, - SIGNAL_UPONOR_STATE_UPDATE, DEVICE_MANUFACTURER ) @@ -48,6 +39,8 @@ async def async_setup_entry(hass, entry, async_add_entities): class UponorClimate(ClimateEntity): + _enable_turn_on_off_backwards_compatibility = False + def __init__(self, state_proxy, thermostat, name): self._state_proxy = state_proxy self._thermostat = thermostat @@ -57,84 +50,52 @@ def __init__(self, state_proxy, thermostat, name): self._is_on = not ((is_cool and temp >= self.max_temp) or (not is_cool and temp <= self.min_temp)) @property - def name(self): - return self._name + def device_info(self): + return { + "identifiers": {(DOMAIN, self._state_proxy.get_thermostat_id(self._thermostat))}, + "name": self._name, + "manufacturer": DEVICE_MANUFACTURER, + "model": self._state_proxy.get_model(), + "sw_version": self._state_proxy.get_version(self._thermostat) + } @property - def should_poll(self): - return False - - async def async_added_to_hass(self): - async_dispatcher_connect( - self.hass, SIGNAL_UPONOR_STATE_UPDATE, self._update_callback - ) - - @callback - def _update_callback(self): - temp = self._state_proxy.get_setpoint(self._thermostat) - is_cool = self._state_proxy.is_cool_enabled() - self._is_on = not ((is_cool and temp >= self.max_temp) or (not is_cool and temp <= self.min_temp)) - self.async_schedule_update_ha_state(True) - + def name(self): + return self._name + @property - def supported_features(self): - if self._is_on: - return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - return 0 - + def unique_id(self): + return self._state_proxy.get_thermostat_id(self._thermostat) + @property - def hvac_action(self): - if not self._is_on: - return CURRENT_HVAC_OFF - if self._state_proxy.is_active(self._thermostat): - return CURRENT_HVAC_COOL if self._state_proxy.is_cool_enabled() else CURRENT_HVAC_HEAT - return CURRENT_HVAC_IDLE + def temperature_unit(self): + return UnitOfTemperature.CELSIUS @property - def hvac_mode(self): - if not self._is_on: - return HVAC_MODE_OFF - if self._state_proxy.is_cool_enabled(): - return HVAC_MODE_COOL - return HVAC_MODE_HEAT - - async def async_set_hvac_mode(self, hvac_mode): - if hvac_mode == HVAC_MODE_OFF and self._is_on: - await self._state_proxy.async_turn_off(self._thermostat) - self._is_on = False - if (hvac_mode == HVAC_MODE_HEAT or hvac_mode == HVAC_MODE_COOL) and not self._is_on: - await self._state_proxy.async_turn_on(self._thermostat) - self._is_on = True - + def supported_features(self): + return ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + @property def hvac_modes(self): if self._state_proxy.is_cool_enabled(): - return [HVAC_MODE_COOL, HVAC_MODE_OFF] - return [HVAC_MODE_HEAT, HVAC_MODE_OFF] - - @property - def preset_mode(self): - if self._state_proxy.is_eco(self._thermostat): - return PRESET_ECO - if self._state_proxy.is_away(): - return PRESET_AWAY - return None - + return [HVACMode.COOL, HVACMode.OFF] + return [HVACMode.HEAT, HVACMode.OFF] + @property def preset_modes(self): return [self.preset_mode] if self.preset_mode is not None else [] - + @property - def temperature_unit(self): - return TEMP_CELSIUS - + def current_humidity(self): + return self._state_proxy.get_humidity(self._thermostat) + @property def current_temperature(self): return self._state_proxy.get_temperature(self._thermostat) - + @property - def current_humidity(self): - return self._state_proxy.get_humidity(self._thermostat) + def target_temperature(self): + return self._state_proxy.get_setpoint(self._thermostat) @property def min_temp(self): @@ -143,11 +104,7 @@ def min_temp(self): @property def max_temp(self): return self._state_proxy.get_max_limit(self._thermostat) - - @property - def target_temperature(self): - return self._state_proxy.get_setpoint(self._thermostat) - + @property def extra_state_attributes(self): return { @@ -158,20 +115,69 @@ def extra_state_attributes(self): } @property - def unique_id(self): - return self._state_proxy.get_thermostat_id(self._thermostat) + def preset_mode(self): + if self._state_proxy.is_eco(self._thermostat): + return PRESET_ECO + if self._state_proxy.is_away(): + return PRESET_AWAY + return None + + @property + def hvac_mode(self): + if not self._is_on: + return HVACMode.OFF + if self._state_proxy.is_cool_enabled(): + return HVACMode.COOL + return HVACMode.HEAT @property - def device_info(self): - return { - "identifiers": {(DOMAIN, self._state_proxy.get_thermostat_id(self._thermostat))}, - "name": self._name, - "manufacturer": DEVICE_MANUFACTURER, - "model": self._state_proxy.get_model(), - "sw_version": self._state_proxy.get_version(self._thermostat) - } + def hvac_action(self): + if not self._is_on: + return HVACAction.OFF + if self._state_proxy.is_active(self._thermostat): + return HVACAction.COOLING if self._state_proxy.is_cool_enabled() else HVACAction.HEATING + return HVACAction.IDLE + + async def async_turn_off(self): + if self._is_on: + await self._state_proxy.async_turn_off(self._thermostat) + self._is_on = False + + async def async_turn_on(self): + if not self._is_on: + await self._state_proxy.async_turn_on(self._thermostat) + self._is_on = True + + # ** Actions ** + async def async_update(self): + # Update uponor (to get HC mode) and thermostat + try: +### se ha elimininado el 0 del update + await self._state_proxy.async_update() + temp = self._state_proxy.get_setpoint(self._thermostat) + is_cool = self._state_proxy.is_cool_enabled() + self._is_on = not ((is_cool and temp >= self.max_temp) or (not is_cool and temp <= self.min_temp)) + + except Exception as ex: + _LOGGER.error("Uponor thermostat was unable to update: %s", ex) + + async def async_set_hvac_mode(self, hvac_mode): + if hvac_mode == HVACMode.OFF and self._is_on: + await self._state_proxy.async_turn_off(self._thermostat) + self._is_on = False + if (hvac_mode == HVACMode.HEAT or hvac_mode == HVACMode.COOL) and not self._is_on: + await self._state_proxy.async_turn_on(self._thermostat) + self._is_on = True + + # if (hvac_mode == HVACMode.HEAT): + # await self._state_proxy.async_switch_to_heating() + # if (hvac_mode == HVACMode.COOL): + # await self._state_proxy.async_switch_to_cooling() + + + async def async_set_temperature(self, **kwargs): + if kwargs.get(ATTR_TEMPERATURE) is None: + return + await self._state_proxy.set_setpoint(self._thermostat, kwargs.get(ATTR_TEMPERATURE)) + - def set_temperature(self, **kwargs): - temp = kwargs.get(ATTR_TEMPERATURE) - if temp is not None and self._is_on: - self._state_proxy.set_setpoint(self._thermostat, temp) diff --git a/custom_components/uponor/config_flow.py b/custom_components/uponor/config_flow.py index dcaf043..e1163b0 100644 --- a/custom_components/uponor/config_flow.py +++ b/custom_components/uponor/config_flow.py @@ -1,6 +1,5 @@ from homeassistant import config_entries import voluptuous as vol -import homeassistant.helpers.config_validation as cv import logging from UponorJnap import UponorJnap @@ -12,7 +11,6 @@ from .const import ( DOMAIN, - SIGNAL_UPONOR_STATE_UPDATE, DEVICE_MANUFACTURER ) diff --git a/custom_components/uponor/switch.py b/custom_components/uponor/switch.py index 10cd687..42ca2ab 100644 --- a/custom_components/uponor/switch.py +++ b/custom_components/uponor/switch.py @@ -1,11 +1,8 @@ from homeassistant.components.switch import SwitchEntity -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.const import CONF_NAME from .const import ( DOMAIN, - SIGNAL_UPONOR_STATE_UPDATE, DEVICE_MANUFACTURER ) @@ -33,9 +30,9 @@ def name(self) -> str: def icon(self): return "mdi:home-export-outline" - @property - def should_poll(self): - return False + # @property + # def should_poll(self): + # return False @property def is_on(self): @@ -47,14 +44,8 @@ async def async_turn_on(self, **kwargs): async def async_turn_off(self, **kwargs): await self._state_proxy.async_set_away(False) - async def async_added_to_hass(self): - async_dispatcher_connect( - self.hass, SIGNAL_UPONOR_STATE_UPDATE, self._update_callback - ) - - @callback - def _update_callback(self): - self.async_schedule_update_ha_state(True) + async def async_update(self): + await self._state_proxy.async_update() @property def unique_id(self): @@ -83,9 +74,9 @@ def name(self) -> str: def icon(self): return "mdi:snowflake" - @property - def should_poll(self): - return False + # @property + # def should_poll(self): + # return False @property def is_on(self): @@ -96,15 +87,9 @@ async def async_turn_on(self, **kwargs): async def async_turn_off(self, **kwargs): await self._state_proxy.async_switch_to_heating() - - async def async_added_to_hass(self): - async_dispatcher_connect( - self.hass, SIGNAL_UPONOR_STATE_UPDATE, self._update_callback - ) - - @callback - def _update_callback(self): - self.async_schedule_update_ha_state(True) + + async def async_update(self): + await self._state_proxy.async_update() @property def unique_id(self):