Add availability to choose entity in Config Flow

This commit is contained in:
Jean-Marc Collin
2023-01-25 23:08:20 +01:00
parent 162b2d1a1b
commit 4786949aeb
8 changed files with 700 additions and 376 deletions

View File

@@ -58,3 +58,41 @@ input_boolean:
fake_presence_sensor1: fake_presence_sensor1:
name: Presence Sensor 1 name: Presence Sensor 1
icon: mdi:home icon: mdi:home
climate:
- platform: generic_thermostat
name: Underlying thermostat1
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat2
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat3
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat4
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat5
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat6
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat7
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat8
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying thermostat9
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1

View File

@@ -29,20 +29,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# hass.data.setdefault(DOMAIN, {}) # hass.data.setdefault(DOMAIN, {})
# TODO 1. Create API instance
api: VersatileThermostatAPI = hass.data.get(DOMAIN) api: VersatileThermostatAPI = hass.data.get(DOMAIN)
if api is None: if api is None:
api = VersatileThermostatAPI(hass) api = VersatileThermostatAPI(hass)
# TODO 2. Validate the API connection (and authentication)
# TODO 3. Store an API object for your platforms to access
api.add_entry(entry) api.add_entry(entry)
entry.async_on_unload(entry.add_update_listener(update_listener))
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
api: VersatileThermostatAPI = hass.data.get(DOMAIN) api: VersatileThermostatAPI = hass.data.get(DOMAIN)

View File

