From 349d8f5c28305d817204ab6e5735c29f67f196a6 Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Mon, 12 Feb 2024 03:37:37 -0500 Subject: [PATCH 01/36] Better teardown and setup of Roborock connections (#106092) Co-authored-by: Robert Resch --- homeassistant/components/roborock/__init__.py | 16 +++++----- .../components/roborock/coordinator.py | 3 +- homeassistant/components/roborock/device.py | 29 +++++++++++++++++-- homeassistant/components/roborock/select.py | 4 +-- homeassistant/components/roborock/sensor.py | 4 +-- homeassistant/components/roborock/vacuum.py | 16 +++++----- tests/components/roborock/test_init.py | 2 +- 7 files changed, 47 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/roborock/__init__.py b/homeassistant/components/roborock/__init__.py index 0b4dfa29e78..a5c896f3740 100644 --- a/homeassistant/components/roborock/__init__.py +++ b/homeassistant/components/roborock/__init__.py @@ -115,6 +115,7 @@ async def setup_device( device.name, ) _LOGGER.debug(err) + await mqtt_client.async_release() raise err coordinator = RoborockDataUpdateCoordinator( hass, device, networking, product_info, mqtt_client @@ -125,6 +126,7 @@ async def setup_device( try: await coordinator.async_config_entry_first_refresh() except ConfigEntryNotReady: + await coordinator.release() if isinstance(coordinator.api, RoborockMqttClient): _LOGGER.warning( "Not setting up %s because the we failed to get data for the first time using the online client. " @@ -153,14 +155,10 @@ async def setup_device( async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Handle removal of an entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - if unload_ok: - await asyncio.gather( - *( - coordinator.release() - for coordinator in hass.data[DOMAIN][entry.entry_id].values() - ) - ) + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + release_tasks = set() + for coordinator in hass.data[DOMAIN][entry.entry_id].values(): + release_tasks.add(coordinator.release()) hass.data[DOMAIN].pop(entry.entry_id) - + await asyncio.gather(*release_tasks) return unload_ok diff --git a/homeassistant/components/roborock/coordinator.py b/homeassistant/components/roborock/coordinator.py index cd08cf871d4..d0ed508df4c 100644 --- a/homeassistant/components/roborock/coordinator.py +++ b/homeassistant/components/roborock/coordinator.py @@ -77,7 +77,8 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]): async def release(self) -> None: """Disconnect from API.""" - await self.api.async_disconnect() + await self.api.async_release() + await self.cloud_api.async_release() async def _update_device_prop(self) -> None: """Update device properties.""" diff --git a/homeassistant/components/roborock/device.py b/homeassistant/components/roborock/device.py index 17531f6c627..2921a372e00 100644 --- a/homeassistant/components/roborock/device.py +++ b/homeassistant/components/roborock/device.py @@ -1,5 +1,4 @@ """Support for Roborock device base class.""" - from typing import Any from roborock.api import AttributeCache, RoborockClient @@ -7,6 +6,7 @@ from roborock.cloud_api import RoborockMqttClient from roborock.command_cache import CacheableAttribute from roborock.containers import Consumable, Status from roborock.exceptions import RoborockException +from roborock.roborock_message import RoborockDataProtocol from roborock.roborock_typing import RoborockCommand from homeassistant.exceptions import HomeAssistantError @@ -24,7 +24,10 @@ class RoborockEntity(Entity): _attr_has_entity_name = True def __init__( - self, unique_id: str, device_info: DeviceInfo, api: RoborockClient + self, + unique_id: str, + device_info: DeviceInfo, + api: RoborockClient, ) -> None: """Initialize the coordinated Roborock Device.""" self._attr_unique_id = unique_id @@ -75,6 +78,9 @@ class RoborockCoordinatedEntity( self, unique_id: str, coordinator: RoborockDataUpdateCoordinator, + listener_request: list[RoborockDataProtocol] + | RoborockDataProtocol + | None = None, ) -> None: """Initialize the coordinated Roborock Device.""" RoborockEntity.__init__( @@ -85,6 +91,23 @@ class RoborockCoordinatedEntity( ) CoordinatorEntity.__init__(self, coordinator=coordinator) self._attr_unique_id = unique_id + if isinstance(listener_request, RoborockDataProtocol): + listener_request = [listener_request] + self.listener_requests = listener_request or [] + + async def async_added_to_hass(self) -> None: + """Add listeners when the device is added to hass.""" + await super().async_added_to_hass() + for listener_request in self.listener_requests: + self.api.add_listener( + listener_request, self._update_from_listener, cache=self.api.cache + ) + + async def async_will_remove_from_hass(self) -> None: + """Remove listeners when the device is removed from hass.""" + for listener_request in self.listener_requests: + self.api.remove_listener(listener_request, self._update_from_listener) + await super().async_will_remove_from_hass() @property def _device_status(self) -> Status: @@ -107,7 +130,7 @@ class RoborockCoordinatedEntity( await self.coordinator.async_refresh() return res - def _update_from_listener(self, value: Status | Consumable): + def _update_from_listener(self, value: Status | Consumable) -> None: """Update the status or consumable data from a listener and then write the new entity state.""" if isinstance(value, Status): self.coordinator.roborock_device_info.props.status = value diff --git a/homeassistant/components/roborock/select.py b/homeassistant/components/roborock/select.py index ae5dd12689d..3fdd10c97d5 100644 --- a/homeassistant/components/roborock/select.py +++ b/homeassistant/components/roborock/select.py @@ -107,10 +107,8 @@ class RoborockSelectEntity(RoborockCoordinatedEntity, SelectEntity): ) -> None: """Create a select entity.""" self.entity_description = entity_description - super().__init__(unique_id, coordinator) + super().__init__(unique_id, coordinator, entity_description.protocol_listener) self._attr_options = options - if (protocol := self.entity_description.protocol_listener) is not None: - self.api.add_listener(protocol, self._update_from_listener, self.api.cache) async def async_select_option(self, option: str) -> None: """Set the option.""" diff --git a/homeassistant/components/roborock/sensor.py b/homeassistant/components/roborock/sensor.py index d5258879acb..8d723ec57cd 100644 --- a/homeassistant/components/roborock/sensor.py +++ b/homeassistant/components/roborock/sensor.py @@ -232,10 +232,8 @@ class RoborockSensorEntity(RoborockCoordinatedEntity, SensorEntity): description: RoborockSensorDescription, ) -> None: """Initialize the entity.""" - super().__init__(unique_id, coordinator) self.entity_description = description - if (protocol := self.entity_description.protocol_listener) is not None: - self.api.add_listener(protocol, self._update_from_listener, self.api.cache) + super().__init__(unique_id, coordinator, description.protocol_listener) @property def native_value(self) -> StateType | datetime.datetime: diff --git a/homeassistant/components/roborock/vacuum.py b/homeassistant/components/roborock/vacuum.py index 3b8f0e756b7..dafbb731bd2 100644 --- a/homeassistant/components/roborock/vacuum.py +++ b/homeassistant/components/roborock/vacuum.py @@ -92,14 +92,16 @@ class RoborockVacuum(RoborockCoordinatedEntity, StateVacuumEntity): ) -> None: """Initialize a vacuum.""" StateVacuumEntity.__init__(self) - RoborockCoordinatedEntity.__init__(self, unique_id, coordinator) + RoborockCoordinatedEntity.__init__( + self, + unique_id, + coordinator, + listener_request=[ + RoborockDataProtocol.FAN_POWER, + RoborockDataProtocol.STATE, + ], + ) self._attr_fan_speed_list = self._device_status.fan_power_options - self.api.add_listener( - RoborockDataProtocol.FAN_POWER, self._update_from_listener, self.api.cache - ) - self.api.add_listener( - RoborockDataProtocol.STATE, self._update_from_listener, self.api.cache - ) @property def state(self) -> str | None: diff --git a/tests/components/roborock/test_init.py b/tests/components/roborock/test_init.py index 5d1afaf8f84..7546e80b003 100644 --- a/tests/components/roborock/test_init.py +++ b/tests/components/roborock/test_init.py @@ -18,7 +18,7 @@ async def test_unload_entry( assert len(hass.config_entries.async_entries(DOMAIN)) == 1 assert setup_entry.state is ConfigEntryState.LOADED with patch( - "homeassistant.components.roborock.coordinator.RoborockLocalClient.async_disconnect" + "homeassistant.components.roborock.coordinator.RoborockLocalClient.async_release" ) as mock_disconnect: assert await hass.config_entries.async_unload(setup_entry.entry_id) await hass.async_block_till_done() From b54a3170f03e7fdf156a4f0247d1992cd0e6732f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Grenotton?= Date: Thu, 15 Feb 2024 12:34:29 +0100 Subject: [PATCH 02/36] Fix freebox pairing in bridge mode (#106131) --- .../components/freebox/config_flow.py | 4 +- homeassistant/components/freebox/router.py | 51 +++++++++++-------- tests/components/freebox/conftest.py | 11 ++++ tests/components/freebox/test_config_flow.py | 28 +++++++++- tests/components/freebox/test_router.py | 36 ++++++++++++- 5 files changed, 104 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index 59b5d65710a..7441def7d4d 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -11,7 +11,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN -from .router import get_api +from .router import get_api, get_hosts_list_if_supported _LOGGER = logging.getLogger(__name__) @@ -69,7 +69,7 @@ class FreeboxFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Check permissions await fbx.system.get_config() - await fbx.lan.get_hosts_list() + await get_hosts_list_if_supported(fbx) # Close connection await fbx.close() diff --git a/homeassistant/components/freebox/router.py b/homeassistant/components/freebox/router.py index 15e3b34bd77..3b13fad0572 100644 --- a/homeassistant/components/freebox/router.py +++ b/homeassistant/components/freebox/router.py @@ -64,6 +64,33 @@ async def get_api(hass: HomeAssistant, host: str) -> Freepybox: return Freepybox(APP_DESC, token_file, API_VERSION) +async def get_hosts_list_if_supported( + fbx_api: Freepybox, +) -> tuple[bool, list[dict[str, Any]]]: + """Hosts list is not supported when freebox is configured in bridge mode.""" + supports_hosts: bool = True + fbx_devices: list[dict[str, Any]] = [] + try: + fbx_devices = await fbx_api.lan.get_hosts_list() or [] + except HttpRequestError as err: + if ( + (matcher := re.search(r"Request failed \(APIResponse: (.+)\)", str(err))) + and is_json(json_str := matcher.group(1)) + and (json_resp := json.loads(json_str)).get("error_code") == "nodev" + ): + # No need to retry, Host list not available + supports_hosts = False + _LOGGER.debug( + "Host list is not available using bridge mode (%s)", + json_resp.get("msg"), + ) + + else: + raise err + + return supports_hosts, fbx_devices + + class FreeboxRouter: """Representation of a Freebox router.""" @@ -111,27 +138,9 @@ class FreeboxRouter: # Access to Host list not available in bridge mode, API return error_code 'nodev' if self.supports_hosts: - try: - fbx_devices = await self._api.lan.get_hosts_list() - except HttpRequestError as err: - if ( - ( - matcher := re.search( - r"Request failed \(APIResponse: (.+)\)", str(err) - ) - ) - and is_json(json_str := matcher.group(1)) - and (json_resp := json.loads(json_str)).get("error_code") == "nodev" - ): - # No need to retry, Host list not available - self.supports_hosts = False - _LOGGER.debug( - "Host list is not available using bridge mode (%s)", - json_resp.get("msg"), - ) - - else: - raise err + self.supports_hosts, fbx_devices = await get_hosts_list_if_supported( + self._api + ) # Adds the Freebox itself fbx_devices.append( diff --git a/tests/components/freebox/conftest.py b/tests/components/freebox/conftest.py index 3ba175cbc75..6042248561c 100644 --- a/tests/components/freebox/conftest.py +++ b/tests/components/freebox/conftest.py @@ -112,3 +112,14 @@ def mock_router_bridge_mode(mock_device_registry_devices, router): ) return router + + +@pytest.fixture +def mock_router_bridge_mode_error(mock_device_registry_devices, router): + """Mock a failed connection to Freebox Bridge mode.""" + + router().lan.get_hosts_list = AsyncMock( + side_effect=HttpRequestError("Request failed (APIResponse: some unknown error)") + ) + + return router diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 6a90bbd9ba8..c19b3c3f3b2 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -69,8 +69,8 @@ async def test_zeroconf(hass: HomeAssistant) -> None: assert result["step_id"] == "link" -async def test_link(hass: HomeAssistant, router: Mock) -> None: - """Test linking.""" +async def internal_test_link(hass: HomeAssistant) -> None: + """Test linking internal, common to both router modes.""" with patch( "homeassistant.components.freebox.async_setup_entry", return_value=True, @@ -91,6 +91,30 @@ async def test_link(hass: HomeAssistant, router: Mock) -> None: assert len(mock_setup_entry.mock_calls) == 1 +async def test_link(hass: HomeAssistant, router: Mock) -> None: + """Test link with standard router mode.""" + await internal_test_link(hass) + + +async def test_link_bridge_mode(hass: HomeAssistant, router_bridge_mode: Mock) -> None: + """Test linking for a freebox in bridge mode.""" + await internal_test_link(hass) + + +async def test_link_bridge_mode_error( + hass: HomeAssistant, mock_router_bridge_mode_error: Mock +) -> None: + """Test linking for a freebox in bridge mode, unknown error received from API.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}, + ) + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["errors"] == {"base": "cannot_connect"} + + async def test_abort_if_already_setup(hass: HomeAssistant) -> None: """Test we abort if component is already setup.""" MockConfigEntry( diff --git a/tests/components/freebox/test_router.py b/tests/components/freebox/test_router.py index 572c168e665..88cf56de2bb 100644 --- a/tests/components/freebox/test_router.py +++ b/tests/components/freebox/test_router.py @@ -1,7 +1,11 @@ """Tests for the Freebox utility methods.""" import json +from unittest.mock import Mock -from homeassistant.components.freebox.router import is_json +from freebox_api.exceptions import HttpRequestError +import pytest + +from homeassistant.components.freebox.router import get_hosts_list_if_supported, is_json from .const import DATA_LAN_GET_HOSTS_LIST_MODE_BRIDGE, DATA_WIFI_GET_GLOBAL_CONFIG @@ -20,3 +24,33 @@ async def test_is_json() -> None: assert not is_json("") assert not is_json("XXX") assert not is_json("{XXX}") + + +async def test_get_hosts_list_if_supported( + router: Mock, +) -> None: + """In router mode, get_hosts_list is supported and list is filled.""" + supports_hosts, fbx_devices = await get_hosts_list_if_supported(router()) + assert supports_hosts is True + # List must not be empty; but it's content depends on how many unit tests are executed... + assert fbx_devices + assert "d633d0c8-958c-43cc-e807-d881b076924b" in str(fbx_devices) + + +async def test_get_hosts_list_if_supported_bridge( + router_bridge_mode: Mock, +) -> None: + """In bridge mode, get_hosts_list is NOT supported and list is empty.""" + supports_hosts, fbx_devices = await get_hosts_list_if_supported( + router_bridge_mode() + ) + assert supports_hosts is False + assert fbx_devices == [] + + +async def test_get_hosts_list_if_supported_bridge_error( + mock_router_bridge_mode_error: Mock, +) -> None: + """Other exceptions must be propagated.""" + with pytest.raises(HttpRequestError): + await get_hosts_list_if_supported(mock_router_bridge_mode_error()) From e825bcc28202e3b4630ee021bf0628d51a3371cb Mon Sep 17 00:00:00 2001 From: Christophe Gagnier Date: Fri, 9 Feb 2024 02:41:48 -0500 Subject: [PATCH 03/36] Update pytechnove to 1.2.2 (#110074) --- .../components/technove/manifest.json | 2 +- .../components/technove/strings.json | 4 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../technove/fixtures/station_bad_status.json | 27 +++++++++++++++ .../technove/snapshots/test_sensor.ambr | 4 +++ tests/components/technove/test_sensor.py | 33 +++++++++++++++++-- 7 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 tests/components/technove/fixtures/station_bad_status.json diff --git a/homeassistant/components/technove/manifest.json b/homeassistant/components/technove/manifest.json index 33739bbd867..c63151560f8 100644 --- a/homeassistant/components/technove/manifest.json +++ b/homeassistant/components/technove/manifest.json @@ -6,6 +6,6 @@ "documentation": "https://www.home-assistant.io/integrations/technove", "integration_type": "device", "iot_class": "local_polling", - "requirements": ["python-technove==1.2.1"], + "requirements": ["python-technove==1.2.2"], "zeroconf": ["_technove-stations._tcp.local."] } diff --git a/homeassistant/components/technove/strings.json b/homeassistant/components/technove/strings.json index 8a850ee610c..f38bf61d8ed 100644 --- a/homeassistant/components/technove/strings.json +++ b/homeassistant/components/technove/strings.json @@ -63,7 +63,9 @@ "state": { "unplugged": "Unplugged", "plugged_waiting": "Plugged, waiting", - "plugged_charging": "Plugged, charging" + "plugged_charging": "Plugged, charging", + "out_of_activation_period": "Out of activation period", + "high_charge_period": "High charge period" } } } diff --git a/requirements_all.txt b/requirements_all.txt index 0c084d0b6b9..e4cc1e4c382 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2284,7 +2284,7 @@ python-songpal==0.16.1 python-tado==0.17.4 # homeassistant.components.technove -python-technove==1.2.1 +python-technove==1.2.2 # homeassistant.components.telegram_bot python-telegram-bot==13.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index de460cbd0c4..c112d7ea3f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1751,7 +1751,7 @@ python-songpal==0.16.1 python-tado==0.17.4 # homeassistant.components.technove -python-technove==1.2.1 +python-technove==1.2.2 # homeassistant.components.telegram_bot python-telegram-bot==13.1 diff --git a/tests/components/technove/fixtures/station_bad_status.json b/tests/components/technove/fixtures/station_bad_status.json new file mode 100644 index 00000000000..ad24ad43211 --- /dev/null +++ b/tests/components/technove/fixtures/station_bad_status.json @@ -0,0 +1,27 @@ +{ + "voltageIn": 238, + "voltageOut": 238, + "maxStationCurrent": 32, + "maxCurrent": 24, + "current": 23.75, + "network_ssid": "Connecting...", + "id": "AA:AA:AA:AA:AA:BB", + "auto_charge": true, + "highChargePeriodActive": false, + "normalPeriodActive": false, + "maxChargePourcentage": 0.9, + "isBatteryProtected": false, + "inSharingMode": true, + "energySession": 12.34, + "energyTotal": 1234, + "version": "1.82", + "rssi": -82, + "name": "TechnoVE Station", + "lastCharge": "1701072080,0,17.39\n", + "time": 1701000000, + "isUpToDate": true, + "isSessionActive": true, + "conflictInSharingConfig": false, + "isStaticIp": false, + "status": 12345 +} diff --git a/tests/components/technove/snapshots/test_sensor.ambr b/tests/components/technove/snapshots/test_sensor.ambr index d38b08631cc..cbaf8813604 100644 --- a/tests/components/technove/snapshots/test_sensor.ambr +++ b/tests/components/technove/snapshots/test_sensor.ambr @@ -297,6 +297,8 @@ 'unplugged', 'plugged_waiting', 'plugged_charging', + 'out_of_activation_period', + 'high_charge_period', ]), }), 'config_entry_id': , @@ -333,6 +335,8 @@ 'unplugged', 'plugged_waiting', 'plugged_charging', + 'out_of_activation_period', + 'high_charge_period', ]), }), 'context': , diff --git a/tests/components/technove/test_sensor.py b/tests/components/technove/test_sensor.py index 5215f62c517..c44aab8ecc4 100644 --- a/tests/components/technove/test_sensor.py +++ b/tests/components/technove/test_sensor.py @@ -5,15 +5,20 @@ from unittest.mock import MagicMock from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion -from technove import Status, TechnoVEError +from technove import Station, Status, TechnoVEError +from homeassistant.components.technove.const import DOMAIN from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from . import setup_with_selected_platforms -from tests.common import MockConfigEntry, async_fire_time_changed +from tests.common import ( + MockConfigEntry, + async_fire_time_changed, + load_json_object_fixture, +) @pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_technove") @@ -93,3 +98,27 @@ async def test_sensor_update_failure( await hass.async_block_till_done() assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + +@pytest.mark.usefixtures("init_integration") +async def test_sensor_unknown_status( + hass: HomeAssistant, + mock_technove: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Test coordinator update failure.""" + entity_id = "sensor.technove_station_status" + + assert hass.states.get(entity_id).state == Status.PLUGGED_CHARGING.value + + mock_technove.update.return_value = Station( + load_json_object_fixture("station_bad_status.json", DOMAIN) + ) + + freezer.tick(timedelta(minutes=5, seconds=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNKNOWN + # Other sensors should still be available + assert hass.states.get("sensor.technove_station_total_energy_usage").state == "1234" From 5a87cde71e90afec6d09762066516271b513e694 Mon Sep 17 00:00:00 2001 From: Malte Franken Date: Fri, 9 Feb 2024 21:31:16 +1100 Subject: [PATCH 04/36] Bump aio-geojson-usgs-earthquakes to 0.3 (#110084) --- homeassistant/components/usgs_earthquakes_feed/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/usgs_earthquakes_feed/manifest.json b/homeassistant/components/usgs_earthquakes_feed/manifest.json index 6dbe43cb4e3..ffb9412703f 100644 --- a/homeassistant/components/usgs_earthquakes_feed/manifest.json +++ b/homeassistant/components/usgs_earthquakes_feed/manifest.json @@ -6,5 +6,5 @@ "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["aio_geojson_usgs_earthquakes"], - "requirements": ["aio-geojson-usgs-earthquakes==0.2"] + "requirements": ["aio-geojson-usgs-earthquakes==0.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index e4cc1e4c382..4b2ce1b59ad 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -179,7 +179,7 @@ aio-geojson-geonetnz-volcano==0.9 aio-geojson-nsw-rfs-incidents==0.7 # homeassistant.components.usgs_earthquakes_feed -aio-geojson-usgs-earthquakes==0.2 +aio-geojson-usgs-earthquakes==0.3 # homeassistant.components.gdacs aio-georss-gdacs==0.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c112d7ea3f9..618fcb9c356 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -158,7 +158,7 @@ aio-geojson-geonetnz-volcano==0.9 aio-geojson-nsw-rfs-incidents==0.7 # homeassistant.components.usgs_earthquakes_feed -aio-geojson-usgs-earthquakes==0.2 +aio-geojson-usgs-earthquakes==0.3 # homeassistant.components.gdacs aio-georss-gdacs==0.9 From 54270df2173c7c4dd06806da4827a806f7dc9deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?A=20Bj=C3=B6rck?= Date: Fri, 9 Feb 2024 16:09:45 +0100 Subject: [PATCH 05/36] Bump yalexs to 1.11.1, fixing camera snapshots from Yale Home (#110089) --- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index 97963b19378..eb9d5237585 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -28,5 +28,5 @@ "documentation": "https://www.home-assistant.io/integrations/august", "iot_class": "cloud_push", "loggers": ["pubnub", "yalexs"], - "requirements": ["yalexs==1.10.0", "yalexs-ble==2.4.1"] + "requirements": ["yalexs==1.11.1", "yalexs-ble==2.4.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 4b2ce1b59ad..f959175cfdd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2880,7 +2880,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.1 # homeassistant.components.august -yalexs==1.10.0 +yalexs==1.11.1 # homeassistant.components.yeelight yeelight==0.7.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 618fcb9c356..270fbfc1cd5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2206,7 +2206,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.1 # homeassistant.components.august -yalexs==1.10.0 +yalexs==1.11.1 # homeassistant.components.yeelight yeelight==0.7.14 From c79bc17d17b6c2831c0efe0be722a242139cf4bf Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 9 Feb 2024 20:05:01 +0100 Subject: [PATCH 06/36] Fix typo in sensor icons configuration (#110133) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/sensor/icons.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/icons.json b/homeassistant/components/sensor/icons.json index 24245d9bf03..f23826cfe95 100644 --- a/homeassistant/components/sensor/icons.json +++ b/homeassistant/components/sensor/icons.json @@ -117,7 +117,7 @@ "speed": { "default": "mdi:speedometer" }, - "sulfur_dioxide": { + "sulphur_dioxide": { "default": "mdi:molecule" }, "temperature": { From 87bd67656b4f2482caa9e0f19d2f45acc073f4ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 10 Feb 2024 09:29:49 -0600 Subject: [PATCH 07/36] Only schedule august activity update when a new activity is seen (#110141) --- homeassistant/components/august/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index 624121b8828..ea27b58d34c 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -249,10 +249,11 @@ class AugustData(AugustSubscriberMixin): device = self.get_device_detail(device_id) activities = activities_from_pubnub_message(device, date_time, message) activity_stream = self.activity_stream - if activities: - activity_stream.async_process_newer_device_activities(activities) + if activities and activity_stream.async_process_newer_device_activities( + activities + ): self.async_signal_device_id_update(device.device_id) - activity_stream.async_schedule_house_id_refresh(device.house_id) + activity_stream.async_schedule_house_id_refresh(device.house_id) @callback def async_stop(self) -> None: From 2a51377cef7194cfe71b0a35219452fdd44bd565 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 9 Feb 2024 16:30:42 -0600 Subject: [PATCH 08/36] Bump yalexs to 1.11.2 (#110144) changelog: https://github.com/bdraco/yalexs/compare/v1.11.1...v1.11.2 --- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index eb9d5237585..a1a7adb4ede 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -28,5 +28,5 @@ "documentation": "https://www.home-assistant.io/integrations/august", "iot_class": "cloud_push", "loggers": ["pubnub", "yalexs"], - "requirements": ["yalexs==1.11.1", "yalexs-ble==2.4.1"] + "requirements": ["yalexs==1.11.2", "yalexs-ble==2.4.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index f959175cfdd..76d0bfb292b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2880,7 +2880,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.1 # homeassistant.components.august -yalexs==1.11.1 +yalexs==1.11.2 # homeassistant.components.yeelight yeelight==0.7.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 270fbfc1cd5..9505b883aa6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2206,7 +2206,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.1 # homeassistant.components.august -yalexs==1.11.1 +yalexs==1.11.2 # homeassistant.components.yeelight yeelight==0.7.14 From 973a13abfa350e50f63f63d43a301b5c1a843108 Mon Sep 17 00:00:00 2001 From: Adam Goode Date: Mon, 12 Feb 2024 09:33:03 -0500 Subject: [PATCH 09/36] Properly report cover positions to prometheus (#110157) --- homeassistant/components/prometheus/__init__.py | 9 ++++++--- tests/components/prometheus/test_init.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index e17ae1190a4..86163704797 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -21,7 +21,10 @@ from homeassistant.components.climate import ( ATTR_TARGET_TEMP_LOW, HVACAction, ) -from homeassistant.components.cover import ATTR_POSITION, ATTR_TILT_POSITION +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, + ATTR_CURRENT_TILT_POSITION, +) from homeassistant.components.http import HomeAssistantView from homeassistant.components.humidifier import ATTR_AVAILABLE_MODES, ATTR_HUMIDITY from homeassistant.components.light import ATTR_BRIGHTNESS @@ -437,7 +440,7 @@ class PrometheusMetrics: float(cover_state == state.state) ) - position = state.attributes.get(ATTR_POSITION) + position = state.attributes.get(ATTR_CURRENT_POSITION) if position is not None: position_metric = self._metric( "cover_position", @@ -446,7 +449,7 @@ class PrometheusMetrics: ) position_metric.labels(**self._labels(state)).set(float(position)) - tilt_position = state.attributes.get(ATTR_TILT_POSITION) + tilt_position = state.attributes.get(ATTR_CURRENT_TILT_POSITION) if tilt_position is not None: tilt_position_metric = self._metric( "cover_tilt_position", diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index af2f2ba5784..7ee534f91ce 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -1352,7 +1352,7 @@ async def cover_fixture( suggested_object_id="position_shade", original_name="Position Shade", ) - cover_position_attributes = {cover.ATTR_POSITION: 50} + cover_position_attributes = {cover.ATTR_CURRENT_POSITION: 50} set_state_with_entry(hass, cover_position, STATE_OPEN, cover_position_attributes) data["cover_position"] = cover_position @@ -1363,7 +1363,7 @@ async def cover_fixture( suggested_object_id="tilt_position_shade", original_name="Tilt Position Shade", ) - cover_tilt_position_attributes = {cover.ATTR_TILT_POSITION: 50} + cover_tilt_position_attributes = {cover.ATTR_CURRENT_TILT_POSITION: 50} set_state_with_entry( hass, cover_tilt_position, STATE_OPEN, cover_tilt_position_attributes ) From 58b28e6df126b08a2f8f6d22f24a35de1840c79a Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Sat, 10 Feb 2024 20:09:02 +0100 Subject: [PATCH 10/36] Fix device class repairs issues placeholders in Group (#110181) fix translation placeholders --- homeassistant/components/group/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/group/sensor.py b/homeassistant/components/group/sensor.py index 47695a275fc..8e1a0a24207 100644 --- a/homeassistant/components/group/sensor.py +++ b/homeassistant/components/group/sensor.py @@ -476,7 +476,7 @@ class SensorGroup(GroupEntity, SensorEntity): translation_placeholders={ "entity_id": self.entity_id, "source_entities": ", ".join(self._entity_ids), - "state_classes:": ", ".join(state_classes), + "state_classes": ", ".join(state_classes), }, ) return None @@ -519,7 +519,7 @@ class SensorGroup(GroupEntity, SensorEntity): translation_placeholders={ "entity_id": self.entity_id, "source_entities": ", ".join(self._entity_ids), - "device_classes:": ", ".join(device_classes), + "device_classes": ", ".join(device_classes), }, ) return None From edb69fb095b36c355d439d903a0ea66dfc765d2e Mon Sep 17 00:00:00 2001 From: DustyArmstrong Date: Tue, 13 Feb 2024 16:24:08 +0000 Subject: [PATCH 11/36] Bump datapoint to 0.9.9 + re-enable Met Office Integration (#110206) --- homeassistant/components/metoffice/__init__.py | 12 +++--------- homeassistant/components/metoffice/data.py | 8 +++----- homeassistant/components/metoffice/helpers.py | 11 ++++------- homeassistant/components/metoffice/manifest.json | 3 +-- homeassistant/components/metoffice/sensor.py | 2 +- requirements_all.txt | 3 +++ requirements_test_all.txt | 3 +++ tests/components/metoffice/conftest.py | 8 +------- 8 files changed, 19 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/metoffice/__init__.py b/homeassistant/components/metoffice/__init__.py index e00215f6073..a658de9a024 100644 --- a/homeassistant/components/metoffice/__init__.py +++ b/homeassistant/components/metoffice/__init__.py @@ -4,9 +4,10 @@ from __future__ import annotations import asyncio import logging import re -import sys from typing import Any +import datapoint + from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_API_KEY, @@ -16,7 +17,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator @@ -34,9 +35,6 @@ from .const import ( from .data import MetOfficeData from .helpers import fetch_data, fetch_site -if sys.version_info < (3, 12): - import datapoint - _LOGGER = logging.getLogger(__name__) PLATFORMS = [Platform.SENSOR, Platform.WEATHER] @@ -44,10 +42,6 @@ PLATFORMS = [Platform.SENSOR, Platform.WEATHER] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a Met Office entry.""" - if sys.version_info >= (3, 12): - raise HomeAssistantError( - "Met Office is not supported on Python 3.12. Please use Python 3.11." - ) latitude = entry.data[CONF_LATITUDE] longitude = entry.data[CONF_LONGITUDE] diff --git a/homeassistant/components/metoffice/data.py b/homeassistant/components/metoffice/data.py index 8512dd4c7a6..c6bb2b4c01b 100644 --- a/homeassistant/components/metoffice/data.py +++ b/homeassistant/components/metoffice/data.py @@ -2,12 +2,10 @@ from __future__ import annotations from dataclasses import dataclass -import sys -if sys.version_info < (3, 12): - from datapoint.Forecast import Forecast - from datapoint.Site import Site - from datapoint.Timestep import Timestep +from datapoint.Forecast import Forecast +from datapoint.Site import Site +from datapoint.Timestep import Timestep @dataclass diff --git a/homeassistant/components/metoffice/helpers.py b/homeassistant/components/metoffice/helpers.py index 389462d573a..5b698bf19da 100644 --- a/homeassistant/components/metoffice/helpers.py +++ b/homeassistant/components/metoffice/helpers.py @@ -2,7 +2,9 @@ from __future__ import annotations import logging -import sys + +import datapoint +from datapoint.Site import Site from homeassistant.helpers.update_coordinator import UpdateFailed from homeassistant.util.dt import utcnow @@ -10,11 +12,6 @@ from homeassistant.util.dt import utcnow from .const import MODE_3HOURLY from .data import MetOfficeData -if sys.version_info < (3, 12): - import datapoint - from datapoint.Site import Site - - _LOGGER = logging.getLogger(__name__) @@ -34,7 +31,7 @@ def fetch_site( def fetch_data(connection: datapoint.Manager, site: Site, mode: str) -> MetOfficeData: """Fetch weather and forecast from Datapoint API.""" try: - forecast = connection.get_forecast_for_site(site.id, mode) + forecast = connection.get_forecast_for_site(site.location_id, mode) except (ValueError, datapoint.exceptions.APIException) as err: _LOGGER.error("Check Met Office connection: %s", err.args) raise UpdateFailed from err diff --git a/homeassistant/components/metoffice/manifest.json b/homeassistant/components/metoffice/manifest.json index 401f2c9d265..17643d7e061 100644 --- a/homeassistant/components/metoffice/manifest.json +++ b/homeassistant/components/metoffice/manifest.json @@ -3,9 +3,8 @@ "name": "Met Office", "codeowners": ["@MrHarcombe", "@avee87"], "config_flow": true, - "disabled": "Integration library not compatible with Python 3.12", "documentation": "https://www.home-assistant.io/integrations/metoffice", "iot_class": "cloud_polling", "loggers": ["datapoint"], - "requirements": ["datapoint==0.9.8;python_version<'3.12'"] + "requirements": ["datapoint==0.9.9"] } diff --git a/homeassistant/components/metoffice/sensor.py b/homeassistant/components/metoffice/sensor.py index 371c396a829..84a51a0d584 100644 --- a/homeassistant/components/metoffice/sensor.py +++ b/homeassistant/components/metoffice/sensor.py @@ -251,6 +251,6 @@ class MetOfficeCurrentSensor( return { ATTR_LAST_UPDATE: self.coordinator.data.now.date, ATTR_SENSOR_ID: self.entity_description.key, - ATTR_SITE_ID: self.coordinator.data.site.id, + ATTR_SITE_ID: self.coordinator.data.site.location_id, ATTR_SITE_NAME: self.coordinator.data.site.name, } diff --git a/requirements_all.txt b/requirements_all.txt index 76d0bfb292b..ed8bc12313a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -671,6 +671,9 @@ crownstone-uart==2.1.0 # homeassistant.components.datadog datadog==0.15.0 +# homeassistant.components.metoffice +datapoint==0.9.9 + # homeassistant.components.bluetooth dbus-fast==2.21.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9505b883aa6..a68230d4019 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -552,6 +552,9 @@ crownstone-uart==2.1.0 # homeassistant.components.datadog datadog==0.15.0 +# homeassistant.components.metoffice +datapoint==0.9.9 + # homeassistant.components.bluetooth dbus-fast==2.21.1 diff --git a/tests/components/metoffice/conftest.py b/tests/components/metoffice/conftest.py index 117bfe417e3..b1d1c9f508e 100644 --- a/tests/components/metoffice/conftest.py +++ b/tests/components/metoffice/conftest.py @@ -1,15 +1,9 @@ """Fixtures for Met Office weather integration tests.""" from unittest.mock import patch +from datapoint.exceptions import APIException import pytest -# All tests are marked as disabled, as the integration is disabled in the -# integration manifest. `datapoint` isn't compatible with Python 3.12 -# -# from datapoint.exceptions import APIException -APIException = Exception -collect_ignore_glob = ["test_*.py"] - @pytest.fixture def mock_simple_manager_fail(): From ad761bb2de8e19c91c3bf84fda3f85eeb01ea9da Mon Sep 17 00:00:00 2001 From: David Bonnes Date: Sun, 11 Feb 2024 11:31:51 +0000 Subject: [PATCH 12/36] Bump evohome-async to 0.4.19 (#110225) bump client to 0.4.19 --- homeassistant/components/evohome/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/evohome/manifest.json b/homeassistant/components/evohome/manifest.json index 0c9bb44d06a..6b893dc8f48 100644 --- a/homeassistant/components/evohome/manifest.json +++ b/homeassistant/components/evohome/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/evohome", "iot_class": "cloud_polling", "loggers": ["evohomeasync", "evohomeasync2"], - "requirements": ["evohome-async==0.4.18"] + "requirements": ["evohome-async==0.4.19"] } diff --git a/requirements_all.txt b/requirements_all.txt index ed8bc12313a..83a7eb0310a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -821,7 +821,7 @@ eufylife-ble-client==0.1.8 # evdev==1.6.1 # homeassistant.components.evohome -evohome-async==0.4.18 +evohome-async==0.4.19 # homeassistant.components.faa_delays faadelays==2023.9.1 From a0ae18a1b6f79f5b5e212a5b2000828c6781830c Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Sun, 11 Feb 2024 09:54:50 +0100 Subject: [PATCH 13/36] Fix state classes issue translation in Group (#110238) Fix state classes translation --- homeassistant/components/group/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/group/strings.json b/homeassistant/components/group/strings.json index 25ae20da995..ba571bb1008 100644 --- a/homeassistant/components/group/strings.json +++ b/homeassistant/components/group/strings.json @@ -265,7 +265,7 @@ }, "state_classes_not_matching": { "title": "State classes is not correct", - "description": "Device classes `{state_classes}` on source entities `{source_entities}` needs to be same for sensor group `{entity_id}`.\n\nPlease correct the state classes on the source entities and reload the group sensor to fix this issue." + "description": "State classes `{state_classes}` on source entities `{source_entities}` needs to be same for sensor group `{entity_id}`.\n\nPlease correct the state classes on the source entities and reload the group sensor to fix this issue." } } } From 6a0c3f1b4fdcb5966e36d8d18b7bd983c95723a1 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 12 Feb 2024 15:03:29 +0100 Subject: [PATCH 14/36] Handle no data error in Electricity Maps config flow (#110259) Co-authored-by: Viktor Andersson <30777521+VIKTORVAV99@users.noreply.github.com> --- homeassistant/components/co2signal/config_flow.py | 3 +++ homeassistant/components/co2signal/strings.json | 5 +---- tests/components/co2signal/test_config_flow.py | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/co2signal/config_flow.py b/homeassistant/components/co2signal/config_flow.py index a678868ee18..a952f016671 100644 --- a/homeassistant/components/co2signal/config_flow.py +++ b/homeassistant/components/co2signal/config_flow.py @@ -8,6 +8,7 @@ from aioelectricitymaps import ( ElectricityMaps, ElectricityMapsError, ElectricityMapsInvalidTokenError, + ElectricityMapsNoDataError, ) import voluptuous as vol @@ -151,6 +152,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await fetch_latest_carbon_intensity(self.hass, em, data) except ElectricityMapsInvalidTokenError: errors["base"] = "invalid_auth" + except ElectricityMapsNoDataError: + errors["base"] = "no_data" except ElectricityMapsError: errors["base"] = "unknown" else: diff --git a/homeassistant/components/co2signal/strings.json b/homeassistant/components/co2signal/strings.json index 89289dd816d..7444cde73d7 100644 --- a/homeassistant/components/co2signal/strings.json +++ b/homeassistant/components/co2signal/strings.json @@ -28,12 +28,9 @@ "error": { "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "unknown": "[%key:common::config_flow::error::unknown%]", - "api_ratelimit": "API Ratelimit exceeded" + "no_data": "No data is available for the location you have selected." }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "unknown": "[%key:common::config_flow::error::unknown%]", - "api_ratelimit": "[%key:component::co2signal::config::error::api_ratelimit%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, diff --git a/tests/components/co2signal/test_config_flow.py b/tests/components/co2signal/test_config_flow.py index 29ce783f33a..518a747f852 100644 --- a/tests/components/co2signal/test_config_flow.py +++ b/tests/components/co2signal/test_config_flow.py @@ -5,6 +5,7 @@ from aioelectricitymaps import ( ElectricityMapsConnectionError, ElectricityMapsError, ElectricityMapsInvalidTokenError, + ElectricityMapsNoDataError, ) import pytest @@ -139,12 +140,9 @@ async def test_form_country(hass: HomeAssistant) -> None: ), (ElectricityMapsError("Something else"), "unknown"), (ElectricityMapsConnectionError("Boom"), "unknown"), + (ElectricityMapsNoDataError("I have no data"), "no_data"), ], - ids=[ - "invalid auth", - "generic error", - "json decode error", - ], + ids=["invalid auth", "generic error", "json decode error", "no data error"], ) async def test_form_error_handling( hass: HomeAssistant, From e7068ae134bc2b6228ce92ab44cf2594ad771295 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 11 Feb 2024 15:11:31 -0500 Subject: [PATCH 15/36] Fix cpu percentage in System Monitor (#110268) * Fix cpu percentage in System Monitor * Tests --- .../components/systemmonitor/coordinator.py | 13 ++++--- .../components/systemmonitor/sensor.py | 4 ++- tests/components/systemmonitor/test_sensor.py | 35 +++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/systemmonitor/coordinator.py b/homeassistant/components/systemmonitor/coordinator.py index 9143d31f163..bf625eacf9a 100644 --- a/homeassistant/components/systemmonitor/coordinator.py +++ b/homeassistant/components/systemmonitor/coordinator.py @@ -1,4 +1,5 @@ """DataUpdateCoordinators for the System monitor integration.""" + from __future__ import annotations from abc import abstractmethod @@ -43,7 +44,8 @@ dataT = TypeVar( | sswap | VirtualMemory | tuple[float, float, float] - | sdiskusage, + | sdiskusage + | None, ) @@ -130,12 +132,15 @@ class SystemMonitorLoadCoordinator(MonitorCoordinator[tuple[float, float, float] return os.getloadavg() -class SystemMonitorProcessorCoordinator(MonitorCoordinator[float]): +class SystemMonitorProcessorCoordinator(MonitorCoordinator[float | None]): """A System monitor Processor Data Update Coordinator.""" - def update_data(self) -> float: + def update_data(self) -> float | None: """Fetch data.""" - return psutil.cpu_percent(interval=None) + cpu_percent = psutil.cpu_percent(interval=None) + if cpu_percent > 0.0: + return cpu_percent + return None class SystemMonitorBootTimeCoordinator(MonitorCoordinator[datetime]): diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index e751ffebb12..813104e2de3 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -344,7 +344,9 @@ SENSOR_TYPES: dict[str, SysMonitorSensorEntityDescription[Any]] = { native_unit_of_measurement=PERCENTAGE, icon=get_cpu_icon(), state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda entity: round(entity.coordinator.data), + value_fn=lambda entity: ( + round(entity.coordinator.data) if entity.coordinator.data else None + ), ), "processor_temperature": SysMonitorSensorEntityDescription[ dict[str, list[shwtemp]] diff --git a/tests/components/systemmonitor/test_sensor.py b/tests/components/systemmonitor/test_sensor.py index 8beeddbefdc..b1b06a378f7 100644 --- a/tests/components/systemmonitor/test_sensor.py +++ b/tests/components/systemmonitor/test_sensor.py @@ -1,4 +1,5 @@ """Test System Monitor sensor.""" + from datetime import timedelta import socket from unittest.mock import Mock, patch @@ -429,3 +430,37 @@ async def test_exception_handling_disk_sensor( assert disk_sensor is not None assert disk_sensor.state == "70.0" assert disk_sensor.attributes["unit_of_measurement"] == "%" + + +async def test_cpu_percentage_is_zero_returns_unknown( + hass: HomeAssistant, + entity_registry_enabled_by_default: None, + mock_psutil: Mock, + mock_added_config_entry: ConfigEntry, + caplog: pytest.LogCaptureFixture, + freezer: FrozenDateTimeFactory, +) -> None: + """Test the sensor.""" + cpu_sensor = hass.states.get("sensor.system_monitor_processor_use") + assert cpu_sensor is not None + assert cpu_sensor.state == "10" + + mock_psutil.cpu_percent.return_value = 0.0 + + freezer.tick(timedelta(minutes=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + cpu_sensor = hass.states.get("sensor.system_monitor_processor_use") + assert cpu_sensor is not None + assert cpu_sensor.state == STATE_UNKNOWN + + mock_psutil.cpu_percent.return_value = 15.0 + + freezer.tick(timedelta(minutes=1)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + cpu_sensor = hass.states.get("sensor.system_monitor_processor_use") + assert cpu_sensor is not None + assert cpu_sensor.state == "15" From 96a10e76b88cc85e00b34cbe84c99c9ac9f127fe Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 11 Feb 2024 20:06:47 +0100 Subject: [PATCH 16/36] Bump aiopegelonline to 0.0.8 (#110274) --- homeassistant/components/pegel_online/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/pegel_online/manifest.json b/homeassistant/components/pegel_online/manifest.json index 9546017d4ff..f38d320b454 100644 --- a/homeassistant/components/pegel_online/manifest.json +++ b/homeassistant/components/pegel_online/manifest.json @@ -7,5 +7,5 @@ "integration_type": "service", "iot_class": "cloud_polling", "loggers": ["aiopegelonline"], - "requirements": ["aiopegelonline==0.0.6"] + "requirements": ["aiopegelonline==0.0.8"] } diff --git a/requirements_all.txt b/requirements_all.txt index 83a7eb0310a..9d656109489 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -318,7 +318,7 @@ aiooncue==0.3.5 aioopenexchangerates==0.4.0 # homeassistant.components.pegel_online -aiopegelonline==0.0.6 +aiopegelonline==0.0.8 # homeassistant.components.acmeda aiopulse==0.4.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a68230d4019..0bc515e4d1e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -291,7 +291,7 @@ aiooncue==0.3.5 aioopenexchangerates==0.4.0 # homeassistant.components.pegel_online -aiopegelonline==0.0.6 +aiopegelonline==0.0.8 # homeassistant.components.acmeda aiopulse==0.4.4 From 159fab7025f8aaeea50a0777a85e393e0e2bd40d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 11 Feb 2024 13:09:11 -0600 Subject: [PATCH 17/36] Bump PySwitchbot to 0.45.0 (#110275) --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 2f92726a6da..401d85e7376 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -39,5 +39,5 @@ "documentation": "https://www.home-assistant.io/integrations/switchbot", "iot_class": "local_push", "loggers": ["switchbot"], - "requirements": ["PySwitchbot==0.44.0"] + "requirements": ["PySwitchbot==0.45.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9d656109489..3656e1c980e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -96,7 +96,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.44.0 +PySwitchbot==0.45.0 # homeassistant.components.switchmate PySwitchmate==0.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0bc515e4d1e..dd8a1f4514a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -84,7 +84,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.44.0 +PySwitchbot==0.45.0 # homeassistant.components.syncthru PySyncThru==0.7.10 From da6c571e6554d3f62c1140875197d0c0431525e0 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Sun, 11 Feb 2024 20:01:16 +0100 Subject: [PATCH 18/36] Update xknxproject to 3.6.0 (#110282) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 397af9ac181..6a304f7de5f 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -12,7 +12,7 @@ "quality_scale": "platinum", "requirements": [ "xknx==2.12.0", - "xknxproject==3.5.0", + "xknxproject==3.6.0", "knx-frontend==2024.1.20.105944" ] } diff --git a/requirements_all.txt b/requirements_all.txt index 3656e1c980e..28d688fbc5c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2862,7 +2862,7 @@ xiaomi-ble==0.23.1 xknx==2.12.0 # homeassistant.components.knx -xknxproject==3.5.0 +xknxproject==3.6.0 # homeassistant.components.bluesound # homeassistant.components.fritz diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dd8a1f4514a..4222538dd14 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2191,7 +2191,7 @@ xiaomi-ble==0.23.1 xknx==2.12.0 # homeassistant.components.knx -xknxproject==3.5.0 +xknxproject==3.6.0 # homeassistant.components.bluesound # homeassistant.components.fritz From 003673cd29360d3ee5fb859d6d55855f539cd904 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sun, 11 Feb 2024 21:16:06 +0100 Subject: [PATCH 19/36] Fix TDBU naming in Motionblinds (#110283) fix TDBU naming --- homeassistant/components/motion_blinds/cover.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/motion_blinds/cover.py b/homeassistant/components/motion_blinds/cover.py index c987e1bb10a..9dde08af5f0 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -400,6 +400,7 @@ class MotionTDBUDevice(MotionPositionDevice): def __init__(self, coordinator, blind, device_class, motor): """Initialize the blind.""" super().__init__(coordinator, blind, device_class) + delattr(self, "_attr_name") self._motor = motor self._motor_key = motor[0] self._attr_translation_key = motor.lower() From da61564f826344f52cb623c50eff900da395264e Mon Sep 17 00:00:00 2001 From: IceBotYT <34712694+IceBotYT@users.noreply.github.com> Date: Fri, 16 Feb 2024 07:56:25 -0500 Subject: [PATCH 20/36] Bump linear-garage-door to 0.2.9 (#110298) --- .../linear_garage_door/coordinator.py | 6 +- .../linear_garage_door/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../linear_garage_door/test_config_flow.py | 86 ++++++++++--------- .../linear_garage_door/test_coordinator.py | 28 +----- 6 files changed, 52 insertions(+), 74 deletions(-) diff --git a/homeassistant/components/linear_garage_door/coordinator.py b/homeassistant/components/linear_garage_door/coordinator.py index 5a17d5a39e4..e9234327429 100644 --- a/homeassistant/components/linear_garage_door/coordinator.py +++ b/homeassistant/components/linear_garage_door/coordinator.py @@ -6,11 +6,12 @@ import logging from typing import Any from linear_garage_door import Linear -from linear_garage_door.errors import InvalidLoginError, ResponseError +from linear_garage_door.errors import InvalidLoginError from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -55,6 +56,7 @@ class LinearUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): email=self._email, password=self._password, device_id=self._device_id, + client_session=async_get_clientsession(self.hass), ) except InvalidLoginError as err: if ( @@ -63,8 +65,6 @@ class LinearUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): ): raise ConfigEntryAuthFailed from err raise ConfigEntryNotReady from err - except ResponseError as err: - raise ConfigEntryNotReady from err if not self._devices: self._devices = await linear.get_devices(self._site_id) diff --git a/homeassistant/components/linear_garage_door/manifest.json b/homeassistant/components/linear_garage_door/manifest.json index c7918e21e20..f1eb4302cf0 100644 --- a/homeassistant/components/linear_garage_door/manifest.json +++ b/homeassistant/components/linear_garage_door/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/linear_garage_door", "iot_class": "cloud_polling", - "requirements": ["linear-garage-door==0.2.7"] + "requirements": ["linear-garage-door==0.2.9"] } diff --git a/requirements_all.txt b/requirements_all.txt index 28d688fbc5c..6557249b176 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1223,7 +1223,7 @@ lightwave==0.24 limitlessled==1.1.3 # homeassistant.components.linear_garage_door -linear-garage-door==0.2.7 +linear-garage-door==0.2.9 # homeassistant.components.linode linode-api==4.1.9b1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4222538dd14..7aec584006f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -974,7 +974,7 @@ librouteros==3.2.0 libsoundtouch==0.8 # homeassistant.components.linear_garage_door -linear-garage-door==0.2.7 +linear-garage-door==0.2.9 # homeassistant.components.lamarzocco lmcloud==0.4.35 diff --git a/tests/components/linear_garage_door/test_config_flow.py b/tests/components/linear_garage_door/test_config_flow.py index 64664745c54..5d1ed36ecb7 100644 --- a/tests/components/linear_garage_door/test_config_flow.py +++ b/tests/components/linear_garage_door/test_config_flow.py @@ -65,54 +65,58 @@ async def test_form(hass: HomeAssistant) -> None: async def test_reauth(hass: HomeAssistant) -> None: """Test reauthentication.""" - entry = await async_init_integration(hass) - - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={ - "source": config_entries.SOURCE_REAUTH, - "entry_id": entry.entry_id, - "title_placeholders": {"name": entry.title}, - "unique_id": entry.unique_id, - }, - data=entry.data, - ) - assert result["type"] == FlowResultType.FORM - assert result["step_id"] == "user" - with patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.login", + "homeassistant.components.linear_garage_door.async_setup_entry", return_value=True, - ), patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.get_sites", - return_value=[{"id": "test-site-id", "name": "test-site-name"}], - ), patch( - "homeassistant.components.linear_garage_door.config_flow.Linear.close", - return_value=None, - ), patch( - "uuid.uuid4", - return_value="test-uuid", ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - "email": "new-email", - "password": "new-password", + entry = await async_init_integration(hass) + + result1 = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": entry.entry_id, + "title_placeholders": {"name": entry.title}, + "unique_id": entry.unique_id, }, + data=entry.data, ) - await hass.async_block_till_done() + assert result1["type"] == FlowResultType.FORM + assert result1["step_id"] == "user" - assert result2["type"] == FlowResultType.ABORT - assert result2["reason"] == "reauth_successful" + with patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.login", + return_value=True, + ), patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.get_sites", + return_value=[{"id": "test-site-id", "name": "test-site-name"}], + ), patch( + "homeassistant.components.linear_garage_door.config_flow.Linear.close", + return_value=None, + ), patch( + "uuid.uuid4", + return_value="test-uuid", + ): + result2 = await hass.config_entries.flow.async_configure( + result1["flow_id"], + { + "email": "new-email", + "password": "new-password", + }, + ) + await hass.async_block_till_done() - entries = hass.config_entries.async_entries() - assert len(entries) == 1 - assert entries[0].data == { - "email": "new-email", - "password": "new-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - } + assert result2["type"] == FlowResultType.ABORT + assert result2["reason"] == "reauth_successful" + + entries = hass.config_entries.async_entries() + assert len(entries) == 1 + assert entries[0].data == { + "email": "new-email", + "password": "new-password", + "site_id": "test-site-id", + "device_id": "test-uuid", + } async def test_form_invalid_login(hass: HomeAssistant) -> None: diff --git a/tests/components/linear_garage_door/test_coordinator.py b/tests/components/linear_garage_door/test_coordinator.py index fc3087db354..1e46d294f3f 100644 --- a/tests/components/linear_garage_door/test_coordinator.py +++ b/tests/components/linear_garage_door/test_coordinator.py @@ -2,7 +2,7 @@ from unittest.mock import patch -from linear_garage_door.errors import InvalidLoginError, ResponseError +from linear_garage_door.errors import InvalidLoginError from homeassistant.components.linear_garage_door.const import DOMAIN from homeassistant.config_entries import ConfigEntryState @@ -45,32 +45,6 @@ async def test_invalid_password( assert flows[0]["context"]["source"] == "reauth" -async def test_response_error(hass: HomeAssistant) -> None: - """Test response error.""" - config_entry = MockConfigEntry( - domain=DOMAIN, - data={ - "email": "test-email", - "password": "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - }, - ) - config_entry.add_to_hass(hass) - - with patch( - "homeassistant.components.linear_garage_door.coordinator.Linear.login", - side_effect=ResponseError, - ): - assert not await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - entries = hass.config_entries.async_entries(DOMAIN) - assert entries - assert len(entries) == 1 - assert entries[0].state == ConfigEntryState.SETUP_RETRY - - async def test_invalid_login( hass: HomeAssistant, ) -> None: From 56ceadaeeb5e558c9dd0a3e5cc07c9ff6d44056a Mon Sep 17 00:00:00 2001 From: Nikolay Vasilchuk Date: Mon, 12 Feb 2024 19:07:22 +0300 Subject: [PATCH 21/36] Fix Starline GPS count sensor (#110348) --- homeassistant/components/starline/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/starline/sensor.py b/homeassistant/components/starline/sensor.py index 1a43601940e..4f02ee1a1f6 100644 --- a/homeassistant/components/starline/sensor.py +++ b/homeassistant/components/starline/sensor.py @@ -139,7 +139,7 @@ class StarlineSensor(StarlineEntity, SensorEntity): if self._key == "mileage" and self._device.mileage: return self._device.mileage.get("val") if self._key == "gps_count" and self._device.position: - return self._device.position["sat_qty"] + return self._device.position.get("sat_qty") return None @property From de619e4ddce75dc8c3e55666b2df06add3e3014f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 12 Feb 2024 12:47:34 -0600 Subject: [PATCH 22/36] Fix zone radius calculation when radius is not 0 (#110354) --- homeassistant/components/zone/__init__.py | 4 +-- tests/components/zone/test_init.py | 40 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py index 01ec041e9d8..86593c36737 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -135,7 +135,7 @@ def async_active_zone( is None # Skip zone that are outside the radius aka the # lat/long is outside the zone - or not (zone_dist - (radius := zone_attrs[ATTR_RADIUS]) < radius) + or not (zone_dist - (zone_radius := zone_attrs[ATTR_RADIUS]) < radius) ): continue @@ -144,7 +144,7 @@ def async_active_zone( zone_dist < min_dist or ( # If same distance, prefer smaller zone - zone_dist == min_dist and radius < closest.attributes[ATTR_RADIUS] + zone_dist == min_dist and zone_radius < closest.attributes[ATTR_RADIUS] ) ): continue diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index 70a399d27a4..2924e6654e2 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -228,6 +228,46 @@ async def test_in_zone_works_for_passive_zones(hass: HomeAssistant) -> None: assert zone.in_zone(hass.states.get("zone.passive_zone"), latitude, longitude) +async def test_async_active_zone_with_non_zero_radius( + hass: HomeAssistant, +) -> None: + """Test async_active_zone with a non-zero radius.""" + latitude = 32.880600 + longitude = -117.237561 + + assert await setup.async_setup_component( + hass, + zone.DOMAIN, + { + "zone": [ + { + "name": "Small Zone", + "latitude": 32.980600, + "longitude": -117.137561, + "radius": 50000, + }, + { + "name": "Big Zone", + "latitude": 32.980600, + "longitude": -117.137561, + "radius": 100000, + }, + ] + }, + ) + + home_state = hass.states.get("zone.home") + assert home_state.attributes["radius"] == 100 + assert home_state.attributes["latitude"] == 32.87336 + assert home_state.attributes["longitude"] == -117.22743 + + active = zone.async_active_zone(hass, latitude, longitude, 5000) + assert active.entity_id == "zone.home" + + active = zone.async_active_zone(hass, latitude, longitude, 0) + assert active.entity_id == "zone.small_zone" + + async def test_core_config_update(hass: HomeAssistant) -> None: """Test updating core config will update home zone.""" assert await setup.async_setup_component(hass, "zone", {}) From 5ba31290b8d8012c76780c81a836f9d05e0f71b0 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:13:47 +0100 Subject: [PATCH 23/36] Bump py-sucks to 0.9.9 (#110397) bump py-sucks to 0.9.9 --- homeassistant/components/ecovacs/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ecovacs/manifest.json b/homeassistant/components/ecovacs/manifest.json index 3fcb2b3211e..819f1db2f69 100644 --- a/homeassistant/components/ecovacs/manifest.json +++ b/homeassistant/components/ecovacs/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/ecovacs", "iot_class": "cloud_push", "loggers": ["sleekxmppfs", "sucks", "deebot_client"], - "requirements": ["py-sucks==0.9.8", "deebot-client==5.1.1"] + "requirements": ["py-sucks==0.9.9", "deebot-client==5.1.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6557249b176..dc4d9874492 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1612,7 +1612,7 @@ py-nightscout==1.2.2 py-schluter==0.1.7 # homeassistant.components.ecovacs -py-sucks==0.9.8 +py-sucks==0.9.9 # homeassistant.components.synology_dsm py-synologydsm-api==2.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7aec584006f..1e0ab79946c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1262,7 +1262,7 @@ py-nextbusnext==1.0.2 py-nightscout==1.2.2 # homeassistant.components.ecovacs -py-sucks==0.9.8 +py-sucks==0.9.9 # homeassistant.components.synology_dsm py-synologydsm-api==2.1.4 From fe84e7a576a0c1d33e63f09e268f955887de2104 Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Mon, 12 Feb 2024 22:31:09 +0100 Subject: [PATCH 24/36] Bump async-upnp-client to 0.38.2 (#110411) --- homeassistant/components/dlna_dmr/manifest.json | 2 +- homeassistant/components/dlna_dms/manifest.json | 2 +- homeassistant/components/samsungtv/manifest.json | 2 +- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/components/upnp/manifest.json | 2 +- homeassistant/components/yeelight/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/dlna_dmr/manifest.json b/homeassistant/components/dlna_dmr/manifest.json index ab5d035dd54..128822cf289 100644 --- a/homeassistant/components/dlna_dmr/manifest.json +++ b/homeassistant/components/dlna_dmr/manifest.json @@ -8,7 +8,7 @@ "documentation": "https://www.home-assistant.io/integrations/dlna_dmr", "iot_class": "local_push", "loggers": ["async_upnp_client"], - "requirements": ["async-upnp-client==0.38.1", "getmac==0.9.4"], + "requirements": ["async-upnp-client==0.38.2", "getmac==0.9.4"], "ssdp": [ { "deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1", diff --git a/homeassistant/components/dlna_dms/manifest.json b/homeassistant/components/dlna_dms/manifest.json index d4a74725467..aaa6e1ee7de 100644 --- a/homeassistant/components/dlna_dms/manifest.json +++ b/homeassistant/components/dlna_dms/manifest.json @@ -8,7 +8,7 @@ "documentation": "https://www.home-assistant.io/integrations/dlna_dms", "iot_class": "local_polling", "quality_scale": "platinum", - "requirements": ["async-upnp-client==0.38.1"], + "requirements": ["async-upnp-client==0.38.2"], "ssdp": [ { "deviceType": "urn:schemas-upnp-org:device:MediaServer:1", diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 780d47e4743..00b8fec8e6a 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -39,7 +39,7 @@ "samsungctl[websocket]==0.7.1", "samsungtvws[async,encrypted]==2.6.0", "wakeonlan==2.1.0", - "async-upnp-client==0.38.1" + "async-upnp-client==0.38.2" ], "ssdp": [ { diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 8afed8b4fd1..2737565822d 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_push", "loggers": ["async_upnp_client"], "quality_scale": "internal", - "requirements": ["async-upnp-client==0.38.1"] + "requirements": ["async-upnp-client==0.38.2"] } diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index 8ce32158016..edfde84a2ac 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -8,7 +8,7 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["async_upnp_client"], - "requirements": ["async-upnp-client==0.38.1", "getmac==0.9.4"], + "requirements": ["async-upnp-client==0.38.2", "getmac==0.9.4"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1" diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index f2a11aaf1fe..20f8ed3ed4d 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -17,7 +17,7 @@ "iot_class": "local_push", "loggers": ["async_upnp_client", "yeelight"], "quality_scale": "platinum", - "requirements": ["yeelight==0.7.14", "async-upnp-client==0.38.1"], + "requirements": ["yeelight==0.7.14", "async-upnp-client==0.38.2"], "zeroconf": [ { "type": "_miio._udp.local.", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 6a9734ad1fa..2e3943d9044 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -6,7 +6,7 @@ aiohttp-zlib-ng==0.3.1 aiohttp==3.9.3 aiohttp_cors==0.7.0 astral==2.2 -async-upnp-client==0.38.1 +async-upnp-client==0.38.2 atomicwrites-homeassistant==1.4.1 attrs==23.2.0 awesomeversion==24.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index dc4d9874492..f1516fb967f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -478,7 +478,7 @@ asterisk_mbox==0.5.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.38.1 +async-upnp-client==0.38.2 # homeassistant.components.keyboard_remote asyncinotify==4.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1e0ab79946c..171843307c3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -430,7 +430,7 @@ arcam-fmj==1.4.0 # homeassistant.components.ssdp # homeassistant.components.upnp # homeassistant.components.yeelight -async-upnp-client==0.38.1 +async-upnp-client==0.38.2 # homeassistant.components.sleepiq asyncsleepiq==1.5.2 From c7634830490649ad782af4dacca71948886705e0 Mon Sep 17 00:00:00 2001 From: Robert Hillis Date: Fri, 16 Feb 2024 05:22:30 -0500 Subject: [PATCH 25/36] Mitigate session closed error in Netgear LTE (#110412) --- homeassistant/components/netgear_lte/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/netgear_lte/__init__.py b/homeassistant/components/netgear_lte/__init__.py index 9faa2f361b9..491ee0efe59 100644 --- a/homeassistant/components/netgear_lte/__init__.py +++ b/homeassistant/components/netgear_lte/__init__.py @@ -212,7 +212,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: host = entry.data[CONF_HOST] password = entry.data[CONF_PASSWORD] - if DOMAIN not in hass.data: + if not (data := hass.data.get(DOMAIN)) or data.websession.closed: websession = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True)) hass.data[DOMAIN] = LTEData(websession) @@ -258,7 +258,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if entry.state == ConfigEntryState.LOADED ] if len(loaded_entries) == 1: - hass.data.pop(DOMAIN) + hass.data.pop(DOMAIN, None) return unload_ok From ec7950aeda0d10444bf9617d0c8735055d300a80 Mon Sep 17 00:00:00 2001 From: wilburCforce <109390391+wilburCforce@users.noreply.github.com> Date: Thu, 8 Feb 2024 06:32:57 -0600 Subject: [PATCH 26/36] Update pylutron to 0.2.11 (#109853) --- homeassistant/components/lutron/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lutron/manifest.json b/homeassistant/components/lutron/manifest.json index 6444aa306a2..67ebebcc25b 100644 --- a/homeassistant/components/lutron/manifest.json +++ b/homeassistant/components/lutron/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/lutron", "iot_class": "local_polling", "loggers": ["pylutron"], - "requirements": ["pylutron==0.2.8"] + "requirements": ["pylutron==0.2.11"] } diff --git a/requirements_all.txt b/requirements_all.txt index f1516fb967f..3a0346acbdd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1931,7 +1931,7 @@ pylitterbot==2023.4.9 pylutron-caseta==0.19.0 # homeassistant.components.lutron -pylutron==0.2.8 +pylutron==0.2.11 # homeassistant.components.mailgun pymailgunner==1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 171843307c3..460e103d41a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1488,7 +1488,7 @@ pylitterbot==2023.4.9 pylutron-caseta==0.19.0 # homeassistant.components.lutron -pylutron==0.2.8 +pylutron==0.2.11 # homeassistant.components.mailgun pymailgunner==1.4 From 479ecc8b94260a401a405bb5a3ebf261694fc25d Mon Sep 17 00:00:00 2001 From: wilburCforce <109390391+wilburCforce@users.noreply.github.com> Date: Tue, 13 Feb 2024 01:37:58 -0600 Subject: [PATCH 27/36] Update pylutron to 0.2.12 (#110414) --- homeassistant/components/lutron/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lutron/manifest.json b/homeassistant/components/lutron/manifest.json index 67ebebcc25b..73f1028bb72 100644 --- a/homeassistant/components/lutron/manifest.json +++ b/homeassistant/components/lutron/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/lutron", "iot_class": "local_polling", "loggers": ["pylutron"], - "requirements": ["pylutron==0.2.11"] + "requirements": ["pylutron==0.2.12"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3a0346acbdd..0c91bfb9e7a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1931,7 +1931,7 @@ pylitterbot==2023.4.9 pylutron-caseta==0.19.0 # homeassistant.components.lutron -pylutron==0.2.11 +pylutron==0.2.12 # homeassistant.components.mailgun pymailgunner==1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 460e103d41a..ada2190a484 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1488,7 +1488,7 @@ pylitterbot==2023.4.9 pylutron-caseta==0.19.0 # homeassistant.components.lutron -pylutron==0.2.11 +pylutron==0.2.12 # homeassistant.components.mailgun pymailgunner==1.4 From 9309e38302af2b768e2b304ea87d51248fa9b026 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 13 Feb 2024 15:07:37 +0100 Subject: [PATCH 28/36] Fix Raspberry Pi utilities installation on Alpine 3.19 (#110463) --- machine/raspberrypi | 3 +-- machine/raspberrypi2 | 3 +-- machine/raspberrypi3 | 3 +-- machine/raspberrypi3-64 | 3 +-- machine/raspberrypi4 | 3 +-- machine/raspberrypi4-64 | 3 +-- machine/raspberrypi5-64 | 3 +-- machine/yellow | 3 +-- 8 files changed, 8 insertions(+), 16 deletions(-) diff --git a/machine/raspberrypi b/machine/raspberrypi index 2ed3b3c8e44..8232d3398a7 100644 --- a/machine/raspberrypi +++ b/machine/raspberrypi @@ -4,5 +4,4 @@ ARG \ FROM $BUILD_FROM RUN apk --no-cache add \ - raspberrypi-userland \ - raspberrypi-userland-libs + raspberrypi-utils diff --git a/machine/raspberrypi2 b/machine/raspberrypi2 index 2ed3b3c8e44..8232d3398a7 100644 --- a/machine/raspberrypi2 +++ b/machine/raspberrypi2 @@ -4,5 +4,4 @@ ARG \ FROM $BUILD_FROM RUN apk --no-cache add \ - raspberrypi-userland \ - raspberrypi-userland-libs + raspberrypi-utils diff --git a/machine/raspberrypi3 b/machine/raspberrypi3 index 2ed3b3c8e44..8232d3398a7 100644 --- a/machine/raspberrypi3 +++ b/machine/raspberrypi3 @@ -4,5 +4,4 @@ ARG \ FROM $BUILD_FROM RUN apk --no-cache add \ - raspberrypi-userland \ - raspberrypi-userland-libs + raspberrypi-utils diff --git a/machine/raspberrypi3-64 b/machine/raspberrypi3-64 index 2ed3b3c8e44..8232d3398a7 100644 --- a/machine/raspberrypi3-64 +++ b/machine/raspberrypi3-64 @@ -4,5 +4,4 @@ ARG \ FROM $BUILD_FROM RUN apk --no-cache add \ - raspberrypi-userland \ - raspberrypi-userland-libs + raspberrypi-utils diff --git a/machine/raspberrypi4 b/machine/raspberrypi4 index 2ed3b3c8e44..8232d3398a7 100644 --- a/machine/raspberrypi4 +++ b/machine/raspberrypi4 @@ -4,5 +4,4 @@ ARG \ FROM $BUILD_FROM RUN apk --no-cache add \ - raspberrypi-userland \ - raspberrypi-userland-libs + raspberrypi-utils diff --git a/machine/raspberrypi4-64 b/machine/raspberrypi4-64 index 2ed3b3c8e44..8232d3398a7 100644 --- a/machine/raspberrypi4-64 +++ b/machine/raspberrypi4-64 @@ -4,5 +4,4 @@ ARG \ FROM $BUILD_FROM RUN apk --no-cache add \ - raspberrypi-userland \ - raspberrypi-userland-libs + raspberrypi-utils diff --git a/machine/raspberrypi5-64 b/machine/raspberrypi5-64 index 2ed3b3c8e44..8232d3398a7 100644 --- a/machine/raspberrypi5-64 +++ b/machine/raspberrypi5-64 @@ -4,5 +4,4 @@ ARG \ FROM $BUILD_FROM RUN apk --no-cache add \ - raspberrypi-userland \ - raspberrypi-userland-libs + raspberrypi-utils diff --git a/machine/yellow b/machine/yellow index 2ed3b3c8e44..8232d3398a7 100644 --- a/machine/yellow +++ b/machine/yellow @@ -4,5 +4,4 @@ ARG \ FROM $BUILD_FROM RUN apk --no-cache add \ - raspberrypi-userland \ - raspberrypi-userland-libs + raspberrypi-utils From 393359a5466300a4b7d131a08f1caaf72bbb62df Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 15 Feb 2024 11:11:16 -0500 Subject: [PATCH 29/36] Coerce to float in Sensibo climate react custom service (#110508) --- homeassistant/components/sensibo/climate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index bcc851e02ae..0ad2a0a714f 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -173,9 +173,9 @@ async def async_setup_entry( platform.async_register_entity_service( SERVICE_ENABLE_CLIMATE_REACT, { - vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): float, + vol.Required(ATTR_HIGH_TEMPERATURE_THRESHOLD): vol.Coerce(float), vol.Required(ATTR_HIGH_TEMPERATURE_STATE): dict, - vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): float, + vol.Required(ATTR_LOW_TEMPERATURE_THRESHOLD): vol.Coerce(float), vol.Required(ATTR_LOW_TEMPERATURE_STATE): dict, vol.Required(ATTR_SMART_TYPE): vol.In( ["temperature", "feelsLike", "humidity"] From cdf67e9bb5a6394945d0aee6b6859dd9e4fe34da Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 14 Feb 2024 11:57:47 -0600 Subject: [PATCH 30/36] Bump orjson to 3.9.14 (#110552) changelog: https://github.com/ijl/orjson/compare/3.9.13...3.9.14 fixes a crasher due to buffer overread (was only partially fixed in 3.9.13) --- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 2e3943d9044..380eb593a55 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -36,7 +36,7 @@ janus==1.0.0 Jinja2==3.1.3 lru-dict==1.3.0 mutagen==1.47.0 -orjson==3.9.13 +orjson==3.9.14 packaging>=23.1 paho-mqtt==1.6.1 Pillow==10.2.0 diff --git a/pyproject.toml b/pyproject.toml index 89551988971..b5d788cc39e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ "cryptography==42.0.2", # pyOpenSSL 23.2.0 is required to work with cryptography 41+ "pyOpenSSL==24.0.0", - "orjson==3.9.13", + "orjson==3.9.14", "packaging>=23.1", "pip>=21.3.1", "python-slugify==8.0.1", diff --git a/requirements.txt b/requirements.txt index 63ea582eba8..76ca93d998d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ lru-dict==1.3.0 PyJWT==2.8.0 cryptography==42.0.2 pyOpenSSL==24.0.0 -orjson==3.9.13 +orjson==3.9.14 packaging>=23.1 pip>=21.3.1 python-slugify==8.0.1 From e5db7278e19ca332b9b9790af12f4f6ae85ed532 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 14 Feb 2024 16:03:30 -0600 Subject: [PATCH 31/36] Fix tplink not updating IP from DHCP discovery and discovering twice (#110557) We only called format_mac on the mac address if we connected to the device during entry creation. Since the format of the mac address from DHCP discovery did not match the format saved in the unique id, the IP would not get updated and a second discovery would appear Thankfully the creation path does format the mac so we did not create any entries with an inconsistantly formatted unique id fixes #110460 --- .../components/tplink/config_flow.py | 2 +- tests/components/tplink/__init__.py | 1 + tests/components/tplink/test_config_flow.py | 60 +++++++++++++++++-- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/tplink/config_flow.py b/homeassistant/components/tplink/config_flow.py index e1e51f19e3a..10c0c16ff7f 100644 --- a/homeassistant/components/tplink/config_flow.py +++ b/homeassistant/components/tplink/config_flow.py @@ -61,7 +61,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: """Handle discovery via dhcp.""" return await self._async_handle_discovery( - discovery_info.ip, discovery_info.macaddress + discovery_info.ip, dr.format_mac(discovery_info.macaddress) ) async def async_step_integration_discovery( diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index 30e59014bbf..4c188fcddcc 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -36,6 +36,7 @@ IP_ADDRESS2 = "127.0.0.2" ALIAS = "My Bulb" MODEL = "HS100" MAC_ADDRESS = "aa:bb:cc:dd:ee:ff" +DHCP_FORMATTED_MAC_ADDRESS = MAC_ADDRESS.replace(":", "") MAC_ADDRESS2 = "11:22:33:44:55:66" DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}" CREDENTIALS_HASH_LEGACY = "" diff --git a/tests/components/tplink/test_config_flow.py b/tests/components/tplink/test_config_flow.py index f5b0ba6c41f..edb19a93207 100644 --- a/tests/components/tplink/test_config_flow.py +++ b/tests/components/tplink/test_config_flow.py @@ -33,6 +33,7 @@ from . import ( DEFAULT_ENTRY_TITLE, DEVICE_CONFIG_DICT_AUTH, DEVICE_CONFIG_DICT_LEGACY, + DHCP_FORMATTED_MAC_ADDRESS, IP_ADDRESS, MAC_ADDRESS, MAC_ADDRESS2, @@ -144,6 +145,7 @@ async def test_discovery_auth( assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["title"] == DEFAULT_ENTRY_TITLE assert result2["data"] == CREATE_ENTRY_DATA_AUTH + assert result2["context"]["unique_id"] == MAC_ADDRESS @pytest.mark.parametrize( @@ -206,6 +208,7 @@ async def test_discovery_auth_errors( ) assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["data"] == CREATE_ENTRY_DATA_AUTH + assert result3["context"]["unique_id"] == MAC_ADDRESS async def test_discovery_new_credentials( @@ -254,6 +257,7 @@ async def test_discovery_new_credentials( ) assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["data"] == CREATE_ENTRY_DATA_AUTH + assert result3["context"]["unique_id"] == MAC_ADDRESS async def test_discovery_new_credentials_invalid( @@ -309,6 +313,7 @@ async def test_discovery_new_credentials_invalid( ) assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["data"] == CREATE_ENTRY_DATA_AUTH + assert result3["context"]["unique_id"] == MAC_ADDRESS async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> None: @@ -365,6 +370,7 @@ async def test_discovery_with_existing_device_present(hass: HomeAssistant) -> No assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["title"] == DEFAULT_ENTRY_TITLE assert result3["data"] == CREATE_ENTRY_DATA_LEGACY + assert result3["context"]["unique_id"] == MAC_ADDRESS await hass.async_block_till_done() mock_setup_entry.assert_called_once() @@ -432,6 +438,7 @@ async def test_manual(hass: HomeAssistant) -> None: assert result4["type"] is FlowResultType.CREATE_ENTRY assert result4["title"] == DEFAULT_ENTRY_TITLE assert result4["data"] == CREATE_ENTRY_DATA_LEGACY + assert result4["context"]["unique_id"] == MAC_ADDRESS # Duplicate result = await hass.config_entries.flow.async_init( @@ -470,6 +477,7 @@ async def test_manual_no_capabilities(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.CREATE_ENTRY assert result["data"] == CREATE_ENTRY_DATA_LEGACY + assert result["context"]["unique_id"] == MAC_ADDRESS async def test_manual_auth( @@ -510,6 +518,7 @@ async def test_manual_auth( assert result3["type"] is FlowResultType.CREATE_ENTRY assert result3["title"] == DEFAULT_ENTRY_TITLE assert result3["data"] == CREATE_ENTRY_DATA_AUTH + assert result3["context"]["unique_id"] == MAC_ADDRESS @pytest.mark.parametrize( @@ -572,6 +581,7 @@ async def test_manual_auth_errors( ) assert result4["type"] is FlowResultType.CREATE_ENTRY assert result4["data"] == CREATE_ENTRY_DATA_AUTH + assert result4["context"]["unique_id"] == MAC_ADDRESS await hass.async_block_till_done() @@ -599,7 +609,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp.DhcpServiceInfo( - ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS + ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS ), ) await hass.async_block_till_done() @@ -611,7 +621,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp.DhcpServiceInfo( - ip=IP_ADDRESS, macaddress="00:00:00:00:00:00", hostname="mock_hostname" + ip=IP_ADDRESS, macaddress="000000000000", hostname="mock_hostname" ), ) await hass.async_block_till_done() @@ -625,7 +635,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_DHCP}, data=dhcp.DhcpServiceInfo( - ip="1.2.3.5", macaddress="00:00:00:00:00:01", hostname="mock_hostname" + ip="1.2.3.5", macaddress="000000000001", hostname="mock_hostname" ), ) await hass.async_block_till_done() @@ -638,7 +648,9 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None: [ ( config_entries.SOURCE_DHCP, - dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS), + dhcp.DhcpServiceInfo( + ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS + ), ), ( config_entries.SOURCE_INTEGRATION_DISCOVERY, @@ -675,6 +687,8 @@ async def test_discovered_by_dhcp_or_discovery( assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["data"] == CREATE_ENTRY_DATA_LEGACY + assert result2["context"]["unique_id"] == MAC_ADDRESS + assert mock_async_setup.called assert mock_async_setup_entry.called @@ -684,7 +698,9 @@ async def test_discovered_by_dhcp_or_discovery( [ ( config_entries.SOURCE_DHCP, - dhcp.DhcpServiceInfo(ip=IP_ADDRESS, macaddress=MAC_ADDRESS, hostname=ALIAS), + dhcp.DhcpServiceInfo( + ip=IP_ADDRESS, macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS + ), ), ( config_entries.SOURCE_INTEGRATION_DISCOVERY, @@ -713,7 +729,7 @@ async def test_discovered_by_dhcp_or_discovery_failed_to_get_device( assert result["reason"] == "cannot_connect" -async def test_discovery_with_ip_change( +async def test_integration_discovery_with_ip_change( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_discovery: AsyncMock, @@ -764,6 +780,36 @@ async def test_discovery_with_ip_change( mock_connect["connect"].assert_awaited_once_with(config=config) +async def test_dhcp_discovery_with_ip_change( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_discovery: AsyncMock, + mock_connect: AsyncMock, +) -> None: + """Test dhcp discovery with an IP change.""" + mock_connect["connect"].side_effect = SmartDeviceException() + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + assert mock_config_entry.state == config_entries.ConfigEntryState.SETUP_RETRY + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 0 + assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY + assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.1" + + discovery_result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=dhcp.DhcpServiceInfo( + ip="127.0.0.2", macaddress=DHCP_FORMATTED_MAC_ADDRESS, hostname=ALIAS + ), + ) + assert discovery_result["type"] is FlowResultType.ABORT + assert discovery_result["reason"] == "already_configured" + assert mock_config_entry.data[CONF_HOST] == "127.0.0.2" + + async def test_reauth( hass: HomeAssistant, mock_added_config_entry: MockConfigEntry, @@ -1022,6 +1068,7 @@ async def test_pick_device_errors( }, ) assert result4["type"] == FlowResultType.CREATE_ENTRY + assert result4["context"]["unique_id"] == MAC_ADDRESS async def test_discovery_timeout_connect( @@ -1046,6 +1093,7 @@ async def test_discovery_timeout_connect( ) await hass.async_block_till_done() assert result2["type"] is FlowResultType.CREATE_ENTRY + assert result2["context"]["unique_id"] == MAC_ADDRESS assert mock_connect["connect"].call_count == 1 From 6f529a2c77dcbec4388d4022cedd87adc017337a Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 15 Feb 2024 18:22:23 +0100 Subject: [PATCH 32/36] Modbus, allow received int to be a float. (#110648) --- homeassistant/components/modbus/base_platform.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index cdc1e7a6986..ac11bab303d 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -199,6 +199,8 @@ class BaseStructPlatform(BasePlatform, RestoreEntity): self._precision = config.get(CONF_PRECISION, 2) else: self._precision = config.get(CONF_PRECISION, 0) + if self._precision > 0 or self._scale != int(self._scale): + self._value_is_int = False def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]: """Do swap as needed.""" From bf002ac0b023797cd875ec8c219384c4f9c3840d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 16 Feb 2024 05:27:13 -0600 Subject: [PATCH 33/36] Fix elkm1 service calls running in the executor (#110655) fixes ``` File "/usr/src/homeassistant/homeassistant/components/elkm1/__init__.py", line 416, in _set_time_service _getelk(service).panel.set_time(dt_util.now()) File "/usr/local/lib/python3.11/site-packages/elkm1_lib/panel.py", line 55, in set_time self._connection.send(rw_encode(datetime)) File "/usr/local/lib/python3.11/site-packages/elkm1_lib/connection.py", line 152, in send self._send(QueuedWrite(msg.message, msg.response_command), priority_send) File "/usr/local/lib/python3.11/site-packages/elkm1_lib/connection.py", line 148, in _send self._check_write_queue.set() File "/usr/local/lib/python3.11/asyncio/locks.py", line 192, in set fut.set_result(True) File "/usr/local/lib/python3.11/asyncio/base_events.py", line 763, in call_soon self._check_thread() File "/usr/local/lib/python3.11/asyncio/base_events.py", line 800, in _check_thread raise RuntimeError( RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one ``` --- homeassistant/components/elkm1/__init__.py | 28 ++++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index b633e1ae620..113fe2ac84e 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -10,7 +10,7 @@ from types import MappingProxyType from typing import Any from elkm1_lib.elements import Element -from elkm1_lib.elk import Elk +from elkm1_lib.elk import Elk, Panel from elkm1_lib.util import parse_url import voluptuous as vol @@ -398,22 +398,30 @@ async def async_wait_for_elk_to_sync( return success +@callback +def _async_get_elk_panel(hass: HomeAssistant, service: ServiceCall) -> Panel: + """Get the ElkM1 panel from a service call.""" + prefix = service.data["prefix"] + elk = _find_elk_by_prefix(hass, prefix) + if elk is None: + raise HomeAssistantError(f"No ElkM1 with prefix '{prefix}' found") + return elk.panel + + def _create_elk_services(hass: HomeAssistant) -> None: - def _getelk(service: ServiceCall) -> Elk: - prefix = service.data["prefix"] - elk = _find_elk_by_prefix(hass, prefix) - if elk is None: - raise HomeAssistantError(f"No ElkM1 with prefix '{prefix}' found") - return elk + """Create ElkM1 services.""" + @callback def _speak_word_service(service: ServiceCall) -> None: - _getelk(service).panel.speak_word(service.data["number"]) + _async_get_elk_panel(hass, service).speak_word(service.data["number"]) + @callback def _speak_phrase_service(service: ServiceCall) -> None: - _getelk(service).panel.speak_phrase(service.data["number"]) + _async_get_elk_panel(hass, service).speak_phrase(service.data["number"]) + @callback def _set_time_service(service: ServiceCall) -> None: - _getelk(service).panel.set_time(dt_util.now()) + _async_get_elk_panel(hass, service).set_time(dt_util.now()) hass.services.async_register( DOMAIN, "speak_word", _speak_word_service, SPEAK_SERVICE_SCHEMA From 34a3e88e0dea101e597ed6faaeef4ff770bb01bd Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 15 Feb 2024 19:17:06 +0100 Subject: [PATCH 34/36] Bump aiounifi to v71 (#110658) --- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index f69dffc2d57..f3092811227 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_push", "loggers": ["aiounifi"], "quality_scale": "platinum", - "requirements": ["aiounifi==70"], + "requirements": ["aiounifi==71"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index 0c91bfb9e7a..e19a9b50c5f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -383,7 +383,7 @@ aiotankerkoenig==0.3.0 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==70 +aiounifi==71 # homeassistant.components.vlc_telnet aiovlc==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ada2190a484..2e6c0825674 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -356,7 +356,7 @@ aiotankerkoenig==0.3.0 aiotractive==0.5.6 # homeassistant.components.unifi -aiounifi==70 +aiounifi==71 # homeassistant.components.vlc_telnet aiovlc==0.1.0 From 8c05ebd031d16d47a1efe11cb1537a246fa6d86f Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 16 Feb 2024 11:47:36 +0100 Subject: [PATCH 35/36] Bump deebot-client to 5.2.1 (#110683) * Bump deebot-client to 5.2.0 * Bumb again * Fix tests --- homeassistant/components/ecovacs/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/ecovacs/conftest.py | 12 ++++++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/ecovacs/manifest.json b/homeassistant/components/ecovacs/manifest.json index 819f1db2f69..61bd425b139 100644 --- a/homeassistant/components/ecovacs/manifest.json +++ b/homeassistant/components/ecovacs/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/ecovacs", "iot_class": "cloud_push", "loggers": ["sleekxmppfs", "sucks", "deebot_client"], - "requirements": ["py-sucks==0.9.9", "deebot-client==5.1.1"] + "requirements": ["py-sucks==0.9.9", "deebot-client==5.2.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index e19a9b50c5f..70c74a10dd4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -687,7 +687,7 @@ debugpy==1.8.0 # decora==0.6 # homeassistant.components.ecovacs -deebot-client==5.1.1 +deebot-client==5.2.1 # homeassistant.components.ihc # homeassistant.components.namecheapdns diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2e6c0825674..89ec09e301c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -562,7 +562,7 @@ dbus-fast==2.21.1 debugpy==1.8.0 # homeassistant.components.ecovacs -deebot-client==5.1.1 +deebot-client==5.2.1 # homeassistant.components.ihc # homeassistant.components.namecheapdns diff --git a/tests/components/ecovacs/conftest.py b/tests/components/ecovacs/conftest.py index d0f0668cc8c..31d7246e6bc 100644 --- a/tests/components/ecovacs/conftest.py +++ b/tests/components/ecovacs/conftest.py @@ -3,7 +3,7 @@ from collections.abc import Generator from typing import Any from unittest.mock import AsyncMock, Mock, patch -from deebot_client.const import PATH_API_APPSVR_APP +from deebot_client import const from deebot_client.device import Device from deebot_client.exceptions import ApiError from deebot_client.models import Credentials @@ -75,9 +75,13 @@ def mock_authenticator(device_fixture: str) -> Generator[Mock, None, None]: query_params: dict[str, Any] | None = None, headers: dict[str, Any] | None = None, ) -> dict[str, Any]: - if path == PATH_API_APPSVR_APP: - return {"code": 0, "devices": devices, "errno": "0"} - raise ApiError("Path not mocked: {path}") + match path: + case const.PATH_API_APPSVR_APP: + return {"code": 0, "devices": devices, "errno": "0"} + case const.PATH_API_USERS_USER: + return {"todo": "result", "result": "ok", "devices": devices} + case _: + raise ApiError("Path not mocked: {path}") authenticator.post_authenticated.side_effect = post_authenticated yield authenticator From b55b2c8da305e57242274e28212ab521f4616c61 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 16 Feb 2024 14:13:26 +0100 Subject: [PATCH 36/36] Bump version to 2024.2.2 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index a19ff18d8f3..84730bbf6d2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -16,7 +16,7 @@ from .helpers.deprecation import ( APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 2 -PATCH_VERSION: Final = "1" +PATCH_VERSION: Final = "2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index b5d788cc39e..0f9f9187e9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.2.1" +version = "2024.2.2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"