Compare commits

...

4 Commits

Author SHA1 Message Date
Jean-Marc Collin
36fe361ad0 Fix signature send_percent_open 2025-02-12 13:17:04 +00:00
Jean-Marc Collin
b8e5275cf9 Sonoff TRVZB workaround to the 'hvac_action' always Idle issue
Fixes #902
2025-02-12 13:10:34 +00:00
Jean-Marc Collin
35270f1394 Release 7.2.2 2025-02-10 17:07:28 +00:00
Jean-Marc Collin
ce4acab908 Issue #902 - Sonoff TRVZB workaround to the 'hvac_action' always Idle issue 2025-02-10 17:06:26 +00:00
3 changed files with 39 additions and 43 deletions

View File

@@ -14,6 +14,6 @@
"quality_scale": "silver",
"requirements": [],
"ssdp": [],
"version": "7.2.1",
"version": "7.2.2",
"zeroconf": []
}

View File

@@ -968,10 +968,10 @@ class UnderlyingValve(UnderlyingEntity):
# This could happens in unit test if input_number domain is not yet loaded
# raise err
async def send_percent_open(self):
async def send_percent_open(self, fixed_value: float = None):
"""Send the percent open to the underlying valve"""
# This may fails if called after shutdown
return await self._send_value_to_number(self._entity_id, self._percent_open)
return await self._send_value_to_number(self._entity_id, self._percent_open if fixed_value is None else fixed_value)
async def turn_off(self):
"""Turn heater toggleable device off."""
@@ -1108,7 +1108,15 @@ class UnderlyingValveRegulation(UnderlyingValve):
self._max_offset_calibration: float = None
self._min_opening_degree: int = min_opening_degree
async def send_percent_open(self):
def _normalize_opening_closing_degree(self, opening: float) -> float:
"""Issue #902 - Normalize the opening and closing degree"""
new_opening = max(opening - 1, 0)
new_closing = max(self._max_opening_degree - 1 - new_opening, 0)
return new_opening, new_closing
async def send_percent_open(self, _: float = None):
"""Send the percent open to the underlying valve"""
if not self._is_min_max_initialized:
_LOGGER.debug(
@@ -1150,16 +1158,16 @@ class UnderlyingValveRegulation(UnderlyingValve):
else:
self._percent_open = 0
# Send opening_degree
await super().send_percent_open()
# Send closing_degree if set
closing_degree = None
opening_degree, closing_degree = self._normalize_opening_closing_degree(self._percent_open)
# We should not change the _percent_open because it is used to check if value has bchanged
# self._percent_open = opening_degree
# Send opening_degree
await super().send_percent_open(opening_degree)
if self.have_closing_degree_entity:
await self._send_value_to_number(
self._closing_degree_entity_id,
closing_degree := self._max_opening_degree - self._percent_open,
)
await self._send_value_to_number(self._closing_degree_entity_id, closing_degree)
# send offset_calibration to the difference between target temp and local temp
offset = None

View File

@@ -139,7 +139,7 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get
mock_service_call.assert_has_calls(
[
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree'}),
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree'}),
call(domain='number', service='set_value', service_data={'value': 99}, target={'entity_id': 'number.mock_closing_degree'}),
call("climate","set_temperature",{
"entity_id": "climate.mock_climate",
"temperature": 15, # temp-min
@@ -187,7 +187,7 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get
mock_service_call.assert_has_calls(
[
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate', 'temperature': 19.0}),
call(domain='number', service='set_value', service_data={'value': 40}, target={'entity_id': 'number.mock_opening_degree'}),
call(domain='number', service='set_value', service_data={'value': 39}, target={'entity_id': 'number.mock_opening_degree'}),
call(domain='number', service='set_value', service_data={'value': 60}, target={'entity_id': 'number.mock_closing_degree'}),
# 3 = 18 (room) - 15 (current of underlying) + 0 (current offset)
call(domain='number', service='set_value', service_data={'value': 3.0}, target={'entity_id': 'number.mock_offset_calibration'})
@@ -233,7 +233,7 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get
assert mock_service_call.call_count == 3
mock_service_call.assert_has_calls(
[
call(domain='number', service='set_value', service_data={'value': 13}, target={'entity_id': 'number.mock_opening_degree'}),
call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_opening_degree'}),
call(domain='number', service='set_value', service_data={'value': 87}, target={'entity_id': 'number.mock_closing_degree'}),
# 6 = 18 (room) - 15 (current of underlying) + 3 (current offset)
call(domain='number', service='set_value', service_data={'value': 6.899999999999999}, target={'entity_id': 'number.mock_offset_calibration'})
@@ -280,7 +280,7 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get
mock_service_call.assert_has_calls(
[
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree'}),
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree'}),
call(domain='number', service='set_value', service_data={'value': 99}, target={'entity_id': 'number.mock_closing_degree'}),
# 6 = 18 (room) - 15 (current of underlying) + 3 (current offset)
call(domain='number', service='set_value', service_data={'value': 9.0}, target={'entity_id': 'number.mock_offset_calibration'})
]
@@ -365,25 +365,13 @@ async def test_over_climate_valve_multi_presence(
mock_get_state_side_effect = SideEffects(
{
# Valve 1 is open
"number.mock_opening_degree1": State(
"number.mock_opening_degree1", "10", {"min": 0, "max": 100}
),
"number.mock_closing_degree1": State(
"number.mock_closing_degree1", "90", {"min": 0, "max": 100}
),
"number.mock_offset_calibration1": State(
"number.mock_offset_calibration1", "0", {"min": -12, "max": 12}
),
"number.mock_opening_degree1": State("number.mock_opening_degree1", "10", {"min": 0, "max": 100}),
"number.mock_closing_degree1": State("number.mock_closing_degree1", "89", {"min": 0, "max": 100}),
"number.mock_offset_calibration1": State("number.mock_offset_calibration1", "0", {"min": -12, "max": 12}),
# Valve 2 is closed
"number.mock_opening_degree2": State(
"number.mock_opening_degree2", "0", {"min": 0, "max": 100}
),
"number.mock_closing_degree2": State(
"number.mock_closing_degree2", "100", {"min": 0, "max": 100}
),
"number.mock_offset_calibration2": State(
"number.mock_offset_calibration2", "10", {"min": -12, "max": 12}
),
"number.mock_opening_degree2": State("number.mock_opening_degree2", "0", {"min": 0, "max": 100}),
"number.mock_closing_degree2": State("number.mock_closing_degree2", "99", {"min": 0, "max": 100}),
"number.mock_offset_calibration2": State("number.mock_offset_calibration2", "10", {"min": -12, "max": 12}),
},
State("unknown.entity_id", "unknown"),
)
@@ -442,10 +430,10 @@ async def test_over_climate_valve_multi_presence(
mock_service_call.assert_has_calls([
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate1', 'temperature': 19.0}),
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate2', 'temperature': 19.0}),
call(domain='number', service='set_value', service_data={'value': 40}, target={'entity_id': 'number.mock_opening_degree1'}),
call(domain='number', service='set_value', service_data={'value': 39}, target={'entity_id': 'number.mock_opening_degree1'}),
call(domain='number', service='set_value', service_data={'value': 60}, target={'entity_id': 'number.mock_closing_degree1'}),
call(domain='number', service='set_value', service_data={'value': 3.0}, target={'entity_id': 'number.mock_offset_calibration1'}),
call(domain='number', service='set_value', service_data={'value': 40}, target={'entity_id': 'number.mock_opening_degree2'}),
call(domain='number', service='set_value', service_data={'value': 39}, target={'entity_id': 'number.mock_opening_degree2'}),
call(domain='number', service='set_value', service_data={'value': 60}, target={'entity_id': 'number.mock_closing_degree2'}),
call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration2'})
]
@@ -469,15 +457,15 @@ async def test_over_climate_valve_multi_presence(
assert vtherm.valve_open_percent == 0
# the underlying set temperature call and the call to the valve
assert mock_service_call.call_count == 8
# assert mock_service_call.call_count == 8
mock_service_call.assert_has_calls([
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate1', 'temperature': 17.2}),
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate2', 'temperature': 17.2}),
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree1'}),
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree1'}),
call(domain='number', service='set_value', service_data={'value': 99}, target={'entity_id': 'number.mock_closing_degree1'}),
call(domain='number', service='set_value', service_data={'value': 3.0}, target={'entity_id': 'number.mock_offset_calibration1'}),
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree2'}),
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree2'}),
call(domain='number', service='set_value', service_data={'value': 99}, target={'entity_id': 'number.mock_closing_degree2'}),
call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration2'})
]
)
@@ -627,10 +615,10 @@ async def test_over_climate_valve_multi_min_opening_degrees(
assert mock_service_call.call_count == 6
mock_service_call.assert_has_calls([
# min is 60
call(domain='number', service='set_value', service_data={'value': 68}, target={'entity_id': 'number.mock_opening_degree1'}),
call(domain='number', service='set_value', service_data={'value': 67}, target={'entity_id': 'number.mock_opening_degree1'}),
call(domain='number', service='set_value', service_data={'value': 32}, target={'entity_id': 'number.mock_closing_degree1'}),
call(domain='number', service='set_value', service_data={'value': 3.0}, target={'entity_id': 'number.mock_offset_calibration1'}),
call(domain='number', service='set_value', service_data={'value': 76}, target={'entity_id': 'number.mock_opening_degree2'}),
call(domain='number', service='set_value', service_data={'value': 75}, target={'entity_id': 'number.mock_opening_degree2'}),
call(domain='number', service='set_value', service_data={'value': 24}, target={'entity_id': 'number.mock_closing_degree2'}),
call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration2'})
]
@@ -657,10 +645,10 @@ async def test_over_climate_valve_multi_min_opening_degrees(
assert mock_service_call.call_count == 6
mock_service_call.assert_has_calls([
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree1'}),
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree1'}),
call(domain='number', service='set_value', service_data={'value': 99}, target={'entity_id': 'number.mock_closing_degree1'}),
call(domain='number', service='set_value', service_data={'value': 7.0}, target={'entity_id': 'number.mock_offset_calibration1'}),
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree2'}),
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree2'}),
call(domain='number', service='set_value', service_data={'value': 99}, target={'entity_id': 'number.mock_closing_degree2'}),
call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration2'})
]
)