diff --git a/custom_components/versatile_thermostat/underlyings.py b/custom_components/versatile_thermostat/underlyings.py index b39ac45..283ece7 100644 --- a/custom_components/versatile_thermostat/underlyings.py +++ b/custom_components/versatile_thermostat/underlyings.py @@ -1158,7 +1158,8 @@ class UnderlyingValveRegulation(UnderlyingValve): 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, + # Patch to fix the hvac_action always Idle issue (issue #902) + closing_degree := max(self._max_opening_degree - 1 - self._percent_open, 0), ) # send offset_calibration to the difference between target temp and local temp diff --git a/tests/test_overclimate_valve.py b/tests/test_overclimate_valve.py index a29cd4c..a57657c 100644 --- a/tests/test_overclimate_valve.py +++ b/tests/test_overclimate_valve.py @@ -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 @@ -188,7 +188,7 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get [ 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': 60}, target={'entity_id': 'number.mock_closing_degree'}), + call(domain='number', service='set_value', service_data={'value': 59}, 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'}) ] @@ -234,7 +234,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': 13}, target={'entity_id': 'number.mock_opening_degree'}), - call(domain='number', service='set_value', service_data={'value': 87}, target={'entity_id': 'number.mock_closing_degree'}), + call(domain='number', service='set_value', service_data={'value': 86}, 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"), ) @@ -443,10 +431,10 @@ async def test_over_climate_valve_multi_presence( 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': 60}, target={'entity_id': 'number.mock_closing_degree1'}), + call(domain='number', service='set_value', service_data={'value': 59}, 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': 60}, target={'entity_id': 'number.mock_closing_degree2'}), + call(domain='number', service='set_value', service_data={'value': 59}, target={'entity_id': 'number.mock_closing_degree2'}), call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration2'}) ] ) @@ -474,10 +462,10 @@ async def test_over_climate_valve_multi_presence( 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'}) ] ) @@ -628,10 +616,10 @@ async def test_over_climate_valve_multi_min_opening_degrees( 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': 32}, target={'entity_id': 'number.mock_closing_degree1'}), + call(domain='number', service='set_value', service_data={'value': 31}, 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': 24}, target={'entity_id': 'number.mock_closing_degree2'}), + call(domain='number', service='set_value', service_data={'value': 23}, 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'}) ] )