21
README-fr.md
21
README-fr.md
@@ -532,7 +532,7 @@ Ce service permet de forcer l'état de présence indépendamment du capteur de p
|
||||
|
||||
Le code pour appeler ce service est le suivant :
|
||||
```
|
||||
service : thermostat_polyvalent.set_presence
|
||||
service : versatile_thermostat.set_presence
|
||||
Les données:
|
||||
présence : "off"
|
||||
cible:
|
||||
@@ -547,7 +547,7 @@ Vous pouvez modifier l'une ou les deux températures (lorsqu'elles sont présent
|
||||
|
||||
Utilisez le code suivant pour régler la température du préréglage :
|
||||
```
|
||||
service : thermostat_polyvalent.set_preset_temperature
|
||||
service : versatile_thermostat.set_preset_temperature
|
||||
date:
|
||||
preset : boost
|
||||
temperature : 17,8
|
||||
@@ -576,8 +576,8 @@ Si le thermostat est en mode ``security`` les nouveaux paramètres sont appliqu
|
||||
|
||||
Pour changer les paramètres de sécurité utilisez le code suivant :
|
||||
```
|
||||
service : thermostat_polyvalent.set_security
|
||||
date:
|
||||
service : versatile_thermostat.set_security
|
||||
data:
|
||||
min_on_percent: "0.5"
|
||||
default_on_percent: "0.1"
|
||||
delay_min: 60
|
||||
@@ -585,6 +585,19 @@ target:
|
||||
entity_id : climate.my_thermostat
|
||||
```
|
||||
|
||||
## ByPass Window Check
|
||||
Ce service permet d'activer ou non un bypass de la vérification des fenetres.
|
||||
Il permet de continuer à chauffer même si la fenetre est detectée ouverte.
|
||||
Mis à ``true`` les modifications de status de la fenetre n'auront plus d'effet sur le thermostat, remis à ``false`` cela s'assurera de désactiver le thermostat si la fenetre est toujours ouverte.
|
||||
|
||||
Pour changer le paramètre de bypass utilisez le code suivant :
|
||||
```
|
||||
service : versatile_thermostat.set_window_bypass
|
||||
data:
|
||||
window_bypass: true
|
||||
target:
|
||||
entity_id : climate.my_thermostat
|
||||
|
||||
# Notifications
|
||||
Les évènements marquant du thermostat sont notifiés par l'intermédiaire du bus de message.
|
||||
Les évènements notifiés sont les suivants:
|
||||
|
||||
13
README.md
13
README.md
@@ -564,13 +564,24 @@ If the thermostat is in ``security`` mode the new settings are applied immediate
|
||||
To change the security settings use the following code:
|
||||
```
|
||||
service : thermostat_polyvalent.set_security
|
||||
date:
|
||||
data:
|
||||
min_on_percent: "0.5"
|
||||
default_on_percent: "0.1"
|
||||
delay_min: 60
|
||||
target:
|
||||
entity_id : climate.my_thermostat
|
||||
```
|
||||
## ByPass Window Check
|
||||
This service is used to bypass the window check implemented to stop thermostat when an open window is detected.
|
||||
When set to ``true`` window event won't have any effect on the thermostat, when set back to ``false`` it will make sure to disable the thermostat if window is still open.
|
||||
|
||||
To change the bypass setting use the following code:
|
||||
```
|
||||
service : thermostat_polyvalent.set_window_bypass
|
||||
data:
|
||||
window_bypass: true
|
||||
target:
|
||||
entity_id : climate.my_thermostat
|
||||
|
||||
# Notifications
|
||||
Significant thermostat events are notified via the message bus.
|
||||
|
||||
@@ -130,6 +130,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
_motion_state: bool
|
||||
_presence_state: bool
|
||||
_window_auto_state: bool
|
||||
#PR - Adding Window ByPass
|
||||
_window_bypass_state: bool
|
||||
_underlyings: list[UnderlyingEntity]
|
||||
_last_change_time: datetime
|
||||
|
||||
@@ -229,6 +231,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
self._window_auto_state = False
|
||||
self._window_auto_on = False
|
||||
self._window_auto_algo = None
|
||||
# PR - Adding Window ByPass
|
||||
self._window_bypass_state = False
|
||||
|
||||
self._current_tz = dt_util.get_time_zone(self._hass.config.time_zone)
|
||||
|
||||
@@ -961,6 +965,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
"""Get the window_auto_state"""
|
||||
return STATE_ON if self._window_auto_state else STATE_OFF
|
||||
|
||||
#PR - Adding Window ByPass
|
||||
@property
|
||||
def window_bypass_state(self) -> bool | None:
|
||||
"""Get the Window Bypass"""
|
||||
return self._window_bypass_state
|
||||
|
||||
@property
|
||||
def security_state(self) -> bool | None:
|
||||
"""Get the security_state"""
|
||||
@@ -1308,7 +1318,16 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
_LOGGER.debug("%s - no change in window state. Forget the event")
|
||||
return
|
||||
|
||||
|
||||
self._window_state = new_state.state
|
||||
|
||||
#PR - Adding Window ByPass
|
||||
_LOGGER.debug("%s - Window ByPass is : %s", self, self._window_bypass_state)
|
||||
if self._window_bypass_state:
|
||||
_LOGGER.info("Window ByPass is activated. Ignore window event")
|
||||
self.update_custom_attributes()
|
||||
return
|
||||
|
||||
if self._window_state == STATE_OFF:
|
||||
_LOGGER.info(
|
||||
"%s - Window is closed. Restoring hvac_mode '%s'",
|
||||
@@ -2107,6 +2126,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
"overpowering_state": self._overpowering_state,
|
||||
"presence_state": self._presence_state,
|
||||
"window_auto_state": self._window_auto_state,
|
||||
#PR - Adding Window ByPass
|
||||
"window_bypass_state": self._window_bypass_state,
|
||||
"security_delay_min": self._security_delay_min,
|
||||
"security_min_on_percent": self._security_min_on_percent,
|
||||
"security_default_on_percent": self._security_default_on_percent,
|
||||
@@ -2224,6 +2245,26 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
await self.async_control_heating()
|
||||
self.update_custom_attributes()
|
||||
|
||||
#PR - Adding Window ByPass
|
||||
async def service_set_window_bypass_state(self, window_bypass):
|
||||
"""Called by a service call:
|
||||
service: versatile_thermostat.set_window_bypass
|
||||
data:
|
||||
window_bypass: True
|
||||
target:
|
||||
entity_id: climate.thermostat_1
|
||||
"""
|
||||
_LOGGER.info("%s - Calling service_set_window_bypass, window_bypass: %s", self, window_bypass)
|
||||
self._window_bypass_state = window_bypass
|
||||
if not self._window_bypass_state and self._window_state == STATE_ON:
|
||||
_LOGGER.info("%s - Last window state was open & ByPass is now off. Set hvac_mode to '%s'", self, HVACMode.OFF)
|
||||
self.save_hvac_mode()
|
||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||
if self._window_bypass_state and self._window_state == STATE_ON:
|
||||
_LOGGER.info("%s - Last window state was open & ByPass is now on. Set hvac_mode to last available mode", self)
|
||||
await self.restore_hvac_mode(True)
|
||||
self.update_custom_attributes()
|
||||
|
||||
def send_event(self, event_type: EventType, data: dict):
|
||||
"""Send an event"""
|
||||
_LOGGER.info("%s - Sending event %s with data: %s", self, event_type, data)
|
||||
|
||||
@@ -38,7 +38,7 @@ async def async_setup_entry(
|
||||
unique_id = entry.entry_id
|
||||
name = entry.data.get(CONF_NAME)
|
||||
|
||||
entities = [SecurityBinarySensor(hass, unique_id, name, entry.data)]
|
||||
entities = [SecurityBinarySensor(hass, unique_id, name, entry.data),WindowByPassBinarySensor(hass, unique_id, name, entry.data)]
|
||||
if entry.data.get(CONF_USE_MOTION_FEATURE):
|
||||
entities.append(MotionBinarySensor(hass, unique_id, name, entry.data))
|
||||
if entry.data.get(CONF_USE_WINDOW_FEATURE):
|
||||
@@ -238,3 +238,38 @@ class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
||||
return "mdi:home-account"
|
||||
else:
|
||||
return "mdi:nature-people"
|
||||
|
||||
#PR - Adding Window ByPass
|
||||
class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
||||
"""Representation of a BinarySensor which exposes the Window ByPass state"""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, unique_id, name, entry_infos
|
||||
) -> None: # pylint: disable=unused-argument
|
||||
"""Initialize the WindowByPass Binary sensor"""
|
||||
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
||||
self._attr_name = "Window bypass"
|
||||
self._attr_unique_id = f"{self._device_name}_window_bypass_state"
|
||||
self._attr_is_on = False
|
||||
|
||||
@callback
|
||||
async def async_my_climate_changed(self, event: Event = None):
|
||||
"""Called when my climate have change"""
|
||||
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
|
||||
old_state = self._attr_is_on
|
||||
if self.my_climate.window_bypass_state in [True, False]:
|
||||
self._attr_is_on = self.my_climate.window_bypass_state
|
||||
if old_state != self._attr_is_on:
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
|
||||
@property
|
||||
def device_class(self) -> BinarySensorDeviceClass | None:
|
||||
return BinarySensorDeviceClass.RUNNING
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
if self._attr_is_on:
|
||||
return "mdi:window-shutter-cog"
|
||||
else:
|
||||
return "mdi:window-shutter-auto"
|
||||
@@ -24,6 +24,8 @@ from .const import (
|
||||
SERVICE_SET_PRESENCE,
|
||||
SERVICE_SET_PRESET_TEMPERATURE,
|
||||
SERVICE_SET_SECURITY,
|
||||
#PR - Adding Window ByPass
|
||||
SERVICE_SET_WINDOW_BYPASS,
|
||||
CONF_THERMOSTAT_TYPE,
|
||||
CONF_THERMOSTAT_SWITCH,
|
||||
CONF_THERMOSTAT_CLIMATE,
|
||||
@@ -98,3 +100,13 @@ async def async_setup_entry(
|
||||
},
|
||||
"service_set_security",
|
||||
)
|
||||
|
||||
#PR - Adding Window ByPass
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_WINDOW_BYPASS,
|
||||
{
|
||||
vol.Required("window_bypass"): vol.In([True, False]
|
||||
),
|
||||
},
|
||||
"service_set_window_bypass_state",
|
||||
)
|
||||
|
||||
@@ -202,6 +202,8 @@ SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
SERVICE_SET_PRESENCE = "set_presence"
|
||||
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
|
||||
SERVICE_SET_SECURITY = "set_security"
|
||||
#PR - Adding Window ByPass
|
||||
SERVICE_SET_WINDOW_BYPASS = "set_window_bypass"
|
||||
|
||||
DEFAULT_SECURITY_MIN_ON_PERCENT = 0.5
|
||||
DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1
|
||||
|
||||
@@ -122,3 +122,19 @@ set_security:
|
||||
step: 0.05
|
||||
unit_of_measurement: "%"
|
||||
mode: slider
|
||||
|
||||
set_window_bypass:
|
||||
name: Set Window ByPass
|
||||
description: Bypass the window state to enable heating with window open.
|
||||
target:
|
||||
entity:
|
||||
integration: versatile_thermostat
|
||||
fields:
|
||||
window_bypass:
|
||||
name: Window ByPass
|
||||
description: ByPass value
|
||||
required: true
|
||||
advanced: false
|
||||
default: true
|
||||
selector:
|
||||
boolean:
|
||||
@@ -671,3 +671,136 @@ async def test_window_auto_no_on_percent(
|
||||
|
||||
# Clean the entity
|
||||
entity.remove_thermostat()
|
||||
|
||||
#PR - Adding Window Bypass
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_window_bypass(
|
||||
hass: HomeAssistant, skip_hass_states_is_state
|
||||
):
|
||||
"""Test the Window management when bypass enabled"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverSwitchMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverSwitchMockName",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
"comfort_temp": 18,
|
||||
"boost_temp": 19,
|
||||
CONF_USE_WINDOW_FEATURE: True,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_HEATER: "switch.mock_switch",
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.01,
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SECURITY_DELAY_MIN: 5,
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
||||
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
|
||||
},
|
||||
)
|
||||
|
||||
entity: VersatileThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theoverswitchmockname"
|
||||
)
|
||||
assert entity
|
||||
|
||||
tpi_algo = entity._prop_algorithm
|
||||
assert tpi_algo
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.overpowering_state is None
|
||||
assert entity.target_temperature == 19
|
||||
|
||||
assert entity.window_state is None
|
||||
|
||||
# change temperature to force turning on the heater
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=False,
|
||||
):
|
||||
await send_temperature_change_event(entity, 15, datetime.now())
|
||||
|
||||
# Heater shoud turn-on
|
||||
assert mock_heater_on.call_count >= 1
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
#Set Window ByPass to true
|
||||
entity._window_bypass_state = True
|
||||
|
||||
# Open the window, condition of time is satisfied, check the thermostat and heater turns off
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=True,
|
||||
):
|
||||
await send_window_change_event(entity, True, False, datetime.now())
|
||||
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# Heater should not be on
|
||||
assert mock_heater_on.call_count == 0
|
||||
# One call in set_hvac_mode turn_off and one call in the control_heating for security
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert mock_condition.call_count == 1
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.window_state == STATE_ON
|
||||
|
||||
# Close the window
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=False,
|
||||
):
|
||||
try_function = await send_window_change_event(
|
||||
entity, False, True, datetime.now(), sleep=False
|
||||
)
|
||||
|
||||
await try_function(None)
|
||||
|
||||
# Wait for initial delay of heater
|
||||
await asyncio.sleep(0.3)
|
||||
|
||||
assert entity.window_state == STATE_OFF
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_send_event.call_count == 0
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
|
||||
# Clean the entity
|
||||
entity.remove_thermostat()
|
||||
Reference in New Issue
Block a user