From 440d83d754709fada7c7e08da2466c1f753d75c2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 29 Jun 2024 07:50:53 -0500 Subject: [PATCH 01/37] Remove legacy foreign key constraint from sqlite states table (#120779) --- .../components/recorder/migration.py | 76 ++++++++++++++-- tests/components/recorder/test_migrate.py | 89 ++++++++++++++++++- .../components/recorder/test_v32_migration.py | 9 +- 3 files changed, 162 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 561b446f4932d0..cf003f72af4006 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -24,7 +24,7 @@ SQLAlchemyError, ) from sqlalchemy.orm.session import Session -from sqlalchemy.schema import AddConstraint, DropConstraint +from sqlalchemy.schema import AddConstraint, CreateTable, DropConstraint from sqlalchemy.sql.expression import true from sqlalchemy.sql.lambdas import StatementLambdaElement @@ -1738,14 +1738,15 @@ def cleanup_legacy_states_event_ids(instance: Recorder) -> bool: # Only drop the index if there are no more event_ids in the states table # ex all NULL assert instance.engine is not None, "engine should never be None" - if instance.dialect_name != SupportedDialect.SQLITE: + if instance.dialect_name == SupportedDialect.SQLITE: # SQLite does not support dropping foreign key constraints - # so we can't drop the index at this time but we can avoid - # looking for legacy rows during purge + # so we have to rebuild the table + rebuild_sqlite_table(session_maker, instance.engine, States) + else: _drop_foreign_key_constraints( session_maker, instance.engine, TABLE_STATES, ["event_id"] ) - _drop_index(session_maker, "states", LEGACY_STATES_EVENT_ID_INDEX) + _drop_index(session_maker, "states", LEGACY_STATES_EVENT_ID_INDEX) instance.use_legacy_events_index = False return True @@ -1894,3 +1895,68 @@ def _mark_migration_done( migration_id=migration.migration_id, version=migration.migration_version ) ) + + +def rebuild_sqlite_table( + session_maker: Callable[[], Session], engine: Engine, table: type[Base] +) -> None: + """Rebuild an SQLite table. + + This must only be called after all migrations are complete + and the database is in a consistent state. + + If the table is not migrated to the current schema this + will likely fail. + """ + table_table = cast(Table, table.__table__) + orig_name = table_table.name + temp_name = f"{table_table.name}_temp_{int(time())}" + + _LOGGER.warning( + "Rebuilding SQLite table %s; This will take a while; Please be patient!", + orig_name, + ) + + try: + # 12 step SQLite table rebuild + # https://www.sqlite.org/lang_altertable.html + with session_scope(session=session_maker()) as session: + # Step 1 - Disable foreign keys + session.connection().execute(text("PRAGMA foreign_keys=OFF")) + # Step 2 - create a transaction + with session_scope(session=session_maker()) as session: + # Step 3 - we know all the indexes, triggers, and views associated with table X + new_sql = str(CreateTable(table_table).compile(engine)).strip("\n") + ";" + source_sql = f"CREATE TABLE {orig_name}" + replacement_sql = f"CREATE TABLE {temp_name}" + assert source_sql in new_sql, f"{source_sql} should be in new_sql" + new_sql = new_sql.replace(source_sql, replacement_sql) + # Step 4 - Create temp table + session.execute(text(new_sql)) + column_names = ",".join([column.name for column in table_table.columns]) + # Step 5 - Transfer content + sql = f"INSERT INTO {temp_name} SELECT {column_names} FROM {orig_name};" # noqa: S608 + session.execute(text(sql)) + # Step 6 - Drop the original table + session.execute(text(f"DROP TABLE {orig_name}")) + # Step 7 - Rename the temp table + session.execute(text(f"ALTER TABLE {temp_name} RENAME TO {orig_name}")) + # Step 8 - Recreate indexes + for index in table_table.indexes: + index.create(session.connection()) + # Step 9 - Recreate views (there are none) + # Step 10 - Check foreign keys + session.execute(text("PRAGMA foreign_key_check")) + # Step 11 - Commit transaction + session.commit() + except SQLAlchemyError: + _LOGGER.exception("Error recreating SQLite table %s", table_table.name) + # Swallow the exception since we do not want to ever raise + # an integrity error as it would cause the database + # to be discarded and recreated from scratch + else: + _LOGGER.warning("Rebuilding SQLite table %s finished", orig_name) + finally: + with session_scope(session=session_maker()) as session: + # Step 12 - Re-enable foreign keys + session.connection().execute(text("PRAGMA foreign_keys=ON")) diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index a21f47716167f4..cb8e402f65a5b8 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -16,7 +16,7 @@ ProgrammingError, SQLAlchemyError, ) -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, scoped_session, sessionmaker from sqlalchemy.pool import StaticPool from homeassistant.bootstrap import async_setup_component @@ -24,6 +24,7 @@ from homeassistant.components.recorder import db_schema, migration from homeassistant.components.recorder.db_schema import ( SCHEMA_VERSION, + Events, RecorderRuns, States, ) @@ -633,3 +634,89 @@ def test_raise_if_exception_missing_empty_cause_str() -> None: with pytest.raises(ProgrammingError): migration.raise_if_exception_missing_str(programming_exc, ["not present"]) + + +def test_rebuild_sqlite_states_table(recorder_db_url: str) -> None: + """Test that we can rebuild the states table in SQLite.""" + if not recorder_db_url.startswith("sqlite://"): + # This test is specific for SQLite + return + + engine = create_engine(recorder_db_url) + session_maker = scoped_session(sessionmaker(bind=engine, future=True)) + with session_scope(session=session_maker()) as session: + db_schema.Base.metadata.create_all(engine) + with session_scope(session=session_maker()) as session: + session.add(States(state="on")) + session.commit() + + migration.rebuild_sqlite_table(session_maker, engine, States) + + with session_scope(session=session_maker()) as session: + assert session.query(States).count() == 1 + assert session.query(States).first().state == "on" + + engine.dispose() + + +def test_rebuild_sqlite_states_table_missing_fails( + recorder_db_url: str, caplog: pytest.LogCaptureFixture +) -> None: + """Test handling missing states table when attempting rebuild.""" + if not recorder_db_url.startswith("sqlite://"): + # This test is specific for SQLite + return + + engine = create_engine(recorder_db_url) + session_maker = scoped_session(sessionmaker(bind=engine, future=True)) + with session_scope(session=session_maker()) as session: + db_schema.Base.metadata.create_all(engine) + + with session_scope(session=session_maker()) as session: + session.add(Events(event_type="state_changed", event_data="{}")) + session.connection().execute(text("DROP TABLE states")) + session.commit() + + migration.rebuild_sqlite_table(session_maker, engine, States) + assert "Error recreating SQLite table states" in caplog.text + caplog.clear() + + # Now rebuild the events table to make sure the database did not + # get corrupted + migration.rebuild_sqlite_table(session_maker, engine, Events) + + with session_scope(session=session_maker()) as session: + assert session.query(Events).count() == 1 + assert session.query(Events).first().event_type == "state_changed" + assert session.query(Events).first().event_data == "{}" + + engine.dispose() + + +def test_rebuild_sqlite_states_table_extra_columns( + recorder_db_url: str, caplog: pytest.LogCaptureFixture +) -> None: + """Test handling extra columns when rebuilding the states table.""" + if not recorder_db_url.startswith("sqlite://"): + # This test is specific for SQLite + return + + engine = create_engine(recorder_db_url) + session_maker = scoped_session(sessionmaker(bind=engine, future=True)) + with session_scope(session=session_maker()) as session: + db_schema.Base.metadata.create_all(engine) + with session_scope(session=session_maker()) as session: + session.add(States(state="on")) + session.commit() + session.connection().execute( + text("ALTER TABLE states ADD COLUMN extra_column TEXT") + ) + + migration.rebuild_sqlite_table(session_maker, engine, States) + assert "Error recreating SQLite table states" not in caplog.text + + with session_scope(session=session_maker()) as session: + assert session.query(States).count() == 1 + assert session.query(States).first().state == "on" + + engine.dispose() diff --git a/tests/components/recorder/test_v32_migration.py b/tests/components/recorder/test_v32_migration.py index a07c63b337611b..e3398fbf0e3bce 100644 --- a/tests/components/recorder/test_v32_migration.py +++ b/tests/components/recorder/test_v32_migration.py @@ -211,10 +211,9 @@ def _get_events_index_names(): ) states_index_names = {index["name"] for index in states_indexes} - # sqlite does not support dropping foreign keys so the - # ix_states_event_id index is not dropped in this case - # but use_legacy_events_index is still False - assert "ix_states_event_id" in states_index_names + # sqlite does not support dropping foreign keys so we had to + # create a new table and copy the data over + assert "ix_states_event_id" not in states_index_names assert recorder.get_instance(hass).use_legacy_events_index is False @@ -342,8 +341,6 @@ def _add_data(): await hass.async_stop() await hass.async_block_till_done() - assert "ix_states_entity_id_last_updated_ts" in states_index_names - async with async_test_home_assistant() as hass: recorder_helper.async_initialize_recorder(hass) assert await async_setup_component( From c54717707e0b92a5aa55651a05dbf801944be1f8 Mon Sep 17 00:00:00 2001 From: "Robert C. Maehl" Date: Sat, 6 Jul 2024 05:12:38 -0400 Subject: [PATCH 02/37] Direct Users to App-Specific Passwords for iCloud integration to prevent MFA spam (#120945) Co-authored-by: Franck Nijhof --- homeassistant/components/icloud/strings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/icloud/strings.json b/homeassistant/components/icloud/strings.json index 96db11d4656f73..22c711e919ab4c 100644 --- a/homeassistant/components/icloud/strings.json +++ b/homeassistant/components/icloud/strings.json @@ -6,7 +6,7 @@ "description": "Enter your credentials", "data": { "username": "[%key:common::config_flow::data::email%]", - "password": "[%key:common::config_flow::data::password%]", + "password": "App-specific password", "with_family": "With family" } }, @@ -14,7 +14,7 @@ "title": "[%key:common::config_flow::title::reauth%]", "description": "Your previously entered password for {username} is no longer working. Update your password to keep using this integration.", "data": { - "password": "[%key:common::config_flow::data::password%]" + "password": "App-specific password" } }, "trusted_device": { From a06af7ee9394fd39c256a1b540657ce316be0367 Mon Sep 17 00:00:00 2001 From: Alan Date: Sat, 6 Jul 2024 10:41:18 +0100 Subject: [PATCH 03/37] LLM to handle int attributes (#121037) --- homeassistant/helpers/llm.py | 2 +- tests/helpers/test_llm.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/llm.py b/homeassistant/helpers/llm.py index ba307a785aca82..506cadbf168bf3 100644 --- a/homeassistant/helpers/llm.py +++ b/homeassistant/helpers/llm.py @@ -483,7 +483,7 @@ def _get_exposed_entities( if attributes := { attr_name: str(attr_value) - if isinstance(attr_value, (Enum, Decimal)) + if isinstance(attr_value, (Enum, Decimal, int)) else attr_value for attr_name, attr_value in state.attributes.items() if attr_name in interesting_attributes diff --git a/tests/helpers/test_llm.py b/tests/helpers/test_llm.py index ad18aa53071571..81fa573852ef4d 100644 --- a/tests/helpers/test_llm.py +++ b/tests/helpers/test_llm.py @@ -408,7 +408,7 @@ async def test_assist_api_prompt( hass.states.async_set( entry1.entity_id, "on", - {"friendly_name": "Kitchen", "temperature": Decimal("0.9")}, + {"friendly_name": "Kitchen", "temperature": Decimal("0.9"), "humidity": 65}, ) hass.states.async_set(entry2.entity_id, "on", {"friendly_name": "Living Room"}) @@ -517,9 +517,7 @@ def create_entity(device: dr.DeviceEntry, write_state=True) -> None: entry1.entity_id: { "names": "Kitchen", "state": "on", - "attributes": { - "temperature": "0.9", - }, + "attributes": {"temperature": "0.9", "humidity": "65"}, }, entry2.entity_id: { "areas": "Test Area, Alternative name", From 1133c41fa8e88df02bec6bf3e5df319e907bff91 Mon Sep 17 00:00:00 2001 From: Rasmus Lundsgaard Date: Sat, 6 Jul 2024 19:20:14 +0200 Subject: [PATCH 04/37] Fix empty list in kodi media_player (#121250) Co-authored-by: Franck Nijhof --- homeassistant/components/kodi/media_player.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 290b3b1e56655f..46dee891e3a801 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -641,12 +641,10 @@ def extra_state_attributes(self) -> dict[str, str | None]: if self.state == MediaPlayerState.OFF: return state_attr - hdr_type = ( - self._item.get("streamdetails", {}).get("video", [{}])[0].get("hdrtype") - ) - if hdr_type == "": - state_attr["dynamic_range"] = "sdr" - else: + state_attr["dynamic_range"] = "sdr" + if (video_details := self._item.get("streamdetails", {}).get("video")) and ( + hdr_type := video_details[0].get("hdrtype") + ): state_attr["dynamic_range"] = hdr_type return state_attr From 803d9c5a8e80db67c1571cb10665da65012f598d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Teme=C5=A1inko?= Date: Fri, 5 Jul 2024 19:12:09 +0200 Subject: [PATCH 05/37] Fix ombi configuration validation (#121314) --- homeassistant/components/ombi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ombi/__init__.py b/homeassistant/components/ombi/__init__.py index a4cbe39f3e0fa2..d63f72592f88f7 100644 --- a/homeassistant/components/ombi/__init__.py +++ b/homeassistant/components/ombi/__init__.py @@ -73,7 +73,7 @@ def urlbase(value) -> str: vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, } ), - cv.has_at_least_one_key("auth"), + cv.has_at_least_one_key(CONF_API_KEY, CONF_PASSWORD), ) }, extra=vol.ALLOW_EXTRA, From 711bdaf37367b49b22b9d7d7e9a2ef192e22dbdf Mon Sep 17 00:00:00 2001 From: Luke Lashley Date: Sat, 6 Jul 2024 02:39:24 -0400 Subject: [PATCH 06/37] Bump anova-wifi to 0.17.0 (#121337) * Bump anova-wifi to 0.16.0 * Bump to .17 --- homeassistant/components/anova/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/anova/manifest.json b/homeassistant/components/anova/manifest.json index 7e605edc217d41..7e032f0e361e06 100644 --- a/homeassistant/components/anova/manifest.json +++ b/homeassistant/components/anova/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/anova", "iot_class": "cloud_push", "loggers": ["anova_wifi"], - "requirements": ["anova-wifi==0.15.0"] + "requirements": ["anova-wifi==0.17.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index f1d7764410448e..ecbc48d6d8a757 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -449,7 +449,7 @@ androidtvremote2==0.1.1 anel-pwrctrl-homeassistant==0.0.1.dev2 # homeassistant.components.anova -anova-wifi==0.15.0 +anova-wifi==0.17.0 # homeassistant.components.anthemav anthemav==1.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 90e59778751379..fa92d55f0131eb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -413,7 +413,7 @@ androidtv[async]==0.0.73 androidtvremote2==0.1.1 # homeassistant.components.anova -anova-wifi==0.15.0 +anova-wifi==0.17.0 # homeassistant.components.anthemav anthemav==1.4.1 From 1d7bddf449766f3c108c0131f7802b214be3f845 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Sat, 6 Jul 2024 18:54:09 +1000 Subject: [PATCH 07/37] Fix initial Wall Connector values in Tessie (#121353) --- homeassistant/components/tessie/entity.py | 2 +- tests/components/tessie/snapshots/test_sensor.ambr | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/tessie/entity.py b/homeassistant/components/tessie/entity.py index d2a59f205fcc13..4c077ce19db39e 100644 --- a/homeassistant/components/tessie/entity.py +++ b/homeassistant/components/tessie/entity.py @@ -42,6 +42,7 @@ def __init__( self.key = key self._attr_translation_key = key super().__init__(coordinator) + self._async_update_attrs() @property def _value(self) -> Any: @@ -132,7 +133,6 @@ def __init__( self._attr_device_info = data.device super().__init__(coordinator, key) - self._async_update_attrs() class TessieWallConnectorEntity(TessieBaseEntity): diff --git a/tests/components/tessie/snapshots/test_sensor.ambr b/tests/components/tessie/snapshots/test_sensor.ambr index afe229feba00b7..0a5ff4603aa0b8 100644 --- a/tests/components/tessie/snapshots/test_sensor.ambr +++ b/tests/components/tessie/snapshots/test_sensor.ambr @@ -2120,7 +2120,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '0.0', }) # --- # name: test_sensors[sensor.wall_connector_power_2-entry] @@ -2177,7 +2177,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': '0.0', }) # --- # name: test_sensors[sensor.wall_connector_state-entry] @@ -2249,7 +2249,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'disconnected', }) # --- # name: test_sensors[sensor.wall_connector_state_2-entry] @@ -2321,7 +2321,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'unknown', + 'state': 'disconnected', }) # --- # name: test_sensors[sensor.wall_connector_vehicle-entry] From a5f4c25a2cbeba113f97ce3d5607e1740d4c0bf1 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 6 Jul 2024 19:09:00 +0200 Subject: [PATCH 08/37] Bump psutil to 6.0.0 (#121385) --- homeassistant/components/systemmonitor/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/systemmonitor/conftest.py | 10 +++++----- tests/components/systemmonitor/test_util.py | 12 +++++------- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/systemmonitor/manifest.json b/homeassistant/components/systemmonitor/manifest.json index 5e1ef6c02de9ca..236f25bb1ed419 100644 --- a/homeassistant/components/systemmonitor/manifest.json +++ b/homeassistant/components/systemmonitor/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/systemmonitor", "iot_class": "local_push", "loggers": ["psutil"], - "requirements": ["psutil-home-assistant==0.0.1", "psutil==5.9.8"] + "requirements": ["psutil-home-assistant==0.0.1", "psutil==6.0.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index ecbc48d6d8a757..7d7dda06b0a7f2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1598,7 +1598,7 @@ proxmoxer==2.0.1 psutil-home-assistant==0.0.1 # homeassistant.components.systemmonitor -psutil==5.9.8 +psutil==6.0.0 # homeassistant.components.pulseaudio_loopback pulsectl==23.5.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fa92d55f0131eb..a0c20bfe3ece58 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1275,7 +1275,7 @@ prometheus-client==0.17.1 psutil-home-assistant==0.0.1 # homeassistant.components.systemmonitor -psutil==5.9.8 +psutil==6.0.0 # homeassistant.components.androidtv pure-python-adb[async]==0.3.0.dev0 diff --git a/tests/components/systemmonitor/conftest.py b/tests/components/systemmonitor/conftest.py index e16debdf263d2b..256114814337e3 100644 --- a/tests/components/systemmonitor/conftest.py +++ b/tests/components/systemmonitor/conftest.py @@ -174,11 +174,11 @@ def mock_psutil(mock_process: list[MockProcess]) -> Generator: "cpu0-thermal": [shwtemp("cpu0-thermal", 50.0, 60.0, 70.0)] } mock_psutil.disk_partitions.return_value = [ - sdiskpart("test", "/", "ext4", "", 1, 1), - sdiskpart("test2", "/media/share", "ext4", "", 1, 1), - sdiskpart("test3", "/incorrect", "", "", 1, 1), - sdiskpart("hosts", "/etc/hosts", "bind", "", 1, 1), - sdiskpart("proc", "/proc/run", "proc", "", 1, 1), + sdiskpart("test", "/", "ext4", ""), + sdiskpart("test2", "/media/share", "ext4", ""), + sdiskpart("test3", "/incorrect", "", ""), + sdiskpart("hosts", "/etc/hosts", "bind", ""), + sdiskpart("proc", "/proc/run", "proc", ""), ] mock_psutil.boot_time.return_value = 1708786800.0 mock_psutil.NoSuchProcess = NoSuchProcess diff --git a/tests/components/systemmonitor/test_util.py b/tests/components/systemmonitor/test_util.py index b35c7b2e96c1d0..582707f3574bc4 100644 --- a/tests/components/systemmonitor/test_util.py +++ b/tests/components/systemmonitor/test_util.py @@ -50,21 +50,19 @@ async def test_disk_util( """Test the disk failures.""" mock_psutil.psutil.disk_partitions.return_value = [ - sdiskpart("test", "/", "ext4", "", 1, 1), # Should be ok - sdiskpart("test2", "/media/share", "ext4", "", 1, 1), # Should be ok - sdiskpart("test3", "/incorrect", "", "", 1, 1), # Should be skipped as no type + sdiskpart("test", "/", "ext4", ""), # Should be ok + sdiskpart("test2", "/media/share", "ext4", ""), # Should be ok + sdiskpart("test3", "/incorrect", "", ""), # Should be skipped as no type sdiskpart( - "proc", "/proc/run", "proc", "", 1, 1 + "proc", "/proc/run", "proc", "" ), # Should be skipped as in skipped disk types sdiskpart( "test4", "/tmpfs/", # noqa: S108 "tmpfs", "", - 1, - 1, ), # Should be skipped as in skipped disk types - sdiskpart("test5", "E:", "cd", "cdrom", 1, 1), # Should be skipped as cdrom + sdiskpart("test5", "E:", "cd", "cdrom"), # Should be skipped as cdrom ] mock_config_entry.add_to_hass(hass) From 8017386c73fffa58ef384c9a072917a7c71650ce Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 6 Jul 2024 19:32:27 +0200 Subject: [PATCH 09/37] Fix unnecessary logging of turn on/off feature flags in Climate (#121387) --- homeassistant/components/climate/__init__.py | 8 +++ tests/components/climate/test_init.py | 62 ++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index bc81ce6e2417bc..98500d97eb7c67 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -377,6 +377,14 @@ def _report_turn_on_off(feature: str, method: str) -> None: # Return if integration has migrated already return + supported_features = self.supported_features + if supported_features & ( + ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_ON + ): + # The entity supports both turn_on and turn_off, the backwards compatibility + # checks are not needed + return + supported_features = self.supported_features if not supported_features & ClimateEntityFeature.TURN_OFF and ( type(self).async_turn_off is not ClimateEntity.async_turn_off diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index a459b991203ba3..4756c265aea8d0 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -709,6 +709,68 @@ async def async_setup_entry_climate_platform( ) +async def test_no_warning_integration_implement_feature_flags( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None +) -> None: + """Test no warning when integration uses the correct feature flags.""" + + class MockClimateEntityTest(MockClimateEntity): + """Mock Climate device.""" + + _attr_supported_features = ( + ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.SWING_MODE + | ClimateEntityFeature.TURN_OFF + | ClimateEntityFeature.TURN_ON + ) + + async def async_setup_entry_init( + hass: HomeAssistant, config_entry: ConfigEntry + ) -> bool: + """Set up test config entry.""" + await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN]) + return True + + async def async_setup_entry_climate_platform( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + ) -> None: + """Set up test climate platform via config entry.""" + async_add_entities( + [MockClimateEntityTest(name="test", entity_id="climate.test")] + ) + + mock_integration( + hass, + MockModule( + "test", + async_setup_entry=async_setup_entry_init, + ), + built_in=False, + ) + mock_platform( + hass, + "test.climate", + MockPlatform(async_setup_entry=async_setup_entry_climate_platform), + ) + + with patch.object( + MockClimateEntityTest, "__module__", "tests.custom_components.climate.test_init" + ): + config_entry = MockConfigEntry(domain="test") + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("climate.test") + assert state is not None + + assert "does not set ClimateEntityFeature" not in caplog.text + assert "implements HVACMode(s):" not in caplog.text + + async def test_turn_on_off_toggle(hass: HomeAssistant) -> None: """Test turn_on/turn_off/toggle methods.""" From 37621e77aecdac8fb7772365711ecfd3eb85060f Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 6 Jul 2024 21:18:02 +0200 Subject: [PATCH 10/37] Fix timezone issue in smhi weather (#121389) --- homeassistant/components/smhi/weather.py | 4 ++- .../smhi/snapshots/test_weather.ambr | 36 +++++++++---------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 3d5642a27847a3..aac4c5d24be438 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -218,7 +218,9 @@ def _get_forecast_data( data.append( { - ATTR_FORECAST_TIME: forecast.valid_time.isoformat(), + ATTR_FORECAST_TIME: forecast.valid_time.replace( + tzinfo=dt_util.UTC + ).isoformat(), ATTR_FORECAST_NATIVE_TEMP: forecast.temperature_max, ATTR_FORECAST_NATIVE_TEMP_LOW: forecast.temperature_min, ATTR_FORECAST_NATIVE_PRECIPITATION: forecast.total_precipitation, diff --git a/tests/components/smhi/snapshots/test_weather.ambr b/tests/components/smhi/snapshots/test_weather.ambr index 0d2f6b3b3bfb28..d825e22d47007a 100644 --- a/tests/components/smhi/snapshots/test_weather.ambr +++ b/tests/components/smhi/snapshots/test_weather.ambr @@ -6,7 +6,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'clear-night', - 'datetime': '2023-08-08T00:00:00', + 'datetime': '2023-08-08T00:00:00+00:00', 'humidity': 100, 'precipitation': 0.0, 'pressure': 992.0, @@ -19,7 +19,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'clear-night', - 'datetime': '2023-08-08T01:00:00', + 'datetime': '2023-08-08T01:00:00+00:00', 'humidity': 100, 'precipitation': 0.0, 'pressure': 992.0, @@ -32,7 +32,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'clear-night', - 'datetime': '2023-08-08T02:00:00', + 'datetime': '2023-08-08T02:00:00+00:00', 'humidity': 97, 'precipitation': 0.0, 'pressure': 992.0, @@ -45,7 +45,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'sunny', - 'datetime': '2023-08-08T03:00:00', + 'datetime': '2023-08-08T03:00:00+00:00', 'humidity': 96, 'precipitation': 0.0, 'pressure': 991.0, @@ -223,7 +223,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'cloudy', - 'datetime': '2023-08-07T12:00:00', + 'datetime': '2023-08-07T12:00:00+00:00', 'humidity': 96, 'precipitation': 0.0, 'pressure': 991.0, @@ -236,7 +236,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'rainy', - 'datetime': '2023-08-08T12:00:00', + 'datetime': '2023-08-08T12:00:00+00:00', 'humidity': 97, 'precipitation': 10.6, 'pressure': 984.0, @@ -249,7 +249,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'rainy', - 'datetime': '2023-08-09T12:00:00', + 'datetime': '2023-08-09T12:00:00+00:00', 'humidity': 95, 'precipitation': 6.3, 'pressure': 1001.0, @@ -262,7 +262,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'cloudy', - 'datetime': '2023-08-10T12:00:00', + 'datetime': '2023-08-10T12:00:00+00:00', 'humidity': 75, 'precipitation': 4.8, 'pressure': 1011.0, @@ -275,7 +275,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'cloudy', - 'datetime': '2023-08-11T12:00:00', + 'datetime': '2023-08-11T12:00:00+00:00', 'humidity': 69, 'precipitation': 0.6, 'pressure': 1015.0, @@ -288,7 +288,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'cloudy', - 'datetime': '2023-08-12T12:00:00', + 'datetime': '2023-08-12T12:00:00+00:00', 'humidity': 82, 'precipitation': 0.0, 'pressure': 1014.0, @@ -301,7 +301,7 @@ dict({ 'cloud_coverage': 75, 'condition': 'partlycloudy', - 'datetime': '2023-08-13T12:00:00', + 'datetime': '2023-08-13T12:00:00+00:00', 'humidity': 59, 'precipitation': 0.0, 'pressure': 1013.0, @@ -314,7 +314,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'partlycloudy', - 'datetime': '2023-08-14T12:00:00', + 'datetime': '2023-08-14T12:00:00+00:00', 'humidity': 56, 'precipitation': 0.0, 'pressure': 1015.0, @@ -327,7 +327,7 @@ dict({ 'cloud_coverage': 88, 'condition': 'partlycloudy', - 'datetime': '2023-08-15T12:00:00', + 'datetime': '2023-08-15T12:00:00+00:00', 'humidity': 64, 'precipitation': 3.6, 'pressure': 1014.0, @@ -340,7 +340,7 @@ dict({ 'cloud_coverage': 75, 'condition': 'partlycloudy', - 'datetime': '2023-08-16T12:00:00', + 'datetime': '2023-08-16T12:00:00+00:00', 'humidity': 61, 'precipitation': 2.4, 'pressure': 1014.0, @@ -358,7 +358,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'cloudy', - 'datetime': '2023-08-07T12:00:00', + 'datetime': '2023-08-07T12:00:00+00:00', 'humidity': 96, 'precipitation': 0.0, 'pressure': 991.0, @@ -373,7 +373,7 @@ dict({ 'cloud_coverage': 75, 'condition': 'partlycloudy', - 'datetime': '2023-08-13T12:00:00', + 'datetime': '2023-08-13T12:00:00+00:00', 'humidity': 59, 'precipitation': 0.0, 'pressure': 1013.0, @@ -388,7 +388,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'fog', - 'datetime': '2023-08-07T09:00:00', + 'datetime': '2023-08-07T09:00:00+00:00', 'humidity': 100, 'precipitation': 0.0, 'pressure': 992.0, @@ -403,7 +403,7 @@ dict({ 'cloud_coverage': 100, 'condition': 'cloudy', - 'datetime': '2023-08-07T15:00:00', + 'datetime': '2023-08-07T15:00:00+00:00', 'humidity': 89, 'precipitation': 0.0, 'pressure': 991.0, From 780f7254c1aac9fc8696f3cbf8a8daf28f42ecd3 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sat, 6 Jul 2024 23:36:53 +0200 Subject: [PATCH 11/37] Fix feature flag in climate (#121398) --- homeassistant/components/climate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 98500d97eb7c67..2c891779c379c0 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -379,7 +379,7 @@ def _report_turn_on_off(feature: str, method: str) -> None: supported_features = self.supported_features if supported_features & ( - ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_ON + ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF ): # The entity supports both turn_on and turn_off, the backwards compatibility # checks are not needed From a72cc3c248bef12dcb482f2220e169679f811872 Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sun, 7 Jul 2024 15:11:28 +0200 Subject: [PATCH 12/37] Allow current empty feeds to be configured in Feedreader (#121421) --- .../components/feedreader/config_flow.py | 14 ------ .../components/feedreader/strings.json | 7 +-- .../components/feedreader/test_config_flow.py | 50 +++---------------- 3 files changed, 7 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/feedreader/config_flow.py b/homeassistant/components/feedreader/config_flow.py index 6fa153b81770be..d367432ff8ccd7 100644 --- a/homeassistant/components/feedreader/config_flow.py +++ b/homeassistant/components/feedreader/config_flow.py @@ -107,13 +107,6 @@ async def async_step_user( return self.abort_on_import_error(user_input[CONF_URL], "url_error") return self.show_user_form(user_input, {"base": "url_error"}) - if not feed.entries: - if self.context["source"] == SOURCE_IMPORT: - return self.abort_on_import_error( - user_input[CONF_URL], "no_feed_entries" - ) - return self.show_user_form(user_input, {"base": "no_feed_entries"}) - feed_title = feed["feed"]["title"] return self.async_create_entry( @@ -161,13 +154,6 @@ async def async_step_reconfigure_confirm( step_id="reconfigure_confirm", errors={"base": "url_error"}, ) - if not feed.entries: - return self.show_user_form( - user_input=user_input, - description_placeholders={"name": self._config_entry.title}, - step_id="reconfigure_confirm", - errors={"base": "no_feed_entries"}, - ) self.hass.config_entries.async_update_entry(self._config_entry, data=user_input) return self.async_abort(reason="reconfigure_successful") diff --git a/homeassistant/components/feedreader/strings.json b/homeassistant/components/feedreader/strings.json index 31881b4112ae62..da66333fa5bcc6 100644 --- a/homeassistant/components/feedreader/strings.json +++ b/homeassistant/components/feedreader/strings.json @@ -18,8 +18,7 @@ "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "error": { - "url_error": "The URL could not be opened.", - "no_feed_entries": "The URL seems not to serve any feed entries." + "url_error": "The URL could not be opened." } }, "options": { @@ -38,10 +37,6 @@ "import_yaml_error_url_error": { "title": "The Feedreader YAML configuration import failed", "description": "Configuring the Feedreader using YAML is being removed but there was a connection error when trying to import the YAML configuration for `{url}`.\n\nPlease verify that url is reachable and accessable for Home Assistant and restart Home Assistant to try again or remove the Feedreader YAML configuration from your configuration.yaml file and continue to set up the integration manually." - }, - "import_yaml_error_no_feed_entries": { - "title": "[%key:component::feedreader::issues::import_yaml_error_url_error::title%]", - "description": "Configuring the Feedreader using YAML is being removed but when trying to import the YAML configuration for `{url}` no feed entries were found.\n\nPlease verify that url serves any feed entries and restart Home Assistant to try again or remove the Feedreader YAML configuration from your configuration.yaml file and continue to set up the integration manually." } } } diff --git a/tests/components/feedreader/test_config_flow.py b/tests/components/feedreader/test_config_flow.py index 48c341492e0f94..669ca665f6bb47 100644 --- a/tests/components/feedreader/test_config_flow.py +++ b/tests/components/feedreader/test_config_flow.py @@ -83,16 +83,6 @@ async def test_user_errors( assert result["step_id"] == "user" assert result["errors"] == {"base": "url_error"} - # no feed entries returned - feedparser.side_effect = None - feedparser.return_value = None - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_URL: URL} - ) - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "user" - assert result["errors"] == {"base": "no_feed_entries"} - # success feedparser.side_effect = None feedparser.return_value = feed_one_event @@ -141,40 +131,25 @@ async def test_import( assert issue_registry.async_get_issue(HA_DOMAIN, "deprecated_yaml_feedreader") -@pytest.mark.parametrize( - ("side_effect", "return_value", "expected_issue_id"), - [ - ( - urllib.error.URLError("Test"), - None, - "import_yaml_error_feedreader_url_error_http_some_rss_local_rss_feed_xml", - ), - ( - None, - None, - "import_yaml_error_feedreader_no_feed_entries_http_some_rss_local_rss_feed_xml", - ), - ], -) async def test_import_errors( hass: HomeAssistant, issue_registry: ir.IssueRegistry, feedparser, setup_entry, feed_one_event, - side_effect, - return_value, - expected_issue_id, ) -> None: """Test starting an import flow which results in an URL error.""" config_entries = hass.config_entries.async_entries(DOMAIN) assert not config_entries # raise URLError - feedparser.side_effect = side_effect - feedparser.return_value = return_value + feedparser.side_effect = urllib.error.URLError("Test") + feedparser.return_value = None assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_URLS: [URL]}}) - assert issue_registry.async_get_issue(DOMAIN, expected_issue_id) + assert issue_registry.async_get_issue( + DOMAIN, + "import_yaml_error_feedreader_url_error_http_some_rss_local_rss_feed_xml", + ) async def test_reconfigure(hass: HomeAssistant, feedparser) -> None: @@ -248,19 +223,6 @@ async def test_reconfigure_errors( assert result["step_id"] == "reconfigure_confirm" assert result["errors"] == {"base": "url_error"} - # no feed entries returned - feedparser.side_effect = None - feedparser.return_value = None - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - user_input={ - CONF_URL: "http://other.rss.local/rss_feed.xml", - }, - ) - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "reconfigure_confirm" - assert result["errors"] == {"base": "no_feed_entries"} - # success feedparser.side_effect = None feedparser.return_value = feed_one_event From 8825c50671e9cb5d62ef4b02266a654a78213a2c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 7 Jul 2024 14:54:29 +0200 Subject: [PATCH 13/37] Fix MPD config flow (#121431) --- homeassistant/components/mpd/manifest.json | 1 + homeassistant/generated/config_flows.py | 1 + homeassistant/generated/integrations.json | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mpd/manifest.json b/homeassistant/components/mpd/manifest.json index e03005fb95aa0a..a361152670a91a 100644 --- a/homeassistant/components/mpd/manifest.json +++ b/homeassistant/components/mpd/manifest.json @@ -2,6 +2,7 @@ "domain": "mpd", "name": "Music Player Daemon (MPD)", "codeowners": [], + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/mpd", "iot_class": "local_polling", "loggers": ["mpd"], diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 463a38feb9f892..0020dc91ccda56 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -352,6 +352,7 @@ "motionblinds_ble", "motioneye", "motionmount", + "mpd", "mqtt", "mullvad", "mutesync", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 0ad8ac09c9e947..a3db08e57c2290 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3814,7 +3814,7 @@ "mpd": { "name": "Music Player Daemon (MPD)", "integration_type": "hub", - "config_flow": false, + "config_flow": true, "iot_class": "local_polling" }, "mqtt": { From cadd8521aeed818374e0214e8d7a593e46bbf8e6 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 7 Jul 2024 15:01:58 +0200 Subject: [PATCH 14/37] Sort mealie mealplans (#121433) --- homeassistant/components/mealie/calendar.py | 3 ++- .../mealie/snapshots/test_calendar.ambr | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/mealie/calendar.py b/homeassistant/components/mealie/calendar.py index fb628754f06da3..1b1c14c2ca00ae 100644 --- a/homeassistant/components/mealie/calendar.py +++ b/homeassistant/components/mealie/calendar.py @@ -60,7 +60,8 @@ def event(self) -> CalendarEvent | None: mealplans = self.coordinator.data[self._entry_type] if not mealplans: return None - return _get_event_from_mealplan(mealplans[0]) + sorted_mealplans = sorted(mealplans, key=lambda x: x.mealplan_date) + return _get_event_from_mealplan(sorted_mealplans[0]) async def async_get_events( self, hass: HomeAssistant, start_date: datetime, end_date: datetime diff --git a/tests/components/mealie/snapshots/test_calendar.ambr b/tests/components/mealie/snapshots/test_calendar.ambr index c3b26e1e9e2cbf..e5a0a697157d36 100644 --- a/tests/components/mealie/snapshots/test_calendar.ambr +++ b/tests/components/mealie/snapshots/test_calendar.ambr @@ -252,12 +252,12 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'all_day': True, - 'description': "Een traybake is eigenlijk altijd een goed idee. Deze zoete aardappel curry traybake dus ook. Waarom? Omdat je alleen maar wat groenten - en in dit geval kip - op een bakplaat (traybake dus) legt, hier wat kruiden aan toevoegt en deze in de oven schuift. Ideaal dus als je geen zin hebt om lang in de keuken te staan. Maar gewoon lekker op de bank wil ploffen om te wachten tot de oven klaar is. Joe! That\\'s what we like. Deze zoete aardappel curry traybake bevat behalve zoete aardappel en curry ook kikkererwten, kippendijfilet en bloemkoolroosjes. Je gebruikt yoghurt en limoen als een soort dressing. En je serveert deze heerlijke traybake met naanbrood. Je kunt natuurljk ook voor deze traybake met chipolataworstjes gaan. Wil je graag meer ovengerechten? Dan moet je eigenlijk even kijken naar onze Ovenbijbel. Onmisbaar in je keuken! We willen je deze zoete aardappelstamppot met prei ook niet onthouden. Megalekker bordje comfortfood als je \\'t ons vraagt.", - 'end_time': '2024-01-23 00:00:00', + 'description': 'Dineren met de boys', + 'end_time': '2024-01-22 00:00:00', 'friendly_name': 'Mealie Dinner', 'location': '', - 'message': 'Zoete aardappel curry traybake', - 'start_time': '2024-01-22 00:00:00', + 'message': 'Aquavite', + 'start_time': '2024-01-21 00:00:00', }), 'context': , 'entity_id': 'calendar.mealie_dinner', @@ -304,12 +304,12 @@ StateSnapshot({ 'attributes': ReadOnlyDict({ 'all_day': True, - 'description': 'Te explicamos paso a paso, de manera sencilla, la elaboración de la receta de pollo al curry con leche de coco en 10 minutos. Ingredientes, tiempo de...', - 'end_time': '2024-01-24 00:00:00', + 'description': 'This All-American beef stew recipe includes tender beef coated in a rich, intense sauce and vegetables that bring complementary texture and flavor.', + 'end_time': '2024-01-23 00:00:00', 'friendly_name': 'Mealie Lunch', 'location': '', - 'message': 'Receta de pollo al curry en 10 minutos (con vídeo incluido)', - 'start_time': '2024-01-23 00:00:00', + 'message': 'All-American Beef Stew Recipe', + 'start_time': '2024-01-22 00:00:00', }), 'context': , 'entity_id': 'calendar.mealie_lunch', From ec105e526508fb719700320dfd66db91f0f69cd1 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sun, 7 Jul 2024 15:03:32 +0200 Subject: [PATCH 15/37] Fix Mealie URL field (#121434) --- homeassistant/components/mealie/strings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mealie/strings.json b/homeassistant/components/mealie/strings.json index 0d67bb89759142..7a89fb85128cac 100644 --- a/homeassistant/components/mealie/strings.json +++ b/homeassistant/components/mealie/strings.json @@ -3,8 +3,11 @@ "step": { "user": { "data": { - "host": "[%key:common::config_flow::data::host%]", + "host": "[%key:common::config_flow::data::url%]", "api_token": "[%key:common::config_flow::data::api_token%]" + }, + "data_description": { + "host": "The URL of your Mealie instance." } } }, From ab94422c18ae4b6da0bb7c0960d3a0b7d24b48fa Mon Sep 17 00:00:00 2001 From: jan iversen Date: Sun, 7 Jul 2024 17:38:17 +0200 Subject: [PATCH 16/37] Bump pymodbus to 3.6.9 (#121445) Bump pymodbus 3.6.9. --- homeassistant/components/modbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/modbus/manifest.json b/homeassistant/components/modbus/manifest.json index 5635adc939211d..292a2ee86a8df3 100644 --- a/homeassistant/components/modbus/manifest.json +++ b/homeassistant/components/modbus/manifest.json @@ -6,5 +6,5 @@ "iot_class": "local_polling", "loggers": ["pymodbus"], "quality_scale": "platinum", - "requirements": ["pymodbus==3.6.8"] + "requirements": ["pymodbus==3.6.9"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7d7dda06b0a7f2..fdb00d10e4db67 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2004,7 +2004,7 @@ pymitv==1.4.3 pymochad==0.2.0 # homeassistant.components.modbus -pymodbus==3.6.8 +pymodbus==3.6.9 # homeassistant.components.monoprice pymonoprice==0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a0c20bfe3ece58..3f89f1a2182a2e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1576,7 +1576,7 @@ pymeteoclimatic==0.1.0 pymochad==0.2.0 # homeassistant.components.modbus -pymodbus==3.6.8 +pymodbus==3.6.9 # homeassistant.components.monoprice pymonoprice==0.4 From 9512f9eec3e9fb7447d2766f7d843e7bd8f37921 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 7 Jul 2024 12:02:47 -0400 Subject: [PATCH 17/37] Bump jaraco.abode to 5.2.1 (#121446) Bump dependency on jaraco.abode to 5.2.1. Closes #121300 --- homeassistant/components/abode/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/abode/manifest.json b/homeassistant/components/abode/manifest.json index de1000319f1628..225edea40caf99 100644 --- a/homeassistant/components/abode/manifest.json +++ b/homeassistant/components/abode/manifest.json @@ -9,5 +9,5 @@ }, "iot_class": "cloud_push", "loggers": ["jaraco.abode", "lomond"], - "requirements": ["jaraco.abode==5.1.2"] + "requirements": ["jaraco.abode==5.2.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index fdb00d10e4db67..93b0853f977eaf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1179,7 +1179,7 @@ isal==1.6.1 ismartgate==5.0.1 # homeassistant.components.abode -jaraco.abode==5.1.2 +jaraco.abode==5.2.1 # homeassistant.components.jellyfin jellyfin-apiclient-python==1.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3f89f1a2182a2e..518f9bef6ae6aa 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -966,7 +966,7 @@ isal==1.6.1 ismartgate==5.0.1 # homeassistant.components.abode -jaraco.abode==5.1.2 +jaraco.abode==5.2.1 # homeassistant.components.jellyfin jellyfin-apiclient-python==1.9.2 From 0a1b46c52fc81e8d575b5068157cd40cf290523e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Jul 2024 13:41:31 -0700 Subject: [PATCH 18/37] Bump yalexs to 6.4.2 (#121467) --- homeassistant/components/august/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/august/fixtures/get_lock.low_keypad_battery.json | 2 +- tests/components/august/test_sensor.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/august/manifest.json b/homeassistant/components/august/manifest.json index f898ce64ce6846..83d0e985b8a19c 100644 --- a/homeassistant/components/august/manifest.json +++ b/homeassistant/components/august/manifest.json @@ -28,5 +28,5 @@ "documentation": "https://www.home-assistant.io/integrations/august", "iot_class": "cloud_push", "loggers": ["pubnub", "yalexs"], - "requirements": ["yalexs==6.4.1", "yalexs-ble==2.4.3"] + "requirements": ["yalexs==6.4.2", "yalexs-ble==2.4.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 93b0853f977eaf..5ac963d62a4ada 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2933,7 +2933,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.3 # homeassistant.components.august -yalexs==6.4.1 +yalexs==6.4.2 # homeassistant.components.yeelight yeelight==0.7.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 518f9bef6ae6aa..0196de3f0cb02a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2292,7 +2292,7 @@ yalesmartalarmclient==0.3.9 yalexs-ble==2.4.3 # homeassistant.components.august -yalexs==6.4.1 +yalexs==6.4.2 # homeassistant.components.yeelight yeelight==0.7.14 diff --git a/tests/components/august/fixtures/get_lock.low_keypad_battery.json b/tests/components/august/fixtures/get_lock.low_keypad_battery.json index 08bdfaa76ed903..43b5513a5271c0 100644 --- a/tests/components/august/fixtures/get_lock.low_keypad_battery.json +++ b/tests/components/august/fixtures/get_lock.low_keypad_battery.json @@ -36,7 +36,7 @@ "currentFirmwareVersion": "2.27.0", "battery": {}, "batteryLevel": "Low", - "batteryRaw": 170 + "batteryRaw": 128 }, "OfflineKeys": { "created": [], diff --git a/tests/components/august/test_sensor.py b/tests/components/august/test_sensor.py index 0227ee64ef1af7..67223e9dff0105 100644 --- a/tests/components/august/test_sensor.py +++ b/tests/components/august/test_sensor.py @@ -88,7 +88,7 @@ async def test_create_lock_with_linked_keypad( assert entry.unique_id == "A6697750D607098BAE8D6BAA11EF8063_device_battery" state = hass.states.get("sensor.front_door_lock_keypad_battery") - assert state.state == "60" + assert state.state == "62" assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == PERCENTAGE entry = entity_registry.async_get("sensor.front_door_lock_keypad_battery") assert entry From 21309eeb5d14098a34a28317ef32dbc9c55d76ed Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 28 Jun 2024 13:35:34 +0200 Subject: [PATCH 19/37] Bump xiaomi-ble to 0.30.1 (#120743) --- homeassistant/components/xiaomi_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 1e0a09015ee1cc..f901b9b412e666 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "iot_class": "local_push", - "requirements": ["xiaomi-ble==0.30.0"] + "requirements": ["xiaomi-ble==0.30.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5ac963d62a4ada..a458f4bd0e4410 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2906,7 +2906,7 @@ wyoming==1.5.4 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.30.0 +xiaomi-ble==0.30.1 # homeassistant.components.knx xknx==2.12.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0196de3f0cb02a..69b08293542422 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2268,7 +2268,7 @@ wyoming==1.5.4 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.30.0 +xiaomi-ble==0.30.1 # homeassistant.components.knx xknx==2.12.2 From 6bf9ec69f31bc2751ba877d94d472643a033cf22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ovidiu=20D=2E=20Ni=C8=9Ban?= Date: Mon, 8 Jul 2024 00:13:43 +0300 Subject: [PATCH 20/37] Bump xiaomi-ble to 0.30.2 (#121471) --- homeassistant/components/xiaomi_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index f901b9b412e666..21e9bc45bb8183 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -24,5 +24,5 @@ "dependencies": ["bluetooth_adapters"], "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "iot_class": "local_push", - "requirements": ["xiaomi-ble==0.30.1"] + "requirements": ["xiaomi-ble==0.30.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index a458f4bd0e4410..76ce0add19c58f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2906,7 +2906,7 @@ wyoming==1.5.4 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.30.1 +xiaomi-ble==0.30.2 # homeassistant.components.knx xknx==2.12.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 69b08293542422..1635796ddf85ae 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2268,7 +2268,7 @@ wyoming==1.5.4 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.30.1 +xiaomi-ble==0.30.2 # homeassistant.components.knx xknx==2.12.2 From 51a6bb1c22ce3aa59518de20bb070449db909447 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jul 2024 11:00:21 +0200 Subject: [PATCH 21/37] Include hass device ID in mobile app get_config webhook (#121496) --- homeassistant/components/mobile_app/webhook.py | 5 +++++ tests/components/mobile_app/test_webhook.py | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index e93b4c5ea9924b..125e4d27247ce3 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -721,10 +721,15 @@ async def webhook_get_config( """Handle a get config webhook.""" hass_config = hass.config.as_dict() + device: dr.DeviceEntry = hass.data[DOMAIN][DATA_DEVICES][ + config_entry.data[CONF_WEBHOOK_ID] + ] + resp = { "latitude": hass_config["latitude"], "longitude": hass_config["longitude"], "elevation": hass_config["elevation"], + "hass_device_id": device.id, "unit_system": hass_config["unit_system"], "location_name": hass_config["location_name"], "time_zone": hass_config["time_zone"], diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index ca5c9936409276..77798c57f10b3b 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -10,7 +10,7 @@ import pytest from homeassistant.components.camera import CameraEntityFeature -from homeassistant.components.mobile_app.const import CONF_SECRET, DOMAIN +from homeassistant.components.mobile_app.const import CONF_SECRET, DATA_DEVICES, DOMAIN from homeassistant.components.tag import EVENT_TAG_SCANNED from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN from homeassistant.const import ( @@ -243,6 +243,7 @@ async def test_webhook_handle_get_config( """Test that we can get config properly.""" webhook_id = create_registrations[1]["webhook_id"] webhook_url = f"/api/webhook/{webhook_id}" + device: dr.DeviceEntry = hass.data[DOMAIN][DATA_DEVICES][webhook_id] # Create two entities for sensor in ( @@ -280,6 +281,7 @@ async def test_webhook_handle_get_config( "latitude": hass_config["latitude"], "longitude": hass_config["longitude"], "elevation": hass_config["elevation"], + "hass_device_id": device.id, "unit_system": hass_config["unit_system"], "location_name": hass_config["location_name"], "time_zone": hass_config["time_zone"], From 4f2c3df518d7c2f2943aa363f01faabb8bff867d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 8 Jul 2024 07:58:18 -0700 Subject: [PATCH 22/37] Fix person tracking in unifiprotect (#121528) --- homeassistant/components/unifiprotect/binary_sensor.py | 2 +- homeassistant/components/unifiprotect/switch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index c4e1aa87df25a0..75156308b1a93a 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -284,7 +284,7 @@ class ProtectBinaryEventEntityDescription( name="Tracking: person", icon="mdi:walk", entity_category=EntityCategory.DIAGNOSTIC, - ufp_required_field="is_ptz", + ufp_required_field="feature_flags.is_ptz", ufp_value="is_person_tracking_enabled", ufp_perm=PermRequired.NO_WRITE, ), diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index ca56a602209f5d..50372d47ea8e89 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -319,7 +319,7 @@ async def _set_highfps(obj: Camera, value: bool) -> None: name="Tracking: person", icon="mdi:walk", entity_category=EntityCategory.CONFIG, - ufp_required_field="is_ptz", + ufp_required_field="feature_flags.is_ptz", ufp_value="is_person_tracking_enabled", ufp_set_method="set_person_track", ufp_perm=PermRequired.WRITE, From e0b01ee94ea9105fe3a38601f8ac8ff5d7d5838f Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 8 Jul 2024 21:38:09 +0200 Subject: [PATCH 23/37] Remove homematic state_class from GAS_POWER sensor (#121533) --- homeassistant/components/homematic/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index eebcad9544623c..b33a725db0fc18 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -156,7 +156,6 @@ key="GAS_POWER", native_unit_of_measurement=UnitOfVolume.CUBIC_METERS, device_class=SensorDeviceClass.GAS, - state_class=SensorStateClass.MEASUREMENT, ), "GAS_ENERGY_COUNTER": SensorEntityDescription( key="GAS_ENERGY_COUNTER", From 138b68ecc02f12abc41fadc2bf45360a8da31d11 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 9 Jul 2024 08:30:10 +0200 Subject: [PATCH 24/37] Update vehicle to 2.2.2 (#121556) --- homeassistant/components/rdw/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rdw/manifest.json b/homeassistant/components/rdw/manifest.json index f44dc7e0f127a3..7af3e86134797c 100644 --- a/homeassistant/components/rdw/manifest.json +++ b/homeassistant/components/rdw/manifest.json @@ -7,5 +7,5 @@ "integration_type": "service", "iot_class": "cloud_polling", "quality_scale": "platinum", - "requirements": ["vehicle==2.2.1"] + "requirements": ["vehicle==2.2.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 76ce0add19c58f..2006d9f37bc204 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2827,7 +2827,7 @@ vacuum-map-parser-roborock==0.1.2 vallox-websocket-api==5.3.0 # homeassistant.components.rdw -vehicle==2.2.1 +vehicle==2.2.2 # homeassistant.components.velbus velbus-aio==2024.7.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1635796ddf85ae..6479e1958f390a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2201,7 +2201,7 @@ vacuum-map-parser-roborock==0.1.2 vallox-websocket-api==5.3.0 # homeassistant.components.rdw -vehicle==2.2.1 +vehicle==2.2.2 # homeassistant.components.velbus velbus-aio==2024.7.5 From 50802f84f0895bbb53d8f528585f23f45cb1ccd6 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 9 Jul 2024 08:29:57 +0200 Subject: [PATCH 25/37] Update tailscale to 0.6.1 (#121557) --- homeassistant/components/tailscale/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tailscale/manifest.json b/homeassistant/components/tailscale/manifest.json index 14f4206f44f034..24f485fcdbdfb7 100644 --- a/homeassistant/components/tailscale/manifest.json +++ b/homeassistant/components/tailscale/manifest.json @@ -7,5 +7,5 @@ "integration_type": "hub", "iot_class": "cloud_polling", "quality_scale": "platinum", - "requirements": ["tailscale==0.6.0"] + "requirements": ["tailscale==0.6.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index 2006d9f37bc204..97ccc3b2a4a5fe 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2683,7 +2683,7 @@ systembridgeconnector==4.0.3 systembridgemodels==4.0.4 # homeassistant.components.tailscale -tailscale==0.6.0 +tailscale==0.6.1 # homeassistant.components.tank_utility tank-utility==1.5.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6479e1958f390a..ecb39324da3473 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2096,7 +2096,7 @@ systembridgeconnector==4.0.3 systembridgemodels==4.0.4 # homeassistant.components.tailscale -tailscale==0.6.0 +tailscale==0.6.1 # homeassistant.components.tellduslive tellduslive==0.10.11 From 5a04a886cfcd1b7f7bcc33cd79b0519fd3f1879f Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Tue, 9 Jul 2024 02:32:34 -0400 Subject: [PATCH 26/37] Fix upb config flow connect (#121571) --- homeassistant/components/upb/config_flow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/upb/config_flow.py b/homeassistant/components/upb/config_flow.py index 1db0b0b6fe3647..40f49e57c606b9 100644 --- a/homeassistant/components/upb/config_flow.py +++ b/homeassistant/components/upb/config_flow.py @@ -39,12 +39,13 @@ def _connected_callback(): url = _make_url_from_data(data) upb = upb_lib.UpbPim({"url": url, "UPStartExportFile": file_path}) + + upb.connect(_connected_callback) + if not upb.config_ok: _LOGGER.error("Missing or invalid UPB file: %s", file_path) raise InvalidUpbFile - upb.connect(_connected_callback) - with suppress(TimeoutError): async with asyncio.timeout(VALIDATE_TIMEOUT): await connected_event.wait() From 73d1973625a2fdd5c96c9b359beae3d1e7a5210c Mon Sep 17 00:00:00 2001 From: Arie Catsman <120491684+catsmanac@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:52:04 +0200 Subject: [PATCH 27/37] Bump pyenphase to 1.20.6 (#121583) bump pyenphase to 1.20.6 --- homeassistant/components/enphase_envoy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index b3c117556bf16a..f5d2778fc9da25 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", "iot_class": "local_polling", "loggers": ["pyenphase"], - "requirements": ["pyenphase==1.20.3"], + "requirements": ["pyenphase==1.20.6"], "zeroconf": [ { "type": "_enphase-envoy._tcp.local." diff --git a/requirements_all.txt b/requirements_all.txt index 97ccc3b2a4a5fe..b3bb75aac31c77 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1827,7 +1827,7 @@ pyeiscp==0.0.7 pyemoncms==0.0.7 # homeassistant.components.enphase_envoy -pyenphase==1.20.3 +pyenphase==1.20.6 # homeassistant.components.envisalink pyenvisalink==4.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ecb39324da3473..f6f634d091bf63 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1435,7 +1435,7 @@ pyefergy==22.5.0 pyegps==0.2.5 # homeassistant.components.enphase_envoy -pyenphase==1.20.3 +pyenphase==1.20.6 # homeassistant.components.everlights pyeverlights==0.1.0 From 37c09dbdb6f8c102d4b65b53fbbe74268b899c75 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Tue, 9 Jul 2024 21:10:15 +0200 Subject: [PATCH 28/37] Allow ambilight when we have connection (philips_js) (#121620) --- homeassistant/components/philips_js/light.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/philips_js/light.py b/homeassistant/components/philips_js/light.py index 8e5005927043cb..1d63b2062e6b3c 100644 --- a/homeassistant/components/philips_js/light.py +++ b/homeassistant/components/philips_js/light.py @@ -385,6 +385,6 @@ def available(self) -> bool: """Return true if entity is available.""" if not super().available: return False - if not self.coordinator.api.on: + if not self._tv.on: return False - return self.coordinator.api.powerstate == "On" + return True From a4c5dee082c6d30a84edbf5ab1f7ed1f155d4221 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 10 Jul 2024 09:29:22 +0200 Subject: [PATCH 29/37] Update frontend to 20240710.0 (#121651) --- 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 186f725c643ea5..57de177da9c228 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==20240705.0"] + "requirements": ["home-assistant-frontend==20240710.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 41d3af2ad47587..6b43f2887626b0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -32,7 +32,7 @@ habluetooth==3.1.3 hass-nabucasa==0.81.1 hassil==1.7.1 home-assistant-bluetooth==1.12.2 -home-assistant-frontend==20240705.0 +home-assistant-frontend==20240710.0 home-assistant-intents==2024.7.3 httpx==0.27.0 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index b3bb75aac31c77..38f8b6a44cb615 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1090,7 +1090,7 @@ hole==0.8.0 holidays==0.52 # homeassistant.components.frontend -home-assistant-frontend==20240705.0 +home-assistant-frontend==20240710.0 # homeassistant.components.conversation home-assistant-intents==2024.7.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f6f634d091bf63..eb46f1e9c40303 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -895,7 +895,7 @@ hole==0.8.0 holidays==0.52 # homeassistant.components.frontend -home-assistant-frontend==20240705.0 +home-assistant-frontend==20240710.0 # homeassistant.components.conversation home-assistant-intents==2024.7.3 From fd0c26cd567a5904c646251a3129bfa09720667d Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 10 Jul 2024 11:11:54 +0200 Subject: [PATCH 30/37] Small fix in velbus cover for the assumed states (#121656) --- homeassistant/components/velbus/cover.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 823d682d339433..8b9d927f3d7b82 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -66,12 +66,16 @@ def is_closed(self) -> bool | None: @property def is_opening(self) -> bool: """Return if the cover is opening.""" - return self._channel.is_opening() + if opening := self._channel.is_opening(): + self._assumed_closed = False + return opening @property def is_closing(self) -> bool: """Return if the cover is closing.""" - return self._channel.is_closing() + if closing := self._channel.is_closing(): + self._assumed_closed = True + return closing @property def current_cover_position(self) -> int | None: @@ -89,13 +93,11 @@ def current_cover_position(self) -> int | None: async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" await self._channel.open() - self._assumed_closed = False @api_call async def async_close_cover(self, **kwargs: Any) -> None: """Close the cover.""" await self._channel.close() - self._assumed_closed = True @api_call async def async_stop_cover(self, **kwargs: Any) -> None: From ec0910e3da20ec1ad813b2e89eb8966d95f11d8f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Jul 2024 09:47:15 +0200 Subject: [PATCH 31/37] Block icloud3 custom integration from breaking the recorder (#121658) --- homeassistant/loader.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 9afad61042052b..a654904d69be84 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -102,6 +102,13 @@ class BlockedIntegration: "mydolphin_plus": BlockedIntegration( AwesomeVersion("1.0.13"), "crashes Home Assistant" ), + # Added in 2024.7.2 because of + # https://github.com/gcobb321/icloud3/issues/349 + # Note: Current version 3.0.5.2, the fixed version is a guesstimate, + # as no solution is available at time of writing. + "icloud3": BlockedIntegration( + AwesomeVersion("3.0.5.3"), "prevents recorder from working" + ), } DATA_COMPONENTS: HassKey[dict[str, ModuleType | ComponentProtocol]] = HassKey( From ac3eecc879b537c25974c37fae5ca76c54e8913f Mon Sep 17 00:00:00 2001 From: tronikos Date: Wed, 10 Jul 2024 02:21:38 -0700 Subject: [PATCH 32/37] Handle errors in Fully Kiosk camera (#121659) --- homeassistant/components/fully_kiosk/camera.py | 11 +++++++++-- tests/components/fully_kiosk/test_camera.py | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fully_kiosk/camera.py b/homeassistant/components/fully_kiosk/camera.py index 99419271c26d11..d55875e094f4bf 100644 --- a/homeassistant/components/fully_kiosk/camera.py +++ b/homeassistant/components/fully_kiosk/camera.py @@ -2,9 +2,12 @@ from __future__ import annotations +from fullykiosk import FullyKioskError + from homeassistant.components.camera import Camera, CameraEntityFeature from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DOMAIN @@ -36,8 +39,12 @@ async def async_camera_image( self, width: int | None = None, height: int | None = None ) -> bytes | None: """Return bytes of camera image.""" - image_bytes: bytes = await self.coordinator.fully.getCamshot() - return image_bytes + try: + image_bytes: bytes = await self.coordinator.fully.getCamshot() + except FullyKioskError as err: + raise HomeAssistantError(err) from err + else: + return image_bytes async def async_turn_on(self) -> None: """Turn on camera.""" diff --git a/tests/components/fully_kiosk/test_camera.py b/tests/components/fully_kiosk/test_camera.py index 4e48749eebb207..a2e7067ff1b3c3 100644 --- a/tests/components/fully_kiosk/test_camera.py +++ b/tests/components/fully_kiosk/test_camera.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock +from fullykiosk import FullyKioskError import pytest from homeassistant.components.camera import async_get_image @@ -41,6 +42,12 @@ async def test_camera( assert mock_fully_kiosk.getCamshot.call_count == 1 assert image.content == b"image_bytes" + fully_kiosk_error = FullyKioskError("error", "status") + mock_fully_kiosk.getCamshot.side_effect = fully_kiosk_error + with pytest.raises(HomeAssistantError) as error: + await async_get_image(hass, entity_camera) + assert error.value.args[0] == fully_kiosk_error + mock_fully_kiosk.getSettings.return_value = {"motionDetection": False} await hass.services.async_call( "camera", From 9c83af37894ad43c6f8590d6f1eedc3afe631463 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Jul 2024 10:24:44 +0200 Subject: [PATCH 33/37] Block places <=2.7.0 custom integration from breaking the recorder (#121662) --- homeassistant/loader.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index a654904d69be84..f889f8fcb6e6ae 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -109,6 +109,11 @@ class BlockedIntegration: "icloud3": BlockedIntegration( AwesomeVersion("3.0.5.3"), "prevents recorder from working" ), + # Added in 2024.7.2 because of + # https://github.com/custom-components/places/issues/289 + "places": BlockedIntegration( + AwesomeVersion("2.7.1"), "prevents recorder from working" + ), } DATA_COMPONENTS: HassKey[dict[str, ModuleType | ComponentProtocol]] = HassKey( From 2151086b0a14de80ced8add03543a5c83ca22daa Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 10 Jul 2024 10:32:42 +0200 Subject: [PATCH 34/37] Fix state for Matter Locks (including optional door sensor) (#121665) --- .../components/matter/binary_sensor.py | 16 ++++ homeassistant/components/matter/lock.py | 73 ++++++++++++------- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/matter/binary_sensor.py b/homeassistant/components/matter/binary_sensor.py index b71c35c9cce436..a6d68682e9d41d 100644 --- a/homeassistant/components/matter/binary_sensor.py +++ b/homeassistant/components/matter/binary_sensor.py @@ -145,4 +145,20 @@ def _update_from_device(self) -> None: required_attributes=(clusters.BooleanState.Attributes.StateValue,), device_type=(device_types.RainSensor,), ), + MatterDiscoverySchema( + platform=Platform.BINARY_SENSOR, + entity_description=MatterBinarySensorEntityDescription( + key="LockDoorStateSensor", + device_class=BinarySensorDeviceClass.DOOR, + # pylint: disable=unnecessary-lambda + measurement_to_ha=lambda x: { + clusters.DoorLock.Enums.DoorStateEnum.kDoorOpen: True, + clusters.DoorLock.Enums.DoorStateEnum.kDoorJammed: True, + clusters.DoorLock.Enums.DoorStateEnum.kDoorForcedOpen: True, + clusters.DoorLock.Enums.DoorStateEnum.kDoorClosed: False, + }.get(x), + ), + entity_class=MatterBinarySensor, + required_attributes=(clusters.DoorLock.Attributes.DoorState,), + ), ] diff --git a/homeassistant/components/matter/lock.py b/homeassistant/components/matter/lock.py index 1cc85fa897ed63..66b5040184c4f9 100644 --- a/homeassistant/components/matter/lock.py +++ b/homeassistant/components/matter/lock.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio from typing import Any from chip.clusters import Objects as clusters @@ -38,6 +39,7 @@ class MatterLock(MatterEntity, LockEntity): """Representation of a Matter lock.""" features: int | None = None + _optimistic_timer: asyncio.TimerHandle | None = None @property def code_format(self) -> str | None: @@ -90,9 +92,15 @@ async def send_device_command( async def async_lock(self, **kwargs: Any) -> None: """Lock the lock with pin if needed.""" - # optimistically signal locking to state machine - self._attr_is_locking = True - self.async_write_ha_state() + if not self._attr_is_locked: + # optimistically signal locking to state machine + self._attr_is_locking = True + self.async_write_ha_state() + # the lock should acknowledge the command with an attribute update + # but bad things may happen, so guard against it with a timer. + self._optimistic_timer = self.hass.loop.call_later( + 5, self._reset_optimistic_state + ) code: str | None = kwargs.get(ATTR_CODE) code_bytes = code.encode() if code else None await self.send_device_command( @@ -101,9 +109,15 @@ async def async_lock(self, **kwargs: Any) -> None: async def async_unlock(self, **kwargs: Any) -> None: """Unlock the lock with pin if needed.""" - # optimistically signal unlocking to state machine - self._attr_is_unlocking = True - self.async_write_ha_state() + if self._attr_is_locked: + # optimistically signal unlocking to state machine + self._attr_is_unlocking = True + self.async_write_ha_state() + # the lock should acknowledge the command with an attribute update + # but bad things may happen, so guard against it with a timer. + self._optimistic_timer = self.hass.loop.call_later( + 5, self._reset_optimistic_state + ) code: str | None = kwargs.get(ATTR_CODE) code_bytes = code.encode() if code else None if self.supports_unbolt: @@ -120,9 +134,14 @@ async def async_unlock(self, **kwargs: Any) -> None: async def async_open(self, **kwargs: Any) -> None: """Open the door latch.""" - # optimistically signal unlocking to state machine - self._attr_is_unlocking = True + # optimistically signal opening to state machine + self._attr_is_opening = True self.async_write_ha_state() + # the lock should acknowledge the command with an attribute update + # but bad things may happen, so guard against it with a timer. + self._optimistic_timer = self.hass.loop.call_later( + 5, self._reset_optimistic_state + ) code: str | None = kwargs.get(ATTR_CODE) code_bytes = code.encode() if code else None await self.send_device_command( @@ -145,38 +164,38 @@ def _update_from_device(self) -> None: ) # always reset the optimisically (un)locking state on state update - self._attr_is_locking = False - self._attr_is_unlocking = False + self._reset_optimistic_state(write_state=False) LOGGER.debug("Lock state: %s for %s", lock_state, self.entity_id) + if lock_state is clusters.DoorLock.Enums.DlLockState.kUnlatched: + self._attr_is_locked = False + self._attr_is_open = True if lock_state is clusters.DoorLock.Enums.DlLockState.kLocked: self._attr_is_locked = True + self._attr_is_open = False elif lock_state in ( clusters.DoorLock.Enums.DlLockState.kUnlocked, - clusters.DoorLock.Enums.DlLockState.kUnlatched, clusters.DoorLock.Enums.DlLockState.kNotFullyLocked, ): self._attr_is_locked = False + self._attr_is_open = False else: - # According to the matter docs a null state can happen during device startup. + # Treat any other state as unknown. + # NOTE: A null state can happen during device startup. self._attr_is_locked = None + self._attr_is_open = None - if self.supports_door_position_sensor: - door_state = self.get_matter_attribute_value( - clusters.DoorLock.Attributes.DoorState - ) - - assert door_state is not None - - LOGGER.debug("Door state: %s for %s", door_state, self.entity_id) - - self._attr_is_jammed = ( - door_state is clusters.DoorLock.Enums.DoorStateEnum.kDoorJammed - ) - self._attr_is_open = ( - door_state is clusters.DoorLock.Enums.DoorStateEnum.kDoorOpen - ) + @callback + def _reset_optimistic_state(self, write_state: bool = True) -> None: + if self._optimistic_timer and not self._optimistic_timer.cancelled(): + self._optimistic_timer.cancel() + self._optimistic_timer = None + self._attr_is_locking = False + self._attr_is_unlocking = False + self._attr_is_opening = False + if write_state: + self.async_write_ha_state() DISCOVERY_SCHEMAS = [ From 05ce3d35b3f2eef7ba30398ee59ceb6f27f635e7 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 10 Jul 2024 11:20:26 +0200 Subject: [PATCH 35/37] Matter lock state follow-up (#121669) --- homeassistant/components/matter/lock.py | 6 +++--- tests/components/matter/fixtures/nodes/door-lock.json | 2 +- tests/components/matter/test_door_lock.py | 11 ++++++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/matter/lock.py b/homeassistant/components/matter/lock.py index 66b5040184c4f9..ae01faa3bc7589 100644 --- a/homeassistant/components/matter/lock.py +++ b/homeassistant/components/matter/lock.py @@ -99,7 +99,7 @@ async def async_lock(self, **kwargs: Any) -> None: # the lock should acknowledge the command with an attribute update # but bad things may happen, so guard against it with a timer. self._optimistic_timer = self.hass.loop.call_later( - 5, self._reset_optimistic_state + 30, self._reset_optimistic_state ) code: str | None = kwargs.get(ATTR_CODE) code_bytes = code.encode() if code else None @@ -116,7 +116,7 @@ async def async_unlock(self, **kwargs: Any) -> None: # the lock should acknowledge the command with an attribute update # but bad things may happen, so guard against it with a timer. self._optimistic_timer = self.hass.loop.call_later( - 5, self._reset_optimistic_state + 30, self._reset_optimistic_state ) code: str | None = kwargs.get(ATTR_CODE) code_bytes = code.encode() if code else None @@ -140,7 +140,7 @@ async def async_open(self, **kwargs: Any) -> None: # the lock should acknowledge the command with an attribute update # but bad things may happen, so guard against it with a timer. self._optimistic_timer = self.hass.loop.call_later( - 5, self._reset_optimistic_state + 30 if self._attr_is_locked else 5, self._reset_optimistic_state ) code: str | None = kwargs.get(ATTR_CODE) code_bytes = code.encode() if code else None diff --git a/tests/components/matter/fixtures/nodes/door-lock.json b/tests/components/matter/fixtures/nodes/door-lock.json index 8a3f0fd68ddf82..b6231e04af4cc5 100644 --- a/tests/components/matter/fixtures/nodes/door-lock.json +++ b/tests/components/matter/fixtures/nodes/door-lock.json @@ -469,7 +469,7 @@ "1/47/65531": [ 0, 1, 2, 14, 15, 16, 19, 65528, 65529, 65530, 65531, 65532, 65533 ], - "1/257/0": 1, + "1/257/0": 0, "1/257/1": 0, "1/257/2": true, "1/257/3": 1, diff --git a/tests/components/matter/test_door_lock.py b/tests/components/matter/test_door_lock.py index 84f0e58a647798..461cc1b7f3d2a8 100644 --- a/tests/components/matter/test_door_lock.py +++ b/tests/components/matter/test_door_lock.py @@ -8,11 +8,10 @@ from homeassistant.components.lock import ( STATE_LOCKED, - STATE_OPEN, STATE_UNLOCKED, LockEntityFeature, ) -from homeassistant.const import ATTR_CODE, STATE_LOCKING, STATE_UNKNOWN +from homeassistant.const import ATTR_CODE, STATE_LOCKING, STATE_OPENING, STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError import homeassistant.helpers.entity_registry as er @@ -64,6 +63,7 @@ async def test_lock( ) matter_client.send_device_command.reset_mock() + await hass.async_block_till_done() state = hass.states.get("lock.mock_door_lock_lock") assert state assert state.state == STATE_LOCKING @@ -208,9 +208,14 @@ async def test_lock_with_unbolt( timed_request_timeout_ms=1000, ) + await hass.async_block_till_done() + state = hass.states.get("lock.mock_door_lock_lock") + assert state + assert state.state == STATE_OPENING + set_node_attribute(door_lock_with_unbolt, 1, 257, 3, 0) await trigger_subscription_callback(hass, matter_client) state = hass.states.get("lock.mock_door_lock_lock") assert state - assert state.state == STATE_OPEN + assert state.state == STATE_LOCKED From 38a44676ebc626752b9844325ccc11d42da9c5bf Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Jul 2024 11:30:16 +0200 Subject: [PATCH 36/37] Block variable <=3.4.4 custom integration from breaking the recorder (#121670) --- homeassistant/loader.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/loader.py b/homeassistant/loader.py index f889f8fcb6e6ae..9acc1682602a92 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -114,6 +114,11 @@ class BlockedIntegration: "places": BlockedIntegration( AwesomeVersion("2.7.1"), "prevents recorder from working" ), + # Added in 2024.7.2 because of + # https://github.com/enkama/hass-variables/issues/120 + "variable": BlockedIntegration( + AwesomeVersion("3.4.4"), "prevents recorder from working" + ), } DATA_COMPONENTS: HassKey[dict[str, ModuleType | ComponentProtocol]] = HassKey( From 71370758a882d1123c7fdb541186baadcd6e078f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Jul 2024 12:06:02 +0200 Subject: [PATCH 37/37] Bump version to 2024.7.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 33087b0bfc1932..8587f9e6137c10 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -24,7 +24,7 @@ APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2024 MINOR_VERSION: Final = 7 -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 138d1dd80b8fe3..82c29948e3c392 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2024.7.1" +version = "2024.7.2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"