Compare commits

..

11 Commits

Author SHA1 Message Date
Jean-Marc Collin c4fc976007 Terminate Add AC mode and fix remove_thermostat sync 2023-07-22 17:05:54 +02:00
Jean-Marc Collin 31d862acab With config_flow ok 2023-07-22 12:21:54 +02:00
Jean-Marc Collin 9709a9eed0 Add AC mode configFlow 2023-07-22 10:47:10 +02:00
Jean-Marc Collin 61eae8c066 FIX #89 _is_aux_heat error 2023-06-25 12:27:00 +02:00
Jean-Marc Collin e16daa3d53 Add hacs.yaml github workflow 2023-05-14 11:11:55 +02:00
Jean-Marc Collin 90a6c926e3 Resolve actions warning 2023-05-14 11:02:29 +02:00
Jean-Marc Collin 64ce3aa0ad Issue #81 - recursive loop when security should be set 2023-04-28 12:12:38 +02:00
Jean-Marc Collin 3f498ffbd3 Add BuyMeCoffee contributors (fr) 2023-04-26 08:23:03 +02:00
Jean-Marc Collin 3236be6c3b Update README.md
Add BuyMeCoffee contributors
2023-04-26 08:21:54 +02:00
Jean-Marc Collin be86fd3ac0 Merge branch 'main' of github.com:jmcollin78/versatile_thermostat into main 2023-04-22 11:06:12 +02:00
Jean-Marc Collin e35ba57bd7 Fix compilation warnings 2023-04-22 11:05:38 +02:00
18 changed files with 330 additions and 129 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: "ubuntu-latest"
name: Validate
steps:
- uses: "actions/checkout@v2"
- uses: "actions/checkout@v3.5.2"
- name: HACS validation
uses: "hacs/action@main"
+17
View File
@@ -0,0 +1,17 @@
name: HACS Action
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
hacs:
name: HACS Action
runs-on: "ubuntu-latest"
steps:
- name: HACS Action
uses: "hacs/action@main"
with:
category: "integration"
+5 -5
View File
@@ -8,7 +8,7 @@ jobs:
runs-on: "ubuntu-latest"
name: Validate
steps:
- uses: "actions/checkout@v2"
- uses: "actions/checkout@v3.5.2"
- name: HACS validation
uses: "hacs/action@main"
@@ -23,8 +23,8 @@ jobs:
runs-on: "ubuntu-latest"
name: Check style formatting
steps:
- uses: "actions/checkout@v2"
- uses: "actions/setup-python@v1"
- uses: "actions/checkout@v3.5.2"
- uses: "actions/setup-python@v4.6.0"
with:
python-version: "3.x"
- run: python3 -m pip install black
@@ -35,9 +35,9 @@ jobs:
name: Run tests
steps:
- name: Check out code from GitHub
uses: "actions/checkout@v2"
uses: "actions/checkout@v3.5.2"
- name: Setup Python
uses: "actions/setup-python@v1"
uses: "actions/setup-python@v4.6.0"
with:
python-version: "3.8"
- name: Install requirements
+3 -3
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: "ubuntu-latest"
name: Validate
steps:
- uses: "actions/checkout@v2"
- uses: "actions/checkout@v3.5.2"
- name: HACS validation
uses: "hacs/action@main"
@@ -26,8 +26,8 @@ jobs:
runs-on: "ubuntu-latest"
name: Check style formatting
steps:
- uses: "actions/checkout@v2"
- uses: "actions/setup-python@v1"
- uses: "actions/checkout@v3.5.2"
- uses: "actions/setup-python@v4.6.0"
with:
python-version: "3.x"
- run: python3 -m pip install black
+1 -1
View File
@@ -62,7 +62,7 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et est une
> * **release majeure 2.0** : ajout du thermostat "over climate" permettant de transformer n'importe quel thermostat en Versatile Thermostat et lui ajouter toutes les fonctions de ce dernier.
# Merci pour la bière [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Un grand merci à @salabur pour la bière. Ca fait très plaisir.
Un grand merci à @salabur, @pvince83 and @bergoglio pour les bières. Ca fait très plaisir.
# Quand l'utiliser et ne pas l'utiliser
+2 -1
View File
@@ -61,7 +61,8 @@ This custom component for Home Assistant is an upgrade and is a complete rewrite
> * **major release 2.0**: addition of the "over climate" thermostat allowing you to transform any thermostat into a Versatile Thermostat and add all the functions of the latter.
# Thanks for the beer [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Many thanks to @salabur for the beer. It's very pleasing. (Vielen Dank an @salabur für das Bier. Es ist sehr erfreulich.)
Many thanks to @salabur, @pvince83 and @bergoglio for the beers. It's very pleasing.
# When to use / not use
This thermostat can control 2 types of equipment:
+1
View File
@@ -27,6 +27,7 @@ fi
if [ "$command" == "hassfest" ]; then
echo "Running container start"
python3 -m script.hassfest
# python -m script.hassfest --requirements --action validate --integration-path config/custom_components/versatile_thermostat/
fi
if [ "$command" == "restart" ]; then
@@ -100,6 +100,8 @@ from .const import (
CONF_DEVICE_POWER,
CONF_PRESETS,
CONF_PRESETS_AWAY,
CONF_PRESETS_WITH_AC,
CONF_PRESETS_AWAY_WITH_AC,
CONF_CYCLE_MIN,
CONF_PROP_FUNCTION,
CONF_TPI_COEF_INT,
@@ -127,10 +129,12 @@ from .const import (
# CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE,
CONF_AC_MODE,
UnknownEntity,
EventType,
ATTR_MEAN_POWER_CYCLE,
ATTR_TOTAL_ENERGY,
PRESET_AC_SUFFIX,
)
from .underlyings import UnderlyingSwitch, UnderlyingClimate, UnderlyingEntity
@@ -289,9 +293,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self,
entry_infos,
)
self._ac_mode = entry_infos.get(CONF_AC_MODE) == True
# convert entry_infos into usable attributes
presets = {}
for key, value in CONF_PRESETS.items():
items = CONF_PRESETS_WITH_AC.items() if self._ac_mode else CONF_PRESETS.items()
for key, value in items:
_LOGGER.debug("looking for key=%s, value=%s", key, value)
if value in entry_infos:
presets[key] = entry_infos.get(value)
@@ -299,7 +306,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
_LOGGER.debug("value %s not found in Entry", value)
presets_away = {}
for key, value in CONF_PRESETS_AWAY.items():
items = (
CONF_PRESETS_AWAY_WITH_AC.items()
if self._ac_mode
else CONF_PRESETS_AWAY.items()
)
for key, value in items:
_LOGGER.debug("looking for key=%s, value=%s", key, value)
if value in entry_infos:
presets_away[key] = entry_infos.get(value)
@@ -394,10 +406,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._presence_on = self._presence_sensor_entity_id is not None
# if self.ac_mode: -> MODE_COOL should be better to use thermostat_over_climate type
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
# else:
self._hvac_list = [HVACMode.HEAT, HVACMode.OFF]
if self._ac_mode:
self._hvac_list = [HVACMode.HEAT, HVACMode.COOL, HVACMode.OFF]
else:
self._hvac_list = [HVACMode.HEAT, HVACMode.OFF]
self._unit = self._hass.config.units.temperature_unit
# Will be restored if possible
@@ -437,7 +449,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._target_temp = None
self._saved_target_temp = PRESET_NONE
self._humidity = None
self._ac_mode = False
self._fan_mode = None
self._swing_mode = None
self._cur_temp = None
@@ -494,13 +505,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if len(presets):
self._support_flags = SUPPORT_FLAGS | ClimateEntityFeature.PRESET_MODE
for key, val in presets.items():
for key, val in CONF_PRESETS.items(): # TODO before presets.items():
if val != 0.0:
self._attr_preset_modes.append(key)
# self._attr_preset_modes = (
# [PRESET_NONE] + list(presets.keys()) + [PRESET_ACTIVITY]
# )
_LOGGER.debug(
"After adding presets, preset_modes to %s", self._attr_preset_modes
)
@@ -609,11 +617,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# Ingore this error which is possible if underlying climate is not found temporary
pass
async def remove_thermostat(self):
def remove_thermostat(self):
"""Called when the thermostat will be removed"""
_LOGGER.info("%s - Removing thermostat", self)
for under in self._underlyings:
await under.remove_entity()
under.remove_entity()
async def async_startup(self):
"""Triggered on startup, used to get old state and set internal states accordingly"""
@@ -1191,7 +1199,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
raise NotImplementedError()
async def async_set_hvac_mode(self, hvac_mode):
async def async_set_hvac_mode(self, hvac_mode, need_control_heating=True):
"""Set new target hvac mode."""
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
@@ -1201,13 +1209,13 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._hvac_mode = hvac_mode
# Delegate to all underlying
need_control_heating = False
sub_need_control_heating = False
for under in self._underlyings:
need_control_heating = (
sub_need_control_heating = (
await under.set_hvac_mode(hvac_mode) or need_control_heating
)
if need_control_heating:
if need_control_heating and sub_need_control_heating:
await self._async_control_heating(force=True)
# Ensure we update the current operation after changing the mode
@@ -1286,13 +1294,18 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
) # in security just keep the current target temperature, the thermostat should be off
if preset_mode == PRESET_POWER:
return self._power_temp
elif self._presence_on is False or self._presence_state in [
STATE_ON,
STATE_HOME,
]:
return self._presets[preset_mode]
else:
return self._presets_away[self.get_preset_away_name(preset_mode)]
# Select _ac presets if in COOL Mode
if self._ac_mode and self._hvac_mode == HVACMode.COOL:
preset_mode = preset_mode + PRESET_AC_SUFFIX
if self._presence_on is False or self._presence_state in [
STATE_ON,
STATE_HOME,
]:
return self._presets[preset_mode]
else:
return self._presets_away[self.get_preset_away_name(preset_mode)]
def get_preset_away_name(self, preset_mode):
"""Get the preset name in away mode (when presence is off)"""
@@ -1452,7 +1465,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self,
self._saved_hvac_mode,
)
await self.restore_hvac_mode()
await self.restore_hvac_mode(True)
elif self._window_state == STATE_ON:
_LOGGER.info(
"%s - Window is open. Set hvac_mode to '%s'", self, HVACMode.OFF
@@ -1578,7 +1591,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
)
_LOGGER.info(
"%s - Underlying climate changed. Event.new_state is %s, hvac_mode=%s, hvac_action=%s, old_hvac_action=%s",
"%s - Underlying climate changed. Event.new_state is %s, current_hvac_mode=%s, new_hvac_action=%s, old_hvac_action=%s",
self,
new_state,
self._hvac_mode,
@@ -1857,7 +1870,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
)
# Set attributes
self._window_auto_state = False
await self.restore_hvac_mode()
await self.restore_hvac_mode(True)
if self._window_call_cancel:
self._window_call_cancel()
@@ -1953,9 +1966,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._hvac_mode,
)
async def restore_hvac_mode(self):
async def restore_hvac_mode(self, need_control_heating=False):
"""Restore a previous hvac_mod"""
await self.async_set_hvac_mode(self._saved_hvac_mode)
await self.async_set_hvac_mode(self._saved_hvac_mode, need_control_heating)
_LOGGER.debug(
"%s - Restored hvac_mode - saved_hvac_mode is %s, hvac_mode is %s",
self,
@@ -2025,7 +2038,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._saved_preset_mode,
)
if self._is_over_climate:
await self.restore_hvac_mode()
await self.restore_hvac_mode(False)
await self.restore_preset_mode()
self.send_event(
EventType.POWER_EVENT,
@@ -2050,7 +2063,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
now - self._last_ext_temperature_mesure.replace(tzinfo=self._current_tz)
).total_seconds() / 60.0
mode_cond = self._is_over_climate or self._hvac_mode != HVACMode.OFF
# TODO before change:
# mode_cond = self._is_over_climate or self._hvac_mode != HVACMode.OFF
# fixed into this. Why if _is_over_climate we could into security even if HVACMode is OFF ?
mode_cond = self._hvac_mode != HVACMode.OFF
temp_cond: bool = (
delta_temp > self._security_delay_min
@@ -2127,7 +2143,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
await self._async_set_preset_mode_internal(PRESET_SECURITY)
# Turn off the underlying climate or heater if security default on_percent is 0
if self._is_over_climate or self._security_default_on_percent <= 0.0:
await self.async_set_hvac_mode(HVACMode.OFF)
await self.async_set_hvac_mode(HVACMode.OFF, False)
if self._prop_algorithm:
self._prop_algorithm.set_security(self._security_default_on_percent)
@@ -2161,7 +2177,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._security_state = ret
# Restore hvac_mode if previously saved
if self._is_over_climate or self._security_default_on_percent <= 0.0:
await self.restore_hvac_mode()
await self.restore_hvac_mode(False)
await self.restore_preset_mode()
if self._prop_algorithm:
self._prop_algorithm.unset_security()
@@ -2294,6 +2310,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self.get_preset_away_name(PRESET_COMFORT)
),
"power_temp": self._power_temp,
"target_temp": self.target_temperature,
"current_temp": self._cur_temp,
"ext_current_temperature": self._cur_ext_temp,
"current_power": self._current_power,
"current_power_max": self._current_power_max,
@@ -61,7 +61,9 @@ from .const import (
CONF_CYCLE_MIN,
CONF_PRESET_POWER,
CONF_PRESETS,
CONF_PRESETS_WITH_AC,
CONF_PRESETS_AWAY,
CONF_PRESETS_AWAY_WITH_AC,
CONF_PRESETS_SELECTIONABLE,
CONF_PROP_FUNCTION,
CONF_TPI_COEF_EXT,
@@ -83,6 +85,7 @@ from .const import (
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
CONF_AC_MODE,
CONF_THERMOSTAT_TYPES,
UnknownEntity,
WindowOpenDetectionMethod,
@@ -132,35 +135,6 @@ def add_suggested_values_to_schema(
return vol.Schema(schema)
# def is_temperature_sensor(sensor: RegistryEntry):
# """Check if a registryEntry is a temperature sensor or assimilable to a temperature sensor"""
# if not sensor.entity_id.startswith(
# INPUT_NUMBER_DOMAIN
# ) and not sensor.entity_id.startswith(SENSOR_DOMAIN):
# return False
# return (
# sensor.device_class == TEMPERATURE
# or sensor.original_device_class == TEMPERATURE
# or sensor.unit_of_measurement in TEMPERATURE_UNITS
# )
#
#
# def is_power_sensor(sensor: RegistryEntry):
# """Check if a registryEntry is a power sensor or assimilable to a temperature sensor"""
# if not sensor.entity_id.startswith(
# INPUT_NUMBER_DOMAIN
# ) and not sensor.entity_id.startswith(SENSOR_DOMAIN):
# return False
# return (
# sensor.unit_of_measurement
# in [
# UnitOfPower.KILO_WATT,
# UnitOfPower.WATT,
# UnitOfPower.BTU_PER_HOUR,
# ]
# )
class VersatileThermostatBaseConfigFlow(FlowHandler):
"""The base Config flow class. Used to put some code in commons."""
@@ -257,6 +231,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
vol.Required(CONF_CLIMATE): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
}
)
@@ -274,6 +249,15 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
}
)
self.STEP_PRESETS_WITH_AC_DATA_SCHEMA = ( # pylint: disable=invalid-name
vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(v, default=0.0): vol.Coerce(float)
for (k, v) in CONF_PRESETS_WITH_AC.items()
}
)
)
self.STEP_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_WINDOW_SENSOR): selector.EntitySelector(
@@ -340,6 +324,27 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
}
)
self.STEP_PRESENCE_WITH_AC_DATA_SCHEMA = ( # pylint: disable=invalid-name
vol.Schema(
{
vol.Optional(CONF_PRESENCE_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[
PERSON_DOMAIN,
BINARY_SENSOR_DOMAIN,
INPUT_BOOLEAN_DOMAIN,
]
),
),
}
).extend(
{
vol.Optional(v, default=17): vol.Coerce(float)
for (k, v) in CONF_PRESETS_AWAY_WITH_AC.items()
}
)
)
self.STEP_ADVANCED_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(
@@ -489,9 +494,12 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step(
"presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, next_step
)
if self._infos.get(CONF_AC_MODE) == True:
schema = self.STEP_PRESETS_WITH_AC_DATA_SCHEMA
else:
schema = self.STEP_PRESETS_DATA_SCHEMA
return await self.generic_step("presets", schema, user_input, next_step)
async def async_step_window(self, user_input: dict | None = None) -> FlowResult:
"""Handle the window sensor flow steps"""
@@ -542,9 +550,14 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"""Handle the presence management flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_presence user_input=%s", user_input)
if self._infos[CONF_AC_MODE]:
schema = self.STEP_PRESENCE_WITH_AC_DATA_SCHEMA
else:
schema = self.STEP_PRESENCE_DATA_SCHEMA
return await self.generic_step(
"presence",
self.STEP_PRESENCE_DATA_SCHEMA,
schema,
user_input,
self.async_step_advanced,
)
@@ -676,9 +689,12 @@ class VersatileThermostatOptionsFlowHandler(
elif self._infos[CONF_USE_PRESENCE_FEATURE]:
next_step = self.async_step_presence
return await self.generic_step(
"presets", self.STEP_PRESETS_DATA_SCHEMA, user_input, next_step
)
if self._infos[CONF_AC_MODE]:
schema = self.STEP_PRESETS_WITH_AC_DATA_SCHEMA
else:
schema = self.STEP_PRESETS_DATA_SCHEMA
return await self.generic_step("presets", schema, user_input, next_step)
async def async_step_window(self, user_input: dict | None = None) -> FlowResult:
"""Handle the window sensor flow steps"""
@@ -736,9 +752,14 @@ class VersatileThermostatOptionsFlowHandler(
"Into OptionsFlowHandler.async_step_presence user_input=%s", user_input
)
if self._infos[CONF_AC_MODE]:
schema = self.STEP_PRESENCE_WITH_AC_DATA_SCHEMA
else:
schema = self.STEP_PRESENCE_DATA_SCHEMA
return await self.generic_step(
"presence",
self.STEP_PRESENCE_DATA_SCHEMA,
schema,
user_input,
self.async_step_advanced,
)
@@ -11,6 +11,11 @@ from homeassistant.components.climate import (
ClimateEntityFeature,
)
PRESET_AC_SUFFIX = "_ac"
PRESET_ECO_AC = PRESET_ECO + PRESET_AC_SUFFIX
PRESET_COMFORT_AC = PRESET_COMFORT + PRESET_AC_SUFFIX
PRESET_BOOST_AC = PRESET_BOOST + PRESET_AC_SUFFIX
from homeassistant.exceptions import HomeAssistantError
from .prop_algorithm import (
@@ -64,6 +69,7 @@ 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_AC_MODE = "ac_mode"
CONF_WINDOW_AUTO_OPEN_THRESHOLD = "window_auto_open_threshold"
CONF_WINDOW_AUTO_CLOSE_THRESHOLD = "window_auto_close_threshold"
CONF_WINDOW_AUTO_MAX_DURATION = "window_auto_max_duration"
@@ -77,14 +83,39 @@ CONF_PRESETS = {
)
}
CONF_PRESETS_WITH_AC = {
p: f"{p}_temp"
for p in (
PRESET_ECO,
PRESET_COMFORT,
PRESET_BOOST,
PRESET_ECO_AC,
PRESET_COMFORT_AC,
PRESET_BOOST_AC,
)
}
PRESET_AWAY_SUFFIX = "_away"
CONF_PRESETS_AWAY = {
p: f"{p}_temp"
for p in (
PRESET_ECO + PRESET_AWAY_SUFFIX,
PRESET_BOOST + PRESET_AWAY_SUFFIX,
PRESET_COMFORT + PRESET_AWAY_SUFFIX,
PRESET_BOOST + PRESET_AWAY_SUFFIX,
)
}
CONF_PRESETS_AWAY_WITH_AC = {
p: f"{p}_temp"
for p in (
PRESET_ECO + PRESET_AWAY_SUFFIX,
PRESET_COMFORT + PRESET_AWAY_SUFFIX,
PRESET_BOOST + PRESET_AWAY_SUFFIX,
PRESET_ECO_AC + PRESET_AWAY_SUFFIX,
PRESET_COMFORT_AC + PRESET_AWAY_SUFFIX,
PRESET_BOOST_AC + PRESET_AWAY_SUFFIX,
)
}
@@ -92,6 +123,8 @@ CONF_PRESETS_SELECTIONABLE = [PRESET_ECO, PRESET_COMFORT, PRESET_BOOST]
CONF_PRESETS_VALUES = list(CONF_PRESETS.values())
CONF_PRESETS_AWAY_VALUES = list(CONF_PRESETS_AWAY.values())
CONF_PRESETS_WITH_AC_VALUES = list(CONF_PRESETS_WITH_AC.values())
CONF_PRESETS_AWAY_WITH_AC_VALUES = list(CONF_PRESETS_AWAY_WITH_AC.values())
ALL_CONF = (
[
@@ -130,9 +163,12 @@ ALL_CONF = (
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
CONF_AC_MODE,
]
+ CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES,
+ CONF_PRESETS_AWAY_VALUES
+ CONF_PRESETS_WITH_AC_VALUES
+ CONF_PRESETS_AWAY_WITH_AC_VALUES,
)
CONF_FUNCTIONS = [
@@ -1 +1,2 @@
homeassistant
homeassistant
ffmpeg
@@ -30,7 +30,8 @@
"heater_entity3_id": "3rd Heater switch",
"heater_entity4_id": "4th Heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "Underlying thermostat"
"climate_entity_id": "Underlying thermostat",
"ac_mode": "AC mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -38,7 +39,8 @@
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id"
"climate_entity_id": "Underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode"
}
},
"tpi": {
@@ -55,7 +57,10 @@
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",
"boost_temp": "Temperature in Boost preset"
"boost_temp": "Temperature in Boost preset",
"eco_ac_temp": "Temperature in Eco preset for AC mode",
"comfort_ac_temp": "Temperature in Comfort preset for AC mode",
"boost_ac_temp": "Temperature in Boost preset for AC mode"
}
},
"window": {
@@ -102,7 +107,10 @@
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence"
"boost_away_temp": "Temperature in Boost preset when no presence",
"eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode",
"comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode",
"boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode"
}
},
"advanced": {
@@ -161,7 +169,8 @@
"heater_entity3_id": "3rd Heater switch",
"heater_entity4_id": "4th Heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "Underlying thermostat"
"climate_entity_id": "Underlying thermostat",
"ac_mode": "AC mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -169,7 +178,8 @@
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id"
"climate_entity_id": "Underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode"
}
},
"tpi": {
@@ -186,7 +196,10 @@
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",
"boost_temp": "Temperature in Boost preset"
"boost_temp": "Temperature in Boost preset",
"eco_ac_temp": "Temperature in Eco preset for AC mode",
"comfort_ac_temp": "Temperature in Comfort preset for AC mode",
"boost_ac_temp": "Temperature in Boost preset for AC mode"
}
},
"window": {
@@ -233,7 +246,10 @@
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence"
"boost_away_temp": "Temperature in Boost preset when no presence",
"eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode",
"comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode",
"boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode"
}
},
"advanced": {
@@ -2,7 +2,7 @@
import asyncio
import logging
from unittest.mock import patch, MagicMock
import pytest
import pytest # pylint: disable=unused-import
from homeassistant.core import HomeAssistant, Event, EVENT_STATE_CHANGED, State
from homeassistant.const import UnitOfTemperature, STATE_ON, STATE_OFF
@@ -9,7 +9,7 @@ from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from pytest_homeassistant_custom_component.common import MockConfigEntry
from .commons import *
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from ..climate import VersatileThermostat
from ..binary_sensor import (
SecurityBinarySensor,
@@ -30,7 +30,8 @@
"heater_entity3_id": "3rd Heater switch",
"heater_entity4_id": "4th Heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "Underlying thermostat"
"climate_entity_id": "Underlying thermostat",
"ac_mode": "AC mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -38,7 +39,8 @@
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id"
"climate_entity_id": "Underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode"
}
},
"tpi": {
@@ -55,7 +57,10 @@
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",
"boost_temp": "Temperature in Boost preset"
"boost_temp": "Temperature in Boost preset",
"eco_ac_temp": "Temperature in Eco preset for AC mode",
"comfort_ac_temp": "Temperature in Comfort preset for AC mode",
"boost_ac_temp": "Temperature in Boost preset for AC mode"
}
},
"window": {
@@ -102,7 +107,10 @@
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence"
"boost_away_temp": "Temperature in Boost preset when no presence",
"eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode",
"comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode",
"boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode"
}
},
"advanced": {
@@ -161,7 +169,8 @@
"heater_entity3_id": "3rd Heater switch",
"heater_entity4_id": "4th Heater switch",
"proportional_function": "Algorithm",
"climate_entity_id": "Underlying thermostat"
"climate_entity_id": "Underlying thermostat",
"ac_mode": "AC mode"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -169,7 +178,8 @@
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id"
"climate_entity_id": "Underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode"
}
},
"tpi": {
@@ -186,7 +196,10 @@
"data": {
"eco_temp": "Temperature in Eco preset",
"comfort_temp": "Temperature in Comfort preset",
"boost_temp": "Temperature in Boost preset"
"boost_temp": "Temperature in Boost preset",
"eco_ac_temp": "Temperature in Eco preset for AC mode",
"comfort_ac_temp": "Temperature in Comfort preset for AC mode",
"boost_ac_temp": "Temperature in Boost preset for AC mode"
}
},
"window": {
@@ -233,7 +246,10 @@
"presence_sensor_entity_id": "Presence sensor entity id (true is present)",
"eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence"
"boost_away_temp": "Temperature in Boost preset when no presence",
"eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode",
"comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode",
"boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode"
}
},
"advanced": {
@@ -29,7 +29,8 @@
"heater_entity3_id": "3ème radiateur",
"heater_entity4_id": "4ème radiateur",
"proportional_function": "Algorithme",
"climate_entity_id": "Thermostat sous-jacent"
"climate_entity_id": "Thermostat sous-jacent",
"ac_mode": "AC mode ?"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -37,7 +38,8 @@
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Entity id du thermostat sous-jacent"
"climate_entity_id": "Entity id du thermostat sous-jacent",
"ac_mode": "Utilisation du mode Air Conditionné (AC)"
}
},
"tpi": {
@@ -54,7 +56,10 @@
"data": {
"eco_temp": "Température en preset Eco",
"comfort_temp": "Température en preset Comfort",
"boost_temp": "Température en preset Boost"
"boost_temp": "Température en preset Boost",
"eco_ac_temp": "Température en preset Eco en mode AC",
"comfort_ac_temp": "Température en preset Comfort en mode AC",
"boost_ac_temp": "Température en preset Boost en mode AC"
}
},
"window": {
@@ -101,7 +106,10 @@
"presence_sensor_entity_id": "Capteur de présence entity id (true si quelqu'un est présent)",
"eco_away_temp": "Température en preset Eco en cas d'absence",
"comfort_away_temp": "Température en preset Comfort en cas d'absence",
"boost_away_temp": "Température en preset Boost en cas d'absence"
"boost_away_temp": "Température en preset Boost en cas d'absence",
"eco_ac_away_temp": "Température en preset Eco en cas d'absence en mode AC",
"comfort_ac_away_temp": "Température en preset Comfort en cas d'absence en mode AC",
"boost_ac_away_temp": "Température en preset Boost en cas d'absence en mode AC"
}
},
"advanced": {
@@ -161,7 +169,8 @@
"heater_entity3_id": "3ème radiateur",
"heater_entity4_id": "4ème radiateur",
"proportional_function": "Algorithme",
"climate_entity_id": "Thermostat sous-jacent"
"climate_entity_id": "Thermostat sous-jacent",
"ac_mode": "AC mode ?"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -169,7 +178,8 @@
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Entity id du thermostat sous-jacent"
"climate_entity_id": "Entity id du thermostat sous-jacent",
"ac_mode": "Utilisation du mode Air Conditionné (AC)"
}
},
"tpi": {
@@ -186,7 +196,10 @@
"data": {
"eco_temp": "Température en preset Eco",
"comfort_temp": "Température en preset Comfort",
"boost_temp": "Température en preset Boost"
"boost_temp": "Température en preset Boost",
"eco_ac_temp": "Température en preset Eco en mode AC",
"comfort_ac_temp": "Température en preset Comfort en mode AC",
"boost_ac_temp": "Température en preset Boost en mode AC"
}
},
"window": {
@@ -233,7 +246,10 @@
"presence_sensor_entity_id": "Capteur de présence entity id (true si quelqu'un est présent)",
"eco_away_temp": "Température en preset Eco en cas d'absence",
"comfort_away_temp": "Température en preset Comfort en cas d'absence",
"boost_away_temp": "Température en preset Boost en cas d'absence"
"boost_away_temp": "Température en preset Boost en cas d'absence",
"eco_ac_away_temp": "Température en preset Eco en cas d'absence en mode AC",
"comfort_ac_away_temp": "Température en preset Comfort en cas d'absence en mode AC",
"boost_ac_away_temp": "Température en preset Boost en cas d'absence en mode AC"
}
},
"advanced": {
@@ -30,7 +30,8 @@
"heater_entity3_id": "Terzo riscaldatore",
"heater_entity4_id": "Quarto riscaldatore",
"proportional_function": "Algoritmo",
"climate_entity_id": "Termostato sottostante"
"climate_entity_id": "Termostato sottostante",
"ac_mode": "AC mode ?"
},
"data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -38,7 +39,8 @@
"heater_entity3_id": "Entity id del terzo riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"heater_entity4_id": "Entity id del quarto riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"proportional_function": "Algoritmo da utilizzare (il TPI per adesso è l'unico)",
"climate_entity_id": "Entity id del termostato sottostante"
"climate_entity_id": "Entity id del termostato sottostante",
"ac_mode": "Utilizzare la modalità AC (Air Conditioned) ?"
}
},
"tpi": {
@@ -55,7 +57,10 @@
"data": {
"eco_temp": "Temperatura nel preset Eco",
"comfort_temp": "Temperatura nel preset Comfort",
"boost_temp": "Temperatura nel preset Boost"
"boost_temp": "Temperatura nel preset Boost",
"eco_ac_temp": "Temperatura nel preset Eco (AC mode)",
"comfort_ac_temp": "Temperatura nel preset Comfort (AC mode)",
"boost_ac_temp": "Temperatura nel preset Boost (AC mode)"
}
},
"window": {
@@ -102,7 +107,10 @@
"presence_sensor_entity_id": "Entity id sensore presenza (true se è presente qualcuno)",
"eco_away_temp": "Temperatura al preset Eco in caso d'assenza",
"comfort_away_temp": "Temperatura al preset Comfort in caso d'assenza",
"boost_away_temp": "Temperatura al preset Boost in caso d'assenza"
"boost_away_temp": "Temperatura al preset Boost in caso d'assenza",
"eco_ac_away_temp": "Temperatura al preset Eco in caso d'assenza (AC mode)",
"comfort_ac_away_temp": "Temperatura al preset Comfort in caso d'assenza (AC mode)",
"boost_ac_away_temp": "Temperatura al preset Boost in caso d'assenza (AC mode)"
}
},
"advanced": {
@@ -161,7 +169,8 @@
"heater_entity3_id": "Terzo interruttore riscaldatore",
"heater_entity4_id": "Quarto interruttore riscaldatore",
"proportional_function": "Algoritmo",
"climate_entity_id": "Termostato sottostante"
"climate_entity_id": "Termostato sottostante",
"ac_mode": "AC mode ?"
},
"data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -169,7 +178,8 @@
"heater_entity3_id": "Entity id del terzo riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"heater_entity4_id": "Entity id del quarto riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"proportional_function": "Algoritmo da utilizzare (il TPI per adesso è l'unico)",
"climate_entity_id": "Entity id del termostato sottostante"
"climate_entity_id": "Entity id del termostato sottostante",
"ac_mode": "Utilizzare la modalità AC (Air Conditioned) ?"
}
},
"tpi": {
@@ -186,7 +196,10 @@
"data": {
"eco_temp": "Temperatura nel preset Eco",
"comfort_temp": "Temperatura nel preset Comfort",
"boost_temp": "Temperatura nel preset Boost"
"boost_temp": "Temperatura nel preset Boost",
"eco_ac_temp": "Temperatura nel preset Eco (AC mode)",
"comfort_ac_temp": "Temperatura nel preset Comfort (AC mode)",
"boost_ac_temp": "Temperatura nel preset Boost (AC mode)"
}
},
"window": {
@@ -233,7 +246,10 @@
"presence_sensor_entity_id": "Entity id sensore presenza (true se è presente qualcuno)",
"eco_away_temp": "Temperatura al preset Eco in caso d'assenza",
"comfort_away_temp": "Temperatura al preset Comfort in caso d'assenza",
"boost_away_temp": "Temperatura al preset Boost in caso d'assenza"
"boost_away_temp": "Temperatura al preset Boost in caso d'assenza",
"eco_ac_away_temp": "Temperatura al preset Eco in caso d'assenza (AC mode)",
"comfort_ac_away_temp": "Temperatura al preset Comfort in caso d'assenza (AC mode)",
"boost_ac_away_temp": "Temperatura al preset Boost in caso d'assenza (AC mode)"
}
},
"advanced": {
@@ -127,7 +127,7 @@ class UnderlyingEntity:
"""Set the target temperature"""
return
async def remove_entity(self):
def remove_entity(self):
"""Remove the underlying entity"""
return
@@ -167,6 +167,7 @@ class UnderlyingSwitch(UnderlyingEntity):
self._should_relaunch_control_heating = False
self._on_time_sec = 0
self._off_time_sec = 0
self._hvac_mode = None
@property
def initial_delay_sec(self):
@@ -174,12 +175,18 @@ class UnderlyingSwitch(UnderlyingEntity):
return self._initial_delay_sec
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
"""Set the HVACmode. Returns true if we need to redo a control_heating"""
"""Set the HVACmode. Returns true if something have change"""
if hvac_mode == HVACMode.OFF:
if self.is_device_active:
await self.turn_off()
await self._cancel_cycle()
return True
if self._hvac_mode != hvac_mode:
self._hvac_mode = hvac_mode
return True
else:
return False
@property
def is_device_active(self):
@@ -221,7 +228,7 @@ class UnderlyingSwitch(UnderlyingEntity):
if self._async_cancel_cycle is not None:
if force:
_LOGGER.debug("%s - we force a new cycle", self)
await self._cancel_cycle()
self._cancel_cycle()
else:
_LOGGER.debug(
"%s - A previous cycle is alredy running and no force -> waits for its end",
@@ -251,7 +258,7 @@ class UnderlyingSwitch(UnderlyingEntity):
else:
_LOGGER.debug("%s - nothing to do", self)
async def _cancel_cycle(self):
def _cancel_cycle(self):
"""Cancel the cycle"""
if self._async_cancel_cycle:
self._async_cancel_cycle()
@@ -361,9 +368,9 @@ class UnderlyingSwitch(UnderlyingEntity):
# increment energy at the end of the cycle
self._thermostat.incremente_energy()
async def remove_entity(self):
"""Remove the entity"""
await self._cancel_cycle()
def remove_entity(self):
"""Remove the entity after stopping its cycle"""
self._cancel_cycle()
class UnderlyingClimate(UnderlyingEntity):
@@ -421,10 +428,10 @@ class UnderlyingClimate(UnderlyingEntity):
"""True if the underlying climate was found"""
return self._underlying_climate is not None
async def set_hvac_mode(self, hvac_mode: HVACMode):
"""Set the HVACmode of the underlying climate"""
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
"""Set the HVACmode of the underlying climate. Returns true if something have change"""
if not self.is_initialized:
return
return False
data = {ATTR_ENTITY_ID: self._entity_id, "hvac_mode": hvac_mode}
await self._hass.services.async_call(
@@ -433,6 +440,8 @@ class UnderlyingClimate(UnderlyingEntity):
data,
)
return True
@property
def is_device_active(self):
"""If the toggleable device is currently active."""
@@ -572,8 +581,41 @@ class UnderlyingClimate(UnderlyingEntity):
return self._underlying_climate.temperature_unit
@property
def target_temperature_step(self) -> str:
def target_temperature_step(self) -> float:
"""Get the target_temperature_step"""
if not self.is_initialized:
return 1
return self._underlying_climate.target_temperature_step
@property
def target_temperature_high(self) -> float:
"""Get the target_temperature_high"""
if not self.is_initialized:
return 30
return self._underlying_climate.target_temperature_high
@property
def target_temperature_low(self) -> float:
"""Get the target_temperature_low"""
if not self.is_initialized:
return 15
return self._underlying_climate.target_temperature_low
@property
def is_aux_heat(self) -> bool:
"""Get the is_aux_heat"""
if not self.is_initialized:
return False
return self._underlying_climate.is_aux_heat
def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
if not self.is_initialized:
return None
return self._underlying_climate.turn_aux_heat_on()
def turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater on."""
if not self.is_initialized:
return None
return self._underlying_climate.turn_aux_heat_off()