# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, too-many-lines """ Test the over_climate with valve regulation """ from unittest.mock import patch, call from datetime import datetime, timedelta import logging from homeassistant.core import HomeAssistant, State from custom_components.versatile_thermostat.thermostat_climate_valve import ( ThermostatOverClimateValve, ) from .commons import * from .const import * logging.getLogger().setLevel(logging.DEBUG) @pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_timers", [True]) # this test fails if run in // with the next because the underlying_valve_regulation is mixed. Don't know why # @pytest.mark.skip async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get): """Test the normal full start of a thermostat in thermostat_over_climate type""" entry = MockConfigEntry( domain=DOMAIN, title="TheOverClimateMockName", unique_id="uniqueId", data={ CONF_NAME: "TheOverClimateMockName", CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", CONF_CYCLE_MIN: 5, CONF_DEVICE_POWER: 1, CONF_USE_MAIN_CENTRAL_CONFIG: False, CONF_USE_CENTRAL_MODE: False, CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", CONF_TEMP_MIN: 15, CONF_TEMP_MAX: 30, CONF_STEP_TEMPERATURE: 0.1, CONF_UNDERLYING_LIST: ["climate.mock_climate"], CONF_AC_MODE: False, CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_VALVE, CONF_AUTO_REGULATION_DTEMP: 0.5, CONF_AUTO_REGULATION_PERIOD_MIN: 2, CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH, CONF_AUTO_REGULATION_USE_DEVICE_TEMP: False, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_TPI_COEF_INT: 0.3, CONF_TPI_COEF_EXT: 0.1, CONF_OPENING_DEGREE_LIST: ["number.mock_opening_degree"], CONF_CLOSING_DEGREE_LIST: ["number.mock_closing_degree"], CONF_OFFSET_CALIBRATION_LIST: ["number.mock_offset_calibration"], } | MOCK_DEFAULT_FEATURE_CONFIG | MOCK_DEFAULT_CENTRAL_CONFIG | MOCK_ADVANCED_CONFIG, ) fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {}) # mock_get_state will be called for each OPENING/CLOSING/OFFSET_CALIBRATION list mock_get_state_side_effect = SideEffects( { "number.mock_opening_degree": State( "number.mock_opening_degree", "0", {"min": 0, "max": 100} ), "number.mock_closing_degree": State( "number.mock_closing_degree", "0", {"min": 0, "max": 100} ), "number.mock_offset_calibration": State( "number.mock_offset_calibration", "0", {"min": -12, "max": 12} ), }, State("unknown.entity_id", "unknown"), ) # 1. initialize the VTherm tz = get_tz(hass) # pylint: disable=invalid-name now: datetime = datetime.now(tz=tz) # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", return_value=fake_underlying_climate) as mock_find_climate, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on vtherm: ThermostatOverClimateValve = await create_thermostat(hass, entry, "climate.theoverclimatemockname") assert vtherm vtherm._set_now(now) assert isinstance(vtherm, ThermostatOverClimateValve) assert vtherm.name == "TheOverClimateMockName" assert vtherm.is_over_climate is True assert vtherm.have_valve_regulation is True assert vtherm.hvac_action is HVACAction.OFF assert vtherm.hvac_mode is HVACMode.OFF assert vtherm.target_temperature == vtherm.min_temp assert vtherm.preset_modes == [ PRESET_NONE, PRESET_FROST_PROTECTION, PRESET_ECO, PRESET_COMFORT, PRESET_BOOST, ] assert vtherm.preset_mode is PRESET_NONE assert vtherm.safety_manager.is_safety_detected is False assert vtherm.window_state is STATE_UNAVAILABLE assert vtherm.motion_state is STATE_UNAVAILABLE assert vtherm.presence_state is STATE_UNAVAILABLE assert vtherm.is_device_active is False assert vtherm.valve_open_percent == 0 # should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT assert mock_send_event.call_count == 2 mock_send_event.assert_has_calls( [ call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_NONE}), call.send_event( EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF}, ), ] ) mock_find_climate.assert_called_once() mock_find_climate.assert_has_calls([call.find_underlying_vtherm()]) # the underlying set temperature call but no call to valve yet because VTherm is off assert mock_service_call.call_count == 3 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("climate","set_temperature",{ "entity_id": "climate.mock_climate", "temperature": 15, # temp-min }, ), # we have no current_temperature yet # call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration'}), ] ) assert mock_get_state.call_count > 5 # each temp sensor + each valve assert vtherm.nb_device_actives == 0 # initialize the temps await set_all_climate_preset_temp(hass, vtherm, None, "theoverclimatemockname") await send_temperature_change_event(vtherm, 18, now, True) await send_ext_temperature_change_event(vtherm, 18, now, True) # 2. Starts heating slowly (18 vs 19) now = now + timedelta(minutes=1) vtherm._set_now(now) await vtherm.async_set_hvac_mode(HVACMode.HEAT) # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on now = now + timedelta(minutes=2) # avoid temporal filter vtherm._set_now(now) await vtherm.async_set_preset_mode(PRESET_COMFORT) await hass.async_block_till_done() assert vtherm.hvac_mode is HVACMode.HEAT assert vtherm.preset_mode is PRESET_COMFORT assert vtherm.target_temperature == 19 assert vtherm.current_temperature == 18 assert vtherm.valve_open_percent == 40 # 0.3*1 + 0.1*1 assert mock_service_call.call_count == 4 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': 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'}) ] ) # set the opening to 40% mock_get_state_side_effect.add_or_update_side_effect( "number.mock_opening_degree", State( "number.mock_opening_degree", "40", {"min": 0, "max": 100} )) assert vtherm.hvac_action is HVACAction.HEATING assert vtherm.is_device_active is True assert vtherm.nb_device_actives == 1 # 2. Starts heating very slowly (18.9 vs 19) now = now + timedelta(minutes=2) vtherm._set_now(now) # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on # set the offset to 3 mock_get_state_side_effect.add_or_update_side_effect( "number.mock_offset_calibration", State( "number.mock_offset_calibration", "3", {"min": -12, "max": 12} )) await send_temperature_change_event(vtherm, 18.9, now, True) await hass.async_block_till_done() assert vtherm.hvac_mode is HVACMode.HEAT assert vtherm.preset_mode is PRESET_COMFORT assert vtherm.target_temperature == 19 assert vtherm.current_temperature == 18.9 assert vtherm.valve_open_percent == 13 # 0.3*0.1 + 0.1*1 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': 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'}) ] ) # set the opening to 13% mock_get_state_side_effect.add_or_update_side_effect( "number.mock_opening_degree", State( "number.mock_opening_degree", "13", {"min": 0, "max": 100} )) assert vtherm.hvac_action is HVACAction.HEATING assert vtherm.is_device_active is True assert vtherm.nb_device_actives == 1 # 3. Stop heating 21 > 19 now = now + timedelta(minutes=2) vtherm._set_now(now) # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on # set the offset to 3 mock_get_state_side_effect.add_or_update_side_effect( "number.mock_offset_calibration", State( "number.mock_offset_calibration", "3", {"min": -12, "max": 12} )) await send_temperature_change_event(vtherm, 21, now, True) await hass.async_block_till_done() assert vtherm.hvac_mode is HVACMode.HEAT assert vtherm.preset_mode is PRESET_COMFORT assert vtherm.target_temperature == 19 assert vtherm.current_temperature == 21 assert vtherm.valve_open_percent == 0 # 0.3* (-2) + 0.1*1 assert mock_service_call.call_count == 3 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'}), # 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'}) ] ) # set the opening to 13% mock_get_state_side_effect.add_or_update_side_effect( "number.mock_opening_degree", State( "number.mock_opening_degree", "0", {"min": 0, "max": 100} )) assert vtherm.hvac_action is HVACAction.OFF assert vtherm.is_device_active is False assert vtherm.nb_device_actives == 0 await hass.async_block_till_done() @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_over_climate_valve_multi_presence( hass: HomeAssistant, skip_hass_states_get ): """Test the normal full start of a thermostat in thermostat_over_climate type""" entry = MockConfigEntry( domain=DOMAIN, title="TheOverClimateMockName", unique_id="uniqueId", data={ CONF_NAME: "TheOverClimateMockName", CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", CONF_CYCLE_MIN: 5, CONF_DEVICE_POWER: 1, CONF_USE_MAIN_CENTRAL_CONFIG: False, CONF_USE_CENTRAL_MODE: False, CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", CONF_TEMP_MIN: 15, CONF_TEMP_MAX: 30, CONF_STEP_TEMPERATURE: 0.1, CONF_UNDERLYING_LIST: ["climate.mock_climate1", "climate.mock_climate2"], CONF_AC_MODE: False, CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_VALVE, CONF_AUTO_REGULATION_DTEMP: 0.5, CONF_AUTO_REGULATION_PERIOD_MIN: 2, CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH, CONF_AUTO_REGULATION_USE_DEVICE_TEMP: False, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_TPI_COEF_INT: 0.3, CONF_TPI_COEF_EXT: 0.1, CONF_OPENING_DEGREE_LIST: [ "number.mock_opening_degree1", "number.mock_opening_degree2", ], CONF_CLOSING_DEGREE_LIST: [ "number.mock_closing_degree1", "number.mock_closing_degree2", ], CONF_OFFSET_CALIBRATION_LIST: [ "number.mock_offset_calibration1", "number.mock_offset_calibration2", ], CONF_USE_PRESENCE_FEATURE: True, CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor", CONF_USE_WINDOW_FEATURE: False, CONF_USE_MOTION_FEATURE: False, CONF_USE_POWER_FEATURE: False, } | MOCK_DEFAULT_CENTRAL_CONFIG | MOCK_ADVANCED_CONFIG, ) fake_underlying_climate1 = MockClimate( hass, "mockUniqueId1", "MockClimateName1", {} ) fake_underlying_climate2 = MockClimate( hass, "mockUniqueId2", "MockClimateName2", {} ) # mock_get_state will be called for each OPENING/CLOSING/OFFSET_CALIBRATION list 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} ), # 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} ), }, State("unknown.entity_id", "unknown"), ) # 1. initialize the VTherm tz = get_tz(hass) # pylint: disable=invalid-name now: datetime = datetime.now(tz=tz) # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", side_effect=[fake_underlying_climate1, fake_underlying_climate2]) as mock_find_climate, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on vtherm: ThermostatOverClimateValve = await create_thermostat(hass, entry, "climate.theoverclimatemockname") assert vtherm assert isinstance(vtherm, ThermostatOverClimateValve) assert vtherm.name == "TheOverClimateMockName" assert vtherm.is_over_climate is True assert vtherm.have_valve_regulation is True vtherm._set_now(now) # initialize the temps await set_all_climate_preset_temp(hass, vtherm, default_temperatures_away, "theoverclimatemockname") await send_temperature_change_event(vtherm, 18, now, True) await send_ext_temperature_change_event(vtherm, 18, now, True) await send_presence_change_event(vtherm, False, True, now) await vtherm.async_set_preset_mode(PRESET_COMFORT) await vtherm.async_set_hvac_mode(HVACMode.HEAT) assert vtherm.target_temperature == 17.2 assert vtherm.nb_device_actives == 0 # 2: set presence on -> should activate the valve and change target # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on now = now + timedelta(minutes=3) vtherm._set_now(now) await send_presence_change_event(vtherm, True, False, now) await hass.async_block_till_done() assert vtherm.is_device_active is True assert vtherm.valve_open_percent == 40 # the underlying set temperature call and the call to the valve assert mock_service_call.call_count == 8 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': 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': 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'}) ] ) assert vtherm.nb_device_actives >= 2 # should be 2 but when run in // with the first test it give 3 # 3: set presence off -> should deactivate the valve and change target # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on now = now + timedelta(minutes=3) vtherm._set_now(now) await send_presence_change_event(vtherm, False, True, now) await hass.async_block_till_done() assert vtherm.is_device_active is False assert vtherm.valve_open_percent == 0 # the underlying set temperature call and the call to the valve 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': 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': 12}, target={'entity_id': 'number.mock_offset_calibration2'}) ] ) assert vtherm.nb_device_actives == 0 @pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_over_climate_valve_multi_min_opening_degrees( hass: HomeAssistant, skip_hass_states_get ): """Test the normal full start of a thermostat in thermostat_over_climate type with valve_regulation and min_opening_degreess set""" entry = MockConfigEntry( domain=DOMAIN, title="TheOverClimateMockName", unique_id="uniqueId", data={ CONF_NAME: "TheOverClimateMockName", CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", CONF_CYCLE_MIN: 5, CONF_DEVICE_POWER: 1, CONF_USE_MAIN_CENTRAL_CONFIG: False, CONF_USE_CENTRAL_MODE: False, CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", CONF_TEMP_MIN: 15, CONF_TEMP_MAX: 30, CONF_STEP_TEMPERATURE: 0.1, CONF_UNDERLYING_LIST: ["climate.mock_climate1", "climate.mock_climate2"], CONF_AC_MODE: False, CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_VALVE, CONF_AUTO_REGULATION_DTEMP: 0.01, CONF_AUTO_REGULATION_PERIOD_MIN: 0, CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH, CONF_AUTO_REGULATION_USE_DEVICE_TEMP: False, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_TPI_COEF_INT: 0.3, CONF_TPI_COEF_EXT: 0.1, CONF_OPENING_DEGREE_LIST: [ "number.mock_opening_degree1", "number.mock_opening_degree2", ], CONF_CLOSING_DEGREE_LIST: [ "number.mock_closing_degree1", "number.mock_closing_degree2", ], CONF_OFFSET_CALIBRATION_LIST: [ "number.mock_offset_calibration1", "number.mock_offset_calibration2", ], CONF_USE_PRESENCE_FEATURE: False, CONF_USE_WINDOW_FEATURE: False, CONF_USE_MOTION_FEATURE: False, CONF_USE_POWER_FEATURE: False, CONF_MIN_OPENING_DEGREES: "60,70", } | MOCK_DEFAULT_CENTRAL_CONFIG | MOCK_ADVANCED_CONFIG, ) fake_underlying_climate1 = MockClimate( hass, "mockUniqueId1", "MockClimateName1", {} ) fake_underlying_climate2 = MockClimate( hass, "mockUniqueId2", "MockClimateName2", {} ) # mock_get_state will be called for each OPENING/CLOSING/OFFSET_CALIBRATION list 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} ), # 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} ), }, State("unknown.entity_id", "unknown"), ) # 1. initialize the VTherm tz = get_tz(hass) # pylint: disable=invalid-name now: datetime = datetime.now(tz=tz) # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", side_effect=[fake_underlying_climate1, fake_underlying_climate2]) as mock_find_climate, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on vtherm: ThermostatOverClimateValve = await create_thermostat(hass, entry, "climate.theoverclimatemockname") assert vtherm assert isinstance(vtherm, ThermostatOverClimateValve) assert vtherm.name == "TheOverClimateMockName" assert vtherm.is_over_climate is True assert vtherm.have_valve_regulation is True vtherm._set_now(now) # initialize the temps await set_all_climate_preset_temp(hass, vtherm, default_temperatures, "theoverclimatemockname") await send_temperature_change_event(vtherm, 20, now, True) await send_ext_temperature_change_event(vtherm, 20, now, True) await send_presence_change_event(vtherm, False, True, now) await vtherm.async_set_preset_mode(PRESET_COMFORT) await vtherm.async_set_hvac_mode(HVACMode.HEAT) assert vtherm.target_temperature == 19 assert vtherm.nb_device_actives == 0 # 2: set temperature -> should activate the valve and change target # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on now = now + timedelta(minutes=3) vtherm._set_now(now) await send_temperature_change_event(vtherm, 18, now, True) await hass.async_block_till_done() assert vtherm.is_device_active is True assert vtherm.valve_open_percent == 20 # the underlying set temperature call and the call to the valve 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': 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': 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'}) ] ) assert vtherm.nb_device_actives >= 2 # should be 2 but when run in // with the first test it give 3 # 3: set high temperature -> should deactivate the valve and change target # fmt: off with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\ patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state: # fmt: on now = now + timedelta(minutes=3) vtherm._set_now(now) await send_temperature_change_event(vtherm, 22, now, True) await hass.async_block_till_done() assert vtherm.is_device_active is False assert vtherm.valve_open_percent == 0 # the underlying set temperature call and the call to the valve 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': 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': 12}, target={'entity_id': 'number.mock_offset_calibration2'}) ] ) assert vtherm.nb_device_actives == 0