@@ -18,6 +18,7 @@ from homeassistant.components.climate import ClimateEntity
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change_event, async_track_state_change_event,
@@ -105,6 +106,10 @@ from .const import (
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_TEMP_MIN, CONF_TEMP_MIN,
HIDDEN_PRESETS, HIDDEN_PRESETS,
CONF_THERMOSTAT_TYPE,
# CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE,
) )
from .prop_algorithm import PropAlgorithm from .prop_algorithm import PropAlgorithm
@@ -128,7 +133,8 @@ async def async_setup_entry(
entity = VersatileThermostat(hass, unique_id, name, entry.data) entity = VersatileThermostat(hass, unique_id, name, entry.data)
async_add_entities([entity], True) async_add_entities([entity], True)
VersatileThermostat.add_entity(entry.entry_id, entity) # No more needed
# VersatileThermostat.add_entity(entry.entry_id, entity)
# Add services # Add services
platform = entity_platform.async_get_current_platform() platform = entity_platform.async_get_current_platform()
@@ -152,12 +158,16 @@ async def async_setup_entry(
"service_set_preset_temperature", "service_set_preset_temperature",
) )
# A test to see if I'm able to get the entity
_LOGGER.error("Plaform entities are: %s", platform.entities)
class VersatileThermostat(ClimateEntity, RestoreEntity): class VersatileThermostat(ClimateEntity, RestoreEntity):
"""Representation of a Versatile Thermostat device.""" """Representation of a Versatile Thermostat device."""
# The list of VersatileThermostat entities # The list of VersatileThermostat entities
_registry: dict[str, object] = {} # No more needed
# _registry: dict[str, object] = {}
def __init__(self, hass, unique_id, name, entry_infos) -> None: def __init__(self, hass, unique_id, name, entry_infos) -> None:
"""Initialize the thermostat.""" """Initialize the thermostat."""
@@ -197,6 +207,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._security_delay_min = None self._security_delay_min = None
self._security_state = None self._security_state = None
self._thermostat_type = None
self._heater_entity_id = None
self._climate_entity_id = None
self.post_init(entry_infos) self.post_init(entry_infos)
def post_init(self, entry_infos): def post_init(self, entry_infos):
@@ -236,7 +250,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._motion_call_cancel = None self._motion_call_cancel = None
# Exploit usable attributs # Exploit usable attributs
self._heater_entity_id = entry_infos.get(CONF_HEATER) self._thermostat_type = entry_infos.get(CONF_THERMOSTAT_TYPE)
if self._thermostat_type == CONF_THERMOSTAT_CLIMATE:
self._climate_entity_id = entry_infos.get(CONF_CLIMATE)
else:
self._heater_entity_id = entry_infos.get(CONF_HEATER)
self._cycle_min = entry_infos.get(CONF_CYCLE_MIN) self._cycle_min = entry_infos.get(CONF_CYCLE_MIN)
self._proportional_function = entry_infos.get(CONF_PROP_FUNCTION) self._proportional_function = entry_infos.get(CONF_PROP_FUNCTION)
self._temp_sensor_entity_id = entry_infos.get(CONF_TEMP_SENSOR) self._temp_sensor_entity_id = entry_infos.get(CONF_TEMP_SENSOR)
@@ -377,6 +396,339 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._heater_entity_id, self._heater_entity_id,
) )
async def async_added_to_hass(self):
"""Run when entity about to be added."""
_LOGGER.debug("Calling async_added_to_hass")
await super().async_added_to_hass()
# Add listener
if self._thermostat_type == CONF_THERMOSTAT_CLIMATE:
self.async_on_remove(
async_track_state_change_event(
self.hass, [self._climate_entity_id], self._async_climate_changed
)
)
else:
self.async_on_remove(
async_track_state_change_event(
self.hass, [self._heater_entity_id], self._async_switch_changed
)
)
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._temp_sensor_entity_id],
self._async_temperature_changed,
)
)
if self._ext_temp_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._ext_temp_sensor_entity_id],
self._async_ext_temperature_changed,
)
)
if self._window_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._window_sensor_entity_id],
self._async_windows_changed,
)
)
if self._motion_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._motion_sensor_entity_id],
self._async_motion_changed,
)
)
if self._power_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._power_sensor_entity_id],
self._async_power_changed,
)
)
if self._max_power_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._max_power_sensor_entity_id],
self._async_max_power_changed,
)
)
if self._presence_on:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._presence_sensor_entity_id],
self._async_presence_changed,
)
)
self.async_on_remove(self.async_remove_thermostat)
await self.async_startup()
# starts the cycle
# if self._cycle_min:
# self.async_on_remove(
# async_track_time_interval(
# self.hass,
# self._async_control_heating,
# interval=timedelta(minutes=self._cycle_min),
# )
# )
def async_remove_thermostat(self):
"""Called when the thermostat will be removed"""
_LOGGER.info("%s - Removing thermostat", self)
if self._async_cancel_cycle:
self._async_cancel_cycle()
self._async_cancel_cycle = None
async def async_startup(self):
"""Triggered on startup, used to get old state and set internal states accordingly"""
_LOGGER.debug("%s - Calling async_startup", self)
@callback
async def _async_startup_internal(*_):
_LOGGER.debug("%s - Calling async_startup_internal", self)
need_write_state = False
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_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:
_LOGGER.debug(
"%s - external temperature sensor have NOT been retrieved cause no external sensor",
self,
)
if self._thermostat_type == CONF_THERMOSTAT_CLIMATE:
climate_state = self.hass.states.get(self._climate_entity_id)
if climate_state and climate_state.state not in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
):
self._hvac_mode = climate_state
need_write_state = True
else:
switch_state = self.hass.states.get(self._heater_entity_id)
if switch_state and switch_state.state not in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
):
self.hass.create_task(self._check_switch_initial_state())
platforms = entity_platform.async_get_platforms(
self._hass, "versatile_thermostat"
)
# A test to see if I'm able to get the entity
_LOGGER.error("Plaform entities are: %s", platforms[1].entities)
underclimate: VersatileThermostat = platforms[1].entities[
"climate.thermostat_2"
]
_LOGGER.error("plateform[1].entitie[thermostat_2 is: %s", underclimate)
_LOGGER.error("thermostat2.preset_modes is: %s", underclimate.preset_modes)
component: EntityComponent[ClimateEntity] = self._hass.data["climate"]
_LOGGER.error("component.entities is: %s", component.get_entity("climate.thermostat_2"))
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
_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
self._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,
):
self._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()
self._prop_algorithm.calculate(
self._target_temp, self._cur_temp, self._cur_ext_temp
)
self.hass.create_task(self._async_control_heating())
await self.get_my_previous_state()
if self.hass.state == CoreState.running:
await _async_startup_internal()
else:
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, _async_startup_internal
)
async def get_my_previous_state(self):
"""Try to get my previou state"""
# Check If we have an old state
old_state = await self.async_get_last_state()
_LOGGER.debug(
"%s - Calling get_my_previous_state old_state is %s", self, old_state
)
if old_state is not None:
# If we have no initial temperature, restore
if self._target_temp is None:
# If we have a previously saved temperature
if old_state.attributes.get(ATTR_TEMPERATURE) is None:
if self._ac_mode:
self._target_temp = self.max_temp
else:
self._target_temp = self.min_temp
_LOGGER.warning(
"%s - Undefined target temperature, falling back to %s",
self,
self._target_temp,
)
else:
self._target_temp = float(old_state.attributes[ATTR_TEMPERATURE])
if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes:
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
self.save_preset_mode()
if not self._hvac_mode and old_state.state:
self._hvac_mode = old_state.state
# is done in startup above
# self._prop_algorithm.calculate(
# self._target_temp, self._cur_temp, self._cur_ext_temp
# )
else:
# No previous state, try and restore defaults
if self._target_temp is None:
if self._ac_mode:
self._target_temp = self.max_temp
else:
self._target_temp = self.min_temp
_LOGGER.warning(
"No previously saved temperature, setting to %s", self._target_temp
)
self._saved_target_temp = self._target_temp
# Set default state to off
if not self._hvac_mode:
self._hvac_mode = HVAC_MODE_OFF
_LOGGER.info(
"%s - restored state is target_temp=%.1f, preset_mode=%s, hvac_mode=%s",
self,
self._target_temp,
self._attr_preset_mode,
self._hvac_mode,
)
def __str__(self): def __str__(self):
return f"VersatileThermostat-{self.name}" return f"VersatileThermostat-{self.name}"
@@ -561,300 +913,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"""Called when the entry have changed in ConfigFlow""" """Called when the entry have changed in ConfigFlow"""
_LOGGER.info("%s - Change entry with the values: %s", self, config_entry.data) _LOGGER.info("%s - Change entry with the values: %s", self, config_entry.data)
async def async_added_to_hass(self):
"""Run when entity about to be added."""
_LOGGER.debug("Calling async_added_to_hass")
await super().async_added_to_hass()
# Add listener
self.async_on_remove(
async_track_state_change_event(
self.hass, [self._heater_entity_id], self._async_switch_changed
)
)
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._temp_sensor_entity_id],
self._async_temperature_changed,
)
)
if self._ext_temp_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._ext_temp_sensor_entity_id],
self._async_ext_temperature_changed,
)
)
if self._window_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._window_sensor_entity_id],
self._async_windows_changed,
)
)
if self._motion_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._motion_sensor_entity_id],
self._async_motion_changed,
)
)
if self._power_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._power_sensor_entity_id],
self._async_power_changed,
)
)
if self._max_power_sensor_entity_id:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._max_power_sensor_entity_id],
self._async_max_power_changed,
)
)
if self._presence_on:
self.async_on_remove(
async_track_state_change_event(
self.hass,
[self._presence_sensor_entity_id],
self._async_presence_changed,
)
)
await self.async_startup()
# starts the cycle
# if self._cycle_min:
# self.async_on_remove(
# async_track_time_interval(
# self.hass,
# self._async_control_heating,
# interval=timedelta(minutes=self._cycle_min),
# )
# )
async def async_startup(self):
"""Triggered on startup, used to get old state and set internal states accordingly"""
_LOGGER.debug("%s - Calling async_startup", self)
@callback
async def _async_startup_internal(*_):
_LOGGER.debug("%s - Calling async_startup_internal", self)
need_write_state = False
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_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:
_LOGGER.debug(
"%s - external temperature sensor have NOT been retrieved cause no external sensor",
self,
)
switch_state = self.hass.states.get(self._heater_entity_id)
if switch_state and switch_state.state not in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
):
self.hass.create_task(self._check_switch_initial_state())
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
_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
self._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,
):
self._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()
self._prop_algorithm.calculate(
self._target_temp, self._cur_temp, self._cur_ext_temp
)
self.hass.create_task(self._async_control_heating())
await self.get_my_previous_state()
if self.hass.state == CoreState.running:
await _async_startup_internal()
else:
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, _async_startup_internal
)
async def get_my_previous_state(self):
"""Try to get my previou state"""
# Check If we have an old state
old_state = await self.async_get_last_state()
_LOGGER.debug(
"%s - Calling get_my_previous_state old_state is %s", self, old_state
)
if old_state is not None:
# If we have no initial temperature, restore
if self._target_temp is None:
# If we have a previously saved temperature
if old_state.attributes.get(ATTR_TEMPERATURE) is None:
if self._ac_mode:
self._target_temp = self.max_temp
else:
self._target_temp = self.min_temp
_LOGGER.warning(
"%s - Undefined target temperature, falling back to %s",
self,
self._target_temp,
)
else:
self._target_temp = float(old_state.attributes[ATTR_TEMPERATURE])
if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes:
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
self.save_preset_mode()
if not self._hvac_mode and old_state.state:
self._hvac_mode = old_state.state
# is done in startup above
# self._prop_algorithm.calculate(
# self._target_temp, self._cur_temp, self._cur_ext_temp
# )
else:
# No previous state, try and restore defaults
if self._target_temp is None:
if self._ac_mode:
self._target_temp = self.max_temp
else:
self._target_temp = self.min_temp
_LOGGER.warning(
"No previously saved temperature, setting to %s", self._target_temp
)
self._saved_target_temp = self._target_temp
# Set default state to off
if not self._hvac_mode:
self._hvac_mode = HVAC_MODE_OFF
_LOGGER.info(
"%s - restored state is target_temp=%.1f, preset_mode=%s, hvac_mode=%s",
self,
self._target_temp,
self._attr_preset_mode,
self._hvac_mode,
)
@callback @callback
async def _async_temperature_changed(self, event): async def _async_temperature_changed(self, event):
"""Handle temperature changes.""" """Handle temperature changes."""
@@ -1289,7 +1347,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
return True return True
async def _async_control_heating(self, force=False, time=None): async def _async_control_heating(self, force=False, _=None):
"""The main function used to run the calculation at each cycle""" """The main function used to run the calculation at each cycle"""
overpowering: bool = await self.check_overpowering() overpowering: bool = await self.check_overpowering()
@@ -1526,25 +1584,28 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
await self._async_set_preset_mode_internal(preset, force=True) await self._async_set_preset_mode_internal(preset, force=True)
await self._async_control_heating(force=True) await self._async_control_heating(force=True)
@classmethod # No more needed
def add_entity(cls, entry_id, entity):
"""Adds an entity into the VersatileRegistry entities"""
_LOGGER.debug("Adding entity %s", entry_id)
cls._registry[entry_id] = entity
_LOGGER.debug("Entity registry is now %s", cls._registry)
@classmethod
async def update_entity(cls, entry_id, infos):
"""Updates an existing entity referenced by entry_id with the infos in arguments"""
entity: VersatileThermostat = cls._registry.get(entry_id)
if entity is None:
_LOGGER.warning(
"Tries to update VersatileThermostat entity %s but was not found in thermostat registry",
entry_id,
)
return
_LOGGER.debug("We have found the entity to update") # @classmethod
entity.post_init(infos) # def add_entity(cls, entry_id, entity):
# """Adds an entity into the VersatileRegistry entities"""
await entity.async_added_to_hass() # _LOGGER.debug("Adding entity %s", entry_id)
# cls._registry[entry_id] = entity
# _LOGGER.debug("Entity registry is now %s", cls._registry)
#
# @classmethod
# async def update_entity(cls, entry_id, infos):
# """Updates an existing entity referenced by entry_id with the infos in arguments"""
# entity: VersatileThermostat = cls._registry.get(entry_id)
# if entity is None:
# _LOGGER.warning(
# "Tries to update VersatileThermostat entity %s but was not found in thermostat registry",
# entry_id,
# )
# return
#
# _LOGGER.debug("We have found the entity to update")
# entity.post_init(infos)
#
# await entity.async_added_to_hass()

