Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9b87b3aee | ||
|
|
c512cb6f74 | ||
|
|
9269240fe3 |
@@ -3,9 +3,9 @@ default_config:
|
|||||||
logger:
|
logger:
|
||||||
default: warning
|
default: warning
|
||||||
logs:
|
logs:
|
||||||
# custom_components.versatile_thermostat: debug
|
custom_components.versatile_thermostat: info
|
||||||
# custom_components.versatile_thermostat.underlyings: debug
|
custom_components.versatile_thermostat.underlyings: info
|
||||||
# custom_components.versatile_thermostat.climate: debug
|
custom_components.versatile_thermostat.climate: info
|
||||||
|
|
||||||
# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
|
# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
|
||||||
debugpy:
|
debugpy:
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ async def async_setup(
|
|||||||
"VersatileThermostat - HA is started, initialize all links between VTherm entities"
|
"VersatileThermostat - HA is started, initialize all links between VTherm entities"
|
||||||
)
|
)
|
||||||
await api.init_vtherm_links()
|
await api.init_vtherm_links()
|
||||||
|
await api.notify_central_mode_change()
|
||||||
|
await api.reload_central_boiler_entities_list()
|
||||||
|
|
||||||
if hass.state == CoreState.running:
|
if hass.state == CoreState.running:
|
||||||
await _async_startup_internal()
|
await _async_startup_internal()
|
||||||
@@ -156,8 +158,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
await api.reload_central_boiler_entities_list()
|
if hass.state == CoreState.running:
|
||||||
await api.init_vtherm_links()
|
await api.reload_central_boiler_entities_list()
|
||||||
|
await api.init_vtherm_links()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -629,7 +629,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
|
|
||||||
self.async_on_remove(self.remove_thermostat)
|
self.async_on_remove(self.remove_thermostat)
|
||||||
|
|
||||||
await self.async_startup()
|
# issue 428. Link to others entities will start at link
|
||||||
|
# await self.async_startup()
|
||||||
|
|
||||||
def remove_thermostat(self):
|
def remove_thermostat(self):
|
||||||
"""Called when the thermostat will be removed"""
|
"""Called when the thermostat will be removed"""
|
||||||
@@ -637,155 +638,157 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
for under in self._underlyings:
|
for under in self._underlyings:
|
||||||
under.remove_entity()
|
under.remove_entity()
|
||||||
|
|
||||||
async def async_startup(self):
|
async def async_startup(self, central_configuration):
|
||||||
"""Triggered on startup, used to get old state and set internal states accordingly"""
|
"""Triggered on startup, used to get old state and set internal states accordingly"""
|
||||||
_LOGGER.debug("%s - Calling async_startup", self)
|
_LOGGER.debug("%s - Calling async_startup", self)
|
||||||
|
|
||||||
@callback
|
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
||||||
async def _async_startup_internal(*_):
|
need_write_state = False
|
||||||
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
|
||||||
need_write_state = False
|
|
||||||
|
|
||||||
# Initialize all UnderlyingEntities
|
await self.get_my_previous_state()
|
||||||
self.init_underlyings()
|
|
||||||
|
|
||||||
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
|
await self.init_presets(central_configuration)
|
||||||
if temperature_state and temperature_state.state not in (
|
|
||||||
|
# Initialize all UnderlyingEntities
|
||||||
|
self.init_underlyings()
|
||||||
|
|
||||||
|
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
|
||||||
|
if temperature_state and temperature_state.state not in (
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - temperature sensor have been retrieved: %.1f",
|
||||||
|
self,
|
||||||
|
float(temperature_state.state),
|
||||||
|
)
|
||||||
|
await self._async_update_temp(temperature_state)
|
||||||
|
need_write_state = True
|
||||||
|
|
||||||
|
if self._ext_temp_sensor_entity_id:
|
||||||
|
ext_temperature_state = self.hass.states.get(
|
||||||
|
self._ext_temp_sensor_entity_id
|
||||||
|
)
|
||||||
|
if ext_temperature_state and ext_temperature_state.state not in (
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
):
|
):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - temperature sensor have been retrieved: %.1f",
|
"%s - external temperature sensor have been retrieved: %.1f",
|
||||||
self,
|
self,
|
||||||
float(temperature_state.state),
|
float(ext_temperature_state.state),
|
||||||
)
|
)
|
||||||
await self._async_update_temp(temperature_state)
|
await self._async_update_ext_temp(ext_temperature_state)
|
||||||
need_write_state = True
|
|
||||||
|
|
||||||
if self._ext_temp_sensor_entity_id:
|
|
||||||
ext_temperature_state = self.hass.states.get(
|
|
||||||
self._ext_temp_sensor_entity_id
|
|
||||||
)
|
|
||||||
if ext_temperature_state and ext_temperature_state.state not in (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
):
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - external temperature sensor have been retrieved: %.1f",
|
|
||||||
self,
|
|
||||||
float(ext_temperature_state.state),
|
|
||||||
)
|
|
||||||
await self._async_update_ext_temp(ext_temperature_state)
|
|
||||||
else:
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - external temperature sensor have NOT been retrieved cause unknown or unavailable",
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - external temperature sensor have NOT been retrieved cause no external sensor",
|
"%s - external temperature sensor have NOT been retrieved cause unknown or unavailable",
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._pmax_on:
|
|
||||||
# try to acquire current power and power max
|
|
||||||
current_power_state = self.hass.states.get(self._power_sensor_entity_id)
|
|
||||||
if current_power_state and current_power_state.state not in (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
):
|
|
||||||
self._current_power = float(current_power_state.state)
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - Current power have been retrieved: %.3f",
|
|
||||||
self,
|
|
||||||
self._current_power,
|
|
||||||
)
|
|
||||||
need_write_state = True
|
|
||||||
|
|
||||||
# Try to acquire power max
|
|
||||||
current_power_max_state = self.hass.states.get(
|
|
||||||
self._max_power_sensor_entity_id
|
|
||||||
)
|
|
||||||
if current_power_max_state and current_power_max_state.state not in (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
):
|
|
||||||
self._current_power_max = float(current_power_max_state.state)
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - Current power max have been retrieved: %.3f",
|
|
||||||
self,
|
|
||||||
self._current_power_max,
|
|
||||||
)
|
|
||||||
need_write_state = True
|
|
||||||
|
|
||||||
# try to acquire window entity state
|
|
||||||
if self._window_sensor_entity_id:
|
|
||||||
window_state = self.hass.states.get(self._window_sensor_entity_id)
|
|
||||||
if window_state and window_state.state not in (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
):
|
|
||||||
self._window_state = window_state.state == STATE_ON
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - Window state have been retrieved: %s",
|
|
||||||
self,
|
|
||||||
self._window_state,
|
|
||||||
)
|
|
||||||
need_write_state = True
|
|
||||||
|
|
||||||
# try to acquire motion entity state
|
|
||||||
if self._motion_sensor_entity_id:
|
|
||||||
motion_state = self.hass.states.get(self._motion_sensor_entity_id)
|
|
||||||
if motion_state and motion_state.state not in (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
):
|
|
||||||
self._motion_state = motion_state.state
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - Motion state have been retrieved: %s",
|
|
||||||
self,
|
|
||||||
self._motion_state,
|
|
||||||
)
|
|
||||||
# recalculate the right target_temp in activity mode
|
|
||||||
await self._async_update_motion_temp()
|
|
||||||
need_write_state = True
|
|
||||||
|
|
||||||
if self._presence_on:
|
|
||||||
# try to acquire presence entity state
|
|
||||||
presence_state = self.hass.states.get(self._presence_sensor_entity_id)
|
|
||||||
if presence_state and presence_state.state not in (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
):
|
|
||||||
await self._async_update_presence(presence_state.state)
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - Presence have been retrieved: %s",
|
|
||||||
self,
|
|
||||||
presence_state.state,
|
|
||||||
)
|
|
||||||
need_write_state = True
|
|
||||||
|
|
||||||
if need_write_state:
|
|
||||||
self.async_write_ha_state()
|
|
||||||
if self._prop_algorithm:
|
|
||||||
self._prop_algorithm.calculate(
|
|
||||||
self._target_temp,
|
|
||||||
self._cur_temp,
|
|
||||||
self._cur_ext_temp,
|
|
||||||
self._hvac_mode or HVACMode.OFF,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.reset_last_change_time()
|
|
||||||
|
|
||||||
await self.get_my_previous_state()
|
|
||||||
|
|
||||||
if self.hass.state == CoreState.running:
|
|
||||||
await _async_startup_internal()
|
|
||||||
else:
|
else:
|
||||||
self.hass.bus.async_listen_once(
|
_LOGGER.debug(
|
||||||
EVENT_HOMEASSISTANT_START, _async_startup_internal
|
"%s - external temperature sensor have NOT been retrieved cause no external sensor",
|
||||||
|
self,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self._pmax_on:
|
||||||
|
# try to acquire current power and power max
|
||||||
|
current_power_state = self.hass.states.get(self._power_sensor_entity_id)
|
||||||
|
if current_power_state and current_power_state.state not in (
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
):
|
||||||
|
self._current_power = float(current_power_state.state)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Current power have been retrieved: %.3f",
|
||||||
|
self,
|
||||||
|
self._current_power,
|
||||||
|
)
|
||||||
|
need_write_state = True
|
||||||
|
|
||||||
|
# Try to acquire power max
|
||||||
|
current_power_max_state = self.hass.states.get(
|
||||||
|
self._max_power_sensor_entity_id
|
||||||
|
)
|
||||||
|
if current_power_max_state and current_power_max_state.state not in (
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
):
|
||||||
|
self._current_power_max = float(current_power_max_state.state)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Current power max have been retrieved: %.3f",
|
||||||
|
self,
|
||||||
|
self._current_power_max,
|
||||||
|
)
|
||||||
|
need_write_state = True
|
||||||
|
|
||||||
|
# try to acquire window entity state
|
||||||
|
if self._window_sensor_entity_id:
|
||||||
|
window_state = self.hass.states.get(self._window_sensor_entity_id)
|
||||||
|
if window_state and window_state.state not in (
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
):
|
||||||
|
self._window_state = window_state.state == STATE_ON
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Window state have been retrieved: %s",
|
||||||
|
self,
|
||||||
|
self._window_state,
|
||||||
|
)
|
||||||
|
need_write_state = True
|
||||||
|
|
||||||
|
# try to acquire motion entity state
|
||||||
|
if self._motion_sensor_entity_id:
|
||||||
|
motion_state = self.hass.states.get(self._motion_sensor_entity_id)
|
||||||
|
if motion_state and motion_state.state not in (
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
):
|
||||||
|
self._motion_state = motion_state.state
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Motion state have been retrieved: %s",
|
||||||
|
self,
|
||||||
|
self._motion_state,
|
||||||
|
)
|
||||||
|
# recalculate the right target_temp in activity mode
|
||||||
|
await self._async_update_motion_temp()
|
||||||
|
need_write_state = True
|
||||||
|
|
||||||
|
if self._presence_on:
|
||||||
|
# try to acquire presence entity state
|
||||||
|
presence_state = self.hass.states.get(self._presence_sensor_entity_id)
|
||||||
|
if presence_state and presence_state.state not in (
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
):
|
||||||
|
await self._async_update_presence(presence_state.state)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Presence have been retrieved: %s",
|
||||||
|
self,
|
||||||
|
presence_state.state,
|
||||||
|
)
|
||||||
|
need_write_state = True
|
||||||
|
|
||||||
|
if need_write_state:
|
||||||
|
self.async_write_ha_state()
|
||||||
|
if self._prop_algorithm:
|
||||||
|
self._prop_algorithm.calculate(
|
||||||
|
self._target_temp,
|
||||||
|
self._cur_temp,
|
||||||
|
self._cur_ext_temp,
|
||||||
|
self._hvac_mode or HVACMode.OFF,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.hass.create_task(self._check_initial_state())
|
||||||
|
|
||||||
|
self.reset_last_change_time()
|
||||||
|
|
||||||
|
# if self.hass.state == CoreState.running:
|
||||||
|
# await _async_startup_internal()
|
||||||
|
# else:
|
||||||
|
# self.hass.bus.async_listen_once(
|
||||||
|
# EVENT_HOMEASSISTANT_START, _async_startup_internal
|
||||||
|
# )
|
||||||
|
|
||||||
def init_underlyings(self):
|
def init_underlyings(self):
|
||||||
"""Initialize all underlyings. Should be overriden if necessary"""
|
"""Initialize all underlyings. Should be overriden if necessary"""
|
||||||
|
|
||||||
@@ -825,19 +828,20 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
# Never restore a Power or Security preset
|
# Never restore a Power or Security preset
|
||||||
if old_preset_mode is not None and old_preset_mode not in HIDDEN_PRESETS:
|
if old_preset_mode is not None and old_preset_mode not in HIDDEN_PRESETS:
|
||||||
# old_preset_mode in self._attr_preset_modes
|
# old_preset_mode in self._attr_preset_modes
|
||||||
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
|
self._attr_preset_mode = old_preset_mode
|
||||||
self.save_preset_mode()
|
self.save_preset_mode()
|
||||||
else:
|
else:
|
||||||
self._attr_preset_mode = PRESET_NONE
|
self._attr_preset_mode = PRESET_NONE
|
||||||
|
|
||||||
if not self._hvac_mode and old_state.state in [
|
if old_state.state in [
|
||||||
HVACMode.OFF,
|
HVACMode.OFF,
|
||||||
HVACMode.HEAT,
|
HVACMode.HEAT,
|
||||||
HVACMode.COOL,
|
HVACMode.COOL,
|
||||||
]:
|
]:
|
||||||
self._hvac_mode = old_state.state
|
self._hvac_mode = old_state.state
|
||||||
else:
|
else:
|
||||||
self._hvac_mode = HVACMode.OFF
|
if not self._hvac_mode:
|
||||||
|
self._hvac_mode = HVACMode.OFF
|
||||||
|
|
||||||
old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY)
|
old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY)
|
||||||
if old_total_energy:
|
if old_total_energy:
|
||||||
@@ -2085,6 +2089,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
new_central_mode,
|
new_central_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
first_init = self._last_central_mode == None
|
||||||
|
|
||||||
self._last_central_mode = new_central_mode
|
self._last_central_mode = new_central_mode
|
||||||
|
|
||||||
def save_all():
|
def save_all():
|
||||||
@@ -2093,7 +2099,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
self.save_hvac_mode()
|
self.save_hvac_mode()
|
||||||
|
|
||||||
if new_central_mode == CENTRAL_MODE_AUTO:
|
if new_central_mode == CENTRAL_MODE_AUTO:
|
||||||
if self.window_state is not STATE_ON:
|
if self.window_state is not STATE_ON and not first_init:
|
||||||
await self.restore_hvac_mode()
|
await self.restore_hvac_mode()
|
||||||
await self.restore_preset_mode()
|
await self.restore_preset_mode()
|
||||||
|
|
||||||
@@ -2707,8 +2713,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
if self._attr_preset_mode:
|
if self._attr_preset_mode:
|
||||||
await self._async_set_preset_mode_internal(self._attr_preset_mode, True)
|
await self._async_set_preset_mode_internal(self._attr_preset_mode, True)
|
||||||
|
|
||||||
self.hass.create_task(self._check_initial_state())
|
|
||||||
|
|
||||||
async def async_turn_off(self) -> None:
|
async def async_turn_off(self) -> None:
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"ssdp": [],
|
"ssdp": [],
|
||||||
"version": "6.0.2",
|
"version": "6.1.0",
|
||||||
"zeroconf": []
|
"zeroconf": []
|
||||||
}
|
}
|
||||||
@@ -353,7 +353,7 @@ class CentralConfigTemperatureNumber(
|
|||||||
# We have to reload all VTherm for which uses the central configuration
|
# We have to reload all VTherm for which uses the central configuration
|
||||||
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
|
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
|
||||||
# Update the VTherms which have temperature in central config
|
# Update the VTherms which have temperature in central config
|
||||||
self.hass.create_task(api.init_vtherm_links(only_use_central=True))
|
self.hass.create_task(api.init_vtherm_preset_with_central())
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"VersatileThermostat-{self.name}"
|
return f"VersatileThermostat-{self.name}"
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ from custom_components.versatile_thermostat.base_thermostat import (
|
|||||||
BaseThermostat,
|
BaseThermostat,
|
||||||
ConfigData,
|
ConfigData,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
DEVICE_MANUFACTURER,
|
DEVICE_MANUFACTURER,
|
||||||
@@ -96,17 +99,20 @@ class CentralModeSelect(SelectEntity, RestoreEntity):
|
|||||||
if old_state is not None:
|
if old_state is not None:
|
||||||
self._attr_current_option = old_state.state
|
self._attr_current_option = old_state.state
|
||||||
|
|
||||||
@callback
|
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
|
||||||
async def _async_startup_internal(*_):
|
api.register_central_mode_select(self)
|
||||||
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
|
||||||
await self.notify_central_mode_change()
|
|
||||||
|
|
||||||
if self.hass.state == CoreState.running:
|
# @callback
|
||||||
await _async_startup_internal()
|
# async def _async_startup_internal(*_):
|
||||||
else:
|
# _LOGGER.debug("%s - Calling async_startup_internal", self)
|
||||||
self.hass.bus.async_listen_once(
|
# await self.notify_central_mode_change()
|
||||||
EVENT_HOMEASSISTANT_START, _async_startup_internal
|
#
|
||||||
)
|
# if self.hass.state == CoreState.running:
|
||||||
|
# await _async_startup_internal()
|
||||||
|
# else:
|
||||||
|
# self.hass.bus.async_listen_once(
|
||||||
|
# EVENT_HOMEASSISTANT_START, _async_startup_internal
|
||||||
|
# )
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def async_select_option(self, option: str) -> None:
|
async def async_select_option(self, option: str) -> None:
|
||||||
@@ -122,17 +128,9 @@ class CentralModeSelect(SelectEntity, RestoreEntity):
|
|||||||
|
|
||||||
async def notify_central_mode_change(self, old_central_mode: str | None = None):
|
async def notify_central_mode_change(self, old_central_mode: str | None = None):
|
||||||
"""Notify all VTherm that the central_mode have change"""
|
"""Notify all VTherm that the central_mode have change"""
|
||||||
|
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
|
||||||
# Update all VTherm states
|
# Update all VTherm states
|
||||||
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
|
await api.notify_central_mode_change(old_central_mode)
|
||||||
for entity in component.entities:
|
|
||||||
if isinstance(entity, BaseThermostat):
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Changing the central_mode. We have find %s to update",
|
|
||||||
entity.name,
|
|
||||||
)
|
|
||||||
await entity.check_central_mode(
|
|
||||||
self._attr_current_option, old_central_mode
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"VersatileThermostat-{self.name}"
|
return f"VersatileThermostat-{self.name}"
|
||||||
|
|||||||
@@ -486,8 +486,8 @@ class UnderlyingClimate(UnderlyingEntity):
|
|||||||
self._underlying_climate,
|
self._underlying_climate,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.info(
|
||||||
"%s - Cannot find the underlying climate entity: %s. Thermostat will not be operational",
|
"%s - Cannot find the underlying climate entity: %s. Thermostat will not be operational. Will try later.",
|
||||||
self,
|
self,
|
||||||
self.entity_id,
|
self.entity_id,
|
||||||
)
|
)
|
||||||
@@ -780,15 +780,19 @@ class UnderlyingValve(UnderlyingEntity):
|
|||||||
"""Send the percent open to the underlying valve"""
|
"""Send the percent open to the underlying valve"""
|
||||||
# This may fails if called after shutdown
|
# This may fails if called after shutdown
|
||||||
try:
|
try:
|
||||||
data = {ATTR_ENTITY_ID: self._entity_id, "value": self._percent_open}
|
data = {"value": self._percent_open}
|
||||||
|
target = {ATTR_ENTITY_ID: self._entity_id}
|
||||||
domain = self._entity_id.split(".")[0]
|
domain = self._entity_id.split(".")[0]
|
||||||
await self._hass.services.async_call(
|
await self._hass.services.async_call(
|
||||||
domain,
|
domain=domain,
|
||||||
SERVICE_SET_VALUE,
|
service=SERVICE_SET_VALUE,
|
||||||
data,
|
service_data=data,
|
||||||
|
target=target,
|
||||||
)
|
)
|
||||||
except ServiceNotFound as err:
|
except ServiceNotFound as err:
|
||||||
_LOGGER.error(err)
|
_LOGGER.error(err)
|
||||||
|
# This could happens in unit test if input_number domain is not yet loaded
|
||||||
|
# raise err
|
||||||
|
|
||||||
async def turn_off(self):
|
async def turn_off(self):
|
||||||
"""Turn heater toggleable device off."""
|
"""Turn heater toggleable device off."""
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class VersatileThermostatAPI(dict):
|
|||||||
self._threshold_number_entity = None
|
self._threshold_number_entity = None
|
||||||
self._nb_active_number_entity = None
|
self._nb_active_number_entity = None
|
||||||
self._central_configuration = None
|
self._central_configuration = None
|
||||||
|
self._central_mode_select = None
|
||||||
# A dict that will store all Number entities which holds the temperature
|
# A dict that will store all Number entities which holds the temperature
|
||||||
self._number_temperatures = dict()
|
self._number_temperatures = dict()
|
||||||
|
|
||||||
@@ -149,8 +150,8 @@ class VersatileThermostatAPI(dict):
|
|||||||
return entity.state
|
return entity.state
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def init_vtherm_links(self, only_use_central=False):
|
async def init_vtherm_links(self):
|
||||||
"""INitialize all VTherms entities links
|
"""Initialize all VTherms entities links
|
||||||
This method is called when HA is fully started (and all entities should be initialized)
|
This method is called when HA is fully started (and all entities should be initialized)
|
||||||
Or when we need to reload all VTherm links (with Number temp entities, central boiler, ...)
|
Or when we need to reload all VTherm links (with Number temp entities, central boiler, ...)
|
||||||
"""
|
"""
|
||||||
@@ -162,12 +163,34 @@ class VersatileThermostatAPI(dict):
|
|||||||
)
|
)
|
||||||
if component:
|
if component:
|
||||||
for entity in component.entities:
|
for entity in component.entities:
|
||||||
if hasattr(entity, "init_presets"):
|
# if hasattr(entity, "init_presets"):
|
||||||
if (
|
# if (
|
||||||
only_use_central is False
|
# only_use_central is False
|
||||||
or entity.use_central_config_temperature
|
# or entity.use_central_config_temperature
|
||||||
):
|
# ):
|
||||||
await entity.init_presets(self.find_central_configuration())
|
# await entity.init_presets(self.find_central_configuration())
|
||||||
|
|
||||||
|
# A little hack to test if the climate is a VTherm. Cannot use isinstance due to circular dependency of BaseThermostat
|
||||||
|
if (
|
||||||
|
entity.device_info
|
||||||
|
and entity.device_info.get("model", None) == DOMAIN
|
||||||
|
):
|
||||||
|
await entity.async_startup(self.find_central_configuration())
|
||||||
|
|
||||||
|
async def init_vtherm_preset_with_central(self):
|
||||||
|
"""Init all VTherm presets when the VTherm uses central temperature"""
|
||||||
|
# Initialization of all preset for all VTherm
|
||||||
|
component: EntityComponent[ClimateEntity] = self._hass.data.get(
|
||||||
|
CLIMATE_DOMAIN, None
|
||||||
|
)
|
||||||
|
if component:
|
||||||
|
for entity in component.entities:
|
||||||
|
if (
|
||||||
|
entity.device_info
|
||||||
|
and entity.device_info.get("model", None) == DOMAIN
|
||||||
|
and entity.use_central_config_temperature
|
||||||
|
):
|
||||||
|
await entity.init_presets(self.find_central_configuration())
|
||||||
|
|
||||||
async def reload_central_boiler_binary_listener(self):
|
async def reload_central_boiler_binary_listener(self):
|
||||||
"""Reloads the BinarySensor entity which listen to the number of
|
"""Reloads the BinarySensor entity which listen to the number of
|
||||||
@@ -180,6 +203,27 @@ class VersatileThermostatAPI(dict):
|
|||||||
if self._nb_active_number_entity is not None:
|
if self._nb_active_number_entity is not None:
|
||||||
await self._nb_active_number_entity.listen_vtherms_entities()
|
await self._nb_active_number_entity.listen_vtherms_entities()
|
||||||
|
|
||||||
|
def register_central_mode_select(self, central_mode_select):
|
||||||
|
"""Register the select entity which holds the central_mode"""
|
||||||
|
self._central_mode_select = central_mode_select
|
||||||
|
|
||||||
|
async def notify_central_mode_change(self, old_central_mode: str | None = None):
|
||||||
|
"""Notify all VTherm that the central_mode have change"""
|
||||||
|
if self._central_mode_select is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update all VTherm states
|
||||||
|
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
|
||||||
|
for entity in component.entities:
|
||||||
|
if entity.device_info and entity.device_info.get("model", None) == DOMAIN:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Changing the central_mode. We have find %s to update",
|
||||||
|
entity.name,
|
||||||
|
)
|
||||||
|
await entity.check_central_mode(
|
||||||
|
self._central_mode_select.state, old_central_mode
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def self_regulation_expert(self):
|
def self_regulation_expert(self):
|
||||||
"""Get the self regulation params"""
|
"""Get the self regulation params"""
|
||||||
@@ -229,6 +273,14 @@ class VersatileThermostatAPI(dict):
|
|||||||
return None
|
return None
|
||||||
return int(self._threshold_number_entity.native_value)
|
return int(self._threshold_number_entity.native_value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def central_mode(self) -> str | None:
|
||||||
|
"""Get the current central mode or None"""
|
||||||
|
if self._central_mode_select:
|
||||||
|
return self._central_mode_select.state
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hass(self):
|
def hass(self):
|
||||||
"""Get the HomeAssistant object"""
|
"""Get the HomeAssistant object"""
|
||||||
|
|||||||
@@ -181,14 +181,18 @@ async def test_over_valve_full_start(
|
|||||||
mock_service_call.assert_has_calls(
|
mock_service_call.assert_has_calls(
|
||||||
[
|
[
|
||||||
call.async_call(
|
call.async_call(
|
||||||
"number",
|
domain="number",
|
||||||
"set_value",
|
service="set_value",
|
||||||
{"entity_id": "number.mock_valve", "value": 90},
|
service_data={"value": 90},
|
||||||
|
target={"entity_id": "number.mock_valve"},
|
||||||
|
# {"entity_id": "number.mock_valve", "value": 90},
|
||||||
),
|
),
|
||||||
call.async_call(
|
call.async_call(
|
||||||
"number",
|
domain="number",
|
||||||
"set_value",
|
service="set_value",
|
||||||
{"entity_id": "number.mock_valve", "value": 98},
|
service_data={"value": 98},
|
||||||
|
target={"entity_id": "number.mock_valve"},
|
||||||
|
# {"entity_id": "number.mock_valve", "value": 98},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -241,9 +245,10 @@ async def test_over_valve_full_start(
|
|||||||
mock_service_call.assert_has_calls(
|
mock_service_call.assert_has_calls(
|
||||||
[
|
[
|
||||||
call.async_call(
|
call.async_call(
|
||||||
"number",
|
domain="number",
|
||||||
"set_value",
|
service="set_value",
|
||||||
{"entity_id": "number.mock_valve", "value": 10},
|
service_data={"value": 10},
|
||||||
|
target={"entity_id": "number.mock_valve"},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -254,20 +259,16 @@ async def test_over_valve_full_start(
|
|||||||
mock_service_call.assert_has_calls(
|
mock_service_call.assert_has_calls(
|
||||||
[
|
[
|
||||||
call.async_call(
|
call.async_call(
|
||||||
"number",
|
domain="number",
|
||||||
"set_value",
|
service="set_value",
|
||||||
{
|
service_data={"value": 10},
|
||||||
"entity_id": "number.mock_valve",
|
target={"entity_id": "number.mock_valve"}, # the min allowed value
|
||||||
"value": 10,
|
|
||||||
}, # the min allowed value
|
|
||||||
),
|
),
|
||||||
call.async_call(
|
call.async_call(
|
||||||
"number",
|
domain="number",
|
||||||
"set_value",
|
service="set_value",
|
||||||
{
|
service_data={"value": 50}, # the min allowed value
|
||||||
"entity_id": "number.mock_valve",
|
target={"entity_id": "number.mock_valve"},
|
||||||
"value": 50,
|
|
||||||
}, # the max allowed value
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -466,9 +467,10 @@ async def test_over_valve_regulation(
|
|||||||
mock_service_call.assert_has_calls(
|
mock_service_call.assert_has_calls(
|
||||||
[
|
[
|
||||||
call.async_call(
|
call.async_call(
|
||||||
"number",
|
domain="number",
|
||||||
"set_value",
|
service="set_value",
|
||||||
{"entity_id": "number.mock_valve", "value": 90},
|
service_data={"value": 90},
|
||||||
|
target={"entity_id": "number.mock_valve"},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -524,9 +526,10 @@ async def test_over_valve_regulation(
|
|||||||
mock_service_call.assert_has_calls(
|
mock_service_call.assert_has_calls(
|
||||||
[
|
[
|
||||||
call.async_call(
|
call.async_call(
|
||||||
"number",
|
domain="number",
|
||||||
"set_value",
|
service="set_value",
|
||||||
{"entity_id": "number.mock_valve", "value": 96},
|
service_data={"value": 96},
|
||||||
|
target={"entity_id": "number.mock_valve"},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user