Compare commits
8 Commits
2.0.0.beta
...
2.0.0.beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d977c4981 | ||
|
|
115df52f67 | ||
|
|
3371197594 | ||
|
|
7e63c9aa79 | ||
|
|
100cfaeac9 | ||
|
|
77631e94d9 | ||
|
|
49c85eeb2b | ||
|
|
bb2c1d328b |
@@ -111,3 +111,10 @@ recorder:
|
|||||||
- switch
|
- switch
|
||||||
- climate
|
- climate
|
||||||
- sensor
|
- sensor
|
||||||
|
|
||||||
|
template:
|
||||||
|
- binary_sensor:
|
||||||
|
- name: maison_occupee
|
||||||
|
unique_id: maison_occupee
|
||||||
|
state: "{{is_state('person.jmc', 'home') }}"
|
||||||
|
device_class: occupancy
|
||||||
|
|||||||
@@ -34,17 +34,15 @@ from homeassistant.helpers import (
|
|||||||
entity_platform,
|
entity_platform,
|
||||||
) # , config_validation as cv
|
) # , config_validation as cv
|
||||||
|
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate import (
|
||||||
DOMAIN as CLIMATE_DOMAIN,
|
DOMAIN as CLIMATE_DOMAIN,
|
||||||
ATTR_PRESET_MODE,
|
ATTR_PRESET_MODE,
|
||||||
# ATTR_FAN_MODE,
|
# ATTR_FAN_MODE,
|
||||||
CURRENT_HVAC_COOL,
|
HVACMode,
|
||||||
CURRENT_HVAC_HEAT,
|
HVACAction,
|
||||||
CURRENT_HVAC_IDLE,
|
# HVAC_MODE_COOL,
|
||||||
CURRENT_HVAC_OFF,
|
# HVAC_MODE_HEAT,
|
||||||
HVAC_MODE_COOL,
|
# HVAC_MODE_OFF,
|
||||||
HVAC_MODE_HEAT,
|
|
||||||
HVAC_MODE_OFF,
|
|
||||||
PRESET_ACTIVITY,
|
PRESET_ACTIVITY,
|
||||||
# PRESET_AWAY,
|
# PRESET_AWAY,
|
||||||
PRESET_BOOST,
|
PRESET_BOOST,
|
||||||
@@ -53,7 +51,8 @@ from homeassistant.components.climate.const import (
|
|||||||
# PRESET_HOME,
|
# PRESET_HOME,
|
||||||
PRESET_NONE,
|
PRESET_NONE,
|
||||||
# PRESET_SLEEP,
|
# PRESET_SLEEP,
|
||||||
SUPPORT_PRESET_MODE,
|
ClimateEntityFeature,
|
||||||
|
# ClimateEntityFeature.PRESET_MODE,
|
||||||
# SUPPORT_TARGET_TEMPERATURE,
|
# SUPPORT_TARGET_TEMPERATURE,
|
||||||
SERVICE_SET_FAN_MODE,
|
SERVICE_SET_FAN_MODE,
|
||||||
SERVICE_SET_HUMIDITY,
|
SERVICE_SET_HUMIDITY,
|
||||||
@@ -63,6 +62,13 @@ from homeassistant.components.climate.const import (
|
|||||||
SERVICE_SET_TEMPERATURE,
|
SERVICE_SET_TEMPERATURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# from homeassistant.components.climate import (
|
||||||
|
# CURRENT_HVAC_HEAT,
|
||||||
|
# HVACAction.IDLE,
|
||||||
|
# HVACAction.OFF,
|
||||||
|
# HVACAction.COOLING,
|
||||||
|
# )
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
# UnitOfTemperature,
|
# UnitOfTemperature,
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
@@ -220,6 +226,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._is_over_climate = False
|
self._is_over_climate = False
|
||||||
self._underlying_climate = None
|
self._underlying_climate = None
|
||||||
|
|
||||||
|
self._attr_translation_key = "versatile_thermostat"
|
||||||
|
|
||||||
self.post_init(entry_infos)
|
self.post_init(entry_infos)
|
||||||
|
|
||||||
def post_init(self, entry_infos):
|
def post_init(self, entry_infos):
|
||||||
@@ -297,7 +305,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# TODO if self.ac_mode:
|
# TODO if self.ac_mode:
|
||||||
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
|
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
|
||||||
# else:
|
# else:
|
||||||
self._hvac_list = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
self._hvac_list = [HVACMode.HEAT, HVACMode.OFF]
|
||||||
self._unit = UnitOfTemperature.CELSIUS
|
self._unit = UnitOfTemperature.CELSIUS
|
||||||
# Will be restored if possible
|
# Will be restored if possible
|
||||||
self._hvac_mode = None # HVAC_MODE_OFF
|
self._hvac_mode = None # HVAC_MODE_OFF
|
||||||
@@ -383,7 +391,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# Calculate all possible presets
|
# Calculate all possible presets
|
||||||
self._attr_preset_modes = [PRESET_NONE]
|
self._attr_preset_modes = [PRESET_NONE]
|
||||||
if len(presets):
|
if len(presets):
|
||||||
self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE
|
self._support_flags = SUPPORT_FLAGS | ClimateEntityFeature.PRESET_MODE
|
||||||
|
|
||||||
for key, val in presets.items():
|
for key, val in presets.items():
|
||||||
if val != 0.0:
|
if val != 0.0:
|
||||||
@@ -741,7 +749,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
# Set default state to off
|
# Set default state to off
|
||||||
if not self._hvac_mode:
|
if not self._hvac_mode:
|
||||||
self._hvac_mode = HVAC_MODE_OFF
|
self._hvac_mode = HVACMode.OFF
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - restored state is target_temp=%.1f, preset_mode=%s, hvac_mode=%s",
|
"%s - restored state is target_temp=%.1f, preset_mode=%s, hvac_mode=%s",
|
||||||
@@ -774,6 +782,50 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
|
|
||||||
return self._hvac_list
|
return self._hvac_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_mode(self) -> str | None:
|
||||||
|
"""Return the fan setting.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.FAN_MODE.
|
||||||
|
"""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.fan_mode
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_modes(self) -> list[str] | None:
|
||||||
|
"""Return the list of available fan modes.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.FAN_MODE.
|
||||||
|
"""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.fan_modes
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def swing_mode(self) -> str | None:
|
||||||
|
"""Return the swing setting.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.SWING_MODE.
|
||||||
|
"""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.swing_mode
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def swing_modes(self) -> list[str] | None:
|
||||||
|
"""Return the list of available swing modes.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.SWING_MODE.
|
||||||
|
"""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.swing_modes
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self):
|
def temperature_unit(self):
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
@@ -796,13 +848,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if self._is_over_climate and self._underlying_climate:
|
if self._is_over_climate and self._underlying_climate:
|
||||||
return self._underlying_climate.hvac_action
|
return self._underlying_climate.hvac_action
|
||||||
|
|
||||||
if self._hvac_mode == HVAC_MODE_OFF:
|
if self._hvac_mode == HVACMode.OFF:
|
||||||
return CURRENT_HVAC_OFF
|
return HVACAction.OFF
|
||||||
if not self._is_device_active:
|
if not self._is_device_active:
|
||||||
return CURRENT_HVAC_IDLE
|
return HVACAction.IDLE
|
||||||
if self._ac_mode:
|
if self._ac_mode:
|
||||||
return CURRENT_HVAC_COOL
|
return HVACAction.COOLING
|
||||||
return CURRENT_HVAC_HEAT
|
return HVACAction.HEATING
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_temperature(self):
|
def target_temperature(self):
|
||||||
@@ -820,20 +872,114 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
@property
|
@property
|
||||||
def _is_device_active(self):
|
def _is_device_active(self):
|
||||||
"""If the toggleable device is currently active."""
|
"""If the toggleable device is currently active."""
|
||||||
if self._is_over_climate or not self.hass.states.get(self._heater_entity_id):
|
if self._is_over_climate:
|
||||||
return None
|
if self._underlying_climate:
|
||||||
|
return self._underlying_climate.hvac_action not in [
|
||||||
return self.hass.states.is_state(self._heater_entity_id, STATE_ON)
|
HVACAction.IDLE,
|
||||||
|
HVACAction.OFF,
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self.hass.states.is_state(self._heater_entity_id, STATE_ON)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
"""Return the sensor temperature."""
|
"""Return the sensor temperature."""
|
||||||
return self._cur_temp
|
return self._cur_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_step(self) -> float | None:
|
||||||
|
"""Return the supported step of target temperature."""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.target_temperature_step
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_high(self) -> float | None:
|
||||||
|
"""Return the highbound target temperature we try to reach.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.TARGET_TEMPERATURE_RANGE.
|
||||||
|
"""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.target_temperature_high
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature_low(self) -> float | None:
|
||||||
|
"""Return the lowbound target temperature we try to reach.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.TARGET_TEMPERATURE_RANGE.
|
||||||
|
"""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.target_temperature_low
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_aux_heat(self) -> bool | None:
|
||||||
|
"""Return true if aux heater.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.AUX_HEAT.
|
||||||
|
"""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.is_aux_heat
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def turn_aux_heat_on(self) -> None:
|
||||||
|
"""Turn auxiliary heater on."""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.turn_aux_heat_on()
|
||||||
|
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
async def async_turn_aux_heat_on(self) -> None:
|
||||||
|
"""Turn auxiliary heater on."""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
await self._underlying_climate.async_turn_aux_heat_on()
|
||||||
|
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def turn_aux_heat_off(self) -> None:
|
||||||
|
"""Turn auxiliary heater off."""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
return self._underlying_climate.turn_aux_heat_off()
|
||||||
|
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
async def async_turn_aux_heat_off(self) -> None:
|
||||||
|
"""Turn auxiliary heater off."""
|
||||||
|
if self._is_over_climate and self._underlying_climate:
|
||||||
|
await self._underlying_climate.async_turn_aux_heat_off()
|
||||||
|
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self) -> str | None:
|
||||||
|
"""Return the current preset mode, e.g., home, away, temp.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.PRESET_MODE.
|
||||||
|
"""
|
||||||
|
return self._attr_preset_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_modes(self) -> list[str] | None:
|
||||||
|
"""Return a list of available preset modes.
|
||||||
|
|
||||||
|
Requires ClimateEntityFeature.PRESET_MODE.
|
||||||
|
"""
|
||||||
|
return self._attr_preset_modes
|
||||||
|
|
||||||
async def async_set_hvac_mode(self, hvac_mode):
|
async def async_set_hvac_mode(self, hvac_mode):
|
||||||
"""Set new target hvac mode."""
|
"""Set new target hvac mode."""
|
||||||
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
|
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
|
||||||
|
|
||||||
|
if hvac_mode is None:
|
||||||
|
return
|
||||||
|
|
||||||
if self._is_over_climate and self._underlying_climate:
|
if self._is_over_climate and self._underlying_climate:
|
||||||
data = {ATTR_ENTITY_ID: self._climate_entity_id, "hvac_mode": hvac_mode}
|
data = {ATTR_ENTITY_ID: self._climate_entity_id, "hvac_mode": hvac_mode}
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
@@ -842,14 +988,14 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# await self._underlying_climate.async_set_hvac_mode(hvac_mode)
|
# await self._underlying_climate.async_set_hvac_mode(hvac_mode)
|
||||||
self._hvac_mode = hvac_mode # self._underlying_climate.hvac_mode
|
self._hvac_mode = hvac_mode # self._underlying_climate.hvac_mode
|
||||||
else:
|
else:
|
||||||
if hvac_mode == HVAC_MODE_HEAT:
|
if hvac_mode == HVACMode.HEAT:
|
||||||
self._hvac_mode = HVAC_MODE_HEAT
|
self._hvac_mode = HVACMode.HEAT
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
elif hvac_mode == HVAC_MODE_COOL:
|
elif hvac_mode == HVACMode.COOL:
|
||||||
self._hvac_mode = HVAC_MODE_COOL
|
self._hvac_mode = HVACMode.COOL
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
elif hvac_mode == HVAC_MODE_OFF:
|
elif hvac_mode == HVACMode.OFF:
|
||||||
self._hvac_mode = HVAC_MODE_OFF
|
self._hvac_mode = HVACMode.OFF
|
||||||
if self._is_device_active:
|
if self._is_device_active:
|
||||||
await self._async_underlying_entity_turn_off()
|
await self._async_underlying_entity_turn_off()
|
||||||
await self._async_control_heating(force=True)
|
await self._async_control_heating(force=True)
|
||||||
@@ -1080,10 +1226,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
await self.restore_hvac_mode()
|
await self.restore_hvac_mode()
|
||||||
elif self._window_state == STATE_ON:
|
elif self._window_state == STATE_ON:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - Window is open. Set hvac_mode to '%s'", self, HVAC_MODE_OFF
|
"%s - Window is open. Set hvac_mode to '%s'", self, HVACMode.OFF
|
||||||
)
|
)
|
||||||
self.save_hvac_mode()
|
self.save_hvac_mode()
|
||||||
await self.async_set_hvac_mode(HVAC_MODE_OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
self.update_custom_attributes()
|
self.update_custom_attributes()
|
||||||
|
|
||||||
if self._window_call_cancel:
|
if self._window_call_cancel:
|
||||||
@@ -1155,7 +1301,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
async def _check_switch_initial_state(self):
|
async def _check_switch_initial_state(self):
|
||||||
"""Prevent the device from keep running if HVAC_MODE_OFF."""
|
"""Prevent the device from keep running if HVAC_MODE_OFF."""
|
||||||
_LOGGER.debug("%s - Calling _check_switch_initial_state", self)
|
_LOGGER.debug("%s - Calling _check_switch_initial_state", self)
|
||||||
if self._hvac_mode == HVAC_MODE_OFF and self._is_device_active:
|
if self._hvac_mode == HVACMode.OFF and self._is_device_active:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"The climate mode is OFF, but the switch device is ON. Turning off device %s",
|
"The climate mode is OFF, but the switch device is ON. Turning off device %s",
|
||||||
self._heater_entity_id,
|
self._heater_entity_id,
|
||||||
@@ -1185,9 +1331,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
# old_state = event.data.get("old_state")
|
# old_state = event.data.get("old_state")
|
||||||
if new_state is None or new_state.state not in [
|
if new_state is None or new_state.state not in [
|
||||||
HVAC_MODE_COOL,
|
HVACMode.OFF,
|
||||||
HVAC_MODE_HEAT,
|
HVACMode.HEAT,
|
||||||
HVAC_MODE_OFF,
|
HVACMode.COOL,
|
||||||
]:
|
]:
|
||||||
return
|
return
|
||||||
self._hvac_mode = new_state.state
|
self._hvac_mode = new_state.state
|
||||||
@@ -1377,11 +1523,17 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
async def _async_underlying_entity_turn_off(self):
|
async def _async_underlying_entity_turn_off(self):
|
||||||
"""Turn heater toggleable device off."""
|
"""Turn heater toggleable device off."""
|
||||||
if not self._is_over_climate:
|
if not self._is_over_climate:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Stopping underlying switch %s", self, self._heater_entity_id
|
||||||
|
)
|
||||||
data = {ATTR_ENTITY_ID: self._heater_entity_id}
|
data = {ATTR_ENTITY_ID: self._heater_entity_id}
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context
|
HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Stopping underlying switch %s", self, self._climate_entity_id
|
||||||
|
)
|
||||||
data = {ATTR_ENTITY_ID: self._climate_entity_id}
|
data = {ATTR_ENTITY_ID: self._climate_entity_id}
|
||||||
await self.hass.services.async_call(
|
await self.hass.services.async_call(
|
||||||
HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context
|
HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context
|
||||||
@@ -1443,11 +1595,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._device_power,
|
self._device_power,
|
||||||
)
|
)
|
||||||
ret = self._current_power + self._device_power >= self._current_power_max
|
ret = self._current_power + self._device_power >= self._current_power_max
|
||||||
if (
|
if not self._overpowering_state and ret and not self._hvac_mode == HVACMode.OFF:
|
||||||
not self._overpowering_state
|
|
||||||
and ret
|
|
||||||
and not self._hvac_mode == HVAC_MODE_OFF
|
|
||||||
):
|
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s - overpowering is detected. Heater preset will be set to 'power'",
|
"%s - overpowering is detected. Heater preset will be set to 'power'",
|
||||||
self,
|
self,
|
||||||
@@ -1495,8 +1643,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
or delta_ext_temp > self._security_delay_min
|
or delta_ext_temp > self._security_delay_min
|
||||||
)
|
)
|
||||||
climate_cond: bool = self._is_over_climate and self.hvac_action not in [
|
climate_cond: bool = self._is_over_climate and self.hvac_action not in [
|
||||||
CURRENT_HVAC_COOL,
|
HVACAction.COOLING,
|
||||||
CURRENT_HVAC_IDLE,
|
HVACAction.IDLE,
|
||||||
]
|
]
|
||||||
switch_cond: bool = (
|
switch_cond: bool = (
|
||||||
not self._is_over_climate
|
not self._is_over_climate
|
||||||
@@ -1534,7 +1682,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self.save_hvac_mode()
|
self.save_hvac_mode()
|
||||||
self.save_preset_mode()
|
self.save_preset_mode()
|
||||||
await self._async_set_preset_mode_internal(PRESET_SECURITY)
|
await self._async_set_preset_mode_internal(PRESET_SECURITY)
|
||||||
await self.async_set_hvac_mode(HVAC_MODE_OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self._security_state
|
self._security_state
|
||||||
@@ -1567,16 +1715,19 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
# Check overpowering condition
|
# Check overpowering condition
|
||||||
overpowering: bool = await self.check_overpowering()
|
overpowering: bool = await self.check_overpowering()
|
||||||
if overpowering:
|
if overpowering:
|
||||||
_LOGGER.debug("%s - End of cycle (0)", self)
|
_LOGGER.debug("%s - End of cycle (overpowering)", self)
|
||||||
return
|
return
|
||||||
|
|
||||||
security: bool = await self.check_security()
|
security: bool = await self.check_security()
|
||||||
if security:
|
if security:
|
||||||
_LOGGER.debug("%s - End of cycle (1)", self)
|
_LOGGER.debug("%s - End of cycle (security)", self)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Stop here if we are off
|
# Stop here if we are off
|
||||||
if self._hvac_mode == HVAC_MODE_OFF:
|
if self._hvac_mode == HVACMode.OFF:
|
||||||
|
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF)", self)
|
||||||
|
if self._is_device_active:
|
||||||
|
await self._async_underlying_entity_turn_off()
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self._is_over_climate:
|
if not self._is_over_climate:
|
||||||
@@ -1606,7 +1757,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
_LOGGER.debug("%s - End of cycle (2)", self)
|
_LOGGER.debug("%s - End of cycle (2)", self)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._hvac_mode == HVAC_MODE_HEAT and on_time_sec > 0:
|
if self._hvac_mode == HVACMode.HEAT and on_time_sec > 0:
|
||||||
|
|
||||||
async def _turn_on_off_later(
|
async def _turn_on_off_later(
|
||||||
on: bool, time, heater_action, next_cycle_action
|
on: bool, time, heater_action, next_cycle_action
|
||||||
@@ -1616,6 +1767,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._async_cancel_cycle = None
|
self._async_cancel_cycle = None
|
||||||
_LOGGER.debug("%s - Stopping cycle during calculation", self)
|
_LOGGER.debug("%s - Stopping cycle during calculation", self)
|
||||||
|
|
||||||
|
if self._hvac_mode == HVACMode.OFF:
|
||||||
|
_LOGGER.debug("%s - End of cycle (HVAC_MODE_OFF - 2)", self)
|
||||||
|
if self._is_device_active:
|
||||||
|
await self._async_underlying_entity_turn_off()
|
||||||
|
return
|
||||||
|
|
||||||
if on:
|
if on:
|
||||||
security = (
|
security = (
|
||||||
await self.check_security()
|
await self.check_security()
|
||||||
|
|||||||
@@ -27,19 +27,19 @@ from homeassistant.helpers.entity_registry import (
|
|||||||
RegistryEntry,
|
RegistryEntry,
|
||||||
async_get,
|
async_get,
|
||||||
)
|
)
|
||||||
from homeassistant.components.climate.const import DOMAIN as CLIMATE_DOMAIN
|
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
|
||||||
from homeassistant.components.climate import ClimateEntity
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
from homeassistant.components.switch.const import DOMAIN as SWITCH_DOMAIN
|
|
||||||
from homeassistant.components.input_boolean import (
|
from homeassistant.components.input_boolean import (
|
||||||
DOMAIN as INPUT_BOOLEAN_DOMAIN,
|
DOMAIN as INPUT_BOOLEAN_DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.sensor.const import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.components.input_number import (
|
from homeassistant.components.input_number import (
|
||||||
DOMAIN as INPUT_NUMBER_DOMAIN,
|
DOMAIN as INPUT_NUMBER_DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.person import DOMAIN as PERSON_DOMAIN
|
from homeassistant.components.person import DOMAIN as PERSON_DOMAIN
|
||||||
|
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||||
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
@@ -167,6 +167,22 @@ 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
|
||||||
|
is_empty: bool = not bool(infos)
|
||||||
|
# Fix features selection depending to infos
|
||||||
|
self._infos[CONF_USE_WINDOW_FEATURE] = (
|
||||||
|
is_empty or self._infos.get(CONF_WINDOW_SENSOR) is not None
|
||||||
|
)
|
||||||
|
self._infos[CONF_USE_MOTION_FEATURE] = (
|
||||||
|
is_empty or self._infos.get(CONF_MOTION_SENSOR) is not None
|
||||||
|
)
|
||||||
|
self._infos[CONF_USE_POWER_FEATURE] = is_empty or (
|
||||||
|
self._infos.get(CONF_POWER_SENSOR) is not None
|
||||||
|
and self._infos.get(CONF_MAX_POWER_SENSOR) is not None
|
||||||
|
)
|
||||||
|
self._infos[CONF_USE_PRESENCE_FEATURE] = (
|
||||||
|
is_empty or self._infos.get(CONF_PRESENCE_SENSOR) is not None
|
||||||
|
)
|
||||||
|
|
||||||
self.hass = async_get_hass()
|
self.hass = async_get_hass()
|
||||||
ent_reg = async_get(hass=self.hass)
|
ent_reg = async_get(hass=self.hass)
|
||||||
|
|
||||||
@@ -199,8 +215,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
_LOGGER.debug("Presence sensor !")
|
_LOGGER.debug("Presence sensor !")
|
||||||
presence_sensors.append(k)
|
presence_sensors.append(k)
|
||||||
|
|
||||||
# window sensor
|
# window sensor and presence
|
||||||
if k.startswith(INPUT_BOOLEAN_DOMAIN):
|
if k.startswith(INPUT_BOOLEAN_DOMAIN) or k.startswith(BINARY_SENSOR_DOMAIN):
|
||||||
_LOGGER.debug("Window or presence sensor !")
|
_LOGGER.debug("Window or presence sensor !")
|
||||||
window_sensors.append(k)
|
window_sensors.append(k)
|
||||||
presence_sensors.append(k)
|
presence_sensors.append(k)
|
||||||
@@ -602,8 +618,18 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
"Into OptionsFlowHandler.async_step_presets user_input=%s", user_input
|
"Into OptionsFlowHandler.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:
|
||||||
@@ -612,8 +638,15 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
"Into OptionsFlowHandler.async_step_window user_input=%s", user_input
|
"Into OptionsFlowHandler.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:
|
||||||
@@ -622,8 +655,14 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
"Into OptionsFlowHandler.async_step_motion user_input=%s", user_input
|
"Into OptionsFlowHandler.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:
|
||||||
@@ -632,11 +671,15 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
"Into OptionsFlowHandler.async_step_power user_input=%s", user_input
|
"Into OptionsFlowHandler.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:
|
||||||
@@ -667,34 +710,20 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
|
|
||||||
async def async_end(self):
|
async def async_end(self):
|
||||||
"""Finalization of the ConfigEntry creation"""
|
"""Finalization of the ConfigEntry creation"""
|
||||||
_LOGGER.debug(
|
if not self._infos[CONF_USE_WINDOW_FEATURE]:
|
||||||
"ConfigFlow.async_finalize - updating entry with: %s", self._infos
|
self._infos[CONF_WINDOW_SENSOR] = None
|
||||||
)
|
if not self._infos[CONF_USE_MOTION_FEATURE]:
|
||||||
# Find eventual existing entity to update it
|
self._infos[CONF_MOTION_SENSOR] = None
|
||||||
# removing entities from registry (they will be recreated)
|
if not self._infos[CONF_USE_POWER_FEATURE]:
|
||||||
|
self._infos[CONF_POWER_SENSOR] = None
|
||||||
|
self._infos[CONF_MAX_POWER_SENSOR] = None
|
||||||
|
if not self._infos[CONF_USE_PRESENCE_FEATURE]:
|
||||||
|
self._infos[CONF_PRESENCE_SENSOR] = None
|
||||||
|
|
||||||
# No need to do that. Only the update_listener on __init__.py is necessary
|
|
||||||
# ent_reg = entity_registry.async_get(self.hass)
|
|
||||||
|
|
||||||
# for entry in entity_registry.async_entries_for_config_entry(
|
|
||||||
# ent_reg, self.config_entry.entry_id
|
|
||||||
# ):
|
|
||||||
# _LOGGER.info(
|
|
||||||
# "Removing entity %s due to configuration change", entry.entity_id
|
|
||||||
# )
|
|
||||||
# ent_reg.async_remove(entry.entity_id)
|
|
||||||
|
|
||||||
# _LOGGER.debug(
|
|
||||||
# "We have found entities to update: %s", self.config_entry.entry_id
|
|
||||||
# )
|
|
||||||
# await VersatileThermostat.update_entity(self.config_entry.entry_id, self._infos)
|
|
||||||
|
|
||||||
# for entity_id in reg_entities.values():
|
|
||||||
# ent_reg.async_remove(entity_id)
|
|
||||||
#
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Recreating entry %s due to configuration change",
|
"Recreating entry %s due to configuration change. New config is now: %s",
|
||||||
self.config_entry.entry_id,
|
self.config_entry.entry_id,
|
||||||
|
self._infos,
|
||||||
)
|
)
|
||||||
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)
|
||||||
|
|||||||
@@ -93,14 +93,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectors": {
|
|
||||||
"thermostat_type": {
|
|
||||||
"options": {
|
|
||||||
"thermostat_over_switch": "Thermostat over a switch",
|
|
||||||
"thermostat_over_climate": "Thermostat over another thermostat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Unexpected error",
|
"unknown": "Unexpected error",
|
||||||
"unknown_entity": "Unknown entity id"
|
"unknown_entity": "Unknown entity id"
|
||||||
@@ -202,14 +194,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectors": {
|
|
||||||
"thermostat_type": {
|
|
||||||
"options": {
|
|
||||||
"thermostat_over_switch": "Thermostat over a switch",
|
|
||||||
"thermostat_over_climate": "Thermostat over another thermostat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Unexpected error",
|
"unknown": "Unexpected error",
|
||||||
"unknown_entity": "Unknown entity id"
|
"unknown_entity": "Unknown entity id"
|
||||||
@@ -217,5 +201,25 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Device is already configured"
|
"already_configured": "Device is already configured"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat over a switch",
|
||||||
|
"thermostat_over_climate": "Thermostat over another thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"versatile_thermostat": {
|
||||||
|
"states_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"power": "Shedding",
|
||||||
|
"security": "Security"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,14 +93,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectors": {
|
|
||||||
"thermostat_type": {
|
|
||||||
"options": {
|
|
||||||
"thermostat_over_switch": "Thermostat over a switch",
|
|
||||||
"thermostat_over_climate": "Thermostat over another thermostat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Unexpected error",
|
"unknown": "Unexpected error",
|
||||||
"unknown_entity": "Unknown entity id"
|
"unknown_entity": "Unknown entity id"
|
||||||
@@ -202,14 +194,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectors": {
|
|
||||||
"thermostat_type": {
|
|
||||||
"options": {
|
|
||||||
"thermostat_over_switch": "Thermostat over a switch",
|
|
||||||
"thermostat_over_climate": "Thermostat over another thermostat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Unexpected error",
|
"unknown": "Unexpected error",
|
||||||
"unknown_entity": "Unknown entity id"
|
"unknown_entity": "Unknown entity id"
|
||||||
@@ -217,5 +201,25 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Device is already configured"
|
"already_configured": "Device is already configured"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat over a switch",
|
||||||
|
"thermostat_over_climate": "Thermostat over another thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"versatile_thermostat": {
|
||||||
|
"states_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"power": "Shedding",
|
||||||
|
"security": "Security"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,14 +92,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectors": {
|
|
||||||
"thermostat_type": {
|
|
||||||
"options": {
|
|
||||||
"thermostat_over_switch": "Thermostat sur un switch",
|
|
||||||
"thermostat_over_climate": "Thermostat sur un autre thermostat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Erreur inattendue",
|
"unknown": "Erreur inattendue",
|
||||||
"unknown_entity": "entity id inconnu"
|
"unknown_entity": "entity id inconnu"
|
||||||
@@ -202,14 +194,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectors": {
|
|
||||||
"thermostat_type": {
|
|
||||||
"options": {
|
|
||||||
"thermostat_over_switch": "Thermostat sur un switch",
|
|
||||||
"thermostat_over_climate": "Thermostat sur un autre thermostat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"unknown": "Erreur inattendue",
|
"unknown": "Erreur inattendue",
|
||||||
"unknown_entity": "entity id inconnu"
|
"unknown_entity": "entity id inconnu"
|
||||||
@@ -217,5 +201,25 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Le device est déjà configuré"
|
"already_configured": "Le device est déjà configuré"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"thermostat_type": {
|
||||||
|
"options": {
|
||||||
|
"thermostat_over_switch": "Thermostat sur un switch",
|
||||||
|
"thermostat_over_climate": "Thermostat sur un autre thermostat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"versatile_thermostat": {
|
||||||
|
"states_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"power": "Délestage",
|
||||||
|
"security": "Sécurité"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user