Add availability to choose entity in Config Flow
This commit is contained in:
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user