diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7bcc297..34c1c1b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,2 +1,2 @@ -FROM mcr.microsoft.com/devcontainers/python:1-3.11 +FROM mcr.microsoft.com/devcontainers/python:1-3.12 RUN apt update && apt install -y ffmpeg diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index eaf12d8..e41ffc3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,7 +22,8 @@ "extensions": [ "ms-python.python", "ms-python.pylint", - "ms-python.vscode-pylance", + // already included into ms-python.python + // "ms-python.vscode-pylance", "ms-python.isort", "ms-python.black-formatter", "visualstudioexptteam.vscodeintellicode", diff --git a/.vscode/launch.json b/.vscode/launch.json index 622909c..ba1e596 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,18 +1,14 @@ { - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Home Assistant (debug)", - "type": "python", - "request": "launch", - "module": "homeassistant", - "justMyCode": false, - "args": [ - "--debug", - "-c", - "config" - ] - } - ] -} \ No newline at end of file + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Home Assistant (debug)", + "type": "debugpy", + "request": "launch", + "module": "homeassistant", + "justMyCode": false, + "args": ["--debug", "-c", "config"] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 965d65a..772c973 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,9 +8,7 @@ "files.associations": { "*.yaml": "home-assistant" }, - "python.testing.pytestArgs": [ - "tests" - ], + "python.testing.pytestArgs": [], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.analysis.extraPaths": [ @@ -18,5 +16,6 @@ "/workspaces/versatile_thermostat/custom_components/versatile_thermostat", "/home/vscode/.local/lib/python3.12/site-packages/homeassistant" ], + "python.experiments.optOutFrom": ["pythonTestAdapter"], "python.formatting.provider": "none" } \ No newline at end of file diff --git a/README-fr.md b/README-fr.md index ee88db6..d337b3f 100644 --- a/README-fr.md +++ b/README-fr.md @@ -158,7 +158,7 @@ Certains thermostat de type TRV sont réputés incompatibles avec le Versatile T 2. Les thermostats « Homematic » (et éventuellement Homematic IP) sont connus pour rencontrer des problèmes avec le Versatile Thermostat en raison des limitations du protocole RF sous-jacent. Ce problème se produit particulièrement lorsque vous essayez de contrôler plusieurs thermostats Homematic à la fois dans une seule instance de VTherm. Afin de réduire la charge du cycle de service, vous pouvez par ex. regroupez les thermostats avec des procédures spécifiques à Homematic (par exemple en utilisant un thermostat mural) et laissez Versatile Thermostat contrôler uniquement le thermostat mural directement. Une autre option consiste à contrôler un seul thermostat et à propager les changements de mode CVC et de température par un automatisme, 3. les thermostats de type Heatzy qui ne supportent pas les commandes de type set_temperature 4. les thermostats de type Rointe ont tendance a se réveiller tout seul. Le reste fonctionne normalement. -5. les TRV de type Aqara SRTS-A01 qui n'ont pas le retour d'état `hvac_action` permettant de savoir si elle chauffe ou pas. Donc les retours d'état sont faussés, le reste à l'air fonctionnel. +5. les TRV de type Aqara SRTS-A01 et MOES TV01-ZB qui n'ont pas le retour d'état `hvac_action` permettant de savoir si elle chauffe ou pas. Donc les retours d'état sont faussés, le reste à l'air fonctionnel. # Pourquoi une nouvelle implémentation du thermostat ? diff --git a/README.md b/README.md index f872cb2..85d167c 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Some TRV type thermostats are known to be incompatible with the Versatile Thermo 2. "Homematic" (and possible Homematic IP) thermostats are known to have problems with Versatile Thermostats because of limitations of the underlying RF protocol. This problem especially occurs when trying to control several Homematic thermostats at once in one Versatile Thermostat instance. In order to reduce duty cycle load, you may e.g. group thermostats with Homematic-specific procedures (e.g. using a wall thermostat) and let Versatile Thermostat only control the wall thermostat directly. Another option is to control only one thermostat and propagate the changes in HVAC mode and temperature by an automation. 3. Thermostat of type Heatzy which doesn't supports the set_temperature command. 4. Thermostats of type Rointe tends to awake alone even if VTherm turns it off. Others functions works fine. -5. TRV of type Aqara SRTS-A01 which doesn't have the return state `hvac_action` allowing to know if it is heating or not. So return states are not available. Others features, seems to work normally. +5. TRV of type Aqara SRTS-A01 and MOES TV01-ZB which doesn't have the return state `hvac_action` allowing to know if it is heating or not. So return states are not available. Others features, seems to work normally. # Why another thermostat implementation ? diff --git a/custom_components/versatile_thermostat/const.py b/custom_components/versatile_thermostat/const.py index 1db60af..3ba3b88 100644 --- a/custom_components/versatile_thermostat/const.py +++ b/custom_components/versatile_thermostat/const.py @@ -40,13 +40,13 @@ HIDDEN_PRESETS = [PRESET_POWER, PRESET_SECURITY] DOMAIN = "versatile_thermostat" # The order is important. -# NUMBER should be after CLIMATE, PLATFORMS: list[Platform] = [ Platform.SELECT, Platform.CLIMATE, Platform.SENSOR, - Platform.BINARY_SENSOR, + # Number should be after CLIMATE Platform.NUMBER, + Platform.BINARY_SENSOR, ] CONF_HEATER = "heater_entity_id" diff --git a/custom_components/versatile_thermostat/vtherm_api.py b/custom_components/versatile_thermostat/vtherm_api.py index 0489840..9116097 100644 --- a/custom_components/versatile_thermostat/vtherm_api.py +++ b/custom_components/versatile_thermostat/vtherm_api.py @@ -1,4 +1,5 @@ """ The API of Versatile Thermostat""" + import logging from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry @@ -106,10 +107,21 @@ class VersatileThermostatAPI(dict): ): """register the two number entities needed for boiler activation""" self._threshold_number_entity = threshold_number_entity + # If sensor and threshold number are initialized, reload the listener + if self._nb_active_number_entity and self._central_boiler_entity: + self._hass.async_add_job(self.reload_central_boiler_binary_listener) def register_nb_device_active_boiler(self, nb_active_number_entity): """register the two number entities needed for boiler activation""" self._nb_active_number_entity = nb_active_number_entity + if self._threshold_number_entity and self._central_boiler_entity: + self._hass.async_add_job(self.reload_central_boiler_binary_listener) + + async def reload_central_boiler_binary_listener(self): + """Reloads the BinarySensor entity which listen to the number of + active devices and the thresholds entities""" + if self._central_boiler_entity: + await self._central_boiler_entity.listen_nb_active_vtherm_entity() async def reload_central_boiler_entities_list(self): """Reload the central boiler list of entities if a central boiler is used""" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py index d88b6b1..61a1717 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,19 @@ from .commons import ( FULL_CENTRAL_CONFIG_WITH_BOILER, ) +# https://github.com/miketheman/pytest-socket/pull/275 +from pytest_socket import socket_allow_hosts + +# ... + + +# ... +def pytest_runtest_setup(): + socket_allow_hosts( + allowed=["localhost", "127.0.0.1", "::1"], allow_unix_socket=True + ) + + pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name diff --git a/tests/test_central_boiler.py b/tests/test_central_boiler.py index 360d4b4..cab2f5c 100644 --- a/tests/test_central_boiler.py +++ b/tests/test_central_boiler.py @@ -156,23 +156,29 @@ async def test_update_central_boiler_state_simple( await switch1.async_turn_on() switch1.async_write_ha_state() # Wait for state event propagation - await asyncio.sleep(0.1) + await asyncio.sleep(1) assert entity.hvac_action == HVACAction.HEATING - assert mock_service_call.call_count >= 1 + assert mock_service_call.call_count == 2 # Sometimes this test fails - # mock_service_call.assert_has_calls( - # [ - # call.service_call( - # "switch", - # "turn_on", - # service_data={}, - # target={"entity_id": "switch.pompe_chaudiere"}, - # ), - # ] - # ) + mock_service_call.assert_has_calls( + [ + call.service_call( + "switch", + "turn_on", + {"entity_id": "switch.switch1"}, + ), + call( + "switch", + "turn_on", + service_data={}, + target={"entity_id": "switch.pompe_chaudiere"}, + ), + ], + any_order=True, + ) assert mock_send_event.call_count >= 1 mock_send_event.assert_has_calls( diff --git a/tests/test_central_config.py b/tests/test_central_config.py index eaa8ce0..8b4291d 100644 --- a/tests/test_central_config.py +++ b/tests/test_central_config.py @@ -31,8 +31,8 @@ from .commons import * # pylint: disable=wildcard-import, unused-wildcard-impor from .const import * # pylint: disable=wildcard-import, unused-wildcard-import -# @pytest.mark.parametrize("expected_lingering_tasks", [True]) -# @pytest.mark.parametrize("expected_lingering_timers", [True]) +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_add_a_central_config(hass: HomeAssistant, skip_hass_states_is_state): """Tests the clean_central_config_doubon of base_thermostat""" central_config_entry = MockConfigEntry( @@ -303,8 +303,8 @@ async def test_full_over_switch_wo_central_config( entity.remove_thermostat() -# @pytest.mark.parametrize("expected_lingering_tasks", [True]) -# @pytest.mark.parametrize("expected_lingering_timers", [True]) +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_full_over_switch_with_central_config( hass: HomeAssistant, skip_hass_states_is_state, init_central_config ): diff --git a/tests/test_central_mode.py b/tests/test_central_mode.py index bcb8ec2..49a2328 100644 --- a/tests/test_central_mode.py +++ b/tests/test_central_mode.py @@ -170,6 +170,8 @@ async def test_config_with_central_mode_none( assert entity.last_central_mode is None # cause no central config exists +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_switch_change_central_mode_true( hass: HomeAssistant, skip_hass_states_is_state, init_central_config ): @@ -310,6 +312,8 @@ async def test_switch_change_central_mode_true( assert entity.preset_mode == PRESET_COMFORT +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_switch_ac_change_central_mode_true( hass: HomeAssistant, skip_hass_states_is_state, init_central_config ): @@ -444,6 +448,8 @@ async def test_switch_ac_change_central_mode_true( assert entity.preset_mode == PRESET_COMFORT +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_climate_ac_change_central_mode_false( hass: HomeAssistant, skip_hass_states_is_state, init_central_config ): @@ -577,6 +583,8 @@ async def test_climate_ac_change_central_mode_false( assert entity.preset_mode == PRESET_COMFORT +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_climate_ac_only_change_central_mode_true( hass: HomeAssistant, skip_hass_states_is_state, init_central_config ): @@ -734,6 +742,8 @@ async def test_climate_ac_only_change_central_mode_true( assert entity.preset_mode == PRESET_ECO +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_switch_change_central_mode_true_with_window( hass: HomeAssistant, skip_hass_states_is_state, init_central_config ): @@ -889,6 +899,8 @@ async def test_switch_change_central_mode_true_with_window( assert entity.window_state is STATE_OFF +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) async def test_switch_change_central_mode_true_with_cool_only_and_window( hass: HomeAssistant, skip_hass_states_is_state, init_central_config ):