Compare commits
4 Commits
7.2.0beta1
...
7.2.0beta2
| Author | SHA1 | Date | |
|---|---|---|---|
| d4072ee8f8 | |||
| 85e6b40e66 | |||
| 7ef94dac7f | |||
| 22a3b646aa |
@@ -37,7 +37,9 @@
|
||||
"yzhang.markdown-all-in-one",
|
||||
"github.vscode-github-actions",
|
||||
"azuretools.vscode-docker",
|
||||
"huizhou.githd"
|
||||
"huizhou.githd",
|
||||
"github.copilot",
|
||||
"github.copilot-chat"
|
||||
],
|
||||
"settings": {
|
||||
"files.eol": "\n",
|
||||
|
||||
@@ -81,6 +81,8 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
max_on_percent=self._max_on_percent,
|
||||
)
|
||||
|
||||
self._is_inversed = config_entry.get(CONF_INVERSE_SWITCH) is True
|
||||
|
||||
lst_switches = config_entry.get(CONF_UNDERLYING_LIST)
|
||||
self._lst_vswitch_on = config_entry.get(CONF_VSWITCH_ON_CMD_LIST, [])
|
||||
self._lst_vswitch_off = config_entry.get(CONF_VSWITCH_OFF_CMD_LIST, [])
|
||||
@@ -101,7 +103,6 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
)
|
||||
)
|
||||
|
||||
self._is_inversed = config_entry.get(CONF_INVERSE_SWITCH) is True
|
||||
self._should_relaunch_control_heating = False
|
||||
|
||||
@overrides
|
||||
|
||||
@@ -228,8 +228,8 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
self._on_time_sec = 0
|
||||
self._off_time_sec = 0
|
||||
self._keep_alive = IntervalCaller(hass, keep_alive_sec)
|
||||
self._vswitch_on = vswitch_on
|
||||
self._vswitch_off = vswitch_off
|
||||
self._vswitch_on = vswitch_on.strip() if vswitch_on else None
|
||||
self._vswitch_off = vswitch_off.strip() if vswitch_off else None
|
||||
self._domain = self._entity_id.split(".")[0]
|
||||
# build command
|
||||
command, data, state_on = self.build_command(use_on=True)
|
||||
@@ -244,7 +244,7 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
|
||||
@overrides
|
||||
@property
|
||||
def is_inversed(self):
|
||||
def is_inversed(self) -> bool:
|
||||
"""Tells if the switch command should be inversed"""
|
||||
return self._thermostat.is_inversed
|
||||
|
||||
@@ -280,7 +280,11 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
# return (self.is_inversed and not real_state) or (
|
||||
# not self.is_inversed and real_state
|
||||
# )
|
||||
return 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:
|
||||
return not is_on
|
||||
|
||||
return is_on
|
||||
|
||||
async def _keep_alive_callback(self):
|
||||
"""Keep alive: Turn on if already turned on, turn off if already turned off."""
|
||||
@@ -312,9 +316,10 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
|
||||
value = None
|
||||
data = {ATTR_ENTITY_ID: self._entity_id}
|
||||
vswitch = self._vswitch_on if use_on and not self.is_inversed else self._vswitch_off
|
||||
take_on = (use_on and not self.is_inversed) or (not use_on and self.is_inversed)
|
||||
vswitch = self._vswitch_on if take_on else self._vswitch_off
|
||||
if vswitch:
|
||||
pattern = r"^(?P<command>[^/]+)(?:/(?P<argument>[^:]+)(?::(?P<value>.*))?)?$"
|
||||
pattern = r"^(?P<command>[^\s/]+)(?:/(?P<argument>[^\s:]+)(?::(?P<value>[^\s]+))?)?$"
|
||||
match = re.match(pattern, vswitch)
|
||||
|
||||
if match:
|
||||
@@ -322,13 +327,16 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
command = match.group("command")
|
||||
argument = match.group("argument")
|
||||
value = match.group("value")
|
||||
data.update({argument: value})
|
||||
if argument is not None and value is not None:
|
||||
data.update({argument: value})
|
||||
else:
|
||||
raise ValueError(f"Invalid input format: {vswitch}")
|
||||
raise ValueError(f"Invalid input format: {vswitch}. Must be conform to 'command[/argument[:value]]'")
|
||||
|
||||
else:
|
||||
command = SERVICE_TURN_ON if use_on and not self.is_inversed else SERVICE_TURN_OFF
|
||||
value = STATE_ON if use_on and not self.is_inversed else STATE_OFF
|
||||
command = SERVICE_TURN_ON if take_on else SERVICE_TURN_OFF
|
||||
|
||||
if value is None:
|
||||
value = STATE_ON if take_on else STATE_OFF
|
||||
|
||||
return command, data, value
|
||||
|
||||
|
||||
@@ -624,6 +624,7 @@ async def test_security_over_climate(
|
||||
assert entity._saved_preset_mode == "none"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_migration_security_safety(
|
||||
hass: HomeAssistant,
|
||||
skip_hass_states_is_state,
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
""" Test of virtual switch """
|
||||
|
||||
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, line-too-long
|
||||
|
||||
from unittest.mock import patch, MagicMock, PropertyMock
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
|
||||
from custom_components.versatile_thermostat.underlyings import UnderlyingSwitch
|
||||
from custom_components.versatile_thermostat.thermostat_switch import ThermostatOverSwitch
|
||||
from .commons import *
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"is_inversed, vswitch_on_command, vswitch_off_command, expected_command_on, expected_data_on, expected_state_on, expected_command_off, expected_data_off, expected_state_off, is_ok",
|
||||
[
|
||||
# Select (with stripping - trim)
|
||||
(
|
||||
False,
|
||||
" select_option/option:comfort ",
|
||||
" select_option/option:frost ",
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "comfort"},
|
||||
PRESET_COMFORT,
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "frost"},
|
||||
PRESET_FROST_PROTECTION,
|
||||
True,
|
||||
),
|
||||
# Inversed Select
|
||||
(
|
||||
True,
|
||||
"select_option/option:comfort",
|
||||
"select_option/option:eco",
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "eco"},
|
||||
PRESET_ECO,
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "comfort"},
|
||||
PRESET_COMFORT,
|
||||
True,
|
||||
),
|
||||
# switch
|
||||
(False, "turn_on", "turn_off", "turn_on", {"entity_id": "switch.test"}, STATE_ON, "turn_off", {"entity_id": "switch.test"}, STATE_OFF, True),
|
||||
# inversed switch
|
||||
(True, "turn_on", "turn_off", "turn_off", {"entity_id": "switch.test"}, STATE_OFF, "turn_on", {"entity_id": "switch.test"}, STATE_ON, True),
|
||||
# Climate
|
||||
(
|
||||
False,
|
||||
"set_hvac_mode/hvac_mode:heat",
|
||||
"set_hvac_mode/hvac_mode:off",
|
||||
"set_hvac_mode",
|
||||
{"entity_id": "switch.test", "hvac_mode": "heat"},
|
||||
HVACMode.HEAT,
|
||||
"set_hvac_mode",
|
||||
{"entity_id": "switch.test", "hvac_mode": "off"},
|
||||
HVACMode.OFF,
|
||||
True,
|
||||
),
|
||||
# Inversed Climate
|
||||
(
|
||||
True,
|
||||
"set_hvac_mode/hvac_mode:heat",
|
||||
"set_hvac_mode/hvac_mode:off",
|
||||
"set_hvac_mode",
|
||||
{"entity_id": "switch.test", "hvac_mode": "off"},
|
||||
HVACMode.OFF,
|
||||
"set_hvac_mode",
|
||||
{"entity_id": "switch.test", "hvac_mode": "heat"},
|
||||
HVACMode.HEAT,
|
||||
True,
|
||||
),
|
||||
# Error cases invalid command
|
||||
(
|
||||
False,
|
||||
"select_ option/option:comfort", # whitespace
|
||||
"select_option/option:frost",
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "comfort"},
|
||||
PRESET_COMFORT,
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "frost"},
|
||||
PRESET_FROST_PROTECTION,
|
||||
False,
|
||||
),
|
||||
(
|
||||
False,
|
||||
"select_option/option comfort", # whitespace
|
||||
"select_option/option:frost",
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "comfort"},
|
||||
PRESET_COMFORT,
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "frost"},
|
||||
PRESET_FROST_PROTECTION,
|
||||
False,
|
||||
),
|
||||
(
|
||||
False,
|
||||
"select_option/option:com fort", # whitespace
|
||||
"select_option/option:frost",
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "comfort"},
|
||||
PRESET_COMFORT,
|
||||
"select_option",
|
||||
{"entity_id": "switch.test", "option": "frost"},
|
||||
PRESET_FROST_PROTECTION,
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_build_command(
|
||||
hass,
|
||||
is_inversed,
|
||||
vswitch_on_command,
|
||||
vswitch_off_command,
|
||||
expected_command_on,
|
||||
expected_data_on,
|
||||
expected_state_on,
|
||||
expected_command_off,
|
||||
expected_data_off,
|
||||
expected_state_off,
|
||||
is_ok,
|
||||
):
|
||||
"""Test the initialisation of a UnderlyingSwitch with some personnalisations commands"""
|
||||
|
||||
vtherm = MagicMock(spec=ThermostatOverSwitch)
|
||||
type(vtherm).is_inversed = PropertyMock(return_value=is_inversed)
|
||||
|
||||
assert vtherm.is_inversed == is_inversed
|
||||
|
||||
try:
|
||||
under = UnderlyingSwitch(hass, vtherm, "switch.test", 0, 0, vswitch_on_command, vswitch_off_command)
|
||||
except ValueError as e:
|
||||
if is_ok:
|
||||
pytest.fail(f"Initialization failed with ValueError: {e}")
|
||||
else:
|
||||
return
|
||||
|
||||
if not is_ok:
|
||||
pytest.fail("There should be a ValueError")
|
||||
return
|
||||
|
||||
assert under.is_inversed == is_inversed
|
||||
|
||||
assert under._on_command.get("command") == expected_command_on
|
||||
assert under._on_command.get("data") == expected_data_on
|
||||
assert under._on_command.get("state") == expected_state_on
|
||||
|
||||
assert under._off_command.get("command") == expected_command_off
|
||||
assert under._off_command.get("data") == expected_data_off
|
||||
assert under._off_command.get("state") == expected_state_off
|
||||
|
||||
# Calling turn-on
|
||||
# fmt: off
|
||||
with patch.object(under, "check_overpowering", return_value=True), \
|
||||
patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||
#fmt: on
|
||||
await under.turn_on()
|
||||
mock_service_call.assert_called_once_with("switch", expected_command_on, expected_data_on)
|
||||
|
||||
# Calling turn-off
|
||||
#fmt: off
|
||||
with patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||
#fmt: on
|
||||
await under.turn_off()
|
||||
mock_service_call.assert_called_once_with("switch", expected_command_off, expected_data_off)
|
||||
Reference in New Issue
Block a user