View File

@@ -1,26 +1,43 @@
"""Config flow for Versatile Thermostat integration.""" """Config flow for Versatile Thermostat integration."""
from __future__ import annotations from __future__ import annotations
from typing import Any
import logging import logging
import copy import copy
from collections.abc import Mapping from collections.abc import Mapping
import voluptuous as vol import voluptuous as vol
from typing import Any
from homeassistant.core import callback from homeassistant.core import callback, async_get_hass
from homeassistant.config_entries import ( from homeassistant.config_entries import (
ConfigEntry, ConfigEntry,
ConfigFlow as HAConfigFlow, ConfigFlow as HAConfigFlow,
OptionsFlow, OptionsFlow,
) )
# import homeassistant.helpers.entity_registry as entity_registry
from homeassistant.data_entry_flow import FlowHandler from homeassistant.data_entry_flow import FlowHandler
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_registry import EntityRegistry, async_get
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.switch import SwitchEntity
from homeassistant.components.switch.const import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.input_boolean import (
InputBoolean,
DOMAIN as INPUT_BOOLEAN_DOMAIN,
)
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor.const import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.input_number import (
InputNumber,
DOMAIN as INPUT_NUMBER_DOMAIN,
)
from .const import ( from .const import (
DOMAIN, DOMAIN,
@@ -51,12 +68,16 @@ from .const import (
CONF_MINIMAL_ACTIVATION_DELAY, CONF_MINIMAL_ACTIVATION_DELAY,
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_TEMP_MIN, CONF_TEMP_MIN,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_CLIMATE,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
CONF_THERMOSTAT_TYPES,
) )
from .climate import VersatileThermostat
# from .climate import VersatileThermostat
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -111,13 +132,46 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
super().__init__() super().__init__()
_LOGGER.debug("CTOR BaseConfigFlow infos: %s", infos) _LOGGER.debug("CTOR BaseConfigFlow infos: %s", infos)
self._infos = infos self._infos = infos
self.hass = async_get_hass()
ent_reg = async_get(hass=self.hass)
climates = [] # self.find_all_climates()
switches = [] # self.find_all_heaters()
temp_sensors = [] # self.find_all_temperature_sensors()
k: str
for k in ent_reg.entities:
v = ent_reg.entities[k]
if k.startswith(CLIMATE_DOMAIN):
climates.append(k)
elif k.startswith(SWITCH_DOMAIN) or k.startswith(INPUT_BOOLEAN_DOMAIN):
switches.append(k)
elif k.startswith(INPUT_NUMBER_DOMAIN):
temp_sensors.append(k)
elif k.startswith(SENSOR_DOMAIN):
_LOGGER.debug("We have found sensor: %s", v)
temp_sensors.append(k)
self.STEP_USER_DATA_SCHEMA = vol.Schema( self.STEP_USER_DATA_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HEATER): cv.string, vol.Required(
vol.Required(CONF_TEMP_SENSOR): cv.string, CONF_THERMOSTAT_TYPE, default=CONF_THERMOSTAT_SWITCH
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): cv.string, ): vol.In(CONF_THERMOSTAT_TYPES),
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int, vol.Required(CONF_TEMP_SENSOR): vol.In(temp_sensors),
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): vol.In(temp_sensors),
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float),
vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float),
vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_MOTION_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_POWER_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_PRESENCE_FEATURE, default=False): cv.boolean,
}
)
self.STEP_THERMOSTAT_SWITCH = vol.Schema(
{
vol.Required(CONF_HEATER): vol.In(switches),
vol.Required( vol.Required(
CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI
): vol.In( ): vol.In(
@@ -125,8 +179,13 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
] ]
), ),
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float), vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float), }
)
self.STEP_THERMOSTAT_CLIMATE = vol.Schema(
{
vol.Required(CONF_CLIMATE): vol.In(climates),
} }
) )
@@ -209,6 +268,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_POWER_SENSOR, CONF_POWER_SENSOR,
CONF_MAX_POWER_SENSOR, CONF_MAX_POWER_SENSOR,
CONF_PRESENCE_SENSOR, CONF_PRESENCE_SENSOR,
CONF_CLIMATE,
]: ]:
d = data.get(conf, None) # pylint: disable=invalid-name d = data.get(conf, None) # pylint: disable=invalid-name
if d is not None and self.hass.states.get(d) is None: if d is not None and self.hass.states.get(d) is None:
@@ -268,9 +328,25 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
_LOGGER.debug("Into ConfigFlow.async_step_user user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_user user_input=%s", user_input)
return await self.generic_step( return await self.generic_step(
"user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_tpi "user", self.STEP_USER_DATA_SCHEMA, user_input, self.async_step_type
) )
async def async_step_type(self, user_input: dict | None = None) -> FlowResult:
"""Handle the flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input)
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH:
return await self.generic_step(
"type", self.STEP_THERMOSTAT_SWITCH, user_input, self.async_step_tpi
)
else:
return await self.generic_step(
"type",
self.STEP_THERMOSTAT_CLIMATE,
user_input,
self.async_step_presets,
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult: async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
"""Handle the flow steps""" """Handle the flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input)
@@ -283,35 +359,63 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"""Handle the presets flow steps""" """Handle the presets flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_presets user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_presets user_input=%s", user_input)
next_step = self.async_step_advanced
if self._infos[CONF_USE_WINDOW_FEATURE]:
next_step = self.async_step_window
elif self._infos[CONF_USE_MOTION_FEATURE]:
next_step = self.async_step_motion
elif self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, self.async_step_window "presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, next_step
) )
async def async_step_window(self, user_input: dict | None = None) -> FlowResult: async def async_step_window(self, user_input: dict | None = None) -> FlowResult:
"""Handle the window sensor flow steps""" """Handle the window sensor flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_window user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_window user_input=%s", user_input)
next_step = self.async_step_advanced
if self._infos[CONF_USE_MOTION_FEATURE]:
next_step = self.async_step_motion
elif self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"window", self.STEP_WINDOW_DATA_SCHEMA, user_input, self.async_step_motion "window", self.STEP_WINDOW_DATA_SCHEMA, user_input, next_step
) )
async def async_step_motion(self, user_input: dict | None = None) -> FlowResult: async def async_step_motion(self, user_input: dict | None = None) -> FlowResult:
"""Handle the window and motion sensor flow steps""" """Handle the window and motion sensor flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_motion user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_motion user_input=%s", user_input)
next_step = self.async_step_advanced
if self._infos[CONF_USE_POWER_FEATURE]:
next_step = self.async_step_power
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"motion", self.STEP_MOTION_DATA_SCHEMA, user_input, self.async_step_power "motion", self.STEP_MOTION_DATA_SCHEMA, user_input, next_step
) )
async def async_step_power(self, user_input: dict | None = None) -> FlowResult: async def async_step_power(self, user_input: dict | None = None) -> FlowResult:
"""Handle the power management flow steps""" """Handle the power management flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_power user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_power user_input=%s", user_input)
next_step = self.async_step_advanced
if self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step( return await self.generic_step(
"power", "power",
self.STEP_POWER_DATA_SCHEMA, self.STEP_POWER_DATA_SCHEMA,
user_input, user_input,
self.async_step_presence, next_step,
) )
async def async_step_presence(self, user_input: dict | None = None) -> FlowResult: async def async_step_presence(self, user_input: dict | None = None) -> FlowResult:
@@ -336,6 +440,45 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
self.async_finalize, # pylint: disable=no-member self.async_finalize, # pylint: disable=no-member
) )
async def async_finalize(self):
"""Should be implemented by Leaf classes"""
raise HomeAssistantError(
"async_finalize not implemented on VersatileThermostat sub-class"
)
def find_all_climates(self) -> list(str):
"""Find all climate known by HA"""
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
ret: list(str) = list()
for entity in component.entities:
ret.append(entity.entity_id)
_LOGGER.debug("Found all climate entities: %s", ret)
return ret
def find_all_heaters(self) -> list(str):
"""Find all heater known by HA"""
component: EntityComponent[SwitchEntity] = self.hass.data[SWITCH_DOMAIN]
ret: list(str) = list()
for entity in component.entities:
ret.append(entity.entity_id)
# component = self.hass.data[INPUT_BOOLEAN_DOMAIN]
# for entity in component.entities:
# ret.append(entity.entity_id)
_LOGGER.debug("Found all switch entities: %s", ret)
return ret
def find_all_temperature_sensors(self) -> list(str):
"""Find all heater known by HA"""
component: EntityComponent[SensorEntity] = self.hass.data[SENSOR_DOMAIN]
ret: list(str) = list()
for entity in component.entities:
ret.append(entity.entity_id)
# component = self.hass.data[INPUT_NUMBER_DOMAIN]
# for entity in component.entities:
# ret.append(entity.entity_id)
_LOGGER.debug("Found all temperature sensore entities: %s", ret)
return ret
class VersatileThermostatConfigFlow( class VersatileThermostatConfigFlow(
VersatileThermostatBaseConfigFlow, HAConfigFlow, domain=DOMAIN VersatileThermostatBaseConfigFlow, HAConfigFlow, domain=DOMAIN
@@ -479,33 +622,33 @@ class VersatileThermostatOptionsFlowHandler(
async def async_end(self): async def async_end(self):
"""Finalization of the ConfigEntry creation""" """Finalization of the ConfigEntry creation"""
_LOGGER.debug( _LOGGER.debug(
"CTOR ConfigFlow.async_finalize - updating entry with: %s", self._infos "ConfigFlow.async_finalize - updating entry with: %s", self._infos
) )
# Find eventual existing entity to update it # Find eventual existing entity to update it
# removing entities from registry (they will be recreated) # removing entities from registry (they will be recreated)
# No need to do that. Only the update_listener on __init__.py is necessary
# ent_reg = entity_registry.async_get(self.hass) # ent_reg = entity_registry.async_get(self.hass)
# reg_entities = {
# ent.unique_id: ent.entity_id
# for ent in entity_registry.async_entries_for_config_entry(
# ent_reg, self.config_entry.entry_id
# )
# }
#
# for entry in entity_registry.async_entries_for_config_entry( # for entry in entity_registry.async_entries_for_config_entry(
# ent_reg, self.config_entry.entry_id # ent_reg, self.config_entry.entry_id
# ): # ):
# entity: VersatileThermostat = ent_reg.async_get(entry.entity_id) # _LOGGER.info(
# entity.async_registry_entry_updated(self._infos) # "Removing entity %s due to configuration change", entry.entity_id
# )
# ent_reg.async_remove(entry.entity_id)
_LOGGER.debug( # _LOGGER.debug(
"We have found entities to update: %s", self.config_entry.entry_id # "We have found entities to update: %s", self.config_entry.entry_id
) # )
await VersatileThermostat.update_entity(self.config_entry.entry_id, self._infos) # await VersatileThermostat.update_entity(self.config_entry.entry_id, self._infos)
# for entity_id in reg_entities.values(): # for entity_id in reg_entities.values():
# _LOGGER.info("Recreating entity %s due to configuration change", entity_id)
# ent_reg.async_remove(entity_id) # ent_reg.async_remove(entity_id)
# #
_LOGGER.info(
"Recreating entry %s due to configuration change",
self.config_entry.entry_id,
)
self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos) self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos)
return self.async_create_entry(title=None, data=None) return self.async_create_entry(title=None, data=None)

View File

@@ -42,6 +42,14 @@ CONF_MINIMAL_ACTIVATION_DELAY = "minimal_activation_delay"
CONF_TEMP_MIN = "temp_min" CONF_TEMP_MIN = "temp_min"
CONF_TEMP_MAX = "temp_max" CONF_TEMP_MAX = "temp_max"
CONF_SECURITY_DELAY_MIN = "security_delay_min" CONF_SECURITY_DELAY_MIN = "security_delay_min"
CONF_THERMOSTAT_TYPE = "thermostat_type"
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
CONF_CLIMATE = "climate_entity_id"
CONF_USE_WINDOW_FEATURE = "use_window_feature"
CONF_USE_MOTION_FEATURE = "use_motion_feature"
CONF_USE_PRESENCE_FEATURE = "use_presence_feature"
CONF_USE_POWER_FEATURE = "use_power_feature"
CONF_PRESETS = { CONF_PRESETS = {
p: f"{p}_temp" p: f"{p}_temp"
@@ -92,6 +100,14 @@ ALL_CONF = (
CONF_TEMP_MIN, CONF_TEMP_MIN,
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_SECURITY_DELAY_MIN, CONF_SECURITY_DELAY_MIN,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
] ]
+ CONF_PRESETS_VALUES + CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES, + CONF_PRESETS_AWAY_VALUES,
@@ -101,6 +117,8 @@ CONF_FUNCTIONS = [
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
] ]
CONF_THERMOSTAT_TYPES = [CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_CLIMATE]
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence" SERVICE_SET_PRESENCE = "set_presence"

View File

@@ -8,15 +8,25 @@
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"heater_entity_id": "Heater entity id", "thermostat_type": "Thermostat type",
"thermostat_over_switch": "Thermostat over a switch",
"thermostat_over_climate": "Thermostat over another thermostat",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed" "temp_max": "Maximal temperature allowed",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection"
} }
}, },
"type": {
"heater_entity_id": "Heater entity id",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"cycle_min": "Cycle duration (minutes)",
"climate_entity_id": "Underlying thermostat entity id"
},
"tpi": { "tpi": {
"title": "TPI", "title": "TPI",
"description": "Time Proportional Integral attributes", "description": "Time Proportional Integral attributes",
@@ -97,15 +107,25 @@
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"heater_entity_id": "Heater entity id", "thermostat_type": "Thermostat type",
"thermostat_over_switch": "Thermostat over a switch",
"thermostat_over_climate": "Thermostat over another thermostat",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed" "temp_max": "Maximal temperature allowed",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection"
} }
}, },
"type": {
"heater_entity_id": "Heater entity id",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"cycle_min": "Cycle duration (minutes)",
"climate_entity_id": "Underlying thermostat entity id"
},
"tpi": { "tpi": {
"title": "TPI", "title": "TPI",
"description": "Time Proportional Integral attributes", "description": "Time Proportional Integral attributes",

View File

@@ -8,15 +8,25 @@
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"heater_entity_id": "Heater entity id", "thermostat_type": "Thermostat type",
"thermostat_over_switch": "Thermostat over a switch",
"thermostat_over_climate": "Thermostat over another thermostat",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed" "temp_max": "Maximal temperature allowed",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection"
} }
}, },
"type": {
"heater_entity_id": "Heater entity id",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"cycle_min": "Cycle duration (minutes)",
"climate_entity_id": "Underlying thermostat entity id"
},
"tpi": { "tpi": {
"title": "TPI", "title": "TPI",
"description": "Time Proportional Integral attributes", "description": "Time Proportional Integral attributes",
@@ -97,15 +107,25 @@
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"heater_entity_id": "Heater entity id", "thermostat_type": "Thermostat type",
"thermostat_over_switch": "Thermostat over a switch",
"thermostat_over_climate": "Thermostat over another thermostat",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "External temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed" "temp_max": "Maximal temperature allowed",
"use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection"
} }
}, },
"type": {
"heater_entity_id": "Heater entity id",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"cycle_min": "Cycle duration (minutes)",
"climate_entity_id": "Underlying thermostat entity id"
},
"tpi": { "tpi": {
"title": "TPI", "title": "TPI",
"description": "Time Proportional Integral attributes", "description": "Time Proportional Integral attributes",

View File

@@ -8,15 +8,25 @@
"description": "Principaux attributs obligatoires", "description": "Principaux attributs obligatoires",
"data": { "data": {
"name": "Nom", "name": "Nom",
"heater_entity_id": "Radiateur entity id", "thermostat_over_switch": "Thermostat sur un switch",
"thermostat_over_climate": "Thermostat sur un autre thermostat",
"temperature_sensor_entity_id": "Température sensor entity id", "temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id", "external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"cycle_min": "Durée du cycle (minutes)", "cycle_min": "Durée du cycle (minutes)",
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
"temp_min": "Température minimale permise", "temp_min": "Température minimale permise",
"temp_max": "Température maximale permise" "temp_max": "Température maximale permise",
"use_window_feature": "Avec détection des ouvertures",
"use_motion_feature": "Avec détection de mouvement",
"use_power_feature": "Avec gestion de la puissance",
"use_presence_feature": "Avec détection de présence"
} }
}, },
"type": {
"heater_entity_id": "Radiateur entity id",
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
"cycle_min": "Durée du cycle (minutes)",
"climate_entity_id": "Thermostat sous-jacent entity id"
},
"tpi": { "tpi": {
"title": "TPI", "title": "TPI",
"description": "Attributs de l'algo Time Proportional Integral", "description": "Attributs de l'algo Time Proportional Integral",
@@ -97,15 +107,25 @@
"description": "Principaux attributs obligatoires", "description": "Principaux attributs obligatoires",
"data": { "data": {
"name": "Nom", "name": "Nom",
"heater_entity_id": "Radiateur entity id", "thermostat_over_switch": "Thermostat sur un switch",
"thermostat_over_climate": "Thermostat sur un autre thermostat",
"temperature_sensor_entity_id": "Température sensor entity id", "temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id", "external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
"cycle_min": "Durée du cycle (minutes)", "cycle_min": "Durée du cycle (minutes)",
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
"temp_min": "Température minimale permise", "temp_min": "Température minimale permise",
"temp_max": "Température maximale permise" "temp_max": "Température maximale permise",
"use_window_feature": "Avec détection des ouvertures",
"use_motion_feature": "Avec détection de mouvement",
"use_power_feature": "Avec gestion de la puissance",
"use_presence_feature": "Avec détection de présence"
} }
}, },
"type": {
"heater_entity_id": "Radiateur entity id",
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
"cycle_min": "Durée du cycle (minutes)",
"climate_entity_id": "Thermostat sous-jacent entity id"
},
"tpi": { "tpi": {
"title": "TPI", "title": "TPI",
"description": "Attributs de l'algo Time Proportional Integral", "description": "Attributs de l'algo Time Proportional Integral",