Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4a719a660 | |||
| ea0d66f0c6 | |||
| 641801084d | |||
| 1576e9c189 | |||
| 313fa26160 | |||
| 832ea78a12 | |||
| 5f167af331 | |||
| c20e641ac1 | |||
| 87064882a3 | |||
| 1c0a7cb4e8 | |||
| b1f2fcd66d |
@@ -191,6 +191,9 @@ input_boolean:
|
|||||||
fake_valve_sonoff_trvzb2:
|
fake_valve_sonoff_trvzb2:
|
||||||
name: Valve Sonoff TRVZB2
|
name: Valve Sonoff TRVZB2
|
||||||
icon: mdi:valve
|
icon: mdi:valve
|
||||||
|
fake_inversed_heater:
|
||||||
|
name: Inversed Heater
|
||||||
|
icon: mdi:radiator-off
|
||||||
|
|
||||||
climate:
|
climate:
|
||||||
- platform: generic_thermostat
|
- platform: generic_thermostat
|
||||||
@@ -314,6 +317,21 @@ switch:
|
|||||||
option: comfort-2
|
option: comfort-2
|
||||||
target:
|
target:
|
||||||
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
|
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
|
||||||
|
- platform: template
|
||||||
|
switches:
|
||||||
|
fake_inversed_switch:
|
||||||
|
friendly_name: "A fake inversed switch"
|
||||||
|
value_template: "{{ is_state('input_boolean.fake_inversed_heater', 'on') }}"
|
||||||
|
turn_on:
|
||||||
|
action: input_boolean.turn_on
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: input_boolean.fake_inversed_heater
|
||||||
|
turn_off:
|
||||||
|
action: input_boolean.turn_off
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: input_boolean.fake_inversed_heater
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
extra_module_url:
|
extra_module_url:
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ class FeatureWindowManager(BaseFeatureManager):
|
|||||||
{
|
{
|
||||||
"window_sensor_entity_id",
|
"window_sensor_entity_id",
|
||||||
"is_window_configured",
|
"is_window_configured",
|
||||||
"is_window_bypass",
|
|
||||||
"window_delay_sec",
|
"window_delay_sec",
|
||||||
"window_off_delay_sec",
|
"window_off_delay_sec",
|
||||||
"window_auto_configured",
|
"window_auto_configured",
|
||||||
@@ -130,6 +129,11 @@ class FeatureWindowManager(BaseFeatureManager):
|
|||||||
@overrides
|
@overrides
|
||||||
async def start_listening(self):
|
async def start_listening(self):
|
||||||
"""Start listening the underlying entity"""
|
"""Start listening the underlying entity"""
|
||||||
|
|
||||||
|
# Try to get last window bypass state
|
||||||
|
old_state = await self._vtherm.async_get_last_state()
|
||||||
|
self._is_window_bypass = True if old_state and old_state.attributes and old_state.attributes.get("is_window_bypass") is True else False
|
||||||
|
|
||||||
if self._is_configured:
|
if self._is_configured:
|
||||||
self.stop_listening()
|
self.stop_listening()
|
||||||
if self._window_sensor_entity_id:
|
if self._window_sensor_entity_id:
|
||||||
@@ -449,7 +453,9 @@ class FeatureWindowManager(BaseFeatureManager):
|
|||||||
"""Set the window bypass flag
|
"""Set the window bypass flag
|
||||||
Return True if state have been changed"""
|
Return True if state have been changed"""
|
||||||
self._is_window_bypass = window_bypass
|
self._is_window_bypass = window_bypass
|
||||||
if not self._is_window_bypass and self._window_state:
|
|
||||||
|
if self._window_state == STATE_ON:
|
||||||
|
if not self._is_window_bypass:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - Last window state was open & ByPass is now off. Set hvac_mode to '%s'",
|
"%s - Last window state was open & ByPass is now off. Set hvac_mode to '%s'",
|
||||||
self,
|
self,
|
||||||
@@ -457,9 +463,8 @@ class FeatureWindowManager(BaseFeatureManager):
|
|||||||
)
|
)
|
||||||
self._vtherm.save_hvac_mode()
|
self._vtherm.save_hvac_mode()
|
||||||
await self._vtherm.async_set_hvac_mode(HVACMode.OFF)
|
await self._vtherm.async_set_hvac_mode(HVACMode.OFF)
|
||||||
return True
|
|
||||||
|
|
||||||
if self._is_window_bypass and self._window_state:
|
if self._is_window_bypass:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - Last window state was open & ByPass is now on. Set hvac_mode to last available mode",
|
"%s - Last window state was open & ByPass is now on. Set hvac_mode to last available mode",
|
||||||
self,
|
self,
|
||||||
@@ -504,10 +509,10 @@ class FeatureWindowManager(BaseFeatureManager):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_window_detected(self) -> bool:
|
def is_window_detected(self) -> bool:
|
||||||
"""Return true if the presence is configured and presence sensor is OFF"""
|
"""Return true if the window is configured and open and bypass is not ON"""
|
||||||
return self._is_configured and (
|
return self._is_configured and (
|
||||||
self._window_state == STATE_ON or self._window_auto_state == STATE_ON
|
self._window_state == STATE_ON or self._window_auto_state == STATE_ON
|
||||||
)
|
) and not self._is_window_bypass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def window_sensor_entity_id(self) -> bool:
|
def window_sensor_entity_id(self) -> bool:
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"ssdp": [],
|
"ssdp": [],
|
||||||
"version": "7.2.0",
|
"version": "7.2.1",
|
||||||
"zeroconf": []
|
"zeroconf": []
|
||||||
}
|
}
|
||||||
@@ -625,6 +625,10 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
changes = False
|
changes = False
|
||||||
new_hvac_mode = new_state.state
|
new_hvac_mode = new_state.state
|
||||||
|
|
||||||
|
# Issue #903 - patch AUTO mode
|
||||||
|
if new_hvac_mode == HVACMode.AUTO:
|
||||||
|
new_hvac_mode = HVACMode.HEAT if not self.ac_mode else HVACMode.COOL
|
||||||
|
|
||||||
old_state = event.data.get("old_state")
|
old_state = event.data.get("old_state")
|
||||||
|
|
||||||
# Issue #829 - refresh underlying command if it comes back to life
|
# Issue #829 - refresh underlying command if it comes back to life
|
||||||
|
|||||||
@@ -281,8 +281,8 @@ class UnderlyingSwitch(UnderlyingEntity):
|
|||||||
# not self.is_inversed and real_state
|
# not self.is_inversed and real_state
|
||||||
# )
|
# )
|
||||||
is_on = self._hass.states.is_state(self._entity_id, self._on_command.get("state"))
|
is_on = self._hass.states.is_state(self._entity_id, self._on_command.get("state"))
|
||||||
if self.is_inversed:
|
# if self.is_inversed:
|
||||||
return not is_on
|
# return not is_on
|
||||||
|
|
||||||
return is_on
|
return is_on
|
||||||
|
|
||||||
|
|||||||
@@ -72,11 +72,11 @@ Then, specify the on and off commands using the format `command[/attribute[:valu
|
|||||||
The available commands depend on the type of underlying device:
|
The available commands depend on the type of underlying device:
|
||||||
|
|
||||||
| Underlying Device Type | Possible On Commands | Possible Off Commands | Applies To |
|
| Underlying Device Type | Possible On Commands | Possible Off Commands | Applies To |
|
||||||
| --------------------------- | ------------------------------------- | ----------------------------------- | ----------------------------- |
|
| --------------------------- | ------------------------------------- | ---------------------------------------------- | ----------------------------- |
|
||||||
| `switch` or `input_boolean` | `turn_on` | `turn_off` | All switches |
|
| `switch` or `input_boolean` | `turn_on` | `turn_off` | All switches |
|
||||||
| `select` or `input_select` | `select_option/option:comfort` | `set_option/option:frost` | Nodon SIN-4-FP-21 and similar |
|
| `select` or `input_select` | `select_option/option:comfort` | `select_option/option:frost_protection` | Nodon SIN-4-FP-21 and similar |
|
||||||
| `climate` (hvac_mode) | `set_hvac_mode/hvac_mode:heat` | `set_hvac_mode/hvac_mode:off` | eCosy (via Tuya Local) |
|
| `climate` (hvac_mode) | `set_hvac_mode/hvac_mode:heat` | `set_hvac_mode/hvac_mode:off` | eCosy (via Tuya Local) |
|
||||||
| `climate` (preset) | `set_preset_mode/preset_mode:comfort` | `set_preset_mode/preset_mode:frost` | Heatzy |
|
| `climate` (preset) | `set_preset_mode/preset_mode:comfort` | `set_preset_mode/preset_mode:frost_protection` | Heatzy |
|
||||||
|
|
||||||
Of course, these examples can be adapted to your specific case.
|
Of course, these examples can be adapted to your specific case.
|
||||||
|
|
||||||
|
|||||||
@@ -70,11 +70,11 @@ et donner la commande d'allumage et d'exinction avec le format `commande[/attrib
|
|||||||
Les commandes possibles dépendent du type de sous-jacents :
|
Les commandes possibles dépendent du type de sous-jacents :
|
||||||
|
|
||||||
| type de sous-jacent | commandes d'allumage possibles | commandes d'extinction possibles | S'applique à |
|
| type de sous-jacent | commandes d'allumage possibles | commandes d'extinction possibles | S'applique à |
|
||||||
| --------------------------- | ------------------------------------- | ----------------------------------- | ------------------------------ |
|
| --------------------------- | ------------------------------------- | ---------------------------------------------- | ------------------------------ |
|
||||||
| `switch` ou `input_boolean` | `turn_on` | `turn_off` | tous les switchs |
|
| `switch` ou `input_boolean` | `turn_on` | `turn_off` | tous les switchs |
|
||||||
| `select` ou `input_select` | `select_option/option:comfort` | `set_option/option:frost` | Nodon SIN-4-FP-21 et assimilés |
|
| `select` ou `input_select` | `select_option/option:comfort` | `select_option/option:frost_protection` | Nodon SIN-4-FP-21 et assimilés |
|
||||||
| `climate` (hvac_mode) | `set_hvac_mode/hvac_mode:heat` | `set_hvac_mode/hvac_mode:off` | eCosy (via Tuya Local) |
|
| `climate` (hvac_mode) | `set_hvac_mode/hvac_mode:heat` | `set_hvac_mode/hvac_mode:off` | eCosy (via Tuya Local) |
|
||||||
| `climate` (preset) | `set_preset_mode/preset_mode:comfort` | `set_preset_mode/preset_mode:frost` | Heatzy |
|
| `climate` (preset) | `set_preset_mode/preset_mode:comfort` | `set_preset_mode/preset_mode:frost_protection` | Heatzy |
|
||||||
|
|
||||||
Evidemment, tous ces exemples peuvent être adaptés à votre cas.
|
Evidemment, tous ces exemples peuvent être adaptés à votre cas.
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
homeassistant==2025.1.2
|
homeassistant==2025.1.4
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ logging.getLogger().setLevel(logging.DEBUG)
|
|||||||
async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
|
async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
|
||||||
"""Test the Window auto management"""
|
"""Test the Window auto management"""
|
||||||
|
|
||||||
|
temps = {
|
||||||
|
"eco": 17,
|
||||||
|
"comfort": 18,
|
||||||
|
"boost": 21,
|
||||||
|
}
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="TheOverSwitchMockName",
|
title="TheOverSwitchMockName",
|
||||||
@@ -30,14 +36,11 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
CONF_CYCLE_MIN: 5,
|
CONF_CYCLE_MIN: 5,
|
||||||
CONF_TEMP_MIN: 15,
|
CONF_TEMP_MIN: 15,
|
||||||
CONF_TEMP_MAX: 30,
|
CONF_TEMP_MAX: 30,
|
||||||
"eco_temp": 17,
|
|
||||||
"comfort_temp": 18,
|
|
||||||
"boost_temp": 21,
|
|
||||||
CONF_USE_WINDOW_FEATURE: False,
|
CONF_USE_WINDOW_FEATURE: False,
|
||||||
CONF_USE_MOTION_FEATURE: False,
|
CONF_USE_MOTION_FEATURE: False,
|
||||||
CONF_USE_POWER_FEATURE: False,
|
CONF_USE_POWER_FEATURE: False,
|
||||||
CONF_USE_PRESENCE_FEATURE: False,
|
CONF_USE_PRESENCE_FEATURE: False,
|
||||||
CONF_HEATER: "switch.mock_switch",
|
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
||||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||||
CONF_TPI_COEF_INT: 0.3,
|
CONF_TPI_COEF_INT: 0.3,
|
||||||
CONF_TPI_COEF_EXT: 0.01,
|
CONF_TPI_COEF_EXT: 0.01,
|
||||||
@@ -51,14 +54,12 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
# 0. Create the entity
|
||||||
"homeassistant.core.ServiceRegistry.async_call"
|
|
||||||
) as mock_service_call, patch(
|
with patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call, patch(
|
||||||
"homeassistant.core.StateMachine.is_state", return_value=True # switch is On
|
"homeassistant.core.StateMachine.is_state", return_value=False # switch is On so is_state(switch, 'off') is False
|
||||||
):
|
):
|
||||||
entity: ThermostatOverSwitch = await create_thermostat(
|
entity: ThermostatOverSwitch = await create_thermostat(hass, entry, "climate.theoverswitchmockname", temps)
|
||||||
hass, entry, "climate.theoverswitchmockname"
|
|
||||||
)
|
|
||||||
assert entity
|
assert entity
|
||||||
assert entity.is_inversed
|
assert entity.is_inversed
|
||||||
|
|
||||||
@@ -78,12 +79,10 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
assert mock_service_call.call_count == 0
|
assert mock_service_call.call_count == 0
|
||||||
|
|
||||||
# 1. Make the temperature down to activate the switch
|
# 1. Make the temperature down to activate the switch
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"), patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
), patch(
|
|
||||||
"homeassistant.core.ServiceRegistry.async_call"
|
"homeassistant.core.ServiceRegistry.async_call"
|
||||||
) as mock_service_call, patch(
|
) as mock_service_call, patch(
|
||||||
"homeassistant.core.StateMachine.is_state", return_value=True # switch is Off
|
"homeassistant.core.StateMachine.is_state", return_value=True # switch is Off so is_state(switch, 'off') is True
|
||||||
):
|
):
|
||||||
event_timestamp = now - timedelta(minutes=4)
|
event_timestamp = now - timedelta(minutes=4)
|
||||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||||
|
|||||||
@@ -700,7 +700,7 @@ async def test_add_number_for_over_switch_use_central_presets_and_restore(
|
|||||||
assert vtherm.use_central_config_temperature is True
|
assert vtherm.use_central_config_temperature is True
|
||||||
|
|
||||||
# We should try to restore all 4 temp entities and the VTherm itself
|
# We should try to restore all 4 temp entities and the VTherm itself
|
||||||
assert mock_restore_state.call_count == 4 + 1
|
assert mock_restore_state.call_count == 4 + 2
|
||||||
|
|
||||||
# 1. We search for NumberEntities
|
# 1. We search for NumberEntities
|
||||||
for preset_name, value in temps.items():
|
for preset_name, value in temps.items():
|
||||||
|
|||||||
@@ -71,6 +71,19 @@ from .commons import *
|
|||||||
HVACMode.HEAT,
|
HVACMode.HEAT,
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
|
# Inversed switch without command personnalisations
|
||||||
|
(
|
||||||
|
True,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"turn_off",
|
||||||
|
{"entity_id": "switch.test"},
|
||||||
|
STATE_OFF,
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "switch.test"},
|
||||||
|
STATE_ON,
|
||||||
|
True,
|
||||||
|
),
|
||||||
# Error cases invalid command
|
# Error cases invalid command
|
||||||
(
|
(
|
||||||
False,
|
False,
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ async def test_window_feature_manager_refresh_sensor_action_frost_only(
|
|||||||
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)) as mock_get_state:
|
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)) as mock_get_state:
|
||||||
# fmt:on
|
# fmt:on
|
||||||
# Configurer les méthodes mockées
|
# Configurer les méthodes mockées
|
||||||
fake_vtherm.save_target_temp = AsyncMock()
|
fake_vtherm.save_target_temp = MagicMock()
|
||||||
fake_vtherm.set_hvac_off_reason = MagicMock()
|
fake_vtherm.set_hvac_off_reason = MagicMock()
|
||||||
fake_vtherm.restore_target_temp = AsyncMock()
|
fake_vtherm.restore_target_temp = AsyncMock()
|
||||||
fake_vtherm.change_target_temperature = AsyncMock()
|
fake_vtherm.change_target_temperature = AsyncMock()
|
||||||
@@ -542,7 +542,7 @@ async def test_window_feature_manager_event_sensor_action_frost_only(
|
|||||||
with patch("homeassistant.helpers.condition.state", return_value=long_enough):
|
with patch("homeassistant.helpers.condition.state", return_value=long_enough):
|
||||||
# fmt:on
|
# fmt:on
|
||||||
# Configurer les méthodes mockées
|
# Configurer les méthodes mockées
|
||||||
fake_vtherm.save_target_temp = AsyncMock()
|
fake_vtherm.save_target_temp = MagicMock()
|
||||||
fake_vtherm.set_hvac_off_reason = MagicMock()
|
fake_vtherm.set_hvac_off_reason = MagicMock()
|
||||||
fake_vtherm.restore_target_temp = AsyncMock()
|
fake_vtherm.restore_target_temp = AsyncMock()
|
||||||
fake_vtherm.change_target_temperature = AsyncMock()
|
fake_vtherm.change_target_temperature = AsyncMock()
|
||||||
|
|||||||
Reference in New Issue
Block a user