From fc83bb17375f2d090f6bde061c064a70c0eb1037 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Mon, 10 Jun 2024 11:23:21 -0700 Subject: [PATCH 01/41] Fix statistic_during_period wrongly prioritizing ST statistics over LT (#115291) * Fix statistic_during_period wrongly prioritizing ST statistics over LT * comment * start of a test * more testcases * fix sts insertion range * update from review * remove unneeded comments * update logic * min/mean/max testing --- .../components/recorder/statistics.py | 20 +- .../components/recorder/test_websocket_api.py | 328 ++++++++++++++++++ 2 files changed, 343 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 7b5c6811e29243..4fe40e6bac8a26 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -1263,6 +1263,7 @@ def _get_oldest_sum_statistic( main_start_time: datetime | None, tail_start_time: datetime | None, oldest_stat: datetime | None, + oldest_5_min_stat: datetime | None, tail_only: bool, metadata_id: int, ) -> float | None: @@ -1307,6 +1308,15 @@ def _get_oldest_sum_statistic_in_sub_period( if ( head_start_time is not None + and oldest_5_min_stat is not None + and ( + # If we want stats older than the short term purge window, don't lookup + # the oldest sum in the short term table, as it would be prioritized + # over older LongTermStats. + (oldest_stat is None) + or (oldest_5_min_stat < oldest_stat) + or (oldest_5_min_stat <= head_start_time) + ) and ( oldest_sum := _get_oldest_sum_statistic_in_sub_period( session, head_start_time, StatisticsShortTerm, metadata_id @@ -1478,12 +1488,11 @@ def statistic_during_period( tail_end_time: datetime | None = None if end_time is None: tail_start_time = now.replace(minute=0, second=0, microsecond=0) + elif tail_only: + tail_start_time = start_time + tail_end_time = end_time elif end_time.minute: - tail_start_time = ( - start_time - if tail_only - else end_time.replace(minute=0, second=0, microsecond=0) - ) + tail_start_time = end_time.replace(minute=0, second=0, microsecond=0) tail_end_time = end_time # Calculate the main period @@ -1518,6 +1527,7 @@ def statistic_during_period( main_start_time, tail_start_time, oldest_stat, + oldest_5_min_stat, tail_only, metadata_id, ) diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index 9c8e0a9203acd3..639e0abeefe4c0 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -794,6 +794,334 @@ def next_id(): } +@pytest.mark.freeze_time(datetime.datetime(2022, 10, 21, 6, 31, tzinfo=datetime.UTC)) +async def test_statistic_during_period_partial_overlap( + recorder_mock: Recorder, + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test statistic_during_period.""" + now = dt_util.utcnow() + + await async_recorder_block_till_done(hass) + client = await hass_ws_client() + + zero = now + start = zero.replace(hour=0, minute=0, second=0, microsecond=0) + + # Sum shall be tracking a hypothetical sensor that is 0 at midnight, and grows by 1 per minute. + # The test will have 4 hours of LTS-only data (0:00-3:59:59), followed by 2 hours of overlapping STS/LTS (4:00-5:59:59), followed by 30 minutes of STS only (6:00-6:29:59) + # similar to how a real recorder might look after purging STS. + + # The datapoint at i=0 (start = 0:00) will be 60 as that is the growth during the hour starting at the start period + imported_stats_hours = [ + { + "start": (start + timedelta(hours=i)), + "min": i * 60, + "max": i * 60 + 60, + "mean": i * 60 + 30, + "sum": (i + 1) * 60, + } + for i in range(6) + ] + + # The datapoint at i=0 (start = 4:00) would be the sensor's value at t=4:05, or 245 + imported_stats_5min = [ + { + "start": (start + timedelta(hours=4, minutes=5 * i)), + "min": 4 * 60 + i * 5, + "max": 4 * 60 + i * 5 + 5, + "mean": 4 * 60 + i * 5 + 2.5, + "sum": 4 * 60 + (i + 1) * 5, + } + for i in range(30) + ] + + assert imported_stats_hours[-1]["sum"] == 360 + assert imported_stats_hours[-1]["start"] == start.replace( + hour=5, minute=0, second=0, microsecond=0 + ) + assert imported_stats_5min[-1]["sum"] == 390 + assert imported_stats_5min[-1]["start"] == start.replace( + hour=6, minute=25, second=0, microsecond=0 + ) + + statId = "sensor.test_overlapping" + imported_metadata = { + "has_mean": False, + "has_sum": True, + "name": "Total imported energy overlapping", + "source": "recorder", + "statistic_id": statId, + "unit_of_measurement": "kWh", + } + + recorder.get_instance(hass).async_import_statistics( + imported_metadata, + imported_stats_hours, + Statistics, + ) + recorder.get_instance(hass).async_import_statistics( + imported_metadata, + imported_stats_5min, + StatisticsShortTerm, + ) + await async_wait_recording_done(hass) + + metadata = get_metadata(hass, statistic_ids={statId}) + metadata_id = metadata[statId][0] + run_cache = get_short_term_statistics_run_cache(hass) + # Verify the import of the short term statistics + # also updates the run cache + assert run_cache.get_latest_ids({metadata_id}) is not None + + # Get all the stats, should consider all hours and 5mins + await client.send_json_auto_id( + { + "type": "recorder/statistic_during_period", + "statistic_id": statId, + } + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] == { + "change": 390, + "max": 390, + "min": 0, + "mean": 195, + } + + async def assert_stat_during_fixed(client, start_time, end_time, expect): + json = { + "type": "recorder/statistic_during_period", + "types": list(expect.keys()), + "statistic_id": statId, + "fixed_period": {}, + } + if start_time: + json["fixed_period"]["start_time"] = start_time.isoformat() + if end_time: + json["fixed_period"]["end_time"] = end_time.isoformat() + + await client.send_json_auto_id(json) + response = await client.receive_json() + assert response["success"] + assert response["result"] == expect + + # One hours worth of growth in LTS-only + start_time = start.replace(hour=1) + end_time = start.replace(hour=2) + await assert_stat_during_fixed( + client, start_time, end_time, {"change": 60, "min": 60, "max": 120, "mean": 90} + ) + + # Five minutes of growth in STS-only + start_time = start.replace(hour=6, minute=15) + end_time = start.replace(hour=6, minute=20) + await assert_stat_during_fixed( + client, + start_time, + end_time, + { + "change": 5, + "min": 6 * 60 + 15, + "max": 6 * 60 + 20, + "mean": 6 * 60 + (15 + 20) / 2, + }, + ) + + # Six minutes of growth in STS-only + start_time = start.replace(hour=6, minute=14) + end_time = start.replace(hour=6, minute=20) + await assert_stat_during_fixed( + client, + start_time, + end_time, + { + "change": 5, + "min": 6 * 60 + 15, + "max": 6 * 60 + 20, + "mean": 6 * 60 + (15 + 20) / 2, + }, + ) + + # Six minutes of growth in STS-only + # 5-minute Change includes start times exactly on or before a statistics start, but end times are not counted unless they are greater than start. + start_time = start.replace(hour=6, minute=15) + end_time = start.replace(hour=6, minute=21) + await assert_stat_during_fixed( + client, + start_time, + end_time, + { + "change": 10, + "min": 6 * 60 + 15, + "max": 6 * 60 + 25, + "mean": 6 * 60 + (15 + 25) / 2, + }, + ) + + # Five minutes of growth in overlapping LTS+STS + start_time = start.replace(hour=5, minute=15) + end_time = start.replace(hour=5, minute=20) + await assert_stat_during_fixed( + client, + start_time, + end_time, + { + "change": 5, + "min": 5 * 60 + 15, + "max": 5 * 60 + 20, + "mean": 5 * 60 + (15 + 20) / 2, + }, + ) + + # Five minutes of growth in overlapping LTS+STS (start of hour) + start_time = start.replace(hour=5, minute=0) + end_time = start.replace(hour=5, minute=5) + await assert_stat_during_fixed( + client, + start_time, + end_time, + {"change": 5, "min": 5 * 60, "max": 5 * 60 + 5, "mean": 5 * 60 + (5) / 2}, + ) + + # Five minutes of growth in overlapping LTS+STS (end of hour) + start_time = start.replace(hour=4, minute=55) + end_time = start.replace(hour=5, minute=0) + await assert_stat_during_fixed( + client, + start_time, + end_time, + { + "change": 5, + "min": 4 * 60 + 55, + "max": 5 * 60, + "mean": 4 * 60 + (55 + 60) / 2, + }, + ) + + # Five minutes of growth in STS-only, with a minute offset. Despite that this does not cover the full period, result is still 5 + start_time = start.replace(hour=6, minute=16) + end_time = start.replace(hour=6, minute=21) + await assert_stat_during_fixed( + client, + start_time, + end_time, + { + "change": 5, + "min": 6 * 60 + 20, + "max": 6 * 60 + 25, + "mean": 6 * 60 + (20 + 25) / 2, + }, + ) + + # 7 minutes of growth in STS-only, spanning two intervals + start_time = start.replace(hour=6, minute=14) + end_time = start.replace(hour=6, minute=21) + await assert_stat_during_fixed( + client, + start_time, + end_time, + { + "change": 10, + "min": 6 * 60 + 15, + "max": 6 * 60 + 25, + "mean": 6 * 60 + (15 + 25) / 2, + }, + ) + + # One hours worth of growth in LTS-only, with arbitrary minute offsets + # Since this does not fully cover the hour, result is None? + start_time = start.replace(hour=1, minute=40) + end_time = start.replace(hour=2, minute=12) + await assert_stat_during_fixed( + client, + start_time, + end_time, + {"change": None, "min": None, "max": None, "mean": None}, + ) + + # One hours worth of growth in LTS-only, with arbitrary minute offsets, covering a whole 1-hour period + start_time = start.replace(hour=1, minute=40) + end_time = start.replace(hour=3, minute=12) + await assert_stat_during_fixed( + client, + start_time, + end_time, + {"change": 60, "min": 120, "max": 180, "mean": 150}, + ) + + # 90 minutes of growth in window overlapping LTS+STS/STS-only (4:41 - 6:11) + start_time = start.replace(hour=4, minute=41) + end_time = start_time + timedelta(minutes=90) + await assert_stat_during_fixed( + client, + start_time, + end_time, + { + "change": 90, + "min": 4 * 60 + 45, + "max": 4 * 60 + 45 + 90, + "mean": 4 * 60 + 45 + 45, + }, + ) + + # 4 hours of growth in overlapping LTS-only/LTS+STS (2:01-6:01) + start_time = start.replace(hour=2, minute=1) + end_time = start_time + timedelta(minutes=240) + # 60 from LTS (3:00-3:59), 125 from STS (25 intervals) (4:00-6:01) + await assert_stat_during_fixed( + client, + start_time, + end_time, + {"change": 185, "min": 3 * 60, "max": 3 * 60 + 185, "mean": 3 * 60 + 185 / 2}, + ) + + # 4 hours of growth in overlapping LTS-only/LTS+STS (1:31-5:31) + start_time = start.replace(hour=1, minute=31) + end_time = start_time + timedelta(minutes=240) + # 120 from LTS (2:00-3:59), 95 from STS (19 intervals) 4:00-5:31 + await assert_stat_during_fixed( + client, + start_time, + end_time, + {"change": 215, "min": 2 * 60, "max": 2 * 60 + 215, "mean": 2 * 60 + 215 / 2}, + ) + + # 5 hours of growth, start time only (1:31-end) + start_time = start.replace(hour=1, minute=31) + end_time = None + # will be actually 2:00 - end + await assert_stat_during_fixed( + client, + start_time, + end_time, + {"change": 4 * 60 + 30, "min": 120, "max": 390, "mean": (390 + 120) / 2}, + ) + + # 5 hours of growth, end_time_only (0:00-5:00) + start_time = None + end_time = start.replace(hour=5) + await assert_stat_during_fixed( + client, + start_time, + end_time, + {"change": 5 * 60, "min": 0, "max": 5 * 60, "mean": (5 * 60) / 2}, + ) + + # 5 hours 1 minute of growth, end_time_only (0:00-5:01) + start_time = None + end_time = start.replace(hour=5, minute=1) + # 4 hours LTS, 1 hour and 5 minutes STS (4:00-5:01) + await assert_stat_during_fixed( + client, + start_time, + end_time, + {"change": 5 * 60 + 5, "min": 0, "max": 5 * 60 + 5, "mean": (5 * 60 + 5) / 2}, + ) + + @pytest.mark.freeze_time(datetime.datetime(2022, 10, 21, 7, 25, tzinfo=datetime.UTC)) @pytest.mark.parametrize( ("calendar_period", "start_time", "end_time"), From 461f0865af2521cdc2883adea4b8fc4d25ee47d5 Mon Sep 17 00:00:00 2001 From: Ruben Bokobza Date: Tue, 11 Jun 2024 08:04:25 +0300 Subject: [PATCH 02/41] Bump pyElectra to 1.2.1 (#118958) --- .strict-typing | 1 - homeassistant/components/electrasmart/manifest.json | 2 +- mypy.ini | 10 ---------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- script/hassfest/requirements.py | 1 - 6 files changed, 3 insertions(+), 15 deletions(-) diff --git a/.strict-typing b/.strict-typing index 313dda48649f1e..86fbf3c3563cc2 100644 --- a/.strict-typing +++ b/.strict-typing @@ -163,7 +163,6 @@ homeassistant.components.easyenergy.* homeassistant.components.ecovacs.* homeassistant.components.ecowitt.* homeassistant.components.efergy.* -homeassistant.components.electrasmart.* homeassistant.components.electric_kiwi.* homeassistant.components.elgato.* homeassistant.components.elkm1.* diff --git a/homeassistant/components/electrasmart/manifest.json b/homeassistant/components/electrasmart/manifest.json index 405d9ee688ad25..e00b818e2a61ed 100644 --- a/homeassistant/components/electrasmart/manifest.json +++ b/homeassistant/components/electrasmart/manifest.json @@ -5,5 +5,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/electrasmart", "iot_class": "cloud_polling", - "requirements": ["pyElectra==1.2.0"] + "requirements": ["pyElectra==1.2.1"] } diff --git a/mypy.ini b/mypy.ini index 4e4d9cc624bb84..ac3945872a1721 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1393,16 +1393,6 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true -[mypy-homeassistant.components.electrasmart.*] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -warn_return_any = true -warn_unreachable = true - [mypy-homeassistant.components.electric_kiwi.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 5d0a195b8e8e4d..abb6563c7c163b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1670,7 +1670,7 @@ pyControl4==1.1.0 pyDuotecno==2024.5.1 # homeassistant.components.electrasmart -pyElectra==1.2.0 +pyElectra==1.2.1 # homeassistant.components.emby pyEmby==1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ae1a1f3fd7211c..ec8893e3db9768 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1326,7 +1326,7 @@ pyControl4==1.1.0 pyDuotecno==2024.5.1 # homeassistant.components.electrasmart -pyElectra==1.2.0 +pyElectra==1.2.1 # homeassistant.components.rfxtrx pyRFXtrx==0.31.1 diff --git a/script/hassfest/requirements.py b/script/hassfest/requirements.py index f9a8ec2db92b1c..d35d96121c5221 100644 --- a/script/hassfest/requirements.py +++ b/script/hassfest/requirements.py @@ -30,7 +30,6 @@ IGNORE_STANDARD_LIBRARY_VIOLATIONS = { # Integrations which have standard library requirements. - "electrasmart", "slide", "suez_water", } From 7da10794a88ea860038c6e5eeeb2d6b327e1644a Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Fri, 7 Jun 2024 00:48:23 +0200 Subject: [PATCH 03/41] Update gardena library to 1.4.2 (#119010) --- homeassistant/components/gardena_bluetooth/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/gardena_bluetooth/manifest.json b/homeassistant/components/gardena_bluetooth/manifest.json index 6598aeaafd8931..1e3ef156d72bfa 100644 --- a/homeassistant/components/gardena_bluetooth/manifest.json +++ b/homeassistant/components/gardena_bluetooth/manifest.json @@ -13,5 +13,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth", "iot_class": "local_polling", - "requirements": ["gardena-bluetooth==1.4.1"] + "requirements": ["gardena-bluetooth==1.4.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index abb6563c7c163b..71f96c11bfe308 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -912,7 +912,7 @@ fyta_cli==0.4.1 gTTS==2.2.4 # homeassistant.components.gardena_bluetooth -gardena-bluetooth==1.4.1 +gardena-bluetooth==1.4.2 # homeassistant.components.google_assistant_sdk gassist-text==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ec8893e3db9768..167790fc162c10 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -747,7 +747,7 @@ fyta_cli==0.4.1 gTTS==2.2.4 # homeassistant.components.gardena_bluetooth -gardena-bluetooth==1.4.1 +gardena-bluetooth==1.4.2 # homeassistant.components.google_assistant_sdk gassist-text==0.0.11 From ebb0a453f41ab21b154487d1d209d5cb2d557af2 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 11 Jun 2024 09:26:44 +0200 Subject: [PATCH 04/41] Calculate attributes when entity information available in Group sensor (#119021) --- homeassistant/components/group/sensor.py | 32 +++++++++++++++- tests/components/group/test_sensor.py | 49 ++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/group/sensor.py b/homeassistant/components/group/sensor.py index 203b1b3fc8ea7b..2e6c321be1e825 100644 --- a/homeassistant/components/group/sensor.py +++ b/homeassistant/components/group/sensor.py @@ -36,7 +36,14 @@ STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.core import HomeAssistant, State, callback +from homeassistant.core import ( + CALLBACK_TYPE, + Event, + EventStateChangedData, + HomeAssistant, + State, + callback, +) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity import ( @@ -45,6 +52,7 @@ get_unit_of_measurement, ) from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.issue_registry import ( IssueSeverity, async_create_issue, @@ -329,6 +337,7 @@ def __init__( self._native_unit_of_measurement = unit_of_measurement self._valid_units: set[str | None] = set() self._can_convert: bool = False + self.calculate_attributes_later: CALLBACK_TYPE | None = None self._attr_name = name if name == DEFAULT_NAME: self._attr_name = f"{DEFAULT_NAME} {sensor_type}".capitalize() @@ -345,13 +354,32 @@ def __init__( async def async_added_to_hass(self) -> None: """When added to hass.""" + for entity_id in self._entity_ids: + if self.hass.states.get(entity_id) is None: + self.calculate_attributes_later = async_track_state_change_event( + self.hass, self._entity_ids, self.calculate_state_attributes + ) + break + if not self.calculate_attributes_later: + await self.calculate_state_attributes() + await super().async_added_to_hass() + + async def calculate_state_attributes( + self, event: Event[EventStateChangedData] | None = None + ) -> None: + """Calculate state attributes.""" + for entity_id in self._entity_ids: + if self.hass.states.get(entity_id) is None: + return + if self.calculate_attributes_later: + self.calculate_attributes_later() + self.calculate_attributes_later = None self._attr_state_class = self._calculate_state_class(self._state_class) self._attr_device_class = self._calculate_device_class(self._device_class) self._attr_native_unit_of_measurement = self._calculate_unit_of_measurement( self._native_unit_of_measurement ) self._valid_units = self._get_valid_units() - await super().async_added_to_hass() @callback def async_update_group_state(self) -> None: diff --git a/tests/components/group/test_sensor.py b/tests/components/group/test_sensor.py index c5331aa2f60518..db642506361dd6 100644 --- a/tests/components/group/test_sensor.py +++ b/tests/components/group/test_sensor.py @@ -763,3 +763,52 @@ async def test_last_sensor(hass: HomeAssistant) -> None: state = hass.states.get("sensor.test_last") assert str(float(value)) == state.state assert entity_id == state.attributes.get("last_entity_id") + + +async def test_sensors_attributes_added_when_entity_info_available( + hass: HomeAssistant, +) -> None: + """Test the sensor calculate attributes once all entities attributes are available.""" + config = { + SENSOR_DOMAIN: { + "platform": GROUP_DOMAIN, + "name": DEFAULT_NAME, + "type": "sum", + "entities": ["sensor.test_1", "sensor.test_2", "sensor.test_3"], + "unique_id": "very_unique_id", + } + } + + entity_ids = config["sensor"]["entities"] + + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.sensor_group_sum") + + assert state.state == STATE_UNAVAILABLE + assert state.attributes.get(ATTR_ENTITY_ID) is None + assert state.attributes.get(ATTR_DEVICE_CLASS) is None + assert state.attributes.get(ATTR_STATE_CLASS) is None + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) is None + + for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items(): + hass.states.async_set( + entity_id, + value, + { + ATTR_DEVICE_CLASS: SensorDeviceClass.VOLUME, + ATTR_STATE_CLASS: SensorStateClass.TOTAL, + ATTR_UNIT_OF_MEASUREMENT: "L", + }, + ) + await hass.async_block_till_done() + + state = hass.states.get("sensor.sensor_group_sum") + + assert float(state.state) == pytest.approx(float(SUM_VALUE)) + assert state.attributes.get(ATTR_ENTITY_ID) == entity_ids + assert state.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.VOLUME + assert state.attributes.get(ATTR_ICON) is None + assert state.attributes.get(ATTR_STATE_CLASS) == SensorStateClass.TOTAL + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "L" From db7a9321be43a689e4903d92e751454e9e45b955 Mon Sep 17 00:00:00 2001 From: tronikos Date: Fri, 7 Jun 2024 11:50:55 -0700 Subject: [PATCH 05/41] Bump google-generativeai to 0.6.0 (#119062) --- .../google_generative_ai_conversation/conversation.py | 10 +++++----- .../google_generative_ai_conversation/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/google_generative_ai_conversation/conversation.py b/homeassistant/components/google_generative_ai_conversation/conversation.py index 6b2f3c11dccff3..6c2bd64a7b544b 100644 --- a/homeassistant/components/google_generative_ai_conversation/conversation.py +++ b/homeassistant/components/google_generative_ai_conversation/conversation.py @@ -4,9 +4,9 @@ from typing import Any, Literal -import google.ai.generativelanguage as glm from google.api_core.exceptions import GoogleAPICallError import google.generativeai as genai +from google.generativeai import protos import google.generativeai.types as genai_types from google.protobuf.json_format import MessageToDict import voluptuous as vol @@ -93,7 +93,7 @@ def _format_tool(tool: llm.Tool) -> dict[str, Any]: parameters = _format_schema(convert(tool.parameters)) - return glm.Tool( + return protos.Tool( { "function_declarations": [ { @@ -349,13 +349,13 @@ async def async_process( LOGGER.debug("Tool response: %s", function_response) tool_responses.append( - glm.Part( - function_response=glm.FunctionResponse( + protos.Part( + function_response=protos.FunctionResponse( name=tool_name, response=function_response ) ) ) - chat_request = glm.Content(parts=tool_responses) + chat_request = protos.Content(parts=tool_responses) intent_response.async_set_speech( " ".join([part.text.strip() for part in chat_response.parts if part.text]) diff --git a/homeassistant/components/google_generative_ai_conversation/manifest.json b/homeassistant/components/google_generative_ai_conversation/manifest.json index 1886b16985fc56..168fee105a0af4 100644 --- a/homeassistant/components/google_generative_ai_conversation/manifest.json +++ b/homeassistant/components/google_generative_ai_conversation/manifest.json @@ -9,5 +9,5 @@ "integration_type": "service", "iot_class": "cloud_polling", "quality_scale": "platinum", - "requirements": ["google-generativeai==0.5.4", "voluptuous-openapi==0.0.4"] + "requirements": ["google-generativeai==0.6.0", "voluptuous-openapi==0.0.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index 71f96c11bfe308..d8267c8d0e7181 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -974,7 +974,7 @@ google-cloud-pubsub==2.13.11 google-cloud-texttospeech==2.12.3 # homeassistant.components.google_generative_ai_conversation -google-generativeai==0.5.4 +google-generativeai==0.6.0 # homeassistant.components.nest google-nest-sdm==4.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 167790fc162c10..12e4bad7fa7d7a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -800,7 +800,7 @@ google-api-python-client==2.71.0 google-cloud-pubsub==2.13.11 # homeassistant.components.google_generative_ai_conversation -google-generativeai==0.5.4 +google-generativeai==0.6.0 # homeassistant.components.nest google-nest-sdm==4.0.4 From a1f2140ed79b8a5d14043e2a1fcd43907a6c36f2 Mon Sep 17 00:00:00 2001 From: kaareseras Date: Tue, 11 Jun 2024 09:18:06 +0200 Subject: [PATCH 06/41] Fix Azure data explorer (#119089) Co-authored-by: Robert Resch --- .../azure_data_explorer/__init__.py | 9 ++--- .../components/azure_data_explorer/client.py | 37 ++++++++++++------- .../azure_data_explorer/config_flow.py | 5 ++- .../components/azure_data_explorer/const.py | 2 +- .../azure_data_explorer/strings.json | 14 ++++--- tests/components/azure_data_explorer/const.py | 8 ++-- 6 files changed, 45 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/azure_data_explorer/__init__.py b/homeassistant/components/azure_data_explorer/__init__.py index 62718d6938e4e3..319f7e4389be65 100644 --- a/homeassistant/components/azure_data_explorer/__init__.py +++ b/homeassistant/components/azure_data_explorer/__init__.py @@ -62,13 +62,12 @@ async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool: Adds an empty filter to hass data. Tries to get a filter from yaml, if present set to hass data. - If config is empty after getting the filter, return, otherwise emit - deprecated warning and pass the rest to the config flow. """ - hass.data.setdefault(DOMAIN, {DATA_FILTER: {}}) + hass.data.setdefault(DOMAIN, {DATA_FILTER: FILTER_SCHEMA({})}) if DOMAIN in yaml_config: - hass.data[DOMAIN][DATA_FILTER] = yaml_config[DOMAIN][CONF_FILTER] + hass.data[DOMAIN][DATA_FILTER] = yaml_config[DOMAIN].pop(CONF_FILTER) + return True @@ -207,6 +206,6 @@ def _parse_event( if "\n" in state.state: return None, dropped + 1 - json_event = str(json.dumps(obj=state, cls=JSONEncoder).encode("utf-8")) + json_event = json.dumps(obj=state, cls=JSONEncoder) return (json_event, dropped) diff --git a/homeassistant/components/azure_data_explorer/client.py b/homeassistant/components/azure_data_explorer/client.py index 40528bc6a6ffe3..88609ff8e103cb 100644 --- a/homeassistant/components/azure_data_explorer/client.py +++ b/homeassistant/components/azure_data_explorer/client.py @@ -23,7 +23,7 @@ CONF_APP_REG_ID, CONF_APP_REG_SECRET, CONF_AUTHORITY_ID, - CONF_USE_FREE, + CONF_USE_QUEUED_CLIENT, ) _LOGGER = logging.getLogger(__name__) @@ -35,7 +35,6 @@ class AzureDataExplorerClient: def __init__(self, data: Mapping[str, Any]) -> None: """Create the right class.""" - self._cluster_ingest_uri = data[CONF_ADX_CLUSTER_INGEST_URI] self._database = data[CONF_ADX_DATABASE_NAME] self._table = data[CONF_ADX_TABLE_NAME] self._ingestion_properties = IngestionProperties( @@ -45,24 +44,36 @@ def __init__(self, data: Mapping[str, Any]) -> None: ingestion_mapping_reference="ha_json_mapping", ) - # Create cLient for ingesting and querying data - kcsb = KustoConnectionStringBuilder.with_aad_application_key_authentication( - self._cluster_ingest_uri, - data[CONF_APP_REG_ID], - data[CONF_APP_REG_SECRET], - data[CONF_AUTHORITY_ID], + # Create client for ingesting data + kcsb_ingest = ( + KustoConnectionStringBuilder.with_aad_application_key_authentication( + data[CONF_ADX_CLUSTER_INGEST_URI], + data[CONF_APP_REG_ID], + data[CONF_APP_REG_SECRET], + data[CONF_AUTHORITY_ID], + ) ) - if data[CONF_USE_FREE] is True: + # Create client for querying data + kcsb_query = ( + KustoConnectionStringBuilder.with_aad_application_key_authentication( + data[CONF_ADX_CLUSTER_INGEST_URI].replace("ingest-", ""), + data[CONF_APP_REG_ID], + data[CONF_APP_REG_SECRET], + data[CONF_AUTHORITY_ID], + ) + ) + + if data[CONF_USE_QUEUED_CLIENT] is True: # Queded is the only option supported on free tear of ADX - self.write_client = QueuedIngestClient(kcsb) + self.write_client = QueuedIngestClient(kcsb_ingest) else: - self.write_client = ManagedStreamingIngestClient.from_dm_kcsb(kcsb) + self.write_client = ManagedStreamingIngestClient.from_dm_kcsb(kcsb_ingest) - self.query_client = KustoClient(kcsb) + self.query_client = KustoClient(kcsb_query) def test_connection(self) -> None: - """Test connection, will throw Exception when it cannot connect.""" + """Test connection, will throw Exception if it cannot connect.""" query = f"{self._table} | take 1" diff --git a/homeassistant/components/azure_data_explorer/config_flow.py b/homeassistant/components/azure_data_explorer/config_flow.py index d8390246b4199c..4ffb5ea7cf7e73 100644 --- a/homeassistant/components/azure_data_explorer/config_flow.py +++ b/homeassistant/components/azure_data_explorer/config_flow.py @@ -10,6 +10,7 @@ from homeassistant import config_entries from homeassistant.config_entries import ConfigFlowResult +from homeassistant.helpers.selector import BooleanSelector from . import AzureDataExplorerClient from .const import ( @@ -19,7 +20,7 @@ CONF_APP_REG_ID, CONF_APP_REG_SECRET, CONF_AUTHORITY_ID, - CONF_USE_FREE, + CONF_USE_QUEUED_CLIENT, DEFAULT_OPTIONS, DOMAIN, ) @@ -34,7 +35,7 @@ vol.Required(CONF_APP_REG_ID): str, vol.Required(CONF_APP_REG_SECRET): str, vol.Required(CONF_AUTHORITY_ID): str, - vol.Optional(CONF_USE_FREE, default=False): bool, + vol.Required(CONF_USE_QUEUED_CLIENT, default=False): BooleanSelector(), } ) diff --git a/homeassistant/components/azure_data_explorer/const.py b/homeassistant/components/azure_data_explorer/const.py index ca98110597a912..a88a6b8b94fb04 100644 --- a/homeassistant/components/azure_data_explorer/const.py +++ b/homeassistant/components/azure_data_explorer/const.py @@ -17,7 +17,7 @@ CONF_SEND_INTERVAL = "send_interval" CONF_MAX_DELAY = "max_delay" CONF_FILTER = DATA_FILTER = "filter" -CONF_USE_FREE = "use_queued_ingestion" +CONF_USE_QUEUED_CLIENT = "use_queued_ingestion" DATA_HUB = "hub" STEP_USER = "user" diff --git a/homeassistant/components/azure_data_explorer/strings.json b/homeassistant/components/azure_data_explorer/strings.json index 640058725794b8..c8ec158a844543 100644 --- a/homeassistant/components/azure_data_explorer/strings.json +++ b/homeassistant/components/azure_data_explorer/strings.json @@ -3,15 +3,19 @@ "step": { "user": { "title": "Setup your Azure Data Explorer integration", - "description": "Enter connection details.", + "description": "Enter connection details", "data": { - "cluster_ingest_uri": "Cluster ingest URI", - "database": "Database name", - "table": "Table name", + "cluster_ingest_uri": "Cluster Ingest URI", + "authority_id": "Authority ID", "client_id": "Client ID", "client_secret": "Client secret", - "authority_id": "Authority ID", + "database": "Database name", + "table": "Table name", "use_queued_ingestion": "Use queued ingestion" + }, + "data_description": { + "cluster_ingest_uri": "Ingest-URI of the cluster", + "use_queued_ingestion": "Must be enabled when using ADX free cluster" } } }, diff --git a/tests/components/azure_data_explorer/const.py b/tests/components/azure_data_explorer/const.py index d29f4d5ba93dbe..d20be1584a198d 100644 --- a/tests/components/azure_data_explorer/const.py +++ b/tests/components/azure_data_explorer/const.py @@ -8,7 +8,7 @@ CONF_APP_REG_SECRET, CONF_AUTHORITY_ID, CONF_SEND_INTERVAL, - CONF_USE_FREE, + CONF_USE_QUEUED_CLIENT, ) AZURE_DATA_EXPLORER_PATH = "homeassistant.components.azure_data_explorer" @@ -29,7 +29,7 @@ } BASIC_OPTIONS = { - CONF_USE_FREE: False, + CONF_USE_QUEUED_CLIENT: False, CONF_SEND_INTERVAL: 5, } @@ -39,10 +39,10 @@ BASE_CONFIG_IMPORT = { CONF_ADX_CLUSTER_INGEST_URI: "https://cluster.region.kusto.windows.net", - CONF_USE_FREE: False, + CONF_USE_QUEUED_CLIENT: False, CONF_SEND_INTERVAL: 5, } -FREE_OPTIONS = {CONF_USE_FREE: True, CONF_SEND_INTERVAL: 5} +FREE_OPTIONS = {CONF_USE_QUEUED_CLIENT: True, CONF_SEND_INTERVAL: 5} BASE_CONFIG_FREE = BASE_CONFIG | FREE_OPTIONS From 87f48b15d150dd0f335263e8b8e306520a902fd8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 8 Jun 2024 16:07:39 -0500 Subject: [PATCH 07/41] Ensure multiple executions of a restart automation in the same event loop iteration are allowed (#119100) * Add test for restarting automation related issue #119097 * fix * add a delay since restart is an infinite loop * tests --- homeassistant/helpers/script.py | 4 - tests/components/automation/test_init.py | 137 ++++++++++++++++++++++- 2 files changed, 136 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 4d315f428c3d70..1a4d57e6929e8a 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1758,10 +1758,6 @@ async def async_run( # runs before sleeping as otherwise if two runs are started at the exact # same time they will cancel each other out. self._log("Restarting") - # Important: yield to the event loop to allow the script to start in case - # the script is restarting itself so it ends up in the script stack and - # the recursion check above will prevent the script from running. - await asyncio.sleep(0) await self.async_stop(update_state=False, spare=run) if started_action: diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 7b3d4c4010ee86..bd5957326ec04f 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -2771,6 +2771,7 @@ async def mock_stop_scripts_at_shutdown(*args): ], "action": [ {"service": "test.automation_started"}, + {"delay": 0.001}, {"service": "script.script1"}, ], } @@ -2817,7 +2818,10 @@ async def async_automation_triggered(event): assert script_warning_msg in caplog.text -@pytest.mark.parametrize("automation_mode", SCRIPT_MODE_CHOICES) +@pytest.mark.parametrize( + "automation_mode", + [mode for mode in SCRIPT_MODE_CHOICES if mode != SCRIPT_MODE_RESTART], +) @pytest.mark.parametrize("wait_for_stop_scripts_after_shutdown", [True]) async def test_recursive_automation( hass: HomeAssistant, automation_mode, caplog: pytest.LogCaptureFixture @@ -2878,6 +2882,68 @@ async def async_service_handler(service): assert "Disallowed recursion detected" not in caplog.text +@pytest.mark.parametrize("wait_for_stop_scripts_after_shutdown", [True]) +async def test_recursive_automation_restart_mode( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test automation restarting itself. + + The automation is an infinite loop since it keeps restarting itself + + - Illegal recursion detection should not be triggered + - Home Assistant should not hang on shut down + """ + stop_scripts_at_shutdown_called = asyncio.Event() + real_stop_scripts_at_shutdown = _async_stop_scripts_at_shutdown + + async def stop_scripts_at_shutdown(*args): + await real_stop_scripts_at_shutdown(*args) + stop_scripts_at_shutdown_called.set() + + with patch( + "homeassistant.helpers.script._async_stop_scripts_at_shutdown", + wraps=stop_scripts_at_shutdown, + ): + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "mode": SCRIPT_MODE_RESTART, + "trigger": [ + {"platform": "event", "event_type": "trigger_automation"}, + ], + "action": [ + {"event": "trigger_automation"}, + {"service": "test.automation_done"}, + ], + } + }, + ) + + service_called = asyncio.Event() + + async def async_service_handler(service): + if service.service == "automation_done": + service_called.set() + + hass.services.async_register("test", "automation_done", async_service_handler) + + hass.bus.async_fire("trigger_automation") + await asyncio.sleep(0) + + # Trigger 1st stage script shutdown + hass.set_state(CoreState.stopping) + hass.bus.async_fire("homeassistant_stop") + await asyncio.wait_for(stop_scripts_at_shutdown_called.wait(), 1) + + # Trigger 2nd stage script shutdown + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=90)) + await hass.async_block_till_done() + + assert "Disallowed recursion detected" not in caplog.text + + async def test_websocket_config( hass: HomeAssistant, hass_ws_client: WebSocketGenerator ) -> None: @@ -3097,3 +3163,72 @@ def _save_event(event): await hass.async_block_till_done() assert len(events) == 2 cancel() + + +async def test_two_automation_call_restart_script_right_after_each_other( + hass: HomeAssistant, +) -> None: + """Test two automations call a restart script right after each other.""" + + events = async_capture_events(hass, "repeat_test_script_finished") + + assert await async_setup_component( + hass, + input_boolean.DOMAIN, + { + input_boolean.DOMAIN: { + "test_1": None, + "test_2": None, + } + }, + ) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "state", + "entity_id": ["input_boolean.test_1", "input_boolean.test_1"], + "from": "off", + "to": "on", + }, + "action": [ + { + "repeat": { + "count": 2, + "sequence": [ + { + "delay": { + "hours": 0, + "minutes": 0, + "seconds": 0, + "milliseconds": 100, + } + } + ], + } + }, + {"event": "repeat_test_script_finished", "event_data": {}}, + ], + "id": "automation_0", + "mode": "restart", + }, + ] + }, + ) + hass.states.async_set("input_boolean.test_1", "off") + hass.states.async_set("input_boolean.test_2", "off") + await hass.async_block_till_done() + hass.states.async_set("input_boolean.test_1", "on") + hass.states.async_set("input_boolean.test_2", "on") + await asyncio.sleep(0) + hass.states.async_set("input_boolean.test_1", "off") + hass.states.async_set("input_boolean.test_2", "off") + await asyncio.sleep(0) + hass.states.async_set("input_boolean.test_1", "on") + hass.states.async_set("input_boolean.test_2", "on") + await hass.async_block_till_done() + assert len(events) == 1 From 96ac566032f10adf73a960bdb58db1a9a269eea0 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Mon, 10 Jun 2024 02:48:11 -0400 Subject: [PATCH 08/41] Fix control 4 on os 2 (#119104) --- homeassistant/components/control4/__init__.py | 7 ++++++- homeassistant/components/control4/media_player.py | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/control4/__init__.py b/homeassistant/components/control4/__init__.py index 86a13de1ac8f95..c9a6eab5c625eb 100644 --- a/homeassistant/components/control4/__init__.py +++ b/homeassistant/components/control4/__init__.py @@ -120,7 +120,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: director_all_items = json.loads(director_all_items) entry_data[CONF_DIRECTOR_ALL_ITEMS] = director_all_items - entry_data[CONF_UI_CONFIGURATION] = json.loads(await director.getUiConfiguration()) + # Check if OS version is 3 or higher to get UI configuration + entry_data[CONF_UI_CONFIGURATION] = None + if int(entry_data[CONF_DIRECTOR_SW_VERSION].split(".")[0]) >= 3: + entry_data[CONF_UI_CONFIGURATION] = json.loads( + await director.getUiConfiguration() + ) # Load options from config entry entry_data[CONF_SCAN_INTERVAL] = entry.options.get( diff --git a/homeassistant/components/control4/media_player.py b/homeassistant/components/control4/media_player.py index 99d8c27face1e0..72aa44faaed5f0 100644 --- a/homeassistant/components/control4/media_player.py +++ b/homeassistant/components/control4/media_player.py @@ -81,11 +81,18 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up Control4 rooms from a config entry.""" + entry_data = hass.data[DOMAIN][entry.entry_id] + ui_config = entry_data[CONF_UI_CONFIGURATION] + + # OS 2 will not have a ui_configuration + if not ui_config: + _LOGGER.debug("No UI Configuration found for Control4") + return + all_rooms = await get_rooms(hass, entry) if not all_rooms: return - entry_data = hass.data[DOMAIN][entry.entry_id] scan_interval = entry_data[CONF_SCAN_INTERVAL] _LOGGER.debug("Scan interval = %s", scan_interval) @@ -119,8 +126,6 @@ async def async_update_data() -> dict[int, dict[str, Any]]: if "parentId" in item and k > 1 } - ui_config = entry_data[CONF_UI_CONFIGURATION] - entity_list = [] for room in all_rooms: room_id = room["id"] From 34477d35595f57a04c9581045c7e1ae2c223629e Mon Sep 17 00:00:00 2001 From: tronikos Date: Sat, 8 Jun 2024 00:02:00 -0700 Subject: [PATCH 09/41] Properly handle escaped unicode characters passed to tools in Google Generative AI (#119117) --- .../conversation.py | 16 +++++++--------- .../test_conversation.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/google_generative_ai_conversation/conversation.py b/homeassistant/components/google_generative_ai_conversation/conversation.py index 6c2bd64a7b544b..65c0dc7fd933eb 100644 --- a/homeassistant/components/google_generative_ai_conversation/conversation.py +++ b/homeassistant/components/google_generative_ai_conversation/conversation.py @@ -2,6 +2,7 @@ from __future__ import annotations +import codecs from typing import Any, Literal from google.api_core.exceptions import GoogleAPICallError @@ -106,14 +107,14 @@ def _format_tool(tool: llm.Tool) -> dict[str, Any]: ) -def _adjust_value(value: Any) -> Any: - """Reverse unnecessary single quotes escaping.""" +def _escape_decode(value: Any) -> Any: + """Recursively call codecs.escape_decode on all values.""" if isinstance(value, str): - return value.replace("\\'", "'") + return codecs.escape_decode(bytes(value, "utf-8"))[0].decode("utf-8") # type: ignore[attr-defined] if isinstance(value, list): - return [_adjust_value(item) for item in value] + return [_escape_decode(item) for item in value] if isinstance(value, dict): - return {k: _adjust_value(v) for k, v in value.items()} + return {k: _escape_decode(v) for k, v in value.items()} return value @@ -334,10 +335,7 @@ async def async_process( for function_call in function_calls: tool_call = MessageToDict(function_call._pb) # noqa: SLF001 tool_name = tool_call["name"] - tool_args = { - key: _adjust_value(value) - for key, value in tool_call["args"].items() - } + tool_args = _escape_decode(tool_call["args"]) LOGGER.debug("Tool call: %s(%s)", tool_name, tool_args) tool_input = llm.ToolInput(tool_name=tool_name, tool_args=tool_args) try: diff --git a/tests/components/google_generative_ai_conversation/test_conversation.py b/tests/components/google_generative_ai_conversation/test_conversation.py index 901216d262fe66..e84efffe7df629 100644 --- a/tests/components/google_generative_ai_conversation/test_conversation.py +++ b/tests/components/google_generative_ai_conversation/test_conversation.py @@ -12,6 +12,9 @@ from homeassistant.components import conversation from homeassistant.components.conversation import trace +from homeassistant.components.google_generative_ai_conversation.conversation import ( + _escape_decode, +) from homeassistant.const import CONF_LLM_HASS_API from homeassistant.core import Context, HomeAssistant from homeassistant.exceptions import HomeAssistantError @@ -504,3 +507,18 @@ async def test_conversation_agent( mock_config_entry.entry_id ) assert agent.supported_languages == "*" + + +async def test_escape_decode() -> None: + """Test _escape_decode.""" + assert _escape_decode( + { + "param1": ["test_value", "param1\\'s value"], + "param2": "param2\\'s value", + "param3": {"param31": "Cheminée", "param32": "Chemin\\303\\251e"}, + } + ) == { + "param1": ["test_value", "param1's value"], + "param2": "param2's value", + "param3": {"param31": "Cheminée", "param32": "Cheminée"}, + } From 0f8ed4e73d0f1c2049ca56732cdedddce4ef2465 Mon Sep 17 00:00:00 2001 From: tronikos Date: Fri, 7 Jun 2024 23:51:42 -0700 Subject: [PATCH 10/41] Catch GoogleAPICallError in Google Generative AI (#119118) --- .../components/google_generative_ai_conversation/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/google_generative_ai_conversation/__init__.py b/homeassistant/components/google_generative_ai_conversation/__init__.py index 523198355d1ccc..f115f3923b68f6 100644 --- a/homeassistant/components/google_generative_ai_conversation/__init__.py +++ b/homeassistant/components/google_generative_ai_conversation/__init__.py @@ -71,7 +71,7 @@ async def generate_content(call: ServiceCall) -> ServiceResponse: try: response = await model.generate_content_async(prompt_parts) except ( - ClientError, + GoogleAPICallError, ValueError, genai_types.BlockedPromptException, genai_types.StopCandidateException, From df96b949858fcb5e5fc324d7eeae8eb92270de04 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sat, 8 Jun 2024 11:44:37 +0300 Subject: [PATCH 11/41] Bump aioshelly to 10.0.1 (#119123) --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 2e8c2d59c1e7b0..b1b00e40c66ef4 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -9,7 +9,7 @@ "iot_class": "local_push", "loggers": ["aioshelly"], "quality_scale": "platinum", - "requirements": ["aioshelly==10.0.0"], + "requirements": ["aioshelly==10.0.1"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index d8267c8d0e7181..5fe0d275939f97 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -353,7 +353,7 @@ aioruuvigateway==0.1.0 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==10.0.0 +aioshelly==10.0.1 # homeassistant.components.skybell aioskybell==22.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 12e4bad7fa7d7a..974d4bd2427885 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -326,7 +326,7 @@ aioruuvigateway==0.1.0 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==10.0.0 +aioshelly==10.0.1 # homeassistant.components.skybell aioskybell==22.7.0 From a696ea18d361ba6b58172c36643cd9aad85db59f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 8 Jun 2024 11:28:45 +0200 Subject: [PATCH 12/41] Bump aiowaqi to 3.1.0 (#119124) --- homeassistant/components/waqi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/waqi/manifest.json b/homeassistant/components/waqi/manifest.json index d742fd72858f41..cb04bd7d6acba9 100644 --- a/homeassistant/components/waqi/manifest.json +++ b/homeassistant/components/waqi/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/waqi", "iot_class": "cloud_polling", "loggers": ["aiowaqi"], - "requirements": ["aiowaqi==3.0.1"] + "requirements": ["aiowaqi==3.1.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5fe0d275939f97..cc10ead5c5e8b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -389,7 +389,7 @@ aiovlc==0.3.2 aiovodafone==0.6.0 # homeassistant.components.waqi -aiowaqi==3.0.1 +aiowaqi==3.1.0 # homeassistant.components.watttime aiowatttime==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 974d4bd2427885..612a919285546f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -362,7 +362,7 @@ aiovlc==0.3.2 aiovodafone==0.6.0 # homeassistant.components.waqi -aiowaqi==3.0.1 +aiowaqi==3.1.0 # homeassistant.components.watttime aiowatttime==0.1.1 From 4bb1ea1da187e74a7b3e8102b6060cb75e1f0599 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 8 Jun 2024 11:53:47 -0400 Subject: [PATCH 13/41] Ensure intent tools have safe names (#119144) --- homeassistant/helpers/llm.py | 13 +++++++++++-- tests/helpers/test_llm.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/llm.py b/homeassistant/helpers/llm.py index 3c240692d52e08..903e52af1a2e09 100644 --- a/homeassistant/helpers/llm.py +++ b/homeassistant/helpers/llm.py @@ -5,8 +5,10 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from enum import Enum +from functools import cache, partial from typing import Any +import slugify as unicode_slug import voluptuous as vol from homeassistant.components.climate.intent import INTENT_GET_TEMPERATURE @@ -175,10 +177,11 @@ class IntentTool(Tool): def __init__( self, + name: str, intent_handler: intent.IntentHandler, ) -> None: """Init the class.""" - self.name = intent_handler.intent_type + self.name = name self.description = ( intent_handler.description or f"Execute Home Assistant {self.name} intent" ) @@ -261,6 +264,9 @@ def __init__(self, hass: HomeAssistant) -> None: id=LLM_API_ASSIST, name="Assist", ) + self.cached_slugify = cache( + partial(unicode_slug.slugify, separator="_", lowercase=False) + ) async def async_get_api_instance(self, llm_context: LLMContext) -> APIInstance: """Return the instance of the API.""" @@ -373,7 +379,10 @@ def _async_get_tools( or intent_handler.platforms & exposed_domains ] - return [IntentTool(intent_handler) for intent_handler in intent_handlers] + return [ + IntentTool(self.cached_slugify(intent_handler.intent_type), intent_handler) + for intent_handler in intent_handlers + ] def _get_exposed_entities( diff --git a/tests/helpers/test_llm.py b/tests/helpers/test_llm.py index 3f61ed8a0ed6ae..6ac17a2fe0efb7 100644 --- a/tests/helpers/test_llm.py +++ b/tests/helpers/test_llm.py @@ -249,6 +249,39 @@ async def test_assist_api_get_timer_tools( assert "HassStartTimer" in [tool.name for tool in api.tools] +async def test_assist_api_tools( + hass: HomeAssistant, llm_context: llm.LLMContext +) -> None: + """Test getting timer tools with Assist API.""" + assert await async_setup_component(hass, "homeassistant", {}) + assert await async_setup_component(hass, "intent", {}) + + llm_context.device_id = "test_device" + + async_register_timer_handler(hass, "test_device", lambda *args: None) + + class MyIntentHandler(intent.IntentHandler): + intent_type = "Super crazy intent with unique nåme" + description = "my intent handler" + + intent.async_register(hass, MyIntentHandler()) + + api = await llm.async_get_api(hass, "assist", llm_context) + assert [tool.name for tool in api.tools] == [ + "HassTurnOn", + "HassTurnOff", + "HassSetPosition", + "HassStartTimer", + "HassCancelTimer", + "HassIncreaseTimer", + "HassDecreaseTimer", + "HassPauseTimer", + "HassUnpauseTimer", + "HassTimerStatus", + "Super_crazy_intent_with_unique_name", + ] + + async def test_assist_api_description( hass: HomeAssistant, llm_context: llm.LLMContext ) -> None: From 7912c9e95cc69f4dd4238f9b9586e98da9140571 Mon Sep 17 00:00:00 2001 From: Tom Brien Date: Sat, 8 Jun 2024 16:53:20 +0100 Subject: [PATCH 14/41] Fix workday timezone (#119148) --- homeassistant/components/workday/binary_sensor.py | 2 +- tests/components/workday/test_binary_sensor.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 205f500746e231..5df8e6c3d75c7a 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -269,7 +269,7 @@ def get_next_interval(self, now: datetime) -> datetime: def _update_state_and_setup_listener(self) -> None: """Update state and setup listener for next interval.""" - now = dt_util.utcnow() + now = dt_util.now() self.update_data(now) self.unsub = async_track_point_in_utc_time( self.hass, self.point_in_time_listener, self.get_next_interval(now) diff --git a/tests/components/workday/test_binary_sensor.py b/tests/components/workday/test_binary_sensor.py index e9f0e8023bc26b..9aa4dd6b5b4347 100644 --- a/tests/components/workday/test_binary_sensor.py +++ b/tests/components/workday/test_binary_sensor.py @@ -1,6 +1,6 @@ """Tests the Home Assistant workday binary sensor.""" -from datetime import date, datetime, timedelta +from datetime import date, datetime, timedelta, timezone from typing import Any from freezegun.api import FrozenDateTimeFactory @@ -68,7 +68,9 @@ async def test_setup( freezer: FrozenDateTimeFactory, ) -> None: """Test setup from various configs.""" - freezer.move_to(datetime(2022, 4, 15, 12, tzinfo=UTC)) # Friday + # Start on a Friday + await hass.config.async_set_time_zone("Europe/Paris") + freezer.move_to(datetime(2022, 4, 15, 0, tzinfo=timezone(timedelta(hours=1)))) await init_integration(hass, config) state = hass.states.get("binary_sensor.workday_sensor") From 40ebf3b2a956b5bf44c5695b1ebfbf230c8cf5cc Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sat, 8 Jun 2024 21:24:59 +0200 Subject: [PATCH 15/41] Bump py-synologydsm-api to 2.4.4 (#119156) bump py-synologydsm-api to 2.4.4 --- homeassistant/components/synology_dsm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index caecfcbd0c9802..b1133fd61ad611 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -7,7 +7,7 @@ "documentation": "https://www.home-assistant.io/integrations/synology_dsm", "iot_class": "local_polling", "loggers": ["synology_dsm"], - "requirements": ["py-synologydsm-api==2.4.2"], + "requirements": ["py-synologydsm-api==2.4.4"], "ssdp": [ { "manufacturer": "Synology", diff --git a/requirements_all.txt b/requirements_all.txt index cc10ead5c5e8b9..4d2a02078a1592 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1649,7 +1649,7 @@ py-schluter==0.1.7 py-sucks==0.9.10 # homeassistant.components.synology_dsm -py-synologydsm-api==2.4.2 +py-synologydsm-api==2.4.4 # homeassistant.components.zabbix py-zabbix==1.1.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 612a919285546f..8a4460b1cc9c3f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1311,7 +1311,7 @@ py-nightscout==1.2.2 py-sucks==0.9.10 # homeassistant.components.synology_dsm -py-synologydsm-api==2.4.2 +py-synologydsm-api==2.4.4 # homeassistant.components.seventeentrack py17track==2021.12.2 From 019d33c06c0db0045d710aa92a0b5e0df735dcf1 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sat, 8 Jun 2024 23:52:14 +0200 Subject: [PATCH 16/41] Use more conservative timeout values in Synology DSM (#119169) use ClientTimeout object --- homeassistant/components/synology_dsm/const.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 35d3008b41631f..11839caf8bef8e 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -2,6 +2,7 @@ from __future__ import annotations +from aiohttp import ClientTimeout from synology_dsm.api.surveillance_station.const import SNAPSHOT_PROFILE_BALANCED from synology_dsm.exceptions import ( SynologyDSMAPIErrorException, @@ -40,7 +41,7 @@ DEFAULT_PORT_SSL = 5001 # Options DEFAULT_SCAN_INTERVAL = 15 # min -DEFAULT_TIMEOUT = 30 # sec +DEFAULT_TIMEOUT = ClientTimeout(total=60, connect=15) DEFAULT_SNAPSHOT_QUALITY = SNAPSHOT_PROFILE_BALANCED ENTITY_UNIT_LOAD = "load" From 9a8e3ad5cc98acd43b6c5e3a7f87145674fb87ee Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Sun, 9 Jun 2024 12:59:40 +0300 Subject: [PATCH 17/41] Handle Shelly BLE errors during connect and disconnect (#119174) --- homeassistant/components/shelly/__init__.py | 9 +--- .../components/shelly/coordinator.py | 18 ++++++- tests/components/shelly/test_coordinator.py | 47 +++++++++++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 1bcd9c7c1e4a21..cc1ea5e81a6fdd 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations -import contextlib from typing import Final from aioshelly.block_device import BlockDevice @@ -301,13 +300,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ShellyConfigEntry) -> b entry, platforms ): if shelly_entry_data.rpc: - with contextlib.suppress(DeviceConnectionError): - # If the device is restarting or has gone offline before - # the ping/pong timeout happens, the shutdown command - # will fail, but we don't care since we are unloading - # and if we setup again, we will fix anything that is - # in an inconsistent state at that time. - await shelly_entry_data.rpc.shutdown() + await shelly_entry_data.rpc.shutdown() return unload_ok diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index c12e1aea289aaa..5bb05d48d62fe7 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -625,7 +625,13 @@ async def _async_connected(self) -> None: if self.connected: # Already connected return self.connected = True - await self._async_run_connected_events() + try: + await self._async_run_connected_events() + except DeviceConnectionError as err: + LOGGER.error( + "Error running connected events for device %s: %s", self.name, err + ) + self.last_update_success = False async def _async_run_connected_events(self) -> None: """Run connected events. @@ -699,10 +705,18 @@ async def shutdown(self) -> None: if self.device.connected: try: await async_stop_scanner(self.device) + await super().shutdown() except InvalidAuthError: self.entry.async_start_reauth(self.hass) return - await super().shutdown() + except DeviceConnectionError as err: + # If the device is restarting or has gone offline before + # the ping/pong timeout happens, the shutdown command + # will fail, but we don't care since we are unloading + # and if we setup again, we will fix anything that is + # in an inconsistent state at that time. + LOGGER.debug("Error during shutdown for device %s: %s", self.name, err) + return await self._async_disconnected(False) diff --git a/tests/components/shelly/test_coordinator.py b/tests/components/shelly/test_coordinator.py index 1dc45a98c446a1..cd750e53f0bf00 100644 --- a/tests/components/shelly/test_coordinator.py +++ b/tests/components/shelly/test_coordinator.py @@ -15,12 +15,14 @@ ATTR_CLICK_TYPE, ATTR_DEVICE, ATTR_GENERATION, + CONF_BLE_SCANNER_MODE, DOMAIN, ENTRY_RELOAD_COOLDOWN, MAX_PUSH_UPDATE_FAILURES, RPC_RECONNECT_INTERVAL, SLEEP_PERIOD_MULTIPLIER, UPDATE_PERIOD_MULTIPLIER, + BLEScannerMode, ) from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState from homeassistant.const import ATTR_DEVICE_ID, STATE_ON, STATE_UNAVAILABLE @@ -485,6 +487,25 @@ async def test_rpc_reload_with_invalid_auth( assert flow["context"].get("entry_id") == entry.entry_id +async def test_rpc_connection_error_during_unload( + hass: HomeAssistant, mock_rpc_device: Mock, caplog: pytest.LogCaptureFixture +) -> None: + """Test RPC DeviceConnectionError suppressed during config entry unload.""" + entry = await init_integration(hass, 2) + + assert entry.state is ConfigEntryState.LOADED + + with patch( + "homeassistant.components.shelly.coordinator.async_stop_scanner", + side_effect=DeviceConnectionError, + ): + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert "Error during shutdown for device" in caplog.text + assert entry.state is ConfigEntryState.NOT_LOADED + + async def test_rpc_click_event( hass: HomeAssistant, mock_rpc_device: Mock, @@ -713,6 +734,32 @@ async def test_rpc_reconnect_error( assert get_entity_state(hass, "switch.test_switch_0") == STATE_UNAVAILABLE +async def test_rpc_error_running_connected_events( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + mock_rpc_device: Mock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test RPC error while running connected events.""" + with patch( + "homeassistant.components.shelly.coordinator.async_ensure_ble_enabled", + side_effect=DeviceConnectionError, + ): + await init_integration( + hass, 2, options={CONF_BLE_SCANNER_MODE: BLEScannerMode.ACTIVE} + ) + + assert "Error running connected events for device" in caplog.text + assert get_entity_state(hass, "switch.test_switch_0") == STATE_UNAVAILABLE + + # Move time to generate reconnect without error + freezer.tick(timedelta(seconds=RPC_RECONNECT_INTERVAL)) + async_fire_time_changed(hass) + await hass.async_block_till_done(wait_background_tasks=True) + + assert get_entity_state(hass, "switch.test_switch_0") == STATE_ON + + async def test_rpc_polling_connection_error( hass: HomeAssistant, freezer: FrozenDateTimeFactory, From d8f3778d77b776e274420cf593ec43a982239099 Mon Sep 17 00:00:00 2001 From: Quentin <39061148+LapsTimeOFF@users.noreply.github.com> Date: Mon, 10 Jun 2024 20:58:15 +0200 Subject: [PATCH 18/41] Fix elgato light color detection (#119177) --- homeassistant/components/elgato/light.py | 10 +++++++++- tests/components/elgato/fixtures/light-strip/info.json | 2 +- tests/components/elgato/snapshots/test_light.ambr | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index 2cd3d611bf5fa1..339bed97f6f83e 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -59,7 +59,15 @@ def __init__(self, coordinator: ElgatoDataUpdateCoordinator) -> None: self._attr_unique_id = coordinator.data.info.serial_number # Elgato Light supporting color, have a different temperature range - if self.coordinator.data.settings.power_on_hue is not None: + if ( + self.coordinator.data.info.product_name + in ( + "Elgato Light Strip", + "Elgato Light Strip Pro", + ) + or self.coordinator.data.settings.power_on_hue + or self.coordinator.data.state.hue is not None + ): self._attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS} self._attr_min_mireds = 153 self._attr_max_mireds = 285 diff --git a/tests/components/elgato/fixtures/light-strip/info.json b/tests/components/elgato/fixtures/light-strip/info.json index e2a816df26e4bc..a8c3200e4b978c 100644 --- a/tests/components/elgato/fixtures/light-strip/info.json +++ b/tests/components/elgato/fixtures/light-strip/info.json @@ -1,5 +1,5 @@ { - "productName": "Elgato Key Light", + "productName": "Elgato Light Strip", "hardwareBoardType": 53, "firmwareBuildNumber": 192, "firmwareVersion": "1.0.3", diff --git a/tests/components/elgato/snapshots/test_light.ambr b/tests/components/elgato/snapshots/test_light.ambr index 6ef773a730474b..e2f663d294bcc2 100644 --- a/tests/components/elgato/snapshots/test_light.ambr +++ b/tests/components/elgato/snapshots/test_light.ambr @@ -218,7 +218,7 @@ 'labels': set({ }), 'manufacturer': 'Elgato', - 'model': 'Elgato Key Light', + 'model': 'Elgato Light Strip', 'name': 'Frenck', 'name_by_user': None, 'serial_number': 'CN11A1A00001', @@ -333,7 +333,7 @@ 'labels': set({ }), 'manufacturer': 'Elgato', - 'model': 'Elgato Key Light', + 'model': 'Elgato Light Strip', 'name': 'Frenck', 'name_by_user': None, 'serial_number': 'CN11A1A00001', From 57cc1f841bc2168bccc60fec520cd3a9313c4adb Mon Sep 17 00:00:00 2001 From: tronikos Date: Sun, 9 Jun 2024 00:45:59 -0700 Subject: [PATCH 19/41] Bump opower to 0.4.7 (#119183) --- homeassistant/components/opower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index 7e16bacdfda0ad..d419fdcb04306b 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -7,5 +7,5 @@ "documentation": "https://www.home-assistant.io/integrations/opower", "iot_class": "cloud_polling", "loggers": ["opower"], - "requirements": ["opower==0.4.6"] + "requirements": ["opower==0.4.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index 4d2a02078a1592..bf9e2351951612 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1501,7 +1501,7 @@ openwrt-luci-rpc==1.1.17 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.4.6 +opower==0.4.7 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8a4460b1cc9c3f..bc5a1524ee73ae 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1201,7 +1201,7 @@ openhomedevice==2.2.0 openwebifpy==4.2.4 # homeassistant.components.opower -opower==0.4.6 +opower==0.4.7 # homeassistant.components.oralb oralb-ble==0.17.6 From c71b6bdac91a9d38532ace9b0014e52968f04b50 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Sun, 9 Jun 2024 11:59:14 +0200 Subject: [PATCH 20/41] Add fallback to entry_id when no mac address is retrieved in enigma2 (#119185) --- homeassistant/components/enigma2/media_player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index 037d82cd6c0200..81f4f830833f8b 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -141,10 +141,10 @@ def __init__( self._device: OpenWebIfDevice = device self._entry = entry - self._attr_unique_id = device.mac_address + self._attr_unique_id = device.mac_address or entry.entry_id self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, device.mac_address)}, + identifiers={(DOMAIN, self._attr_unique_id)}, manufacturer=about["info"]["brand"], model=about["info"]["model"], configuration_url=device.base, From 8d094bf12ea1e83b2a2aedb92bb977215d57a8ae Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 9 Jun 2024 18:14:46 +0200 Subject: [PATCH 21/41] Fix envisalink alarm (#119212) --- .../envisalink/alarm_control_panel.py | 39 +++---------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py index 119608bbb2ab4e..b962621edea5e3 100644 --- a/homeassistant/components/envisalink/alarm_control_panel.py +++ b/homeassistant/components/envisalink/alarm_control_panel.py @@ -116,8 +116,9 @@ def __init__( ): """Initialize the alarm panel.""" self._partition_number = partition_number - self._code = code self._panic_type = panic_type + self._alarm_control_panel_option_default_code = code + self._attr_code_format = CodeFormat.NUMBER _LOGGER.debug("Setting up alarm: %s", alarm_name) super().__init__(alarm_name, info, controller) @@ -141,13 +142,6 @@ def async_update_callback(self, partition): if partition is None or int(partition) == self._partition_number: self.async_write_ha_state() - @property - def code_format(self) -> CodeFormat | None: - """Regex for code format or None if no code is required.""" - if self._code: - return None - return CodeFormat.NUMBER - @property def state(self) -> str: """Return the state of the device.""" @@ -169,34 +163,15 @@ def state(self) -> str: async def async_alarm_disarm(self, code: str | None = None) -> None: """Send disarm command.""" - if code: - self.hass.data[DATA_EVL].disarm_partition(str(code), self._partition_number) - else: - self.hass.data[DATA_EVL].disarm_partition( - str(self._code), self._partition_number - ) + self.hass.data[DATA_EVL].disarm_partition(code, self._partition_number) async def async_alarm_arm_home(self, code: str | None = None) -> None: """Send arm home command.""" - if code: - self.hass.data[DATA_EVL].arm_stay_partition( - str(code), self._partition_number - ) - else: - self.hass.data[DATA_EVL].arm_stay_partition( - str(self._code), self._partition_number - ) + self.hass.data[DATA_EVL].arm_stay_partition(code, self._partition_number) async def async_alarm_arm_away(self, code: str | None = None) -> None: """Send arm away command.""" - if code: - self.hass.data[DATA_EVL].arm_away_partition( - str(code), self._partition_number - ) - else: - self.hass.data[DATA_EVL].arm_away_partition( - str(self._code), self._partition_number - ) + self.hass.data[DATA_EVL].arm_away_partition(code, self._partition_number) async def async_alarm_trigger(self, code: str | None = None) -> None: """Alarm trigger command. Will be used to trigger a panic alarm.""" @@ -204,9 +179,7 @@ async def async_alarm_trigger(self, code: str | None = None) -> None: async def async_alarm_arm_night(self, code: str | None = None) -> None: """Send arm night command.""" - self.hass.data[DATA_EVL].arm_night_partition( - str(code) if code else str(self._code), self._partition_number - ) + self.hass.data[DATA_EVL].arm_night_partition(code, self._partition_number) @callback def async_alarm_keypress(self, keypress=None): From 6a656c5d49fea6c52ed1b08718a86d3628d61294 Mon Sep 17 00:00:00 2001 From: Angel Nunez Mencias Date: Mon, 10 Jun 2024 08:41:22 +0200 Subject: [PATCH 22/41] Fixes crashes when receiving malformed decoded payloads (#119216) Co-authored-by: Jan Bouwhuis --- homeassistant/components/thethingsnetwork/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/thethingsnetwork/manifest.json b/homeassistant/components/thethingsnetwork/manifest.json index b8b1dbd7e1d165..bc132d171f21b7 100644 --- a/homeassistant/components/thethingsnetwork/manifest.json +++ b/homeassistant/components/thethingsnetwork/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/thethingsnetwork", "integration_type": "hub", "iot_class": "cloud_polling", - "requirements": ["ttn_client==0.0.4"] + "requirements": ["ttn_client==1.0.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index bf9e2351951612..859563e3a3b212 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2764,7 +2764,7 @@ transmission-rpc==7.0.3 ttls==1.5.1 # homeassistant.components.thethingsnetwork -ttn_client==0.0.4 +ttn_client==1.0.0 # homeassistant.components.tuya tuya-device-sharing-sdk==0.1.9 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bc5a1524ee73ae..ab14dd61525fe8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2138,7 +2138,7 @@ transmission-rpc==7.0.3 ttls==1.5.1 # homeassistant.components.thethingsnetwork -ttn_client==0.0.4 +ttn_client==1.0.0 # homeassistant.components.tuya tuya-device-sharing-sdk==0.1.9 From 8b415a0376b28ad54ad22b36fc51490c4de34676 Mon Sep 17 00:00:00 2001 From: wittypluck Date: Mon, 10 Jun 2024 08:25:39 +0200 Subject: [PATCH 23/41] Fix Glances v4 network and container issues (glances-api 0.8.0) (#119226) --- homeassistant/components/glances/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/glances/manifest.json b/homeassistant/components/glances/manifest.json index 68101583b48376..e129a375df21ee 100644 --- a/homeassistant/components/glances/manifest.json +++ b/homeassistant/components/glances/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/glances", "iot_class": "local_polling", "loggers": ["glances_api"], - "requirements": ["glances-api==0.7.0"] + "requirements": ["glances-api==0.8.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 859563e3a3b212..cd8e859ec1e885 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -955,7 +955,7 @@ gios==4.0.0 gitterpy==0.1.7 # homeassistant.components.glances -glances-api==0.7.0 +glances-api==0.8.0 # homeassistant.components.goalzero goalzero==0.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ab14dd61525fe8..8ae089f97e047a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -784,7 +784,7 @@ getmac==0.9.4 gios==4.0.0 # homeassistant.components.glances -glances-api==0.7.0 +glances-api==0.8.0 # homeassistant.components.goalzero goalzero==0.2.2 From 7896e7675c8653214fc357ae3e1d19a8be95c2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ethem=20Cem=20=C3=96zkan?= Date: Sun, 9 Jun 2024 22:58:49 +0200 Subject: [PATCH 24/41] Bump python-roborock to 2.3.0 (#119228) --- homeassistant/components/roborock/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roborock/manifest.json b/homeassistant/components/roborock/manifest.json index 3fd6dd7d78215c..42c0f9ba347010 100644 --- a/homeassistant/components/roborock/manifest.json +++ b/homeassistant/components/roborock/manifest.json @@ -7,7 +7,7 @@ "iot_class": "local_polling", "loggers": ["roborock"], "requirements": [ - "python-roborock==2.2.3", + "python-roborock==2.3.0", "vacuum-map-parser-roborock==0.1.2" ] } diff --git a/requirements_all.txt b/requirements_all.txt index cd8e859ec1e885..3eb5891d47068d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2306,7 +2306,7 @@ python-rabbitair==0.0.8 python-ripple-api==0.0.3 # homeassistant.components.roborock -python-roborock==2.2.3 +python-roborock==2.3.0 # homeassistant.components.smarttub python-smarttub==0.0.36 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8ae089f97e047a..9aad243bb7d725 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1794,7 +1794,7 @@ python-qbittorrent==0.4.3 python-rabbitair==0.0.8 # homeassistant.components.roborock -python-roborock==2.2.3 +python-roborock==2.3.0 # homeassistant.components.smarttub python-smarttub==0.0.36 From 1e7ab07d9edfbef07ac3b02441432ae7181a6ede Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 10 Jun 2024 20:20:25 +0200 Subject: [PATCH 25/41] Revert SamsungTV migration (#119234) --- homeassistant/components/samsungtv/__init__.py | 11 +---------- tests/components/samsungtv/test_init.py | 6 +++++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index f49ae276665c9c..992c86d5d7edf2 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -297,16 +297,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> if version == 2: if minor_version < 2: # Cleanup invalid MAC addresses - see #103512 - dev_reg = dr.async_get(hass) - for device in dr.async_entries_for_config_entry( - dev_reg, config_entry.entry_id - ): - new_connections = device.connections.copy() - new_connections.discard((dr.CONNECTION_NETWORK_MAC, "none")) - if new_connections != device.connections: - dev_reg.async_update_device( - device.id, new_connections=new_connections - ) + # Reverted due to device registry collisions - see #119082 / #119249 minor_version = 2 hass.config_entries.async_update_entry(config_entry, minor_version=2) diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index 4efcf62c1dd21f..479664d4ec0c9a 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -220,10 +220,14 @@ async def test_incorrectly_formatted_mac_fixed(hass: HomeAssistant) -> None: @pytest.mark.usefixtures("remotews", "rest_api") +@pytest.mark.xfail async def test_cleanup_mac( hass: HomeAssistant, device_registry: dr.DeviceRegistry, snapshot: SnapshotAssertion ) -> None: - """Test for `none` mac cleanup #103512.""" + """Test for `none` mac cleanup #103512. + + Reverted due to device registry collisions in #119249 / #119082 + """ entry = MockConfigEntry( domain=SAMSUNGTV_DOMAIN, data=MOCK_ENTRY_WS_WITH_MAC, From 119d4c2316c1ff5dd811dcf6a6589af3e3f86c0a Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Mon, 10 Jun 2024 07:47:16 +0200 Subject: [PATCH 26/41] Always provide a currentArmLevel in Google assistant (#119238) --- .../components/google_assistant/trait.py | 32 +++++++++++-------- .../components/google_assistant/test_trait.py | 5 ++- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index e39634a5dd6634..3d1daea9810aca 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -1586,6 +1586,17 @@ def _supported_states(self): if features & required_feature != 0 ] + def _default_arm_state(self): + states = self._supported_states() + + if STATE_ALARM_TRIGGERED in states: + states.remove(STATE_ALARM_TRIGGERED) + + if len(states) != 1: + raise SmartHomeError(ERR_NOT_SUPPORTED, "ArmLevel missing") + + return states[0] + def sync_attributes(self): """Return ArmDisarm attributes for a sync request.""" response = {} @@ -1609,10 +1620,13 @@ def sync_attributes(self): def query_attributes(self): """Return ArmDisarm query attributes.""" armed_state = self.state.attributes.get("next_state", self.state.state) - response = {"isArmed": armed_state in self.state_to_service} - if response["isArmed"]: - response.update({"currentArmLevel": armed_state}) - return response + + if armed_state in self.state_to_service: + return {"isArmed": True, "currentArmLevel": armed_state} + return { + "isArmed": False, + "currentArmLevel": self._default_arm_state(), + } async def execute(self, command, data, params, challenge): """Execute an ArmDisarm command.""" @@ -1620,15 +1634,7 @@ async def execute(self, command, data, params, challenge): # If no arm level given, we can only arm it if there is # only one supported arm type. We never default to triggered. if not (arm_level := params.get("armLevel")): - states = self._supported_states() - - if STATE_ALARM_TRIGGERED in states: - states.remove(STATE_ALARM_TRIGGERED) - - if len(states) != 1: - raise SmartHomeError(ERR_NOT_SUPPORTED, "ArmLevel missing") - - arm_level = states[0] + arm_level = self._default_arm_state() if self.state.state == arm_level: raise SmartHomeError(ERR_ALREADY_ARMED, "System is already armed") diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 0ed4d960edcd15..c72ab3cb85e6ca 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1931,7 +1931,10 @@ async def test_arm_disarm_disarm(hass: HomeAssistant) -> None: } } - assert trt.query_attributes() == {"isArmed": False} + assert trt.query_attributes() == { + "currentArmLevel": "armed_custom_bypass", + "isArmed": False, + } assert trt.can_execute(trait.COMMAND_ARMDISARM, {"arm": False}) From 5beff34069c5dea54cdbafa440a11208ec5e5835 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Jun 2024 13:21:56 -0500 Subject: [PATCH 27/41] Remove myself as codeowner for unifiprotect (#118824) --- CODEOWNERS | 2 -- homeassistant/components/unifiprotect/manifest.json | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 32f885f601561c..97765fd5553362 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1486,8 +1486,6 @@ build.json @home-assistant/supervisor /tests/components/unifi/ @Kane610 /homeassistant/components/unifi_direct/ @tofuSCHNITZEL /homeassistant/components/unifiled/ @florisvdk -/homeassistant/components/unifiprotect/ @bdraco -/tests/components/unifiprotect/ @bdraco /homeassistant/components/upb/ @gwww /tests/components/upb/ @gwww /homeassistant/components/upc_connect/ @pvizeli @fabaff diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 5570d088a7d674..a09db1cf01ae31 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -1,7 +1,7 @@ { "domain": "unifiprotect", "name": "UniFi Protect", - "codeowners": ["@bdraco"], + "codeowners": [], "config_flow": true, "dependencies": ["http", "repairs"], "dhcp": [ @@ -40,7 +40,6 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["pyunifiprotect", "unifi_discovery"], - "quality_scale": "platinum", "requirements": ["pyunifiprotect==5.1.2", "unifi-discovery==1.1.8"], "ssdp": [ { From f9352dfe8f1a94c8b4b589ed88d4fd24e3529c35 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Jun 2024 18:25:39 -0500 Subject: [PATCH 28/41] Switch unifiprotect lib to use uiprotect (#119243) --- homeassistant/components/unifiprotect/__init__.py | 10 +++++----- .../components/unifiprotect/binary_sensor.py | 4 ++-- homeassistant/components/unifiprotect/button.py | 2 +- homeassistant/components/unifiprotect/camera.py | 2 +- homeassistant/components/unifiprotect/config_flow.py | 6 +++--- homeassistant/components/unifiprotect/const.py | 2 +- homeassistant/components/unifiprotect/data.py | 8 ++++---- homeassistant/components/unifiprotect/diagnostics.py | 2 +- homeassistant/components/unifiprotect/entity.py | 2 +- homeassistant/components/unifiprotect/light.py | 2 +- homeassistant/components/unifiprotect/lock.py | 2 +- homeassistant/components/unifiprotect/manifest.json | 4 ++-- .../components/unifiprotect/media_player.py | 4 ++-- .../components/unifiprotect/media_source.py | 12 +++--------- homeassistant/components/unifiprotect/migrate.py | 4 ++-- homeassistant/components/unifiprotect/models.py | 2 +- homeassistant/components/unifiprotect/number.py | 2 +- homeassistant/components/unifiprotect/repairs.py | 6 +++--- homeassistant/components/unifiprotect/select.py | 4 ++-- homeassistant/components/unifiprotect/sensor.py | 2 +- homeassistant/components/unifiprotect/services.py | 6 +++--- homeassistant/components/unifiprotect/switch.py | 2 +- homeassistant/components/unifiprotect/text.py | 2 +- homeassistant/components/unifiprotect/utils.py | 4 ++-- homeassistant/components/unifiprotect/views.py | 4 ++-- requirements_all.txt | 6 +++--- requirements_test_all.txt | 6 +++--- tests/components/unifiprotect/conftest.py | 4 ++-- tests/components/unifiprotect/test_binary_sensor.py | 4 ++-- tests/components/unifiprotect/test_button.py | 2 +- tests/components/unifiprotect/test_camera.py | 4 ++-- tests/components/unifiprotect/test_config_flow.py | 4 ++-- tests/components/unifiprotect/test_diagnostics.py | 2 +- tests/components/unifiprotect/test_init.py | 4 ++-- tests/components/unifiprotect/test_light.py | 4 ++-- tests/components/unifiprotect/test_lock.py | 2 +- tests/components/unifiprotect/test_media_player.py | 4 ++-- tests/components/unifiprotect/test_media_source.py | 4 ++-- tests/components/unifiprotect/test_migrate.py | 2 +- tests/components/unifiprotect/test_number.py | 2 +- tests/components/unifiprotect/test_recorder.py | 2 +- tests/components/unifiprotect/test_repairs.py | 2 +- tests/components/unifiprotect/test_select.py | 4 ++-- tests/components/unifiprotect/test_sensor.py | 11 ++--------- tests/components/unifiprotect/test_services.py | 6 +++--- tests/components/unifiprotect/test_switch.py | 2 +- tests/components/unifiprotect/test_text.py | 2 +- tests/components/unifiprotect/test_views.py | 4 ++-- tests/components/unifiprotect/utils.py | 8 ++++---- 49 files changed, 91 insertions(+), 104 deletions(-) diff --git a/homeassistant/components/unifiprotect/__init__.py b/homeassistant/components/unifiprotect/__init__.py index d85f91be860bd7..0f41011361d87f 100644 --- a/homeassistant/components/unifiprotect/__init__.py +++ b/homeassistant/components/unifiprotect/__init__.py @@ -6,14 +6,14 @@ import logging from aiohttp.client_exceptions import ServerDisconnectedError -from pyunifiprotect.data import Bootstrap -from pyunifiprotect.data.types import FirmwareReleaseChannel -from pyunifiprotect.exceptions import ClientError, NotAuthorized +from uiprotect.data import Bootstrap +from uiprotect.data.types import FirmwareReleaseChannel +from uiprotect.exceptions import ClientError, NotAuthorized -# Import the test_util.anonymize module from the pyunifiprotect package +# Import the test_util.anonymize module from the uiprotect package # in __init__ to ensure it gets imported in the executor since the # diagnostics module will not be imported in the executor. -from pyunifiprotect.test_util.anonymize import anonymize_data # noqa: F401 +from uiprotect.test_util.anonymize import anonymize_data # noqa: F401 from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STOP diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index f779fc7a1ad44f..b6aaed8f975c14 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -6,7 +6,7 @@ import logging from typing import Any -from pyunifiprotect.data import ( +from uiprotect.data import ( NVR, Camera, Light, @@ -16,7 +16,7 @@ ProtectModelWithId, Sensor, ) -from pyunifiprotect.data.nvr import UOSDisk +from uiprotect.data.nvr import UOSDisk from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index db27306aedfcf6..0db05a6cdc97b8 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -6,7 +6,7 @@ import logging from typing import Final -from pyunifiprotect.data import ProtectAdoptableDeviceModel, ProtectModelWithId +from uiprotect.data import ProtectAdoptableDeviceModel, ProtectModelWithId from homeassistant.components.button import ( ButtonDeviceClass, diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index 8e10c09872b573..6b667c1f57e8c9 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -6,7 +6,7 @@ import logging from typing import Any, cast -from pyunifiprotect.data import ( +from uiprotect.data import ( Camera as UFPCamera, CameraChannel, ModelType, diff --git a/homeassistant/components/unifiprotect/config_flow.py b/homeassistant/components/unifiprotect/config_flow.py index 19561a6003d4d3..284b7003485841 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -8,9 +8,9 @@ from typing import Any from aiohttp import CookieJar -from pyunifiprotect import ProtectApiClient -from pyunifiprotect.data import NVR -from pyunifiprotect.exceptions import ClientError, NotAuthorized +from uiprotect import ProtectApiClient +from uiprotect.data import NVR +from uiprotect.exceptions import ClientError, NotAuthorized from unifi_discovery import async_console_is_alive import voluptuous as vol diff --git a/homeassistant/components/unifiprotect/const.py b/homeassistant/components/unifiprotect/const.py index 39be5f0e7cb8ea..f51a58aadc7956 100644 --- a/homeassistant/components/unifiprotect/const.py +++ b/homeassistant/components/unifiprotect/const.py @@ -1,6 +1,6 @@ """Constant definitions for UniFi Protect Integration.""" -from pyunifiprotect.data import ModelType, Version +from uiprotect.data import ModelType, Version from homeassistant.const import Platform diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index b64a08749d57df..7b1c73d6dcc507 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -8,8 +8,8 @@ import logging from typing import Any, cast -from pyunifiprotect import ProtectApiClient -from pyunifiprotect.data import ( +from uiprotect import ProtectApiClient +from uiprotect.data import ( NVR, Bootstrap, Camera, @@ -20,8 +20,8 @@ ProtectAdoptableDeviceModel, WSSubscriptionMessage, ) -from pyunifiprotect.exceptions import ClientError, NotAuthorized -from pyunifiprotect.utils import log_event +from uiprotect.exceptions import ClientError, NotAuthorized +from uiprotect.utils import log_event from homeassistant.config_entries import ConfigEntry from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback diff --git a/homeassistant/components/unifiprotect/diagnostics.py b/homeassistant/components/unifiprotect/diagnostics.py index b85870a08c5a07..ac651f6138d16d 100644 --- a/homeassistant/components/unifiprotect/diagnostics.py +++ b/homeassistant/components/unifiprotect/diagnostics.py @@ -4,7 +4,7 @@ from typing import Any, cast -from pyunifiprotect.test_util.anonymize import anonymize_data +from uiprotect.test_util.anonymize import anonymize_data from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 49478ce0582324..766c93949bd3b5 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -6,7 +6,7 @@ import logging from typing import TYPE_CHECKING, Any -from pyunifiprotect.data import ( +from uiprotect.data import ( NVR, Camera, Chime, diff --git a/homeassistant/components/unifiprotect/light.py b/homeassistant/components/unifiprotect/light.py index 3ce236b3e23272..18e611f23072a7 100644 --- a/homeassistant/components/unifiprotect/light.py +++ b/homeassistant/components/unifiprotect/light.py @@ -5,7 +5,7 @@ import logging from typing import Any -from pyunifiprotect.data import ( +from uiprotect.data import ( Light, ModelType, ProtectAdoptableDeviceModel, diff --git a/homeassistant/components/unifiprotect/lock.py b/homeassistant/components/unifiprotect/lock.py index c54f9b316ff30d..6bb1dd7b4eeafb 100644 --- a/homeassistant/components/unifiprotect/lock.py +++ b/homeassistant/components/unifiprotect/lock.py @@ -5,7 +5,7 @@ import logging from typing import Any, cast -from pyunifiprotect.data import ( +from uiprotect.data import ( Doorlock, LockStatusType, ModelType, diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index a09db1cf01ae31..9cb6ceb7cb9b82 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -39,8 +39,8 @@ "documentation": "https://www.home-assistant.io/integrations/unifiprotect", "integration_type": "hub", "iot_class": "local_push", - "loggers": ["pyunifiprotect", "unifi_discovery"], - "requirements": ["pyunifiprotect==5.1.2", "unifi-discovery==1.1.8"], + "loggers": ["uiprotect", "unifi_discovery"], + "requirements": ["uiprotect==0.3.9", "unifi-discovery==1.1.8"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index 50fec39e9cbcc6..eb17137842be74 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -5,14 +5,14 @@ import logging from typing import Any, cast -from pyunifiprotect.data import ( +from uiprotect.data import ( Camera, ModelType, ProtectAdoptableDeviceModel, ProtectModelWithId, StateType, ) -from pyunifiprotect.exceptions import StreamError +from uiprotect.exceptions import StreamError from homeassistant.components import media_source from homeassistant.components.media_player import ( diff --git a/homeassistant/components/unifiprotect/media_source.py b/homeassistant/components/unifiprotect/media_source.py index 0ff27f562eabc6..1a67efcfd03d20 100644 --- a/homeassistant/components/unifiprotect/media_source.py +++ b/homeassistant/components/unifiprotect/media_source.py @@ -7,15 +7,9 @@ from enum import Enum from typing import Any, NoReturn, cast -from pyunifiprotect.data import ( - Camera, - Event, - EventType, - ModelType, - SmartDetectObjectType, -) -from pyunifiprotect.exceptions import NvrError -from pyunifiprotect.utils import from_js_time +from uiprotect.data import Camera, Event, EventType, ModelType, SmartDetectObjectType +from uiprotect.exceptions import NvrError +from uiprotect.utils import from_js_time from yarl import URL from homeassistant.components.camera import CameraImageView diff --git a/homeassistant/components/unifiprotect/migrate.py b/homeassistant/components/unifiprotect/migrate.py index cfc8cff7618211..a95341f497a264 100644 --- a/homeassistant/components/unifiprotect/migrate.py +++ b/homeassistant/components/unifiprotect/migrate.py @@ -6,8 +6,8 @@ import logging from typing import TypedDict -from pyunifiprotect import ProtectApiClient -from pyunifiprotect.data import Bootstrap +from uiprotect import ProtectApiClient +from uiprotect.data import Bootstrap from homeassistant.components.automation import automations_with_entity from homeassistant.components.script import scripts_with_entity diff --git a/homeassistant/components/unifiprotect/models.py b/homeassistant/components/unifiprotect/models.py index a9c795561357c3..d2ab31d672d7d0 100644 --- a/homeassistant/components/unifiprotect/models.py +++ b/homeassistant/components/unifiprotect/models.py @@ -8,7 +8,7 @@ import logging from typing import TYPE_CHECKING, Any, Generic, TypeVar -from pyunifiprotect.data import NVR, Event, ProtectAdoptableDeviceModel +from uiprotect.data import NVR, Event, ProtectAdoptableDeviceModel from homeassistant.helpers.entity import EntityDescription diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index 49c629ac42f566..ceb8614e77e8de 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -7,7 +7,7 @@ import logging from typing import Any -from pyunifiprotect.data import ( +from uiprotect.data import ( Camera, Doorlock, Light, diff --git a/homeassistant/components/unifiprotect/repairs.py b/homeassistant/components/unifiprotect/repairs.py index baf08c9b5cf455..3cc8967ea0db19 100644 --- a/homeassistant/components/unifiprotect/repairs.py +++ b/homeassistant/components/unifiprotect/repairs.py @@ -4,9 +4,9 @@ from typing import cast -from pyunifiprotect import ProtectApiClient -from pyunifiprotect.data import Bootstrap, Camera, ModelType -from pyunifiprotect.data.types import FirmwareReleaseChannel +from uiprotect import ProtectApiClient +from uiprotect.data import Bootstrap, Camera, ModelType +from uiprotect.data.types import FirmwareReleaseChannel import voluptuous as vol from homeassistant import data_entry_flow diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 6ba90948fcab23..f4a9d58e3467b8 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -8,8 +8,8 @@ import logging from typing import Any, Final -from pyunifiprotect.api import ProtectApiClient -from pyunifiprotect.data import ( +from uiprotect.api import ProtectApiClient +from uiprotect.data import ( Camera, ChimeType, DoorbellMessageType, diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index 63c9e11c6607e8..00849c095f038b 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -7,7 +7,7 @@ import logging from typing import Any, cast -from pyunifiprotect.data import ( +from uiprotect.data import ( NVR, Camera, Light, diff --git a/homeassistant/components/unifiprotect/services.py b/homeassistant/components/unifiprotect/services.py index 8c62664f55b8dc..c5c2ffc8bfe3eb 100644 --- a/homeassistant/components/unifiprotect/services.py +++ b/homeassistant/components/unifiprotect/services.py @@ -7,9 +7,9 @@ from typing import Any, cast from pydantic import ValidationError -from pyunifiprotect.api import ProtectApiClient -from pyunifiprotect.data import Camera, Chime -from pyunifiprotect.exceptions import ClientError +from uiprotect.api import ProtectApiClient +from uiprotect.data import Camera, Chime +from uiprotect.exceptions import ClientError import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDeviceClass diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index bd7cfa4d2a2999..d17b208de128f5 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -6,7 +6,7 @@ import logging from typing import Any -from pyunifiprotect.data import ( +from uiprotect.data import ( NVR, Camera, ProtectAdoptableDeviceModel, diff --git a/homeassistant/components/unifiprotect/text.py b/homeassistant/components/unifiprotect/text.py index 584bd511ee551e..05e6712fa65cbf 100644 --- a/homeassistant/components/unifiprotect/text.py +++ b/homeassistant/components/unifiprotect/text.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Any -from pyunifiprotect.data import ( +from uiprotect.data import ( Camera, DoorbellMessageType, ProtectAdoptableDeviceModel, diff --git a/homeassistant/components/unifiprotect/utils.py b/homeassistant/components/unifiprotect/utils.py index 8199d7299430e4..8a3028bcea73cf 100644 --- a/homeassistant/components/unifiprotect/utils.py +++ b/homeassistant/components/unifiprotect/utils.py @@ -10,8 +10,8 @@ from typing import Any from aiohttp import CookieJar -from pyunifiprotect import ProtectApiClient -from pyunifiprotect.data import ( +from uiprotect import ProtectApiClient +from uiprotect.data import ( Bootstrap, CameraChannel, Light, diff --git a/homeassistant/components/unifiprotect/views.py b/homeassistant/components/unifiprotect/views.py index 0f9bff6368917e..b359fd5d9482fa 100644 --- a/homeassistant/components/unifiprotect/views.py +++ b/homeassistant/components/unifiprotect/views.py @@ -9,8 +9,8 @@ from urllib.parse import urlencode from aiohttp import web -from pyunifiprotect.data import Camera, Event -from pyunifiprotect.exceptions import ClientError +from uiprotect.data import Camera, Event +from uiprotect.exceptions import ClientError from homeassistant.components.http import HomeAssistantView from homeassistant.core import HomeAssistant, callback diff --git a/requirements_all.txt b/requirements_all.txt index 3eb5891d47068d..93c974c3d3701c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2357,9 +2357,6 @@ pytrydan==0.6.1 # homeassistant.components.usb pyudev==0.24.1 -# homeassistant.components.unifiprotect -pyunifiprotect==5.1.2 - # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 @@ -2781,6 +2778,9 @@ twitchAPI==4.0.0 # homeassistant.components.ukraine_alarm uasiren==0.0.1 +# homeassistant.components.unifiprotect +uiprotect==0.3.9 + # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9aad243bb7d725..b0e043949b25a8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1836,9 +1836,6 @@ pytrydan==0.6.1 # homeassistant.components.usb pyudev==0.24.1 -# homeassistant.components.unifiprotect -pyunifiprotect==5.1.2 - # homeassistant.components.uptimerobot pyuptimerobot==22.2.0 @@ -2155,6 +2152,9 @@ twitchAPI==4.0.0 # homeassistant.components.ukraine_alarm uasiren==0.0.1 +# homeassistant.components.unifiprotect +uiprotect==0.3.9 + # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 diff --git a/tests/components/unifiprotect/conftest.py b/tests/components/unifiprotect/conftest.py index 5b3f9653d75324..9eb1ea312c6325 100644 --- a/tests/components/unifiprotect/conftest.py +++ b/tests/components/unifiprotect/conftest.py @@ -13,8 +13,8 @@ from unittest.mock import AsyncMock, Mock, patch import pytest -from pyunifiprotect import ProtectApiClient -from pyunifiprotect.data import ( +from uiprotect import ProtectApiClient +from uiprotect.data import ( NVR, Bootstrap, Camera, diff --git a/tests/components/unifiprotect/test_binary_sensor.py b/tests/components/unifiprotect/test_binary_sensor.py index 81ed02869b8013..dbe8f72b244b48 100644 --- a/tests/components/unifiprotect/test_binary_sensor.py +++ b/tests/components/unifiprotect/test_binary_sensor.py @@ -5,8 +5,8 @@ from datetime import datetime, timedelta from unittest.mock import Mock -from pyunifiprotect.data import Camera, Event, EventType, Light, MountType, Sensor -from pyunifiprotect.data.nvr import EventMetadata +from uiprotect.data import Camera, Event, EventType, Light, MountType, Sensor +from uiprotect.data.nvr import EventMetadata from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.unifiprotect.binary_sensor import ( diff --git a/tests/components/unifiprotect/test_button.py b/tests/components/unifiprotect/test_button.py index a38a29b59993aa..3a2830931790a1 100644 --- a/tests/components/unifiprotect/test_button.py +++ b/tests/components/unifiprotect/test_button.py @@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, Mock -from pyunifiprotect.data.devices import Camera, Chime, Doorlock +from uiprotect.data.devices import Camera, Chime, Doorlock from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, Platform diff --git a/tests/components/unifiprotect/test_camera.py b/tests/components/unifiprotect/test_camera.py index d374f61c2b04de..444898fbd85c50 100644 --- a/tests/components/unifiprotect/test_camera.py +++ b/tests/components/unifiprotect/test_camera.py @@ -4,8 +4,8 @@ from unittest.mock import AsyncMock, Mock -from pyunifiprotect.data import Camera as ProtectCamera, CameraChannel, StateType -from pyunifiprotect.exceptions import NvrError +from uiprotect.data import Camera as ProtectCamera, CameraChannel, StateType +from uiprotect.exceptions import NvrError from homeassistant.components.camera import ( CameraEntityFeature, diff --git a/tests/components/unifiprotect/test_config_flow.py b/tests/components/unifiprotect/test_config_flow.py index 845766809b2dc7..5d02e1cf09891f 100644 --- a/tests/components/unifiprotect/test_config_flow.py +++ b/tests/components/unifiprotect/test_config_flow.py @@ -7,8 +7,8 @@ from unittest.mock import patch import pytest -from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient -from pyunifiprotect.data import NVR, Bootstrap, CloudAccount +from uiprotect import NotAuthorized, NvrError, ProtectApiClient +from uiprotect.data import NVR, Bootstrap, CloudAccount from homeassistant import config_entries from homeassistant.components import dhcp, ssdp diff --git a/tests/components/unifiprotect/test_diagnostics.py b/tests/components/unifiprotect/test_diagnostics.py index b13c069b37cda6..fd882929e961c2 100644 --- a/tests/components/unifiprotect/test_diagnostics.py +++ b/tests/components/unifiprotect/test_diagnostics.py @@ -1,6 +1,6 @@ """Test UniFi Protect diagnostics.""" -from pyunifiprotect.data import NVR, Light +from uiprotect.data import NVR, Light from homeassistant.components.unifiprotect.const import CONF_ALLOW_EA from homeassistant.core import HomeAssistant diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index 9bb2141631b552..3b75afaace86b0 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -4,8 +4,8 @@ from unittest.mock import AsyncMock, patch -from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient -from pyunifiprotect.data import NVR, Bootstrap, CloudAccount, Light +from uiprotect import NotAuthorized, NvrError, ProtectApiClient +from uiprotect.data import NVR, Bootstrap, CloudAccount, Light from homeassistant.components.unifiprotect.const import ( AUTH_RETRIES, diff --git a/tests/components/unifiprotect/test_light.py b/tests/components/unifiprotect/test_light.py index 57867a3c7e9177..bb0b6992e4ea05 100644 --- a/tests/components/unifiprotect/test_light.py +++ b/tests/components/unifiprotect/test_light.py @@ -4,8 +4,8 @@ from unittest.mock import AsyncMock, Mock -from pyunifiprotect.data import Light -from pyunifiprotect.data.types import LEDLevel +from uiprotect.data import Light +from uiprotect.data.types import LEDLevel from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION diff --git a/tests/components/unifiprotect/test_lock.py b/tests/components/unifiprotect/test_lock.py index 6785ea2a4f6d52..62a1cb9ff46e29 100644 --- a/tests/components/unifiprotect/test_lock.py +++ b/tests/components/unifiprotect/test_lock.py @@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, Mock -from pyunifiprotect.data import Doorlock, LockStatusType +from uiprotect.data import Doorlock, LockStatusType from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.const import ( diff --git a/tests/components/unifiprotect/test_media_player.py b/tests/components/unifiprotect/test_media_player.py index 1558d11fbbe076..642a3a1e372f16 100644 --- a/tests/components/unifiprotect/test_media_player.py +++ b/tests/components/unifiprotect/test_media_player.py @@ -5,8 +5,8 @@ from unittest.mock import AsyncMock, Mock, patch import pytest -from pyunifiprotect.data import Camera -from pyunifiprotect.exceptions import StreamError +from uiprotect.data import Camera +from uiprotect.exceptions import StreamError from homeassistant.components.media_player import ( ATTR_MEDIA_CONTENT_TYPE, diff --git a/tests/components/unifiprotect/test_media_source.py b/tests/components/unifiprotect/test_media_source.py index 7e51031128edbe..2cdebeafb04b05 100644 --- a/tests/components/unifiprotect/test_media_source.py +++ b/tests/components/unifiprotect/test_media_source.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, Mock, patch import pytest -from pyunifiprotect.data import ( +from uiprotect.data import ( Bootstrap, Camera, Event, @@ -13,7 +13,7 @@ Permission, SmartDetectObjectType, ) -from pyunifiprotect.exceptions import NvrError +from uiprotect.exceptions import NvrError from homeassistant.components.media_player import BrowseError, MediaClass from homeassistant.components.media_source import MediaSourceItem diff --git a/tests/components/unifiprotect/test_migrate.py b/tests/components/unifiprotect/test_migrate.py index a48925d9c679dc..1fbb650b8000a4 100644 --- a/tests/components/unifiprotect/test_migrate.py +++ b/tests/components/unifiprotect/test_migrate.py @@ -4,7 +4,7 @@ from unittest.mock import patch -from pyunifiprotect.data import Camera +from uiprotect.data import Camera from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN from homeassistant.components.repairs.issue_handler import ( diff --git a/tests/components/unifiprotect/test_number.py b/tests/components/unifiprotect/test_number.py index 3050992457c83c..77a409551b1e93 100644 --- a/tests/components/unifiprotect/test_number.py +++ b/tests/components/unifiprotect/test_number.py @@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Camera, Doorlock, IRLEDMode, Light +from uiprotect.data import Camera, Doorlock, IRLEDMode, Light from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.components.unifiprotect.number import ( diff --git a/tests/components/unifiprotect/test_recorder.py b/tests/components/unifiprotect/test_recorder.py index 3e1a8599ea7db0..94c93413de5bca 100644 --- a/tests/components/unifiprotect/test_recorder.py +++ b/tests/components/unifiprotect/test_recorder.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta from unittest.mock import Mock -from pyunifiprotect.data import Camera, Event, EventType +from uiprotect.data import Camera, Event, EventType from homeassistant.components.recorder import Recorder from homeassistant.components.recorder.history import get_significant_states diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index f4be3164fd54bd..7d76550f7c78cf 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -6,7 +6,7 @@ from http import HTTPStatus from unittest.mock import AsyncMock, Mock -from pyunifiprotect.data import Camera, CloudAccount, ModelType, Version +from uiprotect.data import Camera, CloudAccount, ModelType, Version from homeassistant.components.repairs.issue_handler import ( async_process_repairs_platforms, diff --git a/tests/components/unifiprotect/test_select.py b/tests/components/unifiprotect/test_select.py index 4ac82f45173370..8795af57214702 100644 --- a/tests/components/unifiprotect/test_select.py +++ b/tests/components/unifiprotect/test_select.py @@ -5,7 +5,7 @@ from copy import copy from unittest.mock import AsyncMock, Mock -from pyunifiprotect.data import ( +from uiprotect.data import ( Camera, DoorbellMessageType, IRLEDMode, @@ -17,7 +17,7 @@ RecordingMode, Viewer, ) -from pyunifiprotect.data.nvr import DoorbellMessage +from uiprotect.data.nvr import DoorbellMessage from homeassistant.components.select import ATTR_OPTIONS from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index e593f2243784a4..d8014079bf1d82 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -5,15 +5,8 @@ from datetime import datetime, timedelta from unittest.mock import Mock -from pyunifiprotect.data import ( - NVR, - Camera, - Event, - EventType, - Sensor, - SmartDetectObjectType, -) -from pyunifiprotect.data.nvr import EventMetadata, LicensePlateMetadata +from uiprotect.data import NVR, Camera, Event, EventType, Sensor, SmartDetectObjectType +from uiprotect.data.nvr import EventMetadata, LicensePlateMetadata from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.components.unifiprotect.sensor import ( diff --git a/tests/components/unifiprotect/test_services.py b/tests/components/unifiprotect/test_services.py index 98decab9e4a05f..919af53ef10515 100644 --- a/tests/components/unifiprotect/test_services.py +++ b/tests/components/unifiprotect/test_services.py @@ -5,9 +5,9 @@ from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Camera, Chime, Color, Light, ModelType -from pyunifiprotect.data.devices import CameraZone -from pyunifiprotect.exceptions import BadRequest +from uiprotect.data import Camera, Chime, Color, Light, ModelType +from uiprotect.data.devices import CameraZone +from uiprotect.exceptions import BadRequest from homeassistant.components.unifiprotect.const import ATTR_MESSAGE, DOMAIN from homeassistant.components.unifiprotect.services import ( diff --git a/tests/components/unifiprotect/test_switch.py b/tests/components/unifiprotect/test_switch.py index e421937632c959..16e471c2e7a9ee 100644 --- a/tests/components/unifiprotect/test_switch.py +++ b/tests/components/unifiprotect/test_switch.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, Mock import pytest -from pyunifiprotect.data import Camera, Light, Permission, RecordingMode, VideoMode +from uiprotect.data import Camera, Light, Permission, RecordingMode, VideoMode from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.components.unifiprotect.switch import ( diff --git a/tests/components/unifiprotect/test_text.py b/tests/components/unifiprotect/test_text.py index be2ae93203adcb..3ca11744abbba6 100644 --- a/tests/components/unifiprotect/test_text.py +++ b/tests/components/unifiprotect/test_text.py @@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, Mock -from pyunifiprotect.data import Camera, DoorbellMessageType, LCDMessage +from uiprotect.data import Camera, DoorbellMessageType, LCDMessage from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.components.unifiprotect.text import CAMERA diff --git a/tests/components/unifiprotect/test_views.py b/tests/components/unifiprotect/test_views.py index f7930e5ff9a948..6d190eb4dd60cd 100644 --- a/tests/components/unifiprotect/test_views.py +++ b/tests/components/unifiprotect/test_views.py @@ -6,8 +6,8 @@ from aiohttp import ClientResponse import pytest -from pyunifiprotect.data import Camera, Event, EventType -from pyunifiprotect.exceptions import ClientError +from uiprotect.data import Camera, Event, EventType +from uiprotect.exceptions import ClientError from homeassistant.components.unifiprotect.views import ( async_generate_event_video_url, diff --git a/tests/components/unifiprotect/utils.py b/tests/components/unifiprotect/utils.py index 1ade39dafca21e..ab3aefaa09d8dc 100644 --- a/tests/components/unifiprotect/utils.py +++ b/tests/components/unifiprotect/utils.py @@ -8,8 +8,8 @@ from typing import Any from unittest.mock import Mock -from pyunifiprotect import ProtectApiClient -from pyunifiprotect.data import ( +from uiprotect import ProtectApiClient +from uiprotect.data import ( Bootstrap, Camera, Event, @@ -18,8 +18,8 @@ ProtectAdoptableDeviceModel, WSSubscriptionMessage, ) -from pyunifiprotect.data.bootstrap import ProtectDeviceRef -from pyunifiprotect.test_util.anonymize import random_hex +from uiprotect.data.bootstrap import ProtectDeviceRef +from uiprotect.test_util.anonymize import random_hex from homeassistant.const import Platform from homeassistant.core import HomeAssistant, split_entity_id From a28f5baeeb6fa03871013bdea17ef501e25d496c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Mon, 10 Jun 2024 02:02:38 +0100 Subject: [PATCH 29/41] Fix wrong arg name in Idasen Desk config flow (#119247) --- homeassistant/components/idasen_desk/config_flow.py | 2 +- tests/components/idasen_desk/test_config_flow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/idasen_desk/config_flow.py b/homeassistant/components/idasen_desk/config_flow.py index b7c14089656687..782d4988a3c2d4 100644 --- a/homeassistant/components/idasen_desk/config_flow.py +++ b/homeassistant/components/idasen_desk/config_flow.py @@ -64,7 +64,7 @@ async def async_step_user( desk = Desk(None, monitor_height=False) try: - await desk.connect(discovery_info.device, auto_reconnect=False) + await desk.connect(discovery_info.device, retry=False) except AuthFailedError: errors["base"] = "auth_failed" except TimeoutError: diff --git a/tests/components/idasen_desk/test_config_flow.py b/tests/components/idasen_desk/test_config_flow.py index a861dc5f5e266e..c27cdea58aaa5f 100644 --- a/tests/components/idasen_desk/test_config_flow.py +++ b/tests/components/idasen_desk/test_config_flow.py @@ -305,4 +305,4 @@ async def test_bluetooth_step_success(hass: HomeAssistant) -> None: } assert result2["result"].unique_id == IDASEN_DISCOVERY_INFO.address assert len(mock_setup_entry.mock_calls) == 1 - desk_connect.assert_called_with(ANY, auto_reconnect=False) + desk_connect.assert_called_with(ANY, retry=False) From 38cd84fa5f0c7de9836d21fd5cdfc67b42781c9f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Jun 2024 01:17:29 -0500 Subject: [PATCH 30/41] Fix climate on/off in nexia (#119254) --- homeassistant/components/nexia/climate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index 78c0bc88ef72df..7d09f7108284b7 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -388,12 +388,12 @@ async def async_turn_aux_heat_on(self) -> None: async def async_turn_off(self) -> None: """Turn off the zone.""" - await self.async_set_hvac_mode(OPERATION_MODE_OFF) + await self.async_set_hvac_mode(HVACMode.OFF) self._signal_zone_update() async def async_turn_on(self) -> None: """Turn on the zone.""" - await self.async_set_hvac_mode(OPERATION_MODE_AUTO) + await self.async_set_hvac_mode(HVACMode.AUTO) self._signal_zone_update() async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: From eed126c6d40a3bb791461813384606e494af4334 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 9 Jun 2024 23:35:54 -0700 Subject: [PATCH 31/41] Bump google-nest-sdm to 4.0.5 (#119255) --- homeassistant/components/nest/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index 5a975bb19ec543..d3ba571e65a138 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -20,5 +20,5 @@ "iot_class": "cloud_push", "loggers": ["google_nest_sdm"], "quality_scale": "platinum", - "requirements": ["google-nest-sdm==4.0.4"] + "requirements": ["google-nest-sdm==4.0.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 93c974c3d3701c..609e64b41c406d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -977,7 +977,7 @@ google-cloud-texttospeech==2.12.3 google-generativeai==0.6.0 # homeassistant.components.nest -google-nest-sdm==4.0.4 +google-nest-sdm==4.0.5 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b0e043949b25a8..f1c0293df92110 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -803,7 +803,7 @@ google-cloud-pubsub==2.13.11 google-generativeai==0.6.0 # homeassistant.components.nest -google-nest-sdm==4.0.4 +google-nest-sdm==4.0.5 # homeassistant.components.google_travel_time googlemaps==2.5.1 From 8d40f4d39fd9ef436927fe2b36c72ae2aac2f9a9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Jun 2024 01:36:36 -0500 Subject: [PATCH 32/41] Bump uiprotect to 0.4.0 (#119256) --- homeassistant/components/unifiprotect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index 9cb6ceb7cb9b82..ba6319ab0bae97 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -40,7 +40,7 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["uiprotect", "unifi_discovery"], - "requirements": ["uiprotect==0.3.9", "unifi-discovery==1.1.8"], + "requirements": ["uiprotect==0.4.0", "unifi-discovery==1.1.8"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index 609e64b41c406d..66b40c31df3534 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2779,7 +2779,7 @@ twitchAPI==4.0.0 uasiren==0.0.1 # homeassistant.components.unifiprotect -uiprotect==0.3.9 +uiprotect==0.4.0 # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f1c0293df92110..70285bcd2348b6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2153,7 +2153,7 @@ twitchAPI==4.0.0 uasiren==0.0.1 # homeassistant.components.unifiprotect -uiprotect==0.3.9 +uiprotect==0.4.0 # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 From 74b49556f9b9f51cb19991c4623d889d6bfbbff4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Jun 2024 13:26:52 -0500 Subject: [PATCH 33/41] Improve workday test coverage (#119259) --- .../components/workday/test_binary_sensor.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/tests/components/workday/test_binary_sensor.py b/tests/components/workday/test_binary_sensor.py index 9aa4dd6b5b4347..e973a9f9c28872 100644 --- a/tests/components/workday/test_binary_sensor.py +++ b/tests/components/workday/test_binary_sensor.py @@ -10,6 +10,7 @@ from homeassistant.components.workday.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util from homeassistant.util.dt import UTC from . import ( @@ -144,14 +145,55 @@ async def test_setup_add_holiday( assert state.state == "off" +@pytest.mark.parametrize( + "time_zone", ["Asia/Tokyo", "Europe/Berlin", "America/Chicago", "US/Hawaii"] +) async def test_setup_no_country_weekend( hass: HomeAssistant, freezer: FrozenDateTimeFactory, + time_zone: str, ) -> None: """Test setup shows weekend as non-workday with no country.""" - freezer.move_to(datetime(2020, 2, 23, 12, tzinfo=UTC)) # Sunday + await hass.config.async_set_time_zone(time_zone) + zone = await dt_util.async_get_time_zone(time_zone) + freezer.move_to(datetime(2020, 2, 22, 0, 1, 1, tzinfo=zone)) # Saturday + await init_integration(hass, TEST_CONFIG_NO_COUNTRY) + + state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None + assert state.state == "off" + + freezer.move_to(datetime(2020, 2, 24, 23, 59, 59, tzinfo=zone)) # Monday + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None + assert state.state == "on" + + +@pytest.mark.parametrize( + "time_zone", ["Asia/Tokyo", "Europe/Berlin", "America/Chicago", "US/Hawaii"] +) +async def test_setup_no_country_weekday( + hass: HomeAssistant, + freezer: FrozenDateTimeFactory, + time_zone: str, +) -> None: + """Test setup shows a weekday as a workday with no country.""" + await hass.config.async_set_time_zone(time_zone) + zone = await dt_util.async_get_time_zone(time_zone) + freezer.move_to(datetime(2020, 2, 21, 23, 59, 59, tzinfo=zone)) # Friday await init_integration(hass, TEST_CONFIG_NO_COUNTRY) + state = hass.states.get("binary_sensor.workday_sensor") + assert state is not None + assert state.state == "on" + + freezer.move_to(datetime(2020, 2, 22, 23, 59, 59, tzinfo=zone)) # Saturday + async_fire_time_changed(hass) + await hass.async_block_till_done() + state = hass.states.get("binary_sensor.workday_sensor") assert state is not None assert state.state == "off" From 1929e103c0c4d95f4067515c3662141c58c440b6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 10 Jun 2024 14:55:28 +0200 Subject: [PATCH 34/41] Fix persistence on OpenWeatherMap raised repair issue (#119289) --- homeassistant/components/openweathermap/repairs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/openweathermap/repairs.py b/homeassistant/components/openweathermap/repairs.py index 0f411a45405242..c54484e1e1e99f 100644 --- a/homeassistant/components/openweathermap/repairs.py +++ b/homeassistant/components/openweathermap/repairs.py @@ -73,7 +73,7 @@ def async_create_issue(hass: HomeAssistant, entry_id: str) -> None: domain=DOMAIN, issue_id=_get_issue_id(entry_id), is_fixable=True, - is_persistent=True, + is_persistent=False, severity=ir.IssueSeverity.WARNING, learn_more_url="https://www.home-assistant.io/integrations/openweathermap/", translation_key="deprecated_v25", From 3bc6cf666a1a909a5671935b7756532be7ccca18 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 10 Jun 2024 11:58:26 -0500 Subject: [PATCH 35/41] Bump uiprotect to 0.4.1 (#119308) --- homeassistant/components/unifiprotect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/manifest.json b/homeassistant/components/unifiprotect/manifest.json index ba6319ab0bae97..00a96483f70ac2 100644 --- a/homeassistant/components/unifiprotect/manifest.json +++ b/homeassistant/components/unifiprotect/manifest.json @@ -40,7 +40,7 @@ "integration_type": "hub", "iot_class": "local_push", "loggers": ["uiprotect", "unifi_discovery"], - "requirements": ["uiprotect==0.4.0", "unifi-discovery==1.1.8"], + "requirements": ["uiprotect==0.4.1", "unifi-discovery==1.1.8"], "ssdp": [ { "manufacturer": "Ubiquiti Networks", diff --git a/requirements_all.txt b/requirements_all.txt index 66b40c31df3534..8e8212560b2b21 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2779,7 +2779,7 @@ twitchAPI==4.0.0 uasiren==0.0.1 # homeassistant.components.unifiprotect -uiprotect==0.4.0 +uiprotect==0.4.1 # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 70285bcd2348b6..b0a9af3956e56c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2153,7 +2153,7 @@ twitchAPI==4.0.0 uasiren==0.0.1 # homeassistant.components.unifiprotect -uiprotect==0.4.0 +uiprotect==0.4.1 # homeassistant.components.landisgyr_heat_meter ultraheat-api==0.5.7 From 135735126a1a0e5ba93908864a6f5d375761d040 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 10 Jun 2024 20:09:39 +0200 Subject: [PATCH 36/41] Add more debug logging to Ping integration (#119318) --- homeassistant/components/ping/helpers.py | 31 +++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/ping/helpers.py b/homeassistant/components/ping/helpers.py index f1fd8518d42262..7f1696d2ed9eb6 100644 --- a/homeassistant/components/ping/helpers.py +++ b/homeassistant/components/ping/helpers.py @@ -9,6 +9,7 @@ from icmplib import NameLookupError, async_ping from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import UpdateFailed from .const import ICMP_TIMEOUT, PING_TIMEOUT @@ -58,9 +59,16 @@ async def async_update(self) -> None: timeout=ICMP_TIMEOUT, privileged=self._privileged, ) - except NameLookupError: + except NameLookupError as err: self.is_alive = False - return + raise UpdateFailed(f"Error resolving host: {self.ip_address}") from err + + _LOGGER.debug( + "async_ping returned: reachable=%s sent=%i received=%s", + data.is_alive, + data.packets_sent, + data.packets_received, + ) self.is_alive = data.is_alive if not self.is_alive: @@ -94,6 +102,10 @@ def __init__( async def async_ping(self) -> dict[str, Any] | None: """Send ICMP echo request and return details if success.""" + _LOGGER.debug( + "Pinging %s with: `%s`", self.ip_address, " ".join(self._ping_cmd) + ) + pinger = await asyncio.create_subprocess_exec( *self._ping_cmd, stdin=None, @@ -140,20 +152,17 @@ async def async_ping(self) -> dict[str, Any] | None: if TYPE_CHECKING: assert match is not None rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups() - except TimeoutError: - _LOGGER.exception( - "Timed out running command: `%s`, after: %ss", - self._ping_cmd, - self._count + PING_TIMEOUT, - ) + except TimeoutError as err: if pinger: with suppress(TypeError): await pinger.kill() # type: ignore[func-returns-value] del pinger - return None - except AttributeError: - return None + raise UpdateFailed( + f"Timed out running command: `{self._ping_cmd}`, after: {self._count + PING_TIMEOUT}s" + ) from err + except AttributeError as err: + raise UpdateFailed from err return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": rtt_mdev} async def async_update(self) -> None: From a0ac9fe6c98b6bf5250bdcc4c15de4d5f00c4095 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 10 Jun 2024 20:22:04 +0200 Subject: [PATCH 37/41] Update frontend to 20240610.0 (#119320) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 27322b423d060a..d3d193751053cc 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20240605.0"] + "requirements": ["home-assistant-frontend==20240610.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b1d82e3c58b1a5..c8c9419339df28 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -32,7 +32,7 @@ habluetooth==3.1.1 hass-nabucasa==0.81.1 hassil==1.7.1 home-assistant-bluetooth==1.12.0 -home-assistant-frontend==20240605.0 +home-assistant-frontend==20240610.0 home-assistant-intents==2024.6.5 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 8e8212560b2b21..7f6d59d3a5ea8a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1087,7 +1087,7 @@ hole==0.8.0 holidays==0.50 # homeassistant.components.frontend -home-assistant-frontend==20240605.0 +home-assistant-frontend==20240610.0 # homeassistant.components.conversation home-assistant-intents==2024.6.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b0a9af3956e56c..fc4dc71f3aaa88 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -889,7 +889,7 @@ hole==0.8.0 holidays==0.50 # homeassistant.components.frontend -home-assistant-frontend==20240605.0 +home-assistant-frontend==20240610.0 # homeassistant.components.conversation home-assistant-intents==2024.6.5 From 6ea18a7b240acb5dd1d86dddb45d0cd1fca2b4e9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 10 Jun 2024 21:44:55 +0200 Subject: [PATCH 38/41] Fix statistic_during_period after core restart (#119323) --- .../components/recorder/statistics.py | 25 +++++++++++++++++-- .../components/recorder/test_websocket_api.py | 18 +++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index 4fe40e6bac8a26..0d76cd93724aab 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -1245,7 +1245,7 @@ def _first_statistic( table: type[StatisticsBase], metadata_id: int, ) -> datetime | None: - """Return the data of the oldest statistic row for a given metadata id.""" + """Return the date of the oldest statistic row for a given metadata id.""" stmt = lambda_stmt( lambda: select(table.start_ts) .filter(table.metadata_id == metadata_id) @@ -1257,6 +1257,23 @@ def _first_statistic( return None +def _last_statistic( + session: Session, + table: type[StatisticsBase], + metadata_id: int, +) -> datetime | None: + """Return the date of the newest statistic row for a given metadata id.""" + stmt = lambda_stmt( + lambda: select(table.start_ts) + .filter(table.metadata_id == metadata_id) + .order_by(table.start_ts.desc()) + .limit(1) + ) + if stats := cast(Sequence[Row], execute_stmt_lambda_element(session, stmt)): + return dt_util.utc_from_timestamp(stats[0].start_ts) + return None + + def _get_oldest_sum_statistic( session: Session, head_start_time: datetime | None, @@ -1487,7 +1504,11 @@ def statistic_during_period( tail_start_time: datetime | None = None tail_end_time: datetime | None = None if end_time is None: - tail_start_time = now.replace(minute=0, second=0, microsecond=0) + tail_start_time = _last_statistic(session, Statistics, metadata_id) + if tail_start_time: + tail_start_time += Statistics.duration + else: + tail_start_time = now.replace(minute=0, second=0, microsecond=0) elif tail_only: tail_start_time = start_time tail_end_time = end_time diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index 639e0abeefe4c0..b5c0f0bf02bed3 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -7,6 +7,7 @@ from unittest.mock import ANY, patch from freezegun import freeze_time +from freezegun.api import FrozenDateTimeFactory import pytest from homeassistant.components import recorder @@ -794,17 +795,30 @@ def next_id(): } -@pytest.mark.freeze_time(datetime.datetime(2022, 10, 21, 6, 31, tzinfo=datetime.UTC)) +@pytest.mark.parametrize( + "frozen_time", + [ + # This is the normal case, all statistics runs are available + datetime.datetime(2022, 10, 21, 6, 31, tzinfo=datetime.UTC), + # Statistic only available up until 6:25, this can happen if + # core has been shut down for an hour + datetime.datetime(2022, 10, 21, 7, 31, tzinfo=datetime.UTC), + ], +) async def test_statistic_during_period_partial_overlap( recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator, + freezer: FrozenDateTimeFactory, + frozen_time: datetime, ) -> None: """Test statistic_during_period.""" + client = await hass_ws_client() + + freezer.move_to(frozen_time) now = dt_util.utcnow() await async_recorder_block_till_done(hass) - client = await hass_ws_client() zero = now start = zero.replace(hour=0, minute=0, second=0, microsecond=0) From b656ef4d4f66ffbe25195dcb724a09ea4ca1857d Mon Sep 17 00:00:00 2001 From: swcloudgenie <45437888+swcloudgenie@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:40:24 -0500 Subject: [PATCH 39/41] Fix AladdinConnect OAuth domain (#119336) fix aladdin connect oauth domain --- homeassistant/components/aladdin_connect/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/aladdin_connect/const.py b/homeassistant/components/aladdin_connect/const.py index 0fe607241547c5..a87147c8f09442 100644 --- a/homeassistant/components/aladdin_connect/const.py +++ b/homeassistant/components/aladdin_connect/const.py @@ -2,5 +2,5 @@ DOMAIN = "aladdin_connect" -OAUTH2_AUTHORIZE = "https://app.aladdinconnect.com/login.html" +OAUTH2_AUTHORIZE = "https://app.aladdinconnect.net/login.html" OAUTH2_TOKEN = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1/oauth2/token" From 7ced4e981eb3629caaeffbc59d8122e025b40706 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 11 Jun 2024 09:22:55 +0200 Subject: [PATCH 40/41] Bump `imgw-pib` backend library to version 1.0.5 (#119360) Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- homeassistant/components/imgw_pib/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/imgw_pib/manifest.json b/homeassistant/components/imgw_pib/manifest.json index fe714691f133b3..08946a802f1937 100644 --- a/homeassistant/components/imgw_pib/manifest.json +++ b/homeassistant/components/imgw_pib/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/imgw_pib", "iot_class": "cloud_polling", "quality_scale": "platinum", - "requirements": ["imgw_pib==1.0.4"] + "requirements": ["imgw_pib==1.0.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7f6d59d3a5ea8a..34c2e2bfa465ae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1146,7 +1146,7 @@ iglo==1.2.7 ihcsdk==2.8.5 # homeassistant.components.imgw_pib -imgw_pib==1.0.4 +imgw_pib==1.0.5 # homeassistant.components.incomfort incomfort-client==0.5.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc4dc71f3aaa88..5a0dd8f939ed3c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -933,7 +933,7 @@ idasen-ha==2.5.3 ifaddr==0.2.0 # homeassistant.components.imgw_pib -imgw_pib==1.0.4 +imgw_pib==1.0.5 # homeassistant.components.influxdb influxdb-client==1.24.0 From 415bfb40a76ae4aaba2a1f72f19f643e375ab4e5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 11 Jun 2024 11:21:51 +0200 Subject: [PATCH 41/41] Bump version to 2024.6.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 86be19b95d88c1..500a74140f2af3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -24,7 +24,7 @@ APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 6 -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, 12, 0) diff --git a/pyproject.toml b/pyproject.toml index 867bc1d1513bdc..b71f80bbaf8ded 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.6.1" +version = "2024.6.2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"