From a3f8715fe56f485d68cb6d1646ec6ae331579a7f Mon Sep 17 00:00:00 2001 From: Jean-Marc Collin Date: Sat, 28 Sep 2024 19:36:46 +0200 Subject: [PATCH] HA 2024.9.3 and issue 508 (#510) * HA 2024.9.3 and issue 508 * Fix strings trailing spaces --------- Co-authored-by: Jean-Marc Collin --- .../versatile_thermostat/manifest.json | 4 +- .../versatile_thermostat/strings.json | 8 +- .../versatile_thermostat/translations/el.json | 8 +- .../versatile_thermostat/translations/en.json | 8 +- .../versatile_thermostat/translations/fr.json | 2 +- .../versatile_thermostat/translations/it.json | 8 +- .../versatile_thermostat/underlyings.py | 22 ++-- hacs.json | 4 +- requirements_dev.txt | 2 +- tests/commons.py | 80 ++++++++++++++ tests/test_bugs.py | 101 ++++++++++++++++-- 11 files changed, 213 insertions(+), 34 deletions(-) diff --git a/custom_components/versatile_thermostat/manifest.json b/custom_components/versatile_thermostat/manifest.json index 976fb18..d9ac2e7 100644 --- a/custom_components/versatile_thermostat/manifest.json +++ b/custom_components/versatile_thermostat/manifest.json @@ -14,6 +14,6 @@ "quality_scale": "silver", "requirements": [], "ssdp": [], - "version": "6.2.9", + "version": "6.3.0", "zeroconf": [] -} +} \ No newline at end of file diff --git a/custom_components/versatile_thermostat/strings.json b/custom_components/versatile_thermostat/strings.json index e6a2275..7692c9e 100644 --- a/custom_components/versatile_thermostat/strings.json +++ b/custom_components/versatile_thermostat/strings.json @@ -90,7 +90,7 @@ "auto_regulation_periode_min": "Regulation minimum period", "auto_regulation_use_device_temp": "Use internal temperature of the underlying", "inverse_switch_command": "Inverse switch command", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Mandatory heater entity id", @@ -113,7 +113,7 @@ "auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation", "inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command", - "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" + "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary" } }, "tpi": { @@ -325,7 +325,7 @@ "auto_regulation_periode_min": "Regulation minimum period", "auto_regulation_use_device_temp": "Use internal temperature of the underlying", "inverse_switch_command": "Inverse switch command", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Mandatory heater entity id", @@ -348,7 +348,7 @@ "auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation", "inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command", - "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" + "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary" } }, "tpi": { diff --git a/custom_components/versatile_thermostat/translations/el.json b/custom_components/versatile_thermostat/translations/el.json index 5de35f3..5ae6efb 100644 --- a/custom_components/versatile_thermostat/translations/el.json +++ b/custom_components/versatile_thermostat/translations/el.json @@ -43,7 +43,7 @@ "auto_regulation_dtemp": "Όριο ρύθμισης", "auto_regulation_periode_min": "Ελάχιστη περίοδος ρύθμισης", "inverse_switch_command": "Αντίστροφη εντολή διακόπτη", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Υποχρεωτική ταυτότητα οντότητας θερμαντήρα", @@ -64,7 +64,7 @@ "auto_regulation_dtemp": "Το όριο σε ° κάτω από το οποίο η αλλαγή θερμοκρασίας δεν θα αποστέλλεται", "auto_regulation_periode_min": "Διάρκεια σε λεπτά μεταξύ δύο ενημερώσεων ρύθμισης", "inverse_switch_command": "Για διακόπτη με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστρέψετε την εντολή", - "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" + "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary" } }, "tpi": { @@ -216,7 +216,7 @@ "auto_regulation_dtemp": "Όριο ρύθμισης", "auto_regulation_periode_min": "Ελάχιστη περίοδος ρύθμισης", "inverse_switch_command": "Αντίστροφη εντολή διακόπτη", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Υποχρεωτική ταυτότητα οντότητας θερμαντήρα", @@ -237,7 +237,7 @@ "auto_regulation_dtemp": "Το κατώφλι σε °C κάτω από το οποίο η αλλαγή της θερμοκρασίας δεν θα αποστέλλεται", "auto_regulation_periode_min": "Διάρκεια σε λεπτά μεταξύ δύο ενημερώσεων ρύθμισης", "inverse_switch_command": "Για διακόπτες με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστραφεί η εντολή", - "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" + "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary" } }, "tpi": { diff --git a/custom_components/versatile_thermostat/translations/en.json b/custom_components/versatile_thermostat/translations/en.json index e6a2275..7692c9e 100644 --- a/custom_components/versatile_thermostat/translations/en.json +++ b/custom_components/versatile_thermostat/translations/en.json @@ -90,7 +90,7 @@ "auto_regulation_periode_min": "Regulation minimum period", "auto_regulation_use_device_temp": "Use internal temperature of the underlying", "inverse_switch_command": "Inverse switch command", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Mandatory heater entity id", @@ -113,7 +113,7 @@ "auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation", "inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command", - "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" + "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary" } }, "tpi": { @@ -325,7 +325,7 @@ "auto_regulation_periode_min": "Regulation minimum period", "auto_regulation_use_device_temp": "Use internal temperature of the underlying", "inverse_switch_command": "Inverse switch command", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Mandatory heater entity id", @@ -348,7 +348,7 @@ "auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation", "inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command", - "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" + "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary" } }, "tpi": { diff --git a/custom_components/versatile_thermostat/translations/fr.json b/custom_components/versatile_thermostat/translations/fr.json index ab42f6e..c891cc8 100644 --- a/custom_components/versatile_thermostat/translations/fr.json +++ b/custom_components/versatile_thermostat/translations/fr.json @@ -337,7 +337,7 @@ "auto_regulation_periode_min": "Période minimale de régulation", "auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent", "inverse_switch_command": "Inverser la commande", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Entity id du 1er radiateur obligatoire", diff --git a/custom_components/versatile_thermostat/translations/it.json b/custom_components/versatile_thermostat/translations/it.json index f93147b..8815082 100644 --- a/custom_components/versatile_thermostat/translations/it.json +++ b/custom_components/versatile_thermostat/translations/it.json @@ -42,7 +42,7 @@ "valve_entity4_id": "Quarta valvola", "auto_regulation_mode": "Autoregolamentazione", "inverse_switch_command": "Comando inverso", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Entity id obbligatoria del primo riscaldatore", @@ -62,7 +62,7 @@ "valve_entity4_id": "Entity id della quarta valvola", "auto_regulation_mode": "Regolazione automatica della temperatura target", "inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo", - "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" + "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary" } }, "tpi": { @@ -206,7 +206,7 @@ "valve_entity4_id": "Quarta valvola", "auto_regulation_mode": "Autoregolamentazione", "inverse_switch_command": "Comando inverso", - "auto_fan_mode": " Auto fan mode" + "auto_fan_mode": "Auto fan mode" }, "data_description": { "heater_entity_id": "Entity id obbligatoria del primo riscaldatore", @@ -226,7 +226,7 @@ "valve_entity4_id": "Entity id della quarta valvola", "auto_regulation_mode": "Autoregolamentazione", "inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo", - "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" + "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary" } }, "tpi": { diff --git a/custom_components/versatile_thermostat/underlyings.py b/custom_components/versatile_thermostat/underlyings.py index f1ae0e4..a245079 100644 --- a/custom_components/versatile_thermostat/underlyings.py +++ b/custom_components/versatile_thermostat/underlyings.py @@ -612,12 +612,22 @@ class UnderlyingClimate(UnderlyingEntity): if not self.is_initialized: return - data = { - ATTR_ENTITY_ID: self._entity_id, - "temperature": self.cap_sent_value(temperature), - "target_temp_high": max_temp, - "target_temp_low": min_temp, - } + # Issue 508 we have to take care of service set_temperature or set_range + target_temp = self.cap_sent_value(temperature) + if ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + in self._underlying_climate.supported_features + ): + data = { + ATTR_ENTITY_ID: self._entity_id, + "target_temp_high": target_temp, + "target_temp_low": target_temp, + } + else: + data = { + ATTR_ENTITY_ID: self._entity_id, + "temperature": target_temp, + } await self._hass.services.async_call( CLIMATE_DOMAIN, diff --git a/hacs.json b/hacs.json index 31979fe..468efb1 100644 --- a/hacs.json +++ b/hacs.json @@ -3,5 +3,5 @@ "content_in_root": false, "render_readme": true, "hide_default_branch": false, - "homeassistant": "2024.6.1" -} + "homeassistant": "2024.9.3" +} \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index 1c97616..fc71187 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1 +1 @@ -homeassistant==2024.6.1 +homeassistant==2024.9.3 diff --git a/tests/commons.py b/tests/commons.py index 0768cd4..4b3d2c3 100644 --- a/tests/commons.py +++ b/tests/commons.py @@ -435,6 +435,86 @@ class MagicMockClimate(MagicMock): return 19 +class MagicMockClimateWithTemperatureRange(MagicMock): + """A Magic Mock class for a underlying climate entity""" + + @property + def temperature_unit(self): # pylint: disable=missing-function-docstring + return UnitOfTemperature.CELSIUS + + @property + def hvac_mode(self): # pylint: disable=missing-function-docstring + return HVACMode.HEAT + + @property + def hvac_action(self): # pylint: disable=missing-function-docstring + return HVACAction.IDLE + + @property + def target_temperature(self): # pylint: disable=missing-function-docstring + return 15 + + @property + def current_temperature(self): # pylint: disable=missing-function-docstring + return 14 + + @property + def target_temperature_step( # pylint: disable=missing-function-docstring + self, + ) -> float | None: + return 0.5 + + @property + def target_temperature_high( # pylint: disable=missing-function-docstring + self, + ) -> float | None: + return 35 + + @property + def target_temperature_low( # pylint: disable=missing-function-docstring + self, + ) -> float | None: + return 7 + + @property + def hvac_modes( # pylint: disable=missing-function-docstring + self, + ) -> list[str] | None: + return [HVACMode.HEAT, HVACMode.OFF, HVACMode.COOL] + + @property + def fan_modes( # pylint: disable=missing-function-docstring + self, + ) -> list[str] | None: + return None + + @property + def swing_modes( # pylint: disable=missing-function-docstring + self, + ) -> list[str] | None: + return None + + @property + def fan_mode(self) -> str | None: # pylint: disable=missing-function-docstring + return None + + @property + def swing_mode(self) -> str | None: # pylint: disable=missing-function-docstring + return None + + @property + def supported_features(self): # pylint: disable=missing-function-docstring + return ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + + @property + def min_temp(self): # pylint: disable=missing-function-docstring + return 10 + + @property + def max_temp(self): # pylint: disable=missing-function-docstring + return 31 + + class MockSwitch(SwitchEntity): """A fake switch to be used instead real switch""" diff --git a/tests/test_bugs.py b/tests/test_bugs.py index ebebda0..353b463 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -657,8 +657,8 @@ async def test_bug_272( { "entity_id": "climate.mock_climate", "temperature": 17.5, - "target_temp_high": 30, - "target_temp_low": 15, + # "target_temp_high": 30, + # "target_temp_low": 15, }, ), ] @@ -687,8 +687,8 @@ async def test_bug_272( { "entity_id": "climate.mock_climate", "temperature": 15, # the minimum acceptable - "target_temp_high": 30, - "target_temp_low": 15, + # "target_temp_high": 30, + # "target_temp_low": 15, }, ), ] @@ -714,8 +714,8 @@ async def test_bug_272( { "entity_id": "climate.mock_climate", "temperature": 19, # the maximum acceptable - "target_temp_high": 30, - "target_temp_low": 15, + # "target_temp_high": 30, + # "target_temp_low": 15, }, ), ] @@ -924,3 +924,92 @@ async def test_bug_339( assert api.nb_active_device_for_boiler == 1 entity.remove_thermostat() + + +@pytest.mark.parametrize("expected_lingering_timers", [True]) +async def test_bug_508( + hass: HomeAssistant, + skip_hass_states_is_state, + skip_turn_on_off_heater, + skip_send_event, +): + """Test that it not possible to set the target temperature under the min_temp setting""" + + tz = get_tz(hass) # pylint: disable=invalid-name + now: datetime = datetime.now(tz=tz) + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverClimateMockName", + unique_id="uniqueId", + # default value are min 15°, max 31°, step 0.1 + data=PARTIAL_CLIMATE_CONFIG, # 5 minutes security delay + ) + + # Min_temp is 10 and max_temp is 31 and features contains TARGET_TEMPERATURE_RANGE + fake_underlying_climate = MagicMockClimateWithTemperatureRange() + + with patch( + "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" + ), patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", + return_value=fake_underlying_climate, + ), patch( + "homeassistant.core.ServiceRegistry.async_call" + ) as mock_service_call: + entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname") + + assert entity + + assert entity.name == "TheOverClimateMockName" + assert entity.is_over_climate is True + assert entity.hvac_mode is HVACMode.OFF + # The VTherm value and not the underlying value + assert entity.target_temperature_step == 0.1 + assert entity.target_temperature == entity.min_temp + assert entity.is_regulated is True + + assert mock_service_call.call_count == 0 + + # Set the hvac_mode to HEAT + await entity.async_set_hvac_mode(HVACMode.HEAT) + + # Not In the accepted interval -> should be converted into 10 (the min) and send with target_temp_high and target_temp_low + await entity.async_set_temperature(temperature=8.5) + + # MagicMock climate is already HEAT by default. So there is no SET_HAVC_MODE call + assert mock_service_call.call_count == 1 + mock_service_call.assert_has_calls( + [ + call.async_call( + "climate", + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.mock_climate", + # "temperature": 17.5, + "target_temp_high": 10, + "target_temp_low": 10, + }, + ), + ] + ) + + with patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call: + # Not In the accepted interval -> should be converted into 10 (the min) and send with target_temp_high and target_temp_low + await entity.async_set_temperature(temperature=32) + + # MagicMock climate is already HEAT by default. So there is no SET_HAVC_MODE call + assert mock_service_call.call_count == 1 + mock_service_call.assert_has_calls( + [ + call.async_call( + "climate", + SERVICE_SET_TEMPERATURE, + { + "entity_id": "climate.mock_climate", + "target_temp_high": 31, + "target_temp_low": 31, + }, + ), + ] + )