From 8ffdbfc462350bf1111a40523086dd83b13a057f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 27 Jul 2022 15:10:29 +0200 Subject: [PATCH 01/83] Bumped version to 2022.8.0b0 --- 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 10523aa6d536..02896fcbe961 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0.dev0" +PATCH_VERSION: Final = "0b0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 48d129644147..5306a589863b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0.dev0" +version = "2022.8.0b0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 15f87ca0a199856ed345b6bc926561fa75092450 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 28 Jul 2022 00:25:05 +0000 Subject: [PATCH 02/83] [ci skip] Translation update --- .../components/ambee/translations/de.json | 6 ++++++ .../components/ambee/translations/it.json | 6 ++++++ .../components/ambee/translations/pl.json | 6 ++++++ .../components/ambee/translations/pt-BR.json | 6 ++++++ .../ambee/translations/zh-Hant.json | 6 ++++++ .../components/anthemav/translations/de.json | 6 ++++++ .../components/anthemav/translations/it.json | 6 ++++++ .../anthemav/translations/pt-BR.json | 6 ++++++ .../anthemav/translations/zh-Hant.json | 6 ++++++ .../components/google/translations/it.json | 2 +- .../google/translations/zh-Hant.json | 2 +- .../homeassistant_alerts/translations/de.json | 8 ++++++++ .../homeassistant_alerts/translations/en.json | 8 ++++++++ .../homeassistant_alerts/translations/it.json | 8 ++++++++ .../homeassistant_alerts/translations/pl.json | 8 ++++++++ .../translations/pt-BR.json | 8 ++++++++ .../translations/zh-Hant.json | 8 ++++++++ .../lacrosse_view/translations/de.json | 20 +++++++++++++++++++ .../lacrosse_view/translations/en.json | 20 +++++++++++++++++++ .../lacrosse_view/translations/it.json | 20 +++++++++++++++++++ .../lacrosse_view/translations/pt-BR.json | 20 +++++++++++++++++++ .../lacrosse_view/translations/zh-Hant.json | 20 +++++++++++++++++++ .../components/lyric/translations/de.json | 6 ++++++ .../components/lyric/translations/pt-BR.json | 6 ++++++ .../lyric/translations/zh-Hant.json | 6 ++++++ .../components/mitemp_bt/translations/de.json | 2 +- .../components/mitemp_bt/translations/it.json | 8 ++++++++ .../mitemp_bt/translations/pt-BR.json | 2 +- .../openalpr_local/translations/de.json | 8 ++++++++ .../openalpr_local/translations/it.json | 8 ++++++++ .../openalpr_local/translations/pl.json | 8 ++++++++ .../openalpr_local/translations/pt-BR.json | 8 ++++++++ .../openalpr_local/translations/zh-Hant.json | 8 ++++++++ .../radiotherm/translations/it.json | 3 ++- .../components/senz/translations/de.json | 6 ++++++ .../components/senz/translations/pt-BR.json | 6 ++++++ .../components/senz/translations/zh-Hant.json | 6 ++++++ .../soundtouch/translations/de.json | 6 ++++++ .../soundtouch/translations/it.json | 6 ++++++ .../soundtouch/translations/pt-BR.json | 6 ++++++ .../soundtouch/translations/zh-Hant.json | 6 ++++++ .../spotify/translations/zh-Hant.json | 4 ++-- .../steam_online/translations/zh-Hant.json | 4 ++-- .../uscis/translations/zh-Hant.json | 4 ++-- .../components/xbox/translations/it.json | 6 ++++++ .../components/xbox/translations/pt-BR.json | 6 ++++++ .../components/zha/translations/de.json | 2 ++ .../components/zha/translations/pl.json | 2 ++ .../components/zha/translations/pt-BR.json | 2 ++ .../components/zha/translations/zh-Hant.json | 2 ++ 50 files changed, 342 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/homeassistant_alerts/translations/de.json create mode 100644 homeassistant/components/homeassistant_alerts/translations/en.json create mode 100644 homeassistant/components/homeassistant_alerts/translations/it.json create mode 100644 homeassistant/components/homeassistant_alerts/translations/pl.json create mode 100644 homeassistant/components/homeassistant_alerts/translations/pt-BR.json create mode 100644 homeassistant/components/homeassistant_alerts/translations/zh-Hant.json create mode 100644 homeassistant/components/lacrosse_view/translations/de.json create mode 100644 homeassistant/components/lacrosse_view/translations/en.json create mode 100644 homeassistant/components/lacrosse_view/translations/it.json create mode 100644 homeassistant/components/lacrosse_view/translations/pt-BR.json create mode 100644 homeassistant/components/lacrosse_view/translations/zh-Hant.json create mode 100644 homeassistant/components/mitemp_bt/translations/it.json create mode 100644 homeassistant/components/openalpr_local/translations/de.json create mode 100644 homeassistant/components/openalpr_local/translations/it.json create mode 100644 homeassistant/components/openalpr_local/translations/pl.json create mode 100644 homeassistant/components/openalpr_local/translations/pt-BR.json create mode 100644 homeassistant/components/openalpr_local/translations/zh-Hant.json diff --git a/homeassistant/components/ambee/translations/de.json b/homeassistant/components/ambee/translations/de.json index 4359ab723494..8055ef5210f8 100644 --- a/homeassistant/components/ambee/translations/de.json +++ b/homeassistant/components/ambee/translations/de.json @@ -24,5 +24,11 @@ "description": "Richte Ambee f\u00fcr die Integration mit Home Assistant ein." } } + }, + "issues": { + "pending_removal": { + "description": "Die Ambee-Integration ist dabei, aus Home Assistant entfernt zu werden und wird ab Home Assistant 2022.10 nicht mehr verf\u00fcgbar sein.\n\nDie Integration wird entfernt, weil Ambee seine kostenlosen (begrenzten) Konten entfernt hat und keine M\u00f6glichkeit mehr f\u00fcr regul\u00e4re Nutzer bietet, sich f\u00fcr einen kostenpflichtigen Plan anzumelden.\n\nEntferne den Ambee-Integrationseintrag aus deiner Instanz, um dieses Problem zu beheben.", + "title": "Die Ambee-Integration wird entfernt" + } } } \ No newline at end of file diff --git a/homeassistant/components/ambee/translations/it.json b/homeassistant/components/ambee/translations/it.json index fe97ce336862..db330a9b2398 100644 --- a/homeassistant/components/ambee/translations/it.json +++ b/homeassistant/components/ambee/translations/it.json @@ -24,5 +24,11 @@ "description": "Configura Ambee per l'integrazione con Home Assistant." } } + }, + "issues": { + "pending_removal": { + "description": "L'integrazione Ambee \u00e8 in attesa di rimozione da Home Assistant e non sar\u00e0 pi\u00f9 disponibile a partire da Home Assistant 2022.10. \n\nL'integrazione \u00e8 stata rimossa, perch\u00e9 Ambee ha rimosso i loro account gratuiti (limitati) e non offre pi\u00f9 agli utenti regolari un modo per iscriversi a un piano a pagamento. \n\nRimuovi la voce di integrazione Ambee dalla tua istanza per risolvere questo problema.", + "title": "L'integrazione Ambee verr\u00e0 rimossa" + } } } \ No newline at end of file diff --git a/homeassistant/components/ambee/translations/pl.json b/homeassistant/components/ambee/translations/pl.json index d0b2225cc9ae..255d402175d7 100644 --- a/homeassistant/components/ambee/translations/pl.json +++ b/homeassistant/components/ambee/translations/pl.json @@ -24,5 +24,11 @@ "description": "Skonfiguruj Ambee, aby zintegrowa\u0107 go z Home Assistantem." } } + }, + "issues": { + "pending_removal": { + "description": "Integracja Ambee oczekuje na usuni\u0119cie z Home Assistanta i nie b\u0119dzie ju\u017c dost\u0119pna od Home Assistant 2022.10. \n\nIntegracja jest usuwana, poniewa\u017c Ambee usun\u0105\u0142 ich bezp\u0142atne (ograniczone) konta i nie zapewnia ju\u017c zwyk\u0142ym u\u017cytkownikom mo\u017cliwo\u015bci zarejestrowania si\u0119 w p\u0142atnym planie. \n\nUsu\u0144 integracj\u0119 Ambee z Home Assistanta, aby rozwi\u0105za\u0107 ten problem.", + "title": "Integracja Ambee zostanie usuni\u0119ta" + } } } \ No newline at end of file diff --git a/homeassistant/components/ambee/translations/pt-BR.json b/homeassistant/components/ambee/translations/pt-BR.json index 2d960e17df24..3220de5104ed 100644 --- a/homeassistant/components/ambee/translations/pt-BR.json +++ b/homeassistant/components/ambee/translations/pt-BR.json @@ -24,5 +24,11 @@ "description": "Configure o Ambee para integrar com o Home Assistant." } } + }, + "issues": { + "pending_removal": { + "description": "A integra\u00e7\u00e3o do Ambee est\u00e1 com remo\u00e7\u00e3o pendente do Home Assistant e n\u00e3o estar\u00e1 mais dispon\u00edvel a partir do Home Assistant 2022.10. \n\n A integra\u00e7\u00e3o est\u00e1 sendo removida, porque a Ambee removeu suas contas gratuitas (limitadas) e n\u00e3o oferece mais uma maneira de usu\u00e1rios regulares se inscreverem em um plano pago. \n\n Remova a entrada de integra\u00e7\u00e3o Ambee de sua inst\u00e2ncia para corrigir esse problema.", + "title": "A integra\u00e7\u00e3o Ambee est\u00e1 sendo removida" + } } } \ No newline at end of file diff --git a/homeassistant/components/ambee/translations/zh-Hant.json b/homeassistant/components/ambee/translations/zh-Hant.json index 2e1de25fde2c..ccebea49c6f7 100644 --- a/homeassistant/components/ambee/translations/zh-Hant.json +++ b/homeassistant/components/ambee/translations/zh-Hant.json @@ -24,5 +24,11 @@ "description": "\u8a2d\u5b9a Ambee \u4ee5\u6574\u5408\u81f3 Home Assistant\u3002" } } + }, + "issues": { + "pending_removal": { + "description": "Ambee \u6574\u5408\u5373\u5c07\u7531 Home Assistant \u4e2d\u79fb\u9664\u3001\u4e26\u65bc Home Assistant 2022.10 \u7248\u5f8c\u7121\u6cd5\u518d\u4f7f\u7528\u3002\n\n\u7531\u65bc Ambee \u79fb\u9664\u4e86\u5176\u514d\u8cbb\uff08\u6709\u9650\uff09\u5e33\u865f\u3001\u4e26\u4e14\u4e0d\u518d\u63d0\u4f9b\u4e00\u822c\u4f7f\u7528\u8005\u8a3b\u518a\u4ed8\u8cbb\u670d\u52d9\u3001\u6574\u5408\u5373\u5c07\u79fb\u9664\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant to \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "Ambee \u6574\u5408\u5373\u5c07\u79fb\u9664" + } } } \ No newline at end of file diff --git a/homeassistant/components/anthemav/translations/de.json b/homeassistant/components/anthemav/translations/de.json index 622384629fe4..d751349b0056 100644 --- a/homeassistant/components/anthemav/translations/de.json +++ b/homeassistant/components/anthemav/translations/de.json @@ -15,5 +15,11 @@ } } } + }, + "issues": { + "deprecated_yaml": { + "description": "Die Konfiguration von Anthem A/V-Receivern mit YAML wird entfernt.\n\nDeine bestehende YAML-Konfiguration wurde automatisch in die Benutzeroberfl\u00e4che importiert.\n\nEntferne die Anthem A/V Receivers YAML Konfiguration aus deiner configuration.yaml Datei und starte Home Assistant neu, um dieses Problem zu beheben.", + "title": "Die YAML-Konfiguration von Anthem A/V Receivers wird entfernt" + } } } \ No newline at end of file diff --git a/homeassistant/components/anthemav/translations/it.json b/homeassistant/components/anthemav/translations/it.json index 12b0df56f0f2..b8bec8325810 100644 --- a/homeassistant/components/anthemav/translations/it.json +++ b/homeassistant/components/anthemav/translations/it.json @@ -15,5 +15,11 @@ } } } + }, + "issues": { + "deprecated_yaml": { + "description": "La configurazione di Anthem A/V Receivers tramite YAML verr\u00e0 rimossa.\n\nLa configurazione YAML esistente \u00e8 stata importata automaticamente nell'interfaccia utente.\n\nRimuovere la configurazione YAML di Anthem A/V Receivers dal file configuration.yaml e riavviare Home Assistant per risolvere questo problema.", + "title": "La configurazione YAML di Anthem A/V Receivers verr\u00e0 rimossa" + } } } \ No newline at end of file diff --git a/homeassistant/components/anthemav/translations/pt-BR.json b/homeassistant/components/anthemav/translations/pt-BR.json index 309aca9b8efd..5a6038bb480c 100644 --- a/homeassistant/components/anthemav/translations/pt-BR.json +++ b/homeassistant/components/anthemav/translations/pt-BR.json @@ -15,5 +15,11 @@ } } } + }, + "issues": { + "deprecated_yaml": { + "description": "A configura\u00e7\u00e3o de receptores A/V Anthem usando YAML est\u00e1 sendo removida. \n\n Sua configura\u00e7\u00e3o YAML existente foi importada para a interface do usu\u00e1rio automaticamente. \n\n Remova a configura\u00e7\u00e3o YAML dos receptores A/V do Anthem do arquivo configuration.yaml e reinicie o Home Assistant para corrigir esse problema.", + "title": "A configura\u00e7\u00e3o YAML dos receptores A/V do Anthem est\u00e1 sendo removida" + } } } \ No newline at end of file diff --git a/homeassistant/components/anthemav/translations/zh-Hant.json b/homeassistant/components/anthemav/translations/zh-Hant.json index d1b286afd810..0751331f82f4 100644 --- a/homeassistant/components/anthemav/translations/zh-Hant.json +++ b/homeassistant/components/anthemav/translations/zh-Hant.json @@ -15,5 +15,11 @@ } } } + }, + "issues": { + "deprecated_yaml": { + "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a Anthem A/V \u63a5\u6536\u5668\u5373\u5c07\u65bc Home Assistant 2022.9 \u7248\u4e2d\u9032\u884c\u79fb\u9664\u3002\n\n\u65e2\u6709\u7684 YAML \u8a2d\u5b9a\u5c07\u81ea\u52d5\u532f\u5165\u81f3 UI \u5167\u3002\n\n\u8acb\u65bc configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 Anthem A/V \u63a5\u6536\u5668 YAML \u8a2d\u5b9a\u4e26\u91cd\u65b0\u555f\u52d5 Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "Anthem A/V \u63a5\u6536\u5668 YAML \u8a2d\u5b9a\u5373\u5c07\u79fb\u9664" + } } } \ No newline at end of file diff --git a/homeassistant/components/google/translations/it.json b/homeassistant/components/google/translations/it.json index c29eb8d1a2c0..6e24178996f0 100644 --- a/homeassistant/components/google/translations/it.json +++ b/homeassistant/components/google/translations/it.json @@ -36,7 +36,7 @@ "issues": { "deprecated_yaml": { "description": "La configurazione di Google Calendar in configuration.yaml verr\u00e0 rimossa in Home Assistant 2022.9. \n\nLe credenziali dell'applicazione OAuth esistenti e le impostazioni di accesso sono state importate automaticamente nell'interfaccia utente. Rimuovere la configurazione YAML dal file configuration.yaml e riavviare Home Assistant per risolvere questo problema.", - "title": "La configurazione YAML di Google Calendar \u00e8 stata rimossa" + "title": "La configurazione YAML di Google Calendar verr\u00e0 rimossa" }, "removed_track_new_yaml": { "description": "Hai disabilitato il tracciamento delle entit\u00e0 per Google Calendar in configuration.yaml, il che non \u00e8 pi\u00f9 supportato. \u00c8 necessario modificare manualmente le opzioni di sistema dell'integrazione nell'interfaccia utente per disabilitare le entit\u00e0 appena rilevate da adesso in poi. Rimuovi l'impostazione track_new da configuration.yaml e riavvia Home Assistant per risolvere questo problema.", diff --git a/homeassistant/components/google/translations/zh-Hant.json b/homeassistant/components/google/translations/zh-Hant.json index 43c208d69e82..0d2031c368f4 100644 --- a/homeassistant/components/google/translations/zh-Hant.json +++ b/homeassistant/components/google/translations/zh-Hant.json @@ -36,7 +36,7 @@ "issues": { "deprecated_yaml": { "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a Google \u65e5\u66c6\u5df2\u7d93\u65bc Home Assistant 2022.9 \u7248\u4e2d\u9032\u884c\u79fb\u9664\u3002\n\n\u65e2\u6709\u7684 OAuth \u61c9\u7528\u6191\u8b49\u8207\u5b58\u53d6\u6b0a\u9650\u5c07\u81ea\u52d5\u532f\u5165\u81f3 UI \u5167\u3002\u8acb\u65bc configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u65b0\u555f\u52d5 Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", - "title": "Google \u65e5\u66c6 YAML \u8a2d\u5b9a\u5df2\u7d93\u79fb\u9664" + "title": "Google \u65e5\u66c6 YAML \u8a2d\u5b9a\u5373\u5c07\u79fb\u9664" }, "removed_track_new_yaml": { "description": "\u65bc configuration.yaml \u5167\u6240\u8a2d\u5b9a\u7684 Google \u65e5\u66c6\u5be6\u9ad4\u8ffd\u8e64\u529f\u80fd\uff0c\u7531\u65bc\u4e0d\u518d\u652f\u6301\u3001\u5df2\u7d93\u906d\u5230\u95dc\u9589\u3002\u4e4b\u5f8c\u5fc5\u9808\u624b\u52d5\u900f\u904e\u4ecb\u9762\u5167\u7684\u6574\u5408\u529f\u80fd\u3001\u4ee5\u95dc\u9589\u4efb\u4f55\u65b0\u767c\u73fe\u7684\u5be6\u9ad4\u3002\u8acb\u7531 configuration.yaml \u4e2d\u79fb\u9664R track_new \u4e26\u91cd\u555f Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", diff --git a/homeassistant/components/homeassistant_alerts/translations/de.json b/homeassistant/components/homeassistant_alerts/translations/de.json new file mode 100644 index 000000000000..33cafb8bba30 --- /dev/null +++ b/homeassistant/components/homeassistant_alerts/translations/de.json @@ -0,0 +1,8 @@ +{ + "issues": { + "alert": { + "description": "{description}", + "title": "{title}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant_alerts/translations/en.json b/homeassistant/components/homeassistant_alerts/translations/en.json new file mode 100644 index 000000000000..33cafb8bba30 --- /dev/null +++ b/homeassistant/components/homeassistant_alerts/translations/en.json @@ -0,0 +1,8 @@ +{ + "issues": { + "alert": { + "description": "{description}", + "title": "{title}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant_alerts/translations/it.json b/homeassistant/components/homeassistant_alerts/translations/it.json new file mode 100644 index 000000000000..33cafb8bba30 --- /dev/null +++ b/homeassistant/components/homeassistant_alerts/translations/it.json @@ -0,0 +1,8 @@ +{ + "issues": { + "alert": { + "description": "{description}", + "title": "{title}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant_alerts/translations/pl.json b/homeassistant/components/homeassistant_alerts/translations/pl.json new file mode 100644 index 000000000000..33cafb8bba30 --- /dev/null +++ b/homeassistant/components/homeassistant_alerts/translations/pl.json @@ -0,0 +1,8 @@ +{ + "issues": { + "alert": { + "description": "{description}", + "title": "{title}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant_alerts/translations/pt-BR.json b/homeassistant/components/homeassistant_alerts/translations/pt-BR.json new file mode 100644 index 000000000000..33cafb8bba30 --- /dev/null +++ b/homeassistant/components/homeassistant_alerts/translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "issues": { + "alert": { + "description": "{description}", + "title": "{title}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homeassistant_alerts/translations/zh-Hant.json b/homeassistant/components/homeassistant_alerts/translations/zh-Hant.json new file mode 100644 index 000000000000..33cafb8bba30 --- /dev/null +++ b/homeassistant/components/homeassistant_alerts/translations/zh-Hant.json @@ -0,0 +1,8 @@ +{ + "issues": { + "alert": { + "description": "{description}", + "title": "{title}" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lacrosse_view/translations/de.json b/homeassistant/components/lacrosse_view/translations/de.json new file mode 100644 index 000000000000..d9aa1210fa06 --- /dev/null +++ b/homeassistant/components/lacrosse_view/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "no_locations": "Keine Standorte gefunden", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lacrosse_view/translations/en.json b/homeassistant/components/lacrosse_view/translations/en.json new file mode 100644 index 000000000000..a2a7fd23272c --- /dev/null +++ b/homeassistant/components/lacrosse_view/translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "invalid_auth": "Invalid authentication", + "no_locations": "No locations found", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lacrosse_view/translations/it.json b/homeassistant/components/lacrosse_view/translations/it.json new file mode 100644 index 000000000000..9ce6c75dcbc8 --- /dev/null +++ b/homeassistant/components/lacrosse_view/translations/it.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "invalid_auth": "Autenticazione non valida", + "no_locations": "Nessuna localit\u00e0 trovata", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lacrosse_view/translations/pt-BR.json b/homeassistant/components/lacrosse_view/translations/pt-BR.json new file mode 100644 index 000000000000..29b458e55991 --- /dev/null +++ b/homeassistant/components/lacrosse_view/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_locations": "Nenhum local encontrado", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "password": "Senha", + "username": "Nome de usu\u00e1rio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lacrosse_view/translations/zh-Hant.json b/homeassistant/components/lacrosse_view/translations/zh-Hant.json new file mode 100644 index 000000000000..78235452297c --- /dev/null +++ b/homeassistant/components/lacrosse_view/translations/zh-Hant.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "no_locations": "\u627e\u4e0d\u5230\u5ea7\u6a19", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/de.json b/homeassistant/components/lyric/translations/de.json index b067b2991458..1ef7e65fbf41 100644 --- a/homeassistant/components/lyric/translations/de.json +++ b/homeassistant/components/lyric/translations/de.json @@ -17,5 +17,11 @@ "title": "Integration erneut authentifizieren" } } + }, + "issues": { + "removed_yaml": { + "description": "Die Konfiguration von Honeywell Lyric mit YAML wurde entfernt. \n\nDeine vorhandene YAML-Konfiguration wird von Home Assistant nicht verwendet. \n\nEntferne die YAML-Konfiguration aus deiner configuration.yaml-Datei und starte Home Assistant neu, um dieses Problem zu beheben.", + "title": "Die Honeywell Lyric YAML-Konfiguration wurde entfernt" + } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/pt-BR.json b/homeassistant/components/lyric/translations/pt-BR.json index 907a396d5e2e..d70832bfbf6e 100644 --- a/homeassistant/components/lyric/translations/pt-BR.json +++ b/homeassistant/components/lyric/translations/pt-BR.json @@ -17,5 +17,11 @@ "title": "Reautenticar Integra\u00e7\u00e3o" } } + }, + "issues": { + "removed_yaml": { + "description": "A configura\u00e7\u00e3o do Honeywell Lyric usando YAML foi removida. \n\n Sua configura\u00e7\u00e3o YAML existente n\u00e3o \u00e9 usada pelo Home Assistant. \n\n Remova a configura\u00e7\u00e3o YAML do arquivo configuration.yaml e reinicie o Home Assistant para corrigir esse problema.", + "title": "A configura\u00e7\u00e3o Honeywell Lyric YAML foi removida" + } } } \ No newline at end of file diff --git a/homeassistant/components/lyric/translations/zh-Hant.json b/homeassistant/components/lyric/translations/zh-Hant.json index 850507ec0b3e..bb7fbc3aed67 100644 --- a/homeassistant/components/lyric/translations/zh-Hant.json +++ b/homeassistant/components/lyric/translations/zh-Hant.json @@ -17,5 +17,11 @@ "title": "\u91cd\u65b0\u8a8d\u8b49\u6574\u5408" } } + }, + "issues": { + "removed_yaml": { + "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a Honeywell Lyric \u7684\u529f\u80fd\u5373\u5c07\u79fb\u9664\u3002\n\nHome Assistant \u5c07\u4e0d\u518d\u4f7f\u7528\u73fe\u6709\u7684 YAML \u8a2d\u5b9a\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "Honeywell Lyric YAML \u8a2d\u5b9a\u5373\u5c07\u79fb\u9664" + } } } \ No newline at end of file diff --git a/homeassistant/components/mitemp_bt/translations/de.json b/homeassistant/components/mitemp_bt/translations/de.json index 7d4887ab6380..3c9e6960aeb1 100644 --- a/homeassistant/components/mitemp_bt/translations/de.json +++ b/homeassistant/components/mitemp_bt/translations/de.json @@ -1,7 +1,7 @@ { "issues": { "replaced": { - "description": "Die Integration des Xiaomi Mijia BLE Temperatur- und Luftfeuchtigkeitssensors funktioniert in Home Assistant 2022.7 nicht mehr und wurde in der Version 2022.8 durch die Xiaomi BLE Integration ersetzt.\n\nEs ist kein Migrationspfad m\u00f6glich, daher musst du dein Xiaomi Mijia BLE-Ger\u00e4t manuell mit der neuen Integration hinzuf\u00fcgen.\n\nDeine bestehende Xiaomi Mijia BLE Temperatur- und Luftfeuchtigkeitssensor YAML-Konfiguration wird von Home Assistant nicht mehr verwendet. Entferne die YAML-Konfiguration aus deiner configuration.yaml-Datei und starte Home Assistant neu, um dieses Problem zu beheben.", + "description": "Die Integration des Xiaomi Mijia BLE Temperatur- und Luftfeuchtigkeitssensors funktioniert in Home Assistant 2022.7 nicht mehr und wurde in der Version 2022.8 durch die Xiaomi BLE Integration ersetzt.\n\nEs ist kein Migrationspfad m\u00f6glich, daher musst du dein Xiaomi Mijia BLE-Ger\u00e4t mit der neuen Integration manuell hinzuf\u00fcgen.\n\nDeine bestehende Xiaomi Mijia BLE Temperatur- und Luftfeuchtigkeitssensor YAML-Konfiguration wird von Home Assistant nicht mehr verwendet. Entferne die YAML-Konfiguration aus deiner configuration.yaml-Datei und starte Home Assistant neu, um dieses Problem zu beheben.", "title": "Die Integration des Xiaomi Mijia BLE Temperatur- und Luftfeuchtigkeitssensors wurde ersetzt" } } diff --git a/homeassistant/components/mitemp_bt/translations/it.json b/homeassistant/components/mitemp_bt/translations/it.json new file mode 100644 index 000000000000..cc383e4184c9 --- /dev/null +++ b/homeassistant/components/mitemp_bt/translations/it.json @@ -0,0 +1,8 @@ +{ + "issues": { + "replaced": { + "description": "L'integrazione Xiaomi Mijia BLE Temperature and Humidity Sensor ha smesso di funzionare in Home Assistant 2022.7 ed \u00e8 stata sostituita dall'integrazione Xiaomi BLE nella versione 2022.8. \n\nNon esiste un percorso di migrazione possibile, quindi devi aggiungere manualmente il tuo dispositivo Xiaomi Mijia BLE utilizzando la nuova integrazione. \n\nLa configurazione YAML di Xiaomi Mijia BLE Temperature and Humidity Sensor esistente non \u00e8 pi\u00f9 utilizzata da Home Assistant. Rimuovere la configurazione YAML dal file configuration.yaml e riavviare Home Assistant per risolvere questo problema.", + "title": "L'integrazione Xiaomi Mijia BLE Temperature and Humidity Sensor \u00e8 stata sostituita" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mitemp_bt/translations/pt-BR.json b/homeassistant/components/mitemp_bt/translations/pt-BR.json index 991a749a7296..634f5dd71fd2 100644 --- a/homeassistant/components/mitemp_bt/translations/pt-BR.json +++ b/homeassistant/components/mitemp_bt/translations/pt-BR.json @@ -1,7 +1,7 @@ { "issues": { "replaced": { - "description": "A integra\u00e7\u00e3o do sensor de temperatura e umidade Xiaomi Mijia BLE parou de funcionar no Home Assistant 2022.7 e foi substitu\u00edda pela integra\u00e7\u00e3o Xiaomi BLE na vers\u00e3o 2022.8. \n\n N\u00e3o h\u00e1 caminho de migra\u00e7\u00e3o poss\u00edvel, portanto, voc\u00ea deve adicionar seu dispositivo Xiaomi Mijia BLE usando a nova integra\u00e7\u00e3o manualmente. \n\n Sua configura\u00e7\u00e3o YAML existente do sensor de temperatura e umidade Xiaomi Mijia BLE n\u00e3o \u00e9 mais usada pelo Home Assistant. Remova a configura\u00e7\u00e3o YAML do arquivo configuration.yaml e reinicie o Home Assistant para corrigir esse problema.", + "description": "A integra\u00e7\u00e3o do sensor de temperatura e umidade do Xiaomi Mijia BLE parou de funcionar no Home Assistant 2022.7 e foi substitu\u00edda pela integra\u00e7\u00e3o do Xiaomi BLE na vers\u00e3o 2022.8. \n\n N\u00e3o h\u00e1 caminho de migra\u00e7\u00e3o poss\u00edvel, portanto, voc\u00ea deve adicionar seu dispositivo Xiaomi Mijia BLE usando a nova integra\u00e7\u00e3o manualmente. \n\n Sua configura\u00e7\u00e3o YAML existente do sensor de temperatura e umidade Xiaomi Mijia BLE n\u00e3o \u00e9 mais usada pelo Home Assistant. Remova a configura\u00e7\u00e3o YAML do arquivo configuration.yaml e reinicie o Home Assistant para corrigir esse problema.", "title": "A integra\u00e7\u00e3o do sensor de temperatura e umidade Xiaomi Mijia BLE foi substitu\u00edda" } } diff --git a/homeassistant/components/openalpr_local/translations/de.json b/homeassistant/components/openalpr_local/translations/de.json new file mode 100644 index 000000000000..d517fe0b37fa --- /dev/null +++ b/homeassistant/components/openalpr_local/translations/de.json @@ -0,0 +1,8 @@ +{ + "issues": { + "pending_removal": { + "description": "Die lokale OpenALPR-Integration wird derzeit aus dem Home Assistant entfernt und wird ab Home Assistant 2022.10 nicht mehr verf\u00fcgbar sein.\n\nEntferne die YAML-Konfiguration aus deiner configuration.yaml-Datei und starte Home Assistant neu, um dieses Problem zu beheben.", + "title": "Die lokale OpenALPR-Integration wird entfernt" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openalpr_local/translations/it.json b/homeassistant/components/openalpr_local/translations/it.json new file mode 100644 index 000000000000..26ce80ee584c --- /dev/null +++ b/homeassistant/components/openalpr_local/translations/it.json @@ -0,0 +1,8 @@ +{ + "issues": { + "pending_removal": { + "description": "L'integrazione OpenALPR Local \u00e8 in attesa di rimozione da Home Assistant e non sar\u00e0 pi\u00f9 disponibile a partire da Home Assistant 2022.10. \n\nRimuovi la configurazione YAML dal file configuration.yaml e riavvia Home Assistant per risolvere questo problema.", + "title": "L'integrazione OpenALPR Local verr\u00e0 rimossa" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openalpr_local/translations/pl.json b/homeassistant/components/openalpr_local/translations/pl.json new file mode 100644 index 000000000000..ac367d208094 --- /dev/null +++ b/homeassistant/components/openalpr_local/translations/pl.json @@ -0,0 +1,8 @@ +{ + "issues": { + "pending_removal": { + "description": "Integracja OpenALPR Local oczekuje na usuni\u0119cie z Home Assistanta i nie b\u0119dzie ju\u017c dost\u0119pna od Home Assistant 2022.10. \n\nUsu\u0144 konfiguracj\u0119 YAML z pliku configuration.yaml i uruchom ponownie Home Assistanta, aby rozwi\u0105za\u0107 ten problem.", + "title": "Integracja OpenALPR Local zostanie usuni\u0119ta" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openalpr_local/translations/pt-BR.json b/homeassistant/components/openalpr_local/translations/pt-BR.json new file mode 100644 index 000000000000..96b2c244b5c1 --- /dev/null +++ b/homeassistant/components/openalpr_local/translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "issues": { + "pending_removal": { + "description": "A integra\u00e7\u00e3o do OpenALPR Local est\u00e1 pendente de remo\u00e7\u00e3o do Home Assistant e n\u00e3o estar\u00e1 mais dispon\u00edvel a partir do Home Assistant 2022.10. \n\n Remova a configura\u00e7\u00e3o YAML do arquivo configuration.yaml e reinicie o Home Assistant para corrigir esse problema.", + "title": "A integra\u00e7\u00e3o do OpenALPR Local est\u00e1 sendo removida" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openalpr_local/translations/zh-Hant.json b/homeassistant/components/openalpr_local/translations/zh-Hant.json new file mode 100644 index 000000000000..8ec55e5a0045 --- /dev/null +++ b/homeassistant/components/openalpr_local/translations/zh-Hant.json @@ -0,0 +1,8 @@ +{ + "issues": { + "pending_removal": { + "description": "OpenALPR \u672c\u5730\u7aef\u6574\u5408\u5373\u5c07\u7531 Home Assistant \u4e2d\u79fb\u9664\u3001\u4e26\u65bc Home Assistant 2022.10 \u7248\u5f8c\u7121\u6cd5\u518d\u4f7f\u7528\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant to \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "OpenALPR \u672c\u5730\u7aef\u6574\u5408\u5373\u5c07\u79fb\u9664" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/radiotherm/translations/it.json b/homeassistant/components/radiotherm/translations/it.json index 9e35beb648af..fef1c64746bc 100644 --- a/homeassistant/components/radiotherm/translations/it.json +++ b/homeassistant/components/radiotherm/translations/it.json @@ -21,7 +21,8 @@ }, "issues": { "deprecated_yaml": { - "description": "La configurazione della piattaforma climatica Radio Thermostat tramite YAML \u00e8 stata rimossa in Home Assistant 2022.9. \n\nLa configurazione esistente \u00e8 stata importata automaticamente nell'interfaccia utente. Rimuovere la configurazione YAML dal file configuration.yaml e riavviare Home Assistant per risolvere questo problema." + "description": "La configurazione della piattaforma climatica Radio Thermostat tramite YAML verr\u00e0 rimossa in Home Assistant 2022.9. \n\nLa configurazione esistente \u00e8 stata importata automaticamente nell'interfaccia utente. Rimuovere la configurazione YAML dal file configuration.yaml e riavviare Home Assistant per risolvere questo problema.", + "title": "La configurazione YAML di Radio Thermostat verr\u00e0 rimossa" } }, "options": { diff --git a/homeassistant/components/senz/translations/de.json b/homeassistant/components/senz/translations/de.json index ffbc7bb458f5..fbae91321be7 100644 --- a/homeassistant/components/senz/translations/de.json +++ b/homeassistant/components/senz/translations/de.json @@ -16,5 +16,11 @@ "title": "W\u00e4hle die Authentifizierungsmethode" } } + }, + "issues": { + "removed_yaml": { + "description": "Die Konfiguration von nVent RAYCHEM SENZ mit YAML wurde entfernt. \n\nDeine vorhandene YAML-Konfiguration wird von Home Assistant nicht verwendet. \n\nEntferne die YAML-Konfiguration aus deiner configuration.yaml-Datei und starte Home Assistant neu, um dieses Problem zu beheben.", + "title": "Die nVent RAYCHEM SENZ YAML Konfiguration wurde entfernt" + } } } \ No newline at end of file diff --git a/homeassistant/components/senz/translations/pt-BR.json b/homeassistant/components/senz/translations/pt-BR.json index 7e3ff2f64a9a..02c31d978165 100644 --- a/homeassistant/components/senz/translations/pt-BR.json +++ b/homeassistant/components/senz/translations/pt-BR.json @@ -16,5 +16,11 @@ "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" } } + }, + "issues": { + "removed_yaml": { + "description": "A configura\u00e7\u00e3o do nVent RAYCHEM SENZ usando YAML foi removida. \n\n Sua configura\u00e7\u00e3o YAML existente n\u00e3o \u00e9 usada pelo Home Assistant. \n\n Remova a configura\u00e7\u00e3o YAML do arquivo configuration.yaml e reinicie o Home Assistant para corrigir esse problema.", + "title": "A configura\u00e7\u00e3o nVent RAYCHEM SENZ YAML foi removida" + } } } \ No newline at end of file diff --git a/homeassistant/components/senz/translations/zh-Hant.json b/homeassistant/components/senz/translations/zh-Hant.json index 3bf08cf34c7d..7094ee18a023 100644 --- a/homeassistant/components/senz/translations/zh-Hant.json +++ b/homeassistant/components/senz/translations/zh-Hant.json @@ -16,5 +16,11 @@ "title": "\u9078\u64c7\u9a57\u8b49\u6a21\u5f0f" } } + }, + "issues": { + "removed_yaml": { + "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a nVent RAYCHEM SENZ \u7684\u529f\u80fd\u5373\u5c07\u79fb\u9664\u3002\n\nHome Assistant \u5c07\u4e0d\u518d\u4f7f\u7528\u73fe\u6709\u7684 YAML \u8a2d\u5b9a\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "nVent RAYCHEM SENZ YAML \u8a2d\u5b9a\u5373\u5c07\u79fb\u9664" + } } } \ No newline at end of file diff --git a/homeassistant/components/soundtouch/translations/de.json b/homeassistant/components/soundtouch/translations/de.json index 8d28b9888341..379516e31bef 100644 --- a/homeassistant/components/soundtouch/translations/de.json +++ b/homeassistant/components/soundtouch/translations/de.json @@ -17,5 +17,11 @@ "title": "Best\u00e4tige das Hinzuf\u00fcgen des Bose SoundTouch-Ger\u00e4ts" } } + }, + "issues": { + "deprecated_yaml": { + "description": "Die Konfiguration von Bose SoundTouch mit YAML wird entfernt. \n\nDeine vorhandene YAML-Konfiguration wurde automatisch in die Benutzeroberfl\u00e4che importiert. \n\nEntferne die Bose SoundTouch YAML-Konfiguration aus deiner configuration.yaml-Datei und starte Home Assistant neu, um dieses Problem zu beheben.", + "title": "Die Bose SoundTouch YAML-Konfiguration wird entfernt" + } } } \ No newline at end of file diff --git a/homeassistant/components/soundtouch/translations/it.json b/homeassistant/components/soundtouch/translations/it.json index a14492bf5c3d..f9c6d512b2a9 100644 --- a/homeassistant/components/soundtouch/translations/it.json +++ b/homeassistant/components/soundtouch/translations/it.json @@ -17,5 +17,11 @@ "title": "Conferma l'aggiunta del dispositivo Bose SoundTouch" } } + }, + "issues": { + "deprecated_yaml": { + "description": "La configurazione di Bose SoundTouch tramite YAML verr\u00e0 rimossa.\n\nLa configurazione YAML esistente \u00e8 stata importata automaticamente nell'interfaccia utente.\n\nRimuovere la configurazione YAML di Bose SoundTouch dal file configuration.yaml e riavviare Home Assistant per risolvere questo problema.", + "title": "La configurazione YAML di Bose SoundTouch verr\u00e0 rimossa" + } } } \ No newline at end of file diff --git a/homeassistant/components/soundtouch/translations/pt-BR.json b/homeassistant/components/soundtouch/translations/pt-BR.json index e707219f3429..7446cbc5a06c 100644 --- a/homeassistant/components/soundtouch/translations/pt-BR.json +++ b/homeassistant/components/soundtouch/translations/pt-BR.json @@ -17,5 +17,11 @@ "title": "Confirme a adi\u00e7\u00e3o do dispositivo Bose SoundTouch" } } + }, + "issues": { + "deprecated_yaml": { + "description": "A configura\u00e7\u00e3o do Bose SoundTouch usando YAML est\u00e1 sendo removida. \n\n Sua configura\u00e7\u00e3o YAML existente foi importada para a interface do usu\u00e1rio automaticamente. \n\n Remova a configura\u00e7\u00e3o YAML do Bose SoundTouch do arquivo configuration.yaml e reinicie o Home Assistant para corrigir esse problema.", + "title": "A configura\u00e7\u00e3o YAML do Bose SoundTouch est\u00e1 sendo removida" + } } } \ No newline at end of file diff --git a/homeassistant/components/soundtouch/translations/zh-Hant.json b/homeassistant/components/soundtouch/translations/zh-Hant.json index 08231b8571da..f3d8e8e8560e 100644 --- a/homeassistant/components/soundtouch/translations/zh-Hant.json +++ b/homeassistant/components/soundtouch/translations/zh-Hant.json @@ -17,5 +17,11 @@ "title": "\u78ba\u8a8d\u65b0\u589e Bose SoundTouch \u88dd\u7f6e" } } + }, + "issues": { + "deprecated_yaml": { + "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a Bose SoundTouch \u5373\u5c07\u65bc Home Assistant 2022.9 \u7248\u4e2d\u9032\u884c\u79fb\u9664\u3002\n\n\u65e2\u6709\u7684 YAML \u8a2d\u5b9a\u5c07\u81ea\u52d5\u532f\u5165\u81f3 UI \u5167\u3002\n\n\u8acb\u65bc configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 Bose SoundTouch YAML \u8a2d\u5b9a\u4e26\u91cd\u65b0\u555f\u52d5 Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "Bose SoundTouch YAML \u8a2d\u5b9a\u5373\u5c07\u79fb\u9664" + } } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/zh-Hant.json b/homeassistant/components/spotify/translations/zh-Hant.json index ce89e224a8c9..52773f3e411f 100644 --- a/homeassistant/components/spotify/translations/zh-Hant.json +++ b/homeassistant/components/spotify/translations/zh-Hant.json @@ -21,8 +21,8 @@ }, "issues": { "removed_yaml": { - "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a Spotify \u7684\u529f\u80fd\u5df2\u906d\u5230\u79fb\u9664\u3002\n\nHome Assistant \u5c07\u4e0d\u518d\u4f7f\u7528\u73fe\u6709\u7684 YAML \u8a2d\u5b9a\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", - "title": "Spotify YAML \u8a2d\u5b9a\u5df2\u7d93\u79fb\u9664" + "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a Spotify \u7684\u529f\u80fd\u5373\u5c07\u79fb\u9664\u3002\n\nHome Assistant \u5c07\u4e0d\u518d\u4f7f\u7528\u73fe\u6709\u7684 YAML \u8a2d\u5b9a\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "Spotify YAML \u8a2d\u5b9a\u5373\u5c07\u79fb\u9664" } }, "system_health": { diff --git a/homeassistant/components/steam_online/translations/zh-Hant.json b/homeassistant/components/steam_online/translations/zh-Hant.json index e3c725532e9d..8b0c932735bd 100644 --- a/homeassistant/components/steam_online/translations/zh-Hant.json +++ b/homeassistant/components/steam_online/translations/zh-Hant.json @@ -26,8 +26,8 @@ }, "issues": { "removed_yaml": { - "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a Steam \u7684\u529f\u80fd\u5df2\u906d\u5230\u79fb\u9664\u3002\n\nHome Assistant \u5c07\u4e0d\u518d\u4f7f\u7528\u73fe\u6709\u7684 YAML \u8a2d\u5b9a\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", - "title": "Steam YAML \u8a2d\u5b9a\u5df2\u7d93\u79fb\u9664" + "description": "\u4f7f\u7528 YAML \u8a2d\u5b9a Steam \u7684\u529f\u80fd\u5373\u5c07\u79fb\u9664\u3002\n\nHome Assistant \u5c07\u4e0d\u518d\u4f7f\u7528\u73fe\u6709\u7684 YAML \u8a2d\u5b9a\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "Steam YAML \u8a2d\u5b9a\u5373\u5c07\u79fb\u9664" } }, "options": { diff --git a/homeassistant/components/uscis/translations/zh-Hant.json b/homeassistant/components/uscis/translations/zh-Hant.json index 4a5882dbd951..ccc72d3d1dd4 100644 --- a/homeassistant/components/uscis/translations/zh-Hant.json +++ b/homeassistant/components/uscis/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "issues": { "pending_removal": { - "description": "\u7f8e\u570b\u516c\u6c11\u8207\u79fb\u6c11\u670d\u52d9\uff08USCIS: U.S. Citizenship and Immigration Services\uff09\u6574\u5408\u6b63\u8a08\u5283\u7531 Home Assistant \u4e2d\u79fb\u9664\u3001\u4e26\u8acb\u65bc Home Assistant 2022.10 \u7248\u5f8c\u7121\u6cd5\u518d\u4f7f\u7528\u3002\n\n\u6574\u5408\u6b63\u5728\u79fb\u9664\u4e2d\u3001\u7531\u65bc\u4f7f\u7528\u4e86\u7db2\u8def\u8cc7\u6599\u64f7\u53d6\uff08webscraping\uff09\u65b9\u5f0f\u3001\u5c07\u4e0d\u88ab\u5141\u8a31\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant to \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", - "title": "USCIS \u6574\u5408\u6b63\u6e96\u5099\u79fb\u9664" + "description": "\u7f8e\u570b\u516c\u6c11\u8207\u79fb\u6c11\u670d\u52d9\uff08USCIS: U.S. Citizenship and Immigration Services\uff09\u6574\u5408\u5373\u5c07\u7531 Home Assistant \u4e2d\u79fb\u9664\u3001\u4e26\u65bc Home Assistant 2022.10 \u7248\u5f8c\u7121\u6cd5\u518d\u4f7f\u7528\u3002\n\n\u7531\u65bc\u4f7f\u7528\u4e86\u4e0d\u88ab\u5141\u8a31\u7684\u7db2\u8def\u8cc7\u6599\u64f7\u53d6\uff08webscraping\uff09\u65b9\u5f0f\u3001\u6574\u5408\u5373\u5c07\u79fb\u9664\u3002\n\n\u7531 configuration.yaml \u6a94\u6848\u4e2d\u79fb\u9664 YAML \u8a2d\u5b9a\u4e26\u91cd\u555f Home Assistant to \u4ee5\u4fee\u6b63\u6b64\u554f\u984c\u3002", + "title": "USCIS \u6574\u5408\u5373\u5c07\u79fb\u9664" } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/it.json b/homeassistant/components/xbox/translations/it.json index e60c37c9e5fa..6cf5bf15bb99 100644 --- a/homeassistant/components/xbox/translations/it.json +++ b/homeassistant/components/xbox/translations/it.json @@ -13,5 +13,11 @@ "title": "Scegli il metodo di autenticazione" } } + }, + "issues": { + "deprecated_yaml": { + "description": "La configurazione di Xbox in configuration.yaml verr\u00e0 rimossa in Home Assistant 2022.9. \n\nLe credenziali dell'applicazione OAuth esistenti e le impostazioni di accesso sono state importate automaticamente nell'interfaccia utente. Rimuovere la configurazione YAML dal file configuration.yaml e riavviare Home Assistant per risolvere questo problema.", + "title": "La configurazione YAML di Xbox verr\u00e0 rimossa" + } } } \ No newline at end of file diff --git a/homeassistant/components/xbox/translations/pt-BR.json b/homeassistant/components/xbox/translations/pt-BR.json index 7f788c1ebb83..d1bb02e84ddb 100644 --- a/homeassistant/components/xbox/translations/pt-BR.json +++ b/homeassistant/components/xbox/translations/pt-BR.json @@ -13,5 +13,11 @@ "title": "Escolha o m\u00e9todo de autentica\u00e7\u00e3o" } } + }, + "issues": { + "deprecated_yaml": { + "description": "A configura\u00e7\u00e3o do Xbox em configuration.yaml est\u00e1 sendo removida no Home Assistant 2022.9. \n\n Suas credenciais de aplicativo OAuth e configura\u00e7\u00f5es de acesso existentes foram importadas para a interface do usu\u00e1rio automaticamente. Remova a configura\u00e7\u00e3o YAML do arquivo configuration.yaml e reinicie o Home Assistant para corrigir esse problema.", + "title": "A configura\u00e7\u00e3o YAML do Xbox est\u00e1 sendo removida" + } } } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/de.json b/homeassistant/components/zha/translations/de.json index 9b8acdf5b871..b4ad58636a76 100644 --- a/homeassistant/components/zha/translations/de.json +++ b/homeassistant/components/zha/translations/de.json @@ -46,11 +46,13 @@ "title": "Optionen f\u00fcr die Alarmsteuerung" }, "zha_options": { + "always_prefer_xy_color_mode": "Immer den XY-Farbmodus bevorzugen", "consider_unavailable_battery": "Batteriebetriebene Ger\u00e4te als nicht verf\u00fcgbar betrachten nach (Sekunden)", "consider_unavailable_mains": "Netzbetriebene Ger\u00e4te als nicht verf\u00fcgbar betrachten nach (Sekunden)", "default_light_transition": "Standardlicht\u00fcbergangszeit (Sekunden)", "enable_identify_on_join": "Aktiviere den Identifikationseffekt, wenn Ger\u00e4te dem Netzwerk beitreten", "enhanced_light_transition": "Aktiviere einen verbesserten Lichtfarben-/Temperatur\u00fcbergang aus einem ausgeschalteten Zustand", + "light_transitioning_flag": "Erweiterten Helligkeitsregler w\u00e4hrend des Licht\u00fcbergangs aktivieren", "title": "Globale Optionen" } }, diff --git a/homeassistant/components/zha/translations/pl.json b/homeassistant/components/zha/translations/pl.json index 0b8e90f5ac0c..99c519782db1 100644 --- a/homeassistant/components/zha/translations/pl.json +++ b/homeassistant/components/zha/translations/pl.json @@ -46,11 +46,13 @@ "title": "Opcje panelu alarmowego" }, "zha_options": { + "always_prefer_xy_color_mode": "Zawsze preferuj tryb kolor\u00f3w XY", "consider_unavailable_battery": "Uznaj urz\u0105dzenia zasilane bateryjnie za niedost\u0119pne po (sekundach)", "consider_unavailable_mains": "Uznaj urz\u0105dzenia zasilane z gniazdka za niedost\u0119pne po (sekundach)", "default_light_transition": "Domy\u015blny czas efektu przej\u015bcia dla \u015bwiat\u0142a (w sekundach)", "enable_identify_on_join": "W\u0142\u0105cz efekt identyfikacji, gdy urz\u0105dzenia do\u0142\u0105czaj\u0105 do sieci", "enhanced_light_transition": "W\u0142\u0105cz ulepszone przej\u015bcie koloru \u015bwiat\u0142a/temperatury ze stanu wy\u0142\u0105czenia", + "light_transitioning_flag": "W\u0142\u0105cz suwak zwi\u0119kszonej jasno\u015bci podczas przej\u015bcia \u015bwiat\u0142a", "title": "Opcje og\u00f3lne" } }, diff --git a/homeassistant/components/zha/translations/pt-BR.json b/homeassistant/components/zha/translations/pt-BR.json index ba54b4aba873..69b8ced6970b 100644 --- a/homeassistant/components/zha/translations/pt-BR.json +++ b/homeassistant/components/zha/translations/pt-BR.json @@ -46,11 +46,13 @@ "title": "Op\u00e7\u00f5es do painel de controle de alarme" }, "zha_options": { + "always_prefer_xy_color_mode": "Sempre prefira o modo de cor XY", "consider_unavailable_battery": "Considerar dispositivos alimentados por bateria indispon\u00edveis ap\u00f3s (segundos)", "consider_unavailable_mains": "Considerar os dispositivos alimentados pela rede indispon\u00edveis ap\u00f3s (segundos)", "default_light_transition": "Tempo de transi\u00e7\u00e3o de luz padr\u00e3o (segundos)", "enable_identify_on_join": "Ativar o efeito de identifica\u00e7\u00e3o quando os dispositivos ingressarem na rede", "enhanced_light_transition": "Ative a transi\u00e7\u00e3o de cor/temperatura da luz aprimorada de um estado desligado", + "light_transitioning_flag": "Ative o controle deslizante de brilho aprimorado durante a transi\u00e7\u00e3o de luz", "title": "Op\u00e7\u00f5es globais" } }, diff --git a/homeassistant/components/zha/translations/zh-Hant.json b/homeassistant/components/zha/translations/zh-Hant.json index 9505da31e807..546a2f77c318 100644 --- a/homeassistant/components/zha/translations/zh-Hant.json +++ b/homeassistant/components/zha/translations/zh-Hant.json @@ -46,11 +46,13 @@ "title": "\u8b66\u6212\u63a7\u5236\u9762\u677f\u9078\u9805" }, "zha_options": { + "always_prefer_xy_color_mode": "\u504f\u597d XY \u8272\u5f69\u6a21\u5f0f", "consider_unavailable_battery": "\u5c07\u96fb\u6c60\u4f9b\u96fb\u88dd\u7f6e\u8996\u70ba\u4e0d\u53ef\u7528\uff08\u79d2\u6578\uff09", "consider_unavailable_mains": "\u5c07\u4e3b\u4f9b\u96fb\u88dd\u7f6e\u8996\u70ba\u4e0d\u53ef\u7528\uff08\u79d2\u6578\uff09", "default_light_transition": "\u9810\u8a2d\u71c8\u5149\u8f49\u63db\u6642\u9593\uff08\u79d2\uff09", "enable_identify_on_join": "\u7576\u88dd\u7f6e\u52a0\u5165\u7db2\u8def\u6642\u3001\u958b\u555f\u8b58\u5225\u6548\u679c", "enhanced_light_transition": "\u958b\u555f\u7531\u95dc\u9589\u72c0\u614b\u589e\u5f37\u5149\u8272/\u8272\u6eab\u8f49\u63db", + "light_transitioning_flag": "\u958b\u555f\u71c8\u5149\u8f49\u63db\u589e\u5f37\u4eae\u5ea6\u8abf\u6574\u5217", "title": "Global \u9078\u9805" } }, From 3b8650d05353ad2c43d8175469f162ca6b0015b7 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 28 Jul 2022 11:41:03 +0200 Subject: [PATCH 03/83] Make Axis utilise forward_entry_setups (#75178) --- homeassistant/components/axis/__init__.py | 26 ++++-- homeassistant/components/axis/config_flow.py | 11 +-- homeassistant/components/axis/device.py | 86 ++++++++------------ tests/components/axis/test_config_flow.py | 4 +- tests/components/axis/test_device.py | 17 ++-- 5 files changed, 62 insertions(+), 82 deletions(-) diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index 5e211c000289..4af066f4e891 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -4,28 +4,36 @@ import logging from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_DEVICE, CONF_MAC, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.entity_registry import async_migrate_entries -from .const import DOMAIN as AXIS_DOMAIN -from .device import AxisNetworkDevice +from .const import DOMAIN as AXIS_DOMAIN, PLATFORMS +from .device import AxisNetworkDevice, get_axis_device +from .errors import AuthenticationRequired, CannotConnect _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: - """Set up the Axis component.""" + """Set up the Axis integration.""" hass.data.setdefault(AXIS_DOMAIN, {}) - device = AxisNetworkDevice(hass, config_entry) - - if not await device.async_setup(): - return False - - hass.data[AXIS_DOMAIN][config_entry.unique_id] = device + try: + api = await get_axis_device(hass, config_entry.data) + except CannotConnect as err: + raise ConfigEntryNotReady from err + except AuthenticationRequired as err: + raise ConfigEntryAuthFailed from err + device = hass.data[AXIS_DOMAIN][config_entry.unique_id] = AxisNetworkDevice( + hass, config_entry, api + ) await device.async_update_device_registry() + await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) + device.async_setup_events() + config_entry.add_update_listener(device.async_new_address_callback) config_entry.async_on_unload( hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.shutdown) ) diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index f94c27dc2ac1..1ce2f08c0458 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Mapping from ipaddress import ip_address +from types import MappingProxyType from typing import Any from urllib.parse import urlsplit @@ -32,7 +33,7 @@ from .const import ( DEFAULT_VIDEO_SOURCE, DOMAIN as AXIS_DOMAIN, ) -from .device import AxisNetworkDevice, get_device +from .device import AxisNetworkDevice, get_axis_device from .errors import AuthenticationRequired, CannotConnect AXIS_OUI = {"00:40:8c", "ac:cc:8e", "b8:a4:4f"} @@ -66,13 +67,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): if user_input is not None: try: - device = await get_device( - self.hass, - host=user_input[CONF_HOST], - port=user_input[CONF_PORT], - username=user_input[CONF_USERNAME], - password=user_input[CONF_PASSWORD], - ) + device = await get_axis_device(self.hass, MappingProxyType(user_input)) serial = device.vapix.serial_number await self.async_set_unique_id(format_mac(serial)) diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index d0d5e230d2f3..683991d0f65a 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -1,6 +1,8 @@ """Axis network device abstraction.""" import asyncio +from types import MappingProxyType +from typing import Any import async_timeout import axis @@ -24,7 +26,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant, callback -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -50,15 +51,15 @@ from .errors import AuthenticationRequired, CannotConnect class AxisNetworkDevice: """Manages a Axis device.""" - def __init__(self, hass, config_entry): + def __init__(self, hass, config_entry, api): """Initialize the device.""" self.hass = hass self.config_entry = config_entry - self.available = True + self.api = api - self.api = None - self.fw_version = None - self.product_type = None + self.available = True + self.fw_version = api.vapix.firmware_version + self.product_type = api.vapix.product_type @property def host(self): @@ -184,7 +185,7 @@ class AxisNetworkDevice: sw_version=self.fw_version, ) - async def use_mqtt(self, hass: HomeAssistant, component: str) -> None: + async def async_use_mqtt(self, hass: HomeAssistant, component: str) -> None: """Set up to use MQTT.""" try: status = await self.api.vapix.mqtt.get_client_status() @@ -209,50 +210,18 @@ class AxisNetworkDevice: # Setup and teardown methods - async def async_setup(self): - """Set up the device.""" - try: - self.api = await get_device( - self.hass, - host=self.host, - port=self.port, - username=self.username, - password=self.password, + def async_setup_events(self): + """Set up the device events.""" + + if self.option_events: + self.api.stream.connection_status_callback.append( + self.async_connection_status_callback ) + self.api.enable_events(event_callback=self.async_event_callback) + self.api.stream.start() - except CannotConnect as err: - raise ConfigEntryNotReady from err - - except AuthenticationRequired as err: - raise ConfigEntryAuthFailed from err - - self.fw_version = self.api.vapix.firmware_version - self.product_type = self.api.vapix.product_type - - async def start_platforms(): - await asyncio.gather( - *( - self.hass.config_entries.async_forward_entry_setup( - self.config_entry, platform - ) - for platform in PLATFORMS - ) - ) - if self.option_events: - self.api.stream.connection_status_callback.append( - self.async_connection_status_callback - ) - self.api.enable_events(event_callback=self.async_event_callback) - self.api.stream.start() - - if self.api.vapix.mqtt: - async_when_setup(self.hass, MQTT_DOMAIN, self.use_mqtt) - - self.hass.async_create_task(start_platforms()) - - self.config_entry.add_update_listener(self.async_new_address_callback) - - return True + if self.api.vapix.mqtt: + async_when_setup(self.hass, MQTT_DOMAIN, self.async_use_mqtt) @callback def disconnect_from_stream(self): @@ -274,14 +243,21 @@ class AxisNetworkDevice: ) -async def get_device( - hass: HomeAssistant, host: str, port: int, username: str, password: str +async def get_axis_device( + hass: HomeAssistant, + config: MappingProxyType[str, Any], ) -> axis.AxisDevice: """Create a Axis device.""" session = get_async_client(hass, verify_ssl=False) device = axis.AxisDevice( - Configuration(session, host, port=port, username=username, password=password) + Configuration( + session, + config[CONF_HOST], + port=config[CONF_PORT], + username=config[CONF_USERNAME], + password=config[CONF_PASSWORD], + ) ) try: @@ -291,11 +267,13 @@ async def get_device( return device except axis.Unauthorized as err: - LOGGER.warning("Connected to device at %s but not registered", host) + LOGGER.warning( + "Connected to device at %s but not registered", config[CONF_HOST] + ) raise AuthenticationRequired from err except (asyncio.TimeoutError, axis.RequestError) as err: - LOGGER.error("Error connecting to the Axis device at %s", host) + LOGGER.error("Error connecting to the Axis device at %s", config[CONF_HOST]) raise CannotConnect from err except axis.AxisException as err: diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index 1459ae215d94..2daf350ac932 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -123,7 +123,7 @@ async def test_flow_fails_faulty_credentials(hass): assert result["step_id"] == SOURCE_USER with patch( - "homeassistant.components.axis.config_flow.get_device", + "homeassistant.components.axis.config_flow.get_axis_device", side_effect=config_flow.AuthenticationRequired, ): result = await hass.config_entries.flow.async_configure( @@ -149,7 +149,7 @@ async def test_flow_fails_cannot_connect(hass): assert result["step_id"] == SOURCE_USER with patch( - "homeassistant.components.axis.config_flow.get_device", + "homeassistant.components.axis.config_flow.get_axis_device", side_effect=config_flow.CannotConnect, ): result = await hass.config_entries.flow.async_configure( diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index 4717e2915c1c..ba6df6e2e2d7 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -441,7 +441,7 @@ async def test_device_reset(hass): async def test_device_not_accessible(hass): """Failed setup schedules a retry of setup.""" - with patch.object(axis.device, "get_device", side_effect=axis.errors.CannotConnect): + with patch.object(axis, "get_axis_device", side_effect=axis.errors.CannotConnect): await setup_axis_integration(hass) assert hass.data[AXIS_DOMAIN] == {} @@ -449,7 +449,7 @@ async def test_device_not_accessible(hass): async def test_device_trigger_reauth_flow(hass): """Failed authentication trigger a reauthentication flow.""" with patch.object( - axis.device, "get_device", side_effect=axis.errors.AuthenticationRequired + axis, "get_axis_device", side_effect=axis.errors.AuthenticationRequired ), patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: await setup_axis_integration(hass) mock_flow_init.assert_called_once() @@ -458,7 +458,7 @@ async def test_device_trigger_reauth_flow(hass): async def test_device_unknown_error(hass): """Unknown errors are handled.""" - with patch.object(axis.device, "get_device", side_effect=Exception): + with patch.object(axis, "get_axis_device", side_effect=Exception): await setup_axis_integration(hass) assert hass.data[AXIS_DOMAIN] == {} @@ -468,7 +468,7 @@ async def test_new_event_sends_signal(hass): entry = Mock() entry.data = ENTRY_CONFIG - axis_device = axis.device.AxisNetworkDevice(hass, entry) + axis_device = axis.device.AxisNetworkDevice(hass, entry, Mock()) with patch.object(axis.device, "async_dispatcher_send") as mock_dispatch_send: axis_device.async_event_callback(action=OPERATION_INITIALIZED, event_id="event") @@ -484,8 +484,7 @@ async def test_shutdown(): entry = Mock() entry.data = ENTRY_CONFIG - axis_device = axis.device.AxisNetworkDevice(hass, entry) - axis_device.api = Mock() + axis_device = axis.device.AxisNetworkDevice(hass, entry, Mock()) await axis_device.shutdown(None) @@ -497,7 +496,7 @@ async def test_get_device_fails(hass): with patch( "axis.vapix.Vapix.request", side_effect=axislib.Unauthorized ), pytest.raises(axis.errors.AuthenticationRequired): - await axis.device.get_device(hass, host="", port="", username="", password="") + await axis.device.get_axis_device(hass, ENTRY_CONFIG) async def test_get_device_device_unavailable(hass): @@ -505,7 +504,7 @@ async def test_get_device_device_unavailable(hass): with patch( "axis.vapix.Vapix.request", side_effect=axislib.RequestError ), pytest.raises(axis.errors.CannotConnect): - await axis.device.get_device(hass, host="", port="", username="", password="") + await axis.device.get_axis_device(hass, ENTRY_CONFIG) async def test_get_device_unknown_error(hass): @@ -513,4 +512,4 @@ async def test_get_device_unknown_error(hass): with patch( "axis.vapix.Vapix.request", side_effect=axislib.AxisException ), pytest.raises(axis.errors.AuthenticationRequired): - await axis.device.get_device(hass, host="", port="", username="", password="") + await axis.device.get_axis_device(hass, ENTRY_CONFIG) From 70731c0bc7042da51db5bc0a122a8a0f1f5e86eb Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Wed, 27 Jul 2022 16:06:33 -0400 Subject: [PATCH 04/83] Add Insteon lock and load controller devices (#75632) --- homeassistant/components/insteon/const.py | 1 + homeassistant/components/insteon/ipdb.py | 5 + homeassistant/components/insteon/lock.py | 49 ++++++++ .../components/insteon/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/insteon/mock_devices.py | 21 +++- tests/components/insteon/test_lock.py | 109 ++++++++++++++++++ 8 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/insteon/lock.py create mode 100644 tests/components/insteon/test_lock.py diff --git a/homeassistant/components/insteon/const.py b/homeassistant/components/insteon/const.py index fb7b2387d736..5337ccd36c38 100644 --- a/homeassistant/components/insteon/const.py +++ b/homeassistant/components/insteon/const.py @@ -44,6 +44,7 @@ INSTEON_PLATFORMS = [ Platform.COVER, Platform.FAN, Platform.LIGHT, + Platform.LOCK, Platform.SWITCH, ] diff --git a/homeassistant/components/insteon/ipdb.py b/homeassistant/components/insteon/ipdb.py index 6866e0523684..7f4ff92380fb 100644 --- a/homeassistant/components/insteon/ipdb.py +++ b/homeassistant/components/insteon/ipdb.py @@ -1,5 +1,6 @@ """Utility methods for the Insteon platform.""" from pyinsteon.device_types import ( + AccessControl_Morningstar, ClimateControl_Thermostat, ClimateControl_WirelessThermostat, DimmableLightingControl, @@ -12,6 +13,7 @@ from pyinsteon.device_types import ( DimmableLightingControl_OutletLinc, DimmableLightingControl_SwitchLinc, DimmableLightingControl_ToggleLinc, + EnergyManagement_LoadController, GeneralController_ControlLinc, GeneralController_MiniRemote_4, GeneralController_MiniRemote_8, @@ -44,11 +46,13 @@ from homeassistant.components.climate import DOMAIN as CLIMATE from homeassistant.components.cover import DOMAIN as COVER from homeassistant.components.fan import DOMAIN as FAN from homeassistant.components.light import DOMAIN as LIGHT +from homeassistant.components.lock import DOMAIN as LOCK from homeassistant.components.switch import DOMAIN as SWITCH from .const import ON_OFF_EVENTS DEVICE_PLATFORM = { + AccessControl_Morningstar: {LOCK: [1]}, DimmableLightingControl: {LIGHT: [1], ON_OFF_EVENTS: [1]}, DimmableLightingControl_DinRail: {LIGHT: [1], ON_OFF_EVENTS: [1]}, DimmableLightingControl_FanLinc: {LIGHT: [1], FAN: [2], ON_OFF_EVENTS: [1, 2]}, @@ -67,6 +71,7 @@ DEVICE_PLATFORM = { DimmableLightingControl_OutletLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]}, DimmableLightingControl_SwitchLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]}, DimmableLightingControl_ToggleLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]}, + EnergyManagement_LoadController: {SWITCH: [1], BINARY_SENSOR: [2]}, GeneralController_ControlLinc: {ON_OFF_EVENTS: [1]}, GeneralController_MiniRemote_4: {ON_OFF_EVENTS: range(1, 5)}, GeneralController_MiniRemote_8: {ON_OFF_EVENTS: range(1, 9)}, diff --git a/homeassistant/components/insteon/lock.py b/homeassistant/components/insteon/lock.py new file mode 100644 index 000000000000..17a7cf20111f --- /dev/null +++ b/homeassistant/components/insteon/lock.py @@ -0,0 +1,49 @@ +"""Support for INSTEON locks.""" + +from typing import Any + +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import SIGNAL_ADD_ENTITIES +from .insteon_entity import InsteonEntity +from .utils import async_add_insteon_entities + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Insteon locks from a config entry.""" + + @callback + def async_add_insteon_lock_entities(discovery_info=None): + """Add the Insteon entities for the platform.""" + async_add_insteon_entities( + hass, LOCK_DOMAIN, InsteonLockEntity, async_add_entities, discovery_info + ) + + signal = f"{SIGNAL_ADD_ENTITIES}_{LOCK_DOMAIN}" + async_dispatcher_connect(hass, signal, async_add_insteon_lock_entities) + async_add_insteon_lock_entities() + + +class InsteonLockEntity(InsteonEntity, LockEntity): + """A Class for an Insteon lock entity.""" + + @property + def is_locked(self) -> bool: + """Return the boolean response if the node is on.""" + return bool(self._insteon_device_group.value) + + async def async_lock(self, **kwargs: Any) -> None: + """Lock the device.""" + await self._insteon_device.async_lock() + + async def async_unlock(self, **kwargs: Any) -> None: + """Unlock the device.""" + await self._insteon_device.async_unlock() diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index c48d502c16e7..577383e89762 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/insteon", "dependencies": ["http", "websocket_api"], "requirements": [ - "pyinsteon==1.1.3", + "pyinsteon==1.2.0", "insteon-frontend-home-assistant==0.2.0" ], "codeowners": ["@teharris1"], diff --git a/requirements_all.txt b/requirements_all.txt index ea3f2dd3e6c1..57ddf87a256e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1569,7 +1569,7 @@ pyialarm==2.2.0 pyicloud==1.0.0 # homeassistant.components.insteon -pyinsteon==1.1.3 +pyinsteon==1.2.0 # homeassistant.components.intesishome pyintesishome==1.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9f19f813747d..1f650a56a308 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1076,7 +1076,7 @@ pyialarm==2.2.0 pyicloud==1.0.0 # homeassistant.components.insteon -pyinsteon==1.1.3 +pyinsteon==1.2.0 # homeassistant.components.ipma pyipma==2.0.5 diff --git a/tests/components/insteon/mock_devices.py b/tests/components/insteon/mock_devices.py index ef64b1e09690..417769d6696a 100644 --- a/tests/components/insteon/mock_devices.py +++ b/tests/components/insteon/mock_devices.py @@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch from pyinsteon.address import Address from pyinsteon.constants import ALDBStatus, ResponseStatus from pyinsteon.device_types import ( + AccessControl_Morningstar, DimmableLightingControl_KeypadLinc_8, GeneralController_RemoteLinc, Hub, @@ -59,12 +60,13 @@ class MockDevices: async def async_load(self, *args, **kwargs): """Load the mock devices.""" - if self._connected: + if self._connected and not self._devices: addr0 = Address("AA.AA.AA") addr1 = Address("11.11.11") addr2 = Address("22.22.22") addr3 = Address("33.33.33") addr4 = Address("44.44.44") + addr5 = Address("55.55.55") self._devices[addr0] = Hub(addr0, 0x03, 0x00, 0x00, "Hub AA.AA.AA", "0") self._devices[addr1] = MockSwitchLinc( addr1, 0x02, 0x00, 0x00, "Device 11.11.11", "1" @@ -78,9 +80,12 @@ class MockDevices: self._devices[addr4] = SensorsActuators_IOLink( addr4, 0x07, 0x00, 0x00, "Device 44.44.44", "4" ) + self._devices[addr5] = AccessControl_Morningstar( + addr5, 0x0F, 0x0A, 0x00, "Device 55.55.55", "5" + ) for device in [ - self._devices[addr] for addr in [addr1, addr2, addr3, addr4] + self._devices[addr] for addr in [addr1, addr2, addr3, addr4, addr5] ]: device.async_read_config = AsyncMock() device.aldb.async_write = AsyncMock() @@ -99,7 +104,9 @@ class MockDevices: return_value=ResponseStatus.SUCCESS ) - for device in [self._devices[addr] for addr in [addr2, addr3, addr4]]: + for device in [ + self._devices[addr] for addr in [addr2, addr3, addr4, addr5] + ]: device.async_status = AsyncMock() self._devices[addr1].async_status = AsyncMock(side_effect=AttributeError) self._devices[addr0].aldb.async_load = AsyncMock() @@ -117,6 +124,12 @@ class MockDevices: return_value=ResponseStatus.FAILURE ) + self._devices[addr5].async_lock = AsyncMock( + return_value=ResponseStatus.SUCCESS + ) + self._devices[addr5].async_unlock = AsyncMock( + return_value=ResponseStatus.SUCCESS + ) self.modem = self._devices[addr0] self.modem.async_read_config = AsyncMock() @@ -155,6 +168,6 @@ class MockDevices: yield address await asyncio.sleep(0.01) - def subscribe(self, listener): + def subscribe(self, listener, force_strong_ref=False): """Mock the subscribe function.""" subscribe_topic(listener, DEVICE_LIST_CHANGED) diff --git a/tests/components/insteon/test_lock.py b/tests/components/insteon/test_lock.py new file mode 100644 index 000000000000..6f847543a9f0 --- /dev/null +++ b/tests/components/insteon/test_lock.py @@ -0,0 +1,109 @@ +"""Tests for the Insteon lock.""" + +from unittest.mock import patch + +import pytest + +from homeassistant.components import insteon +from homeassistant.components.insteon import ( + DOMAIN, + insteon_entity, + utils as insteon_utils, +) +from homeassistant.components.lock import ( # SERVICE_LOCK,; SERVICE_UNLOCK, + DOMAIN as LOCK_DOMAIN, +) +from homeassistant.const import ( # ATTR_ENTITY_ID,; + EVENT_HOMEASSISTANT_STOP, + STATE_LOCKED, + STATE_UNLOCKED, + Platform, +) +from homeassistant.helpers import entity_registry as er + +from .const import MOCK_USER_INPUT_PLM +from .mock_devices import MockDevices + +from tests.common import MockConfigEntry + +devices = MockDevices() + + +@pytest.fixture(autouse=True) +def lock_platform_only(): + """Only setup the lock and required base platforms to speed up tests.""" + with patch( + "homeassistant.components.insteon.INSTEON_PLATFORMS", + (Platform.LOCK,), + ): + yield + + +@pytest.fixture(autouse=True) +def patch_setup_and_devices(): + """Patch the Insteon setup process and devices.""" + with patch.object(insteon, "async_connect", new=mock_connection), patch.object( + insteon, "async_close" + ), patch.object(insteon, "devices", devices), patch.object( + insteon_utils, "devices", devices + ), patch.object( + insteon_entity, "devices", devices + ): + yield + + +async def mock_connection(*args, **kwargs): + """Return a successful connection.""" + return True + + +async def test_lock_lock(hass): + """Test locking an Insteon lock device.""" + + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT_PLM) + config_entry.add_to_hass(hass) + registry_entity = er.async_get(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + try: + lock = registry_entity.async_get("lock.device_55_55_55_55_55_55") + state = hass.states.get(lock.entity_id) + assert state.state is STATE_UNLOCKED + + # lock via UI + await hass.services.async_call( + LOCK_DOMAIN, "lock", {"entity_id": lock.entity_id}, blocking=True + ) + assert devices["55.55.55"].async_lock.call_count == 1 + finally: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + + +async def test_lock_unlock(hass): + """Test locking an Insteon lock device.""" + + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT_PLM) + config_entry.add_to_hass(hass) + registry_entity = er.async_get(hass) + + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + devices["55.55.55"].groups[1].set_value(255) + + try: + lock = registry_entity.async_get("lock.device_55_55_55_55_55_55") + state = hass.states.get(lock.entity_id) + + assert state.state is STATE_LOCKED + + # lock via UI + await hass.services.async_call( + LOCK_DOMAIN, "unlock", {"entity_id": lock.entity_id}, blocking=True + ) + assert devices["55.55.55"].async_unlock.call_count == 1 + finally: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() From 7811518d7c6c538c32242e4f8e575157b80da03a Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 27 Jul 2022 16:39:39 -0500 Subject: [PATCH 05/83] Add Leviton as a supported brand of ZwaveJS (#75729) Co-authored-by: Martin Hjelmare --- homeassistant/components/zwave_js/manifest.json | 5 ++++- homeassistant/generated/supported_brands.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index 76f5d94b5890..8b6ecefc5f5c 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -30,5 +30,8 @@ } ], "zeroconf": ["_zwave-js-server._tcp.local."], - "loggers": ["zwave_js_server"] + "loggers": ["zwave_js_server"], + "supported_brands": { + "leviton_z_wave": "Leviton Z-Wave" + } } diff --git a/homeassistant/generated/supported_brands.py b/homeassistant/generated/supported_brands.py index 589e0462cf75..4e151f5578d6 100644 --- a/homeassistant/generated/supported_brands.py +++ b/homeassistant/generated/supported_brands.py @@ -11,5 +11,6 @@ HAS_SUPPORTED_BRANDS = ( "motion_blinds", "overkiz", "renault", - "wemo" + "wemo", + "zwave_js" ) From 15e6fcca41f42ba666881f8170207613a2104884 Mon Sep 17 00:00:00 2001 From: Brandon West Date: Thu, 28 Jul 2022 12:27:48 -0400 Subject: [PATCH 06/83] Bump russound_rio to 0.1.8 (#75837) --- homeassistant/components/russound_rio/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/russound_rio/manifest.json b/homeassistant/components/russound_rio/manifest.json index 4b9b7a2c8d03..e844f478322d 100644 --- a/homeassistant/components/russound_rio/manifest.json +++ b/homeassistant/components/russound_rio/manifest.json @@ -2,7 +2,7 @@ "domain": "russound_rio", "name": "Russound RIO", "documentation": "https://www.home-assistant.io/integrations/russound_rio", - "requirements": ["russound_rio==0.1.7"], + "requirements": ["russound_rio==0.1.8"], "codeowners": [], "iot_class": "local_push", "loggers": ["russound_rio"] diff --git a/requirements_all.txt b/requirements_all.txt index 57ddf87a256e..95da109691c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2123,7 +2123,7 @@ rtsp-to-webrtc==0.5.1 russound==0.1.9 # homeassistant.components.russound_rio -russound_rio==0.1.7 +russound_rio==0.1.8 # homeassistant.components.yamaha rxv==0.7.0 From 96587c1227efae30865acc9f074dce62cab229a5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 27 Jul 2022 12:59:43 -0700 Subject: [PATCH 07/83] Remove learn more URL from Home Assistant alerts (#75838) --- .../homeassistant_alerts/__init__.py | 5 +-- .../fixtures/alerts_no_url.json | 34 ------------------- .../homeassistant_alerts/test_init.py | 15 +++----- 3 files changed, 5 insertions(+), 49 deletions(-) delete mode 100644 tests/components/homeassistant_alerts/fixtures/alerts_no_url.json diff --git a/homeassistant/components/homeassistant_alerts/__init__.py b/homeassistant/components/homeassistant_alerts/__init__.py index 1aedd6c5419d..12ba4dce8ba0 100644 --- a/homeassistant/components/homeassistant_alerts/__init__.py +++ b/homeassistant/components/homeassistant_alerts/__init__.py @@ -75,7 +75,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: DOMAIN, issue_id, is_fixable=False, - learn_more_url=alert.alert_url, severity=IssueSeverity.WARNING, translation_key="alert", translation_placeholders={ @@ -112,7 +111,6 @@ class IntegrationAlert: integration: str filename: str date_updated: str | None - alert_url: str | None @property def issue_id(self) -> str: @@ -147,7 +145,7 @@ class AlertUpdateCoordinator(DataUpdateCoordinator[dict[str, IntegrationAlert]]) result = {} for alert in alerts: - if "alert_url" not in alert or "integrations" not in alert: + if "integrations" not in alert: continue if "homeassistant" in alert: @@ -177,7 +175,6 @@ class AlertUpdateCoordinator(DataUpdateCoordinator[dict[str, IntegrationAlert]]) integration=integration["package"], filename=alert["filename"], date_updated=alert.get("date_updated"), - alert_url=alert["alert_url"], ) result[integration_alert.issue_id] = integration_alert diff --git a/tests/components/homeassistant_alerts/fixtures/alerts_no_url.json b/tests/components/homeassistant_alerts/fixtures/alerts_no_url.json deleted file mode 100644 index 89f277cf69b6..000000000000 --- a/tests/components/homeassistant_alerts/fixtures/alerts_no_url.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "title": "Dark Sky API closed for new users", - "created": "2020-03-31T14:40:00.000Z", - "integrations": [ - { - "package": "darksky" - } - ], - "github_issue": "https://github.com/home-assistant/home-assistant.io/pull/12591", - "homeassistant": { - "package": "homeassistant", - "affected_from_version": "0.30" - }, - "filename": "dark_sky.markdown", - "alert_url": "https://alerts.home-assistant.io/#dark_sky.markdown" - }, - { - "title": "Hikvision Security Vulnerability", - "created": "2021-09-20T22:08:00.000Z", - "integrations": [ - { - "package": "hikvision" - }, - { - "package": "hikvisioncam" - } - ], - "filename": "hikvision.markdown", - "homeassistant": { - "package": "homeassistant" - } - } -] diff --git a/tests/components/homeassistant_alerts/test_init.py b/tests/components/homeassistant_alerts/test_init.py index c0b6f4710335..cb39fb73108a 100644 --- a/tests/components/homeassistant_alerts/test_init.py +++ b/tests/components/homeassistant_alerts/test_init.py @@ -133,7 +133,7 @@ async def test_alerts( "ignored": False, "is_fixable": False, "issue_id": f"{alert}_{integration}", - "learn_more_url": f"https://alerts.home-assistant.io/#{alert}", + "learn_more_url": None, "severity": "warning", "translation_key": "alert", "translation_placeholders": { @@ -149,13 +149,6 @@ async def test_alerts( @pytest.mark.parametrize( "ha_version, fixture, expected_alerts", ( - ( - "2022.7.0", - "alerts_no_url.json", - [ - ("dark_sky.markdown", "darksky"), - ], - ), ( "2022.7.0", "alerts_no_integrations.json", @@ -220,7 +213,7 @@ async def test_bad_alerts( "ignored": False, "is_fixable": False, "issue_id": f"{alert}_{integration}", - "learn_more_url": f"https://alerts.home-assistant.io/#{alert}", + "learn_more_url": None, "severity": "warning", "translation_key": "alert", "translation_placeholders": { @@ -381,7 +374,7 @@ async def test_alerts_change( "ignored": False, "is_fixable": False, "issue_id": f"{alert}_{integration}", - "learn_more_url": f"https://alerts.home-assistant.io/#{alert}", + "learn_more_url": None, "severity": "warning", "translation_key": "alert", "translation_placeholders": { @@ -420,7 +413,7 @@ async def test_alerts_change( "ignored": False, "is_fixable": False, "issue_id": f"{alert}_{integration}", - "learn_more_url": f"https://alerts.home-assistant.io/#{alert}", + "learn_more_url": None, "severity": "warning", "translation_key": "alert", "translation_placeholders": { From 937fd490f2d8326e4175e98ce78d26ef2a4e8243 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 27 Jul 2022 13:53:51 -0700 Subject: [PATCH 08/83] Add issue_domain to repairs (#75839) --- .../components/homeassistant_alerts/__init__.py | 1 + homeassistant/components/repairs/issue_handler.py | 2 ++ homeassistant/components/repairs/issue_registry.py | 6 ++++++ tests/components/demo/test_init.py | 5 +++++ tests/components/homeassistant_alerts/test_init.py | 4 ++++ tests/components/repairs/test_init.py | 13 +++++++++++++ tests/components/repairs/test_websocket_api.py | 7 +++++++ 7 files changed, 38 insertions(+) diff --git a/homeassistant/components/homeassistant_alerts/__init__.py b/homeassistant/components/homeassistant_alerts/__init__.py index 12ba4dce8ba0..d405b9e257d9 100644 --- a/homeassistant/components/homeassistant_alerts/__init__.py +++ b/homeassistant/components/homeassistant_alerts/__init__.py @@ -75,6 +75,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: DOMAIN, issue_id, is_fixable=False, + issue_domain=alert.integration, severity=IssueSeverity.WARNING, translation_key="alert", translation_placeholders={ diff --git a/homeassistant/components/repairs/issue_handler.py b/homeassistant/components/repairs/issue_handler.py index 8eff4ac64fe1..c139026ec480 100644 --- a/homeassistant/components/repairs/issue_handler.py +++ b/homeassistant/components/repairs/issue_handler.py @@ -86,6 +86,7 @@ def async_create_issue( domain: str, issue_id: str, *, + issue_domain: str | None = None, breaks_in_ha_version: str | None = None, is_fixable: bool, learn_more_url: str | None = None, @@ -106,6 +107,7 @@ def async_create_issue( issue_registry.async_get_or_create( domain, issue_id, + issue_domain=issue_domain, breaks_in_ha_version=breaks_in_ha_version, is_fixable=is_fixable, learn_more_url=learn_more_url, diff --git a/homeassistant/components/repairs/issue_registry.py b/homeassistant/components/repairs/issue_registry.py index 5c459309cc01..bd201f1007cc 100644 --- a/homeassistant/components/repairs/issue_registry.py +++ b/homeassistant/components/repairs/issue_registry.py @@ -30,6 +30,8 @@ class IssueEntry: domain: str is_fixable: bool | None issue_id: str + # Used if an integration creates issues for other integrations (ie alerts) + issue_domain: str | None learn_more_url: str | None severity: IssueSeverity | None translation_key: str | None @@ -58,6 +60,7 @@ class IssueRegistry: domain: str, issue_id: str, *, + issue_domain: str | None = None, breaks_in_ha_version: str | None = None, is_fixable: bool, learn_more_url: str | None = None, @@ -75,6 +78,7 @@ class IssueRegistry: dismissed_version=None, domain=domain, is_fixable=is_fixable, + issue_domain=issue_domain, issue_id=issue_id, learn_more_url=learn_more_url, severity=severity, @@ -93,6 +97,7 @@ class IssueRegistry: active=True, breaks_in_ha_version=breaks_in_ha_version, is_fixable=is_fixable, + issue_domain=issue_domain, learn_more_url=learn_more_url, severity=severity, translation_key=translation_key, @@ -155,6 +160,7 @@ class IssueRegistry: domain=issue["domain"], is_fixable=None, issue_id=issue["issue_id"], + issue_domain=None, learn_more_url=None, severity=None, translation_key=None, diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index 85ff2a164052..5b322cb776ff 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -95,6 +95,7 @@ async def test_issues_created(hass, hass_client, hass_ws_client): "ignored": False, "is_fixable": False, "issue_id": "transmogrifier_deprecated", + "issue_domain": None, "learn_more_url": "https://en.wiktionary.org/wiki/transmogrifier", "severity": "warning", "translation_key": "transmogrifier_deprecated", @@ -108,6 +109,7 @@ async def test_issues_created(hass, hass_client, hass_ws_client): "ignored": False, "is_fixable": True, "issue_id": "out_of_blinker_fluid", + "issue_domain": None, "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", "severity": "critical", "translation_key": "out_of_blinker_fluid", @@ -121,6 +123,7 @@ async def test_issues_created(hass, hass_client, hass_ws_client): "ignored": False, "is_fixable": False, "issue_id": "unfixable_problem", + "issue_domain": None, "learn_more_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "severity": "warning", "translation_key": "unfixable_problem", @@ -180,6 +183,7 @@ async def test_issues_created(hass, hass_client, hass_ws_client): "ignored": False, "is_fixable": False, "issue_id": "transmogrifier_deprecated", + "issue_domain": None, "learn_more_url": "https://en.wiktionary.org/wiki/transmogrifier", "severity": "warning", "translation_key": "transmogrifier_deprecated", @@ -193,6 +197,7 @@ async def test_issues_created(hass, hass_client, hass_ws_client): "ignored": False, "is_fixable": False, "issue_id": "unfixable_problem", + "issue_domain": None, "learn_more_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "severity": "warning", "translation_key": "unfixable_problem", diff --git a/tests/components/homeassistant_alerts/test_init.py b/tests/components/homeassistant_alerts/test_init.py index cb39fb73108a..a0fb2e8557d5 100644 --- a/tests/components/homeassistant_alerts/test_init.py +++ b/tests/components/homeassistant_alerts/test_init.py @@ -133,6 +133,7 @@ async def test_alerts( "ignored": False, "is_fixable": False, "issue_id": f"{alert}_{integration}", + "issue_domain": integration, "learn_more_url": None, "severity": "warning", "translation_key": "alert", @@ -213,6 +214,7 @@ async def test_bad_alerts( "ignored": False, "is_fixable": False, "issue_id": f"{alert}_{integration}", + "issue_domain": integration, "learn_more_url": None, "severity": "warning", "translation_key": "alert", @@ -374,6 +376,7 @@ async def test_alerts_change( "ignored": False, "is_fixable": False, "issue_id": f"{alert}_{integration}", + "issue_domain": integration, "learn_more_url": None, "severity": "warning", "translation_key": "alert", @@ -413,6 +416,7 @@ async def test_alerts_change( "ignored": False, "is_fixable": False, "issue_id": f"{alert}_{integration}", + "issue_domain": integration, "learn_more_url": None, "severity": "warning", "translation_key": "alert", diff --git a/tests/components/repairs/test_init.py b/tests/components/repairs/test_init.py index 2f82a084968e..d70f6c6e11d1 100644 --- a/tests/components/repairs/test_init.py +++ b/tests/components/repairs/test_init.py @@ -85,6 +85,7 @@ async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None: created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -97,6 +98,7 @@ async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None: issues[0]["issue_id"], breaks_in_ha_version=issues[0]["breaks_in_ha_version"], is_fixable=issues[0]["is_fixable"], + issue_domain="my_issue_domain", learn_more_url="blablabla", severity=issues[0]["severity"], translation_key=issues[0]["translation_key"], @@ -113,6 +115,7 @@ async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None: dismissed_version=None, ignored=False, learn_more_url="blablabla", + issue_domain="my_issue_domain", ) @@ -206,6 +209,7 @@ async def test_ignore_issue(hass: HomeAssistant, hass_ws_client) -> None: created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -226,6 +230,7 @@ async def test_ignore_issue(hass: HomeAssistant, hass_ws_client) -> None: created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -245,6 +250,7 @@ async def test_ignore_issue(hass: HomeAssistant, hass_ws_client) -> None: created="2022-07-19T07:53:05+00:00", dismissed_version=ha_version, ignored=True, + issue_domain=None, ) for issue in issues ] @@ -264,6 +270,7 @@ async def test_ignore_issue(hass: HomeAssistant, hass_ws_client) -> None: created="2022-07-19T07:53:05+00:00", dismissed_version=ha_version, ignored=True, + issue_domain=None, ) for issue in issues ] @@ -292,6 +299,7 @@ async def test_ignore_issue(hass: HomeAssistant, hass_ws_client) -> None: dismissed_version=ha_version, ignored=True, learn_more_url="blablabla", + issue_domain=None, ) # Unignore the same issue @@ -309,6 +317,7 @@ async def test_ignore_issue(hass: HomeAssistant, hass_ws_client) -> None: dismissed_version=None, ignored=False, learn_more_url="blablabla", + issue_domain=None, ) for issue in issues ] @@ -359,6 +368,7 @@ async def test_delete_issue(hass: HomeAssistant, hass_ws_client, freezer) -> Non created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -378,6 +388,7 @@ async def test_delete_issue(hass: HomeAssistant, hass_ws_client, freezer) -> Non created="2022-07-19T07:53:05+00:00", dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -428,6 +439,7 @@ async def test_delete_issue(hass: HomeAssistant, hass_ws_client, freezer) -> Non created="2022-07-19T08:53:05+00:00", dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -501,6 +513,7 @@ async def test_sync_methods( "ignored": False, "is_fixable": True, "issue_id": "sync_issue", + "issue_domain": None, "learn_more_url": "https://theuselessweb.com", "severity": "error", "translation_key": "abc_123", diff --git a/tests/components/repairs/test_websocket_api.py b/tests/components/repairs/test_websocket_api.py index 73d1898fcb7e..d778b0438327 100644 --- a/tests/components/repairs/test_websocket_api.py +++ b/tests/components/repairs/test_websocket_api.py @@ -61,6 +61,7 @@ async def create_issues(hass, ws_client): created=ANY, dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -154,6 +155,7 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None: created=ANY, dismissed_version=ha_version, ignored=True, + issue_domain=None, ) for issue in issues ] @@ -183,6 +185,7 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None: created=ANY, dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -226,6 +229,7 @@ async def test_fix_non_existing_issue( created=ANY, dismissed_version=None, ignored=False, + issue_domain=None, ) for issue in issues ] @@ -383,6 +387,7 @@ async def test_list_issues(hass: HomeAssistant, hass_storage, hass_ws_client) -> "dismissed_version": None, "domain": "test", "issue_id": "issue_3_inactive", + "issue_domain": None, }, ] }, @@ -404,6 +409,7 @@ async def test_list_issues(hass: HomeAssistant, hass_storage, hass_ws_client) -> "domain": "test", "is_fixable": True, "issue_id": "issue_1", + "issue_domain": None, "learn_more_url": "https://theuselessweb.com", "severity": "error", "translation_key": "abc_123", @@ -414,6 +420,7 @@ async def test_list_issues(hass: HomeAssistant, hass_storage, hass_ws_client) -> "domain": "test", "is_fixable": False, "issue_id": "issue_2", + "issue_domain": None, "learn_more_url": "https://theuselessweb.com/abc", "severity": "other", "translation_key": "even_worse", From add9ff57363a3fef6b11cbeaa2aad25c164ae29b Mon Sep 17 00:00:00 2001 From: Rolf Berkenbosch <30292281+rolfberkenbosch@users.noreply.github.com> Date: Wed, 27 Jul 2022 23:50:41 +0200 Subject: [PATCH 09/83] Fix fetching MeteoAlarm XML data (#75840) --- homeassistant/components/meteoalarm/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/meteoalarm/manifest.json b/homeassistant/components/meteoalarm/manifest.json index 35333f6ea015..9a3da54d34f5 100644 --- a/homeassistant/components/meteoalarm/manifest.json +++ b/homeassistant/components/meteoalarm/manifest.json @@ -2,7 +2,7 @@ "domain": "meteoalarm", "name": "MeteoAlarm", "documentation": "https://www.home-assistant.io/integrations/meteoalarm", - "requirements": ["meteoalertapi==0.2.0"], + "requirements": ["meteoalertapi==0.3.0"], "codeowners": ["@rolfberkenbosch"], "iot_class": "cloud_polling", "loggers": ["meteoalertapi"] diff --git a/requirements_all.txt b/requirements_all.txt index 95da109691c8..9c48e9984ebc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1025,7 +1025,7 @@ meater-python==0.0.8 messagebird==1.2.0 # homeassistant.components.meteoalarm -meteoalertapi==0.2.0 +meteoalertapi==0.3.0 # homeassistant.components.meteo_france meteofrance-api==1.0.2 From e7ff97bac0240ee87989de01428e905137a71f40 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 27 Jul 2022 22:58:58 +0200 Subject: [PATCH 10/83] Fix temperature unit in evohome (#75842) --- homeassistant/components/evohome/climate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index 841619af6f1c..c1a630d0d05e 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -126,6 +126,8 @@ async def async_setup_platform( class EvoClimateEntity(EvoDevice, ClimateEntity): """Base for an evohome Climate device.""" + _attr_temperature_unit = TEMP_CELSIUS + def __init__(self, evo_broker, evo_device) -> None: """Initialize a Climate device.""" super().__init__(evo_broker, evo_device) @@ -316,7 +318,6 @@ class EvoController(EvoClimateEntity): _attr_icon = "mdi:thermostat" _attr_precision = PRECISION_TENTHS - _attr_temperature_unit = TEMP_CELSIUS def __init__(self, evo_broker, evo_device) -> None: """Initialize a Honeywell TCC Controller/Location.""" From b4d2c25f8e9cea621ec2d2f78373fd9d8a610631 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 27 Jul 2022 23:49:22 +0200 Subject: [PATCH 11/83] Raise YAML removal issue for Xbox (#75843) --- homeassistant/components/xbox/__init__.py | 12 +++++++++++- homeassistant/components/xbox/manifest.json | 2 +- homeassistant/components/xbox/strings.json | 6 ++++++ homeassistant/components/xbox/translations/en.json | 6 ++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xbox/__init__.py b/homeassistant/components/xbox/__init__.py index 6e8492e45cac..c49fd55e8c86 100644 --- a/homeassistant/components/xbox/__init__.py +++ b/homeassistant/components/xbox/__init__.py @@ -21,6 +21,7 @@ from xbox.webapi.api.provider.smartglass.models import ( ) from homeassistant.components import application_credentials +from homeassistant.components.repairs import IssueSeverity, async_create_issue from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, Platform from homeassistant.core import HomeAssistant @@ -74,9 +75,18 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: config[DOMAIN][CONF_CLIENT_ID], config[DOMAIN][CONF_CLIENT_SECRET] ), ) + async_create_issue( + hass, + DOMAIN, + "deprecated_yaml", + breaks_in_ha_version="2022.9.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + ) _LOGGER.warning( "Configuration of Xbox integration in YAML is deprecated and " - "will be removed in a future release; Your existing configuration " + "will be removed in Home Assistant 2022.9.; Your existing configuration " "(including OAuth Application Credentials) has been imported into " "the UI automatically and can be safely removed from your " "configuration.yaml file" diff --git a/homeassistant/components/xbox/manifest.json b/homeassistant/components/xbox/manifest.json index 5adfa54a9010..8857a55d66d7 100644 --- a/homeassistant/components/xbox/manifest.json +++ b/homeassistant/components/xbox/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xbox", "requirements": ["xbox-webapi==2.0.11"], - "dependencies": ["auth", "application_credentials"], + "dependencies": ["auth", "application_credentials", "repairs"], "codeowners": ["@hunterjm"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/xbox/strings.json b/homeassistant/components/xbox/strings.json index accd67759418..68af0176fa81 100644 --- a/homeassistant/components/xbox/strings.json +++ b/homeassistant/components/xbox/strings.json @@ -13,5 +13,11 @@ "create_entry": { "default": "[%key:common::config_flow::create_entry::authenticated%]" } + }, + "issues": { + "deprecated_yaml": { + "title": "The Xbox YAML configuration is being removed", + "description": "Configuring the Xbox in configuration.yaml is being removed in Home Assistant 2022.9.\n\nYour existing OAuth Application Credentials and access settings have been imported into the UI automatically. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } } } diff --git a/homeassistant/components/xbox/translations/en.json b/homeassistant/components/xbox/translations/en.json index 0bb1266bded5..2ef065af458d 100644 --- a/homeassistant/components/xbox/translations/en.json +++ b/homeassistant/components/xbox/translations/en.json @@ -13,5 +13,11 @@ "title": "Pick Authentication Method" } } + }, + "issues": { + "deprecated_yaml": { + "description": "Configuring the Xbox in configuration.yaml is being removed in Home Assistant 2022.9.\n\nYour existing OAuth Application Credentials and access settings have been imported into the UI automatically. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue.", + "title": "The Xbox YAML configuration is being removed" + } } } \ No newline at end of file From 2dc318be54f00a610e7007a041bab2cdf09acbb1 Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Thu, 28 Jul 2022 12:05:56 +0300 Subject: [PATCH 12/83] Add issue to repairs for deprecated Simplepush YAML configuration (#75850) --- homeassistant/components/simplepush/manifest.json | 1 + homeassistant/components/simplepush/notify.py | 12 ++++++++++++ homeassistant/components/simplepush/strings.json | 6 ++++++ .../components/simplepush/translations/en.json | 6 ++++++ 4 files changed, 25 insertions(+) diff --git a/homeassistant/components/simplepush/manifest.json b/homeassistant/components/simplepush/manifest.json index 7c37546485a4..6b4ee263ba69 100644 --- a/homeassistant/components/simplepush/manifest.json +++ b/homeassistant/components/simplepush/manifest.json @@ -5,6 +5,7 @@ "requirements": ["simplepush==1.1.4"], "codeowners": ["@engrbm87"], "config_flow": true, + "dependencies": ["repairs"], "iot_class": "cloud_polling", "loggers": ["simplepush"] } diff --git a/homeassistant/components/simplepush/notify.py b/homeassistant/components/simplepush/notify.py index e9cd9813175c..358d95c770ac 100644 --- a/homeassistant/components/simplepush/notify.py +++ b/homeassistant/components/simplepush/notify.py @@ -14,6 +14,8 @@ from homeassistant.components.notify import ( BaseNotificationService, ) from homeassistant.components.notify.const import ATTR_DATA +from homeassistant.components.repairs.issue_handler import async_create_issue +from homeassistant.components.repairs.models import IssueSeverity from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import CONF_EVENT, CONF_PASSWORD from homeassistant.core import HomeAssistant @@ -41,6 +43,16 @@ async def async_get_service( discovery_info: DiscoveryInfoType | None = None, ) -> SimplePushNotificationService | None: """Get the Simplepush notification service.""" + async_create_issue( + hass, + DOMAIN, + "deprecated_yaml", + breaks_in_ha_version="2022.9.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + ) + if discovery_info is None: hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/homeassistant/components/simplepush/strings.json b/homeassistant/components/simplepush/strings.json index 0031dc32340c..77ed05c4b48f 100644 --- a/homeassistant/components/simplepush/strings.json +++ b/homeassistant/components/simplepush/strings.json @@ -17,5 +17,11 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } + }, + "issues": { + "deprecated_yaml": { + "title": "The Simplepush YAML configuration is being removed", + "description": "Configuring Simplepush using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the Simplepush YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } } } diff --git a/homeassistant/components/simplepush/translations/en.json b/homeassistant/components/simplepush/translations/en.json index a36a3b2b273e..bf373d8baf0e 100644 --- a/homeassistant/components/simplepush/translations/en.json +++ b/homeassistant/components/simplepush/translations/en.json @@ -17,5 +17,11 @@ } } } + }, + "issues": { + "deprecated_yaml": { + "title": "The Simplepush YAML configuration is being removed", + "description": "Configuring Simplepush using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the Simplepush YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } } } \ No newline at end of file From c10ed6edbaae83e1dffec0796ae53794dcea3ee8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Jul 2022 12:39:10 +0200 Subject: [PATCH 13/83] Fix unit of measurement usage in COSignal (#75856) --- homeassistant/components/co2signal/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/co2signal/sensor.py b/homeassistant/components/co2signal/sensor.py index f75146646985..841848621ec6 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -103,8 +103,8 @@ class CO2Sensor(CoordinatorEntity[CO2SignalCoordinator], SensorEntity): @property def native_unit_of_measurement(self) -> str | None: """Return the unit of measurement.""" - if self.entity_description.unit_of_measurement: - return self.entity_description.unit_of_measurement + if self.entity_description.native_unit_of_measurement: + return self.entity_description.native_unit_of_measurement return cast( str, self.coordinator.data["units"].get(self.entity_description.key) ) From a000687eb58abf69004d17aa24777b37db3a8aa9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Jul 2022 16:43:32 +0200 Subject: [PATCH 14/83] Fix HTTP 404 being logged as a stack trace (#75861) --- homeassistant/components/http/static.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index c4dc97727a92..6cb1bafdacac 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -33,7 +33,7 @@ def _get_file_path( return None if filepath.is_file(): return filepath - raise HTTPNotFound + raise FileNotFoundError class CachingStaticResource(StaticResource): From 4be623a4922681f8cbc491cef81115f0dbda4ca3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Jul 2022 18:13:16 +0200 Subject: [PATCH 15/83] Remove state class from daily net sensors in DSMR Reader (#75864) --- homeassistant/components/dsmr_reader/definitions.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/homeassistant/components/dsmr_reader/definitions.py b/homeassistant/components/dsmr_reader/definitions.py index 9f4ee7ed918d..ac61837afec0 100644 --- a/homeassistant/components/dsmr_reader/definitions.py +++ b/homeassistant/components/dsmr_reader/definitions.py @@ -225,42 +225,36 @@ SENSORS: tuple[DSMRReaderSensorEntityDescription, ...] = ( name="Low tariff usage", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL_INCREASING, ), DSMRReaderSensorEntityDescription( key="dsmr/day-consumption/electricity2", name="High tariff usage", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL_INCREASING, ), DSMRReaderSensorEntityDescription( key="dsmr/day-consumption/electricity1_returned", name="Low tariff return", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL_INCREASING, ), DSMRReaderSensorEntityDescription( key="dsmr/day-consumption/electricity2_returned", name="High tariff return", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL_INCREASING, ), DSMRReaderSensorEntityDescription( key="dsmr/day-consumption/electricity_merged", name="Power usage total", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL_INCREASING, ), DSMRReaderSensorEntityDescription( key="dsmr/day-consumption/electricity_returned_merged", name="Power return total", device_class=SensorDeviceClass.ENERGY, native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, - state_class=SensorStateClass.TOTAL_INCREASING, ), DSMRReaderSensorEntityDescription( key="dsmr/day-consumption/electricity1_cost", From 2bf10799ed68c96a86d16812922da53e8271451a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Jul 2022 18:15:27 +0200 Subject: [PATCH 16/83] Fix incorrect sensor key in DSMR (#75865) --- homeassistant/components/dsmr/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 7ab1a3bb45b4..aa01c798072a 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -85,7 +85,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, ), DSMRSensorEntityDescription( - key="electricity_delivery", + key="current_electricity_delivery", name="Power production", obis_reference=obis_references.CURRENT_ELECTRICITY_DELIVERY, device_class=SensorDeviceClass.POWER, From f98d95c76f30eb0bbcdeab72d0a931b8971ec9b8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Jul 2022 18:20:39 +0200 Subject: [PATCH 17/83] Fix camera token to trigger authentication IP ban (#75870) --- homeassistant/components/camera/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 247f73c89f22..3bf86dedea11 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -715,7 +715,9 @@ class CameraView(HomeAssistantView): ) if not authenticated: - raise web.HTTPUnauthorized() + if request[KEY_AUTHENTICATED]: + raise web.HTTPUnauthorized() + raise web.HTTPForbidden() if not camera.is_on: _LOGGER.debug("Camera is off") From 38909855bfe811275245b28b4a1e31877c58bea3 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 28 Jul 2022 11:20:10 -0500 Subject: [PATCH 18/83] Update frontend to 20220728.0 (#75872) --- 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 2a1fb2d3b37e..45331491aa04 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20220727.0"], + "requirements": ["home-assistant-frontend==20220728.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 2554f0185b4a..1f86ff4c7c50 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -18,7 +18,7 @@ cryptography==36.0.2 fnvhash==0.1.0 hass-nabucasa==0.54.1 home-assistant-bluetooth==1.3.0 -home-assistant-frontend==20220727.0 +home-assistant-frontend==20220728.0 httpx==0.23.0 ifaddr==0.1.7 jinja2==3.1.2 diff --git a/requirements_all.txt b/requirements_all.txt index 9c48e9984ebc..24a8a5ee1bdf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -839,7 +839,7 @@ hole==0.7.0 holidays==0.14.2 # homeassistant.components.frontend -home-assistant-frontend==20220727.0 +home-assistant-frontend==20220728.0 # homeassistant.components.home_connect homeconnect==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1f650a56a308..81e192ce360d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -616,7 +616,7 @@ hole==0.7.0 holidays==0.14.2 # homeassistant.components.frontend -home-assistant-frontend==20220727.0 +home-assistant-frontend==20220728.0 # homeassistant.components.home_connect homeconnect==0.7.1 From 53870dd0bce72dd2e65736028f57aac3d05ab51c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Jul 2022 19:09:46 +0200 Subject: [PATCH 19/83] Bumped version to 2022.8.0b1 --- 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 02896fcbe961..dfa12ccd8244 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b0" +PATCH_VERSION: Final = "0b1" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 5306a589863b..f5b35dd63dc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0b0" +version = "2022.8.0b1" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From e4e36b51b6e54f2e11554433944de44ca0c30db4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 28 Jul 2022 11:14:13 -1000 Subject: [PATCH 20/83] Add startup timeout to bluetooth (#75848) Co-authored-by: Martin Hjelmare --- .../components/bluetooth/__init__.py | 11 +++++++- tests/components/bluetooth/test_init.py | 28 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index 551e93d5bd99..d2bdb54d5baf 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -1,6 +1,7 @@ """The bluetooth integration.""" from __future__ import annotations +import asyncio from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta @@ -8,6 +9,7 @@ from enum import Enum import logging from typing import Final, Union +import async_timeout from bleak import BleakError from bleak.backends.device import BLEDevice from bleak.backends.scanner import AdvertisementData @@ -43,6 +45,7 @@ _LOGGER = logging.getLogger(__name__) UNAVAILABLE_TRACK_SECONDS: Final = 60 * 5 +START_TIMEOUT = 15 SOURCE_LOCAL: Final = "local" @@ -300,7 +303,13 @@ class BluetoothManager: self._device_detected, {} ) try: - await self.scanner.start() + async with async_timeout.timeout(START_TIMEOUT): + await self.scanner.start() + except asyncio.TimeoutError as ex: + self._cancel_device_detected() + raise ConfigEntryNotReady( + f"Timed out starting Bluetooth after {START_TIMEOUT} seconds" + ) from ex except (FileNotFoundError, BleakError) as ex: self._cancel_device_detected() raise ConfigEntryNotReady(f"Failed to start Bluetooth: {ex}") from ex diff --git a/tests/components/bluetooth/test_init.py b/tests/components/bluetooth/test_init.py index bb2c5f49cc92..66a9ed396ec9 100644 --- a/tests/components/bluetooth/test_init.py +++ b/tests/components/bluetooth/test_init.py @@ -1,4 +1,5 @@ """Tests for the Bluetooth integration.""" +import asyncio from datetime import timedelta from unittest.mock import MagicMock, patch @@ -95,6 +96,33 @@ async def test_setup_and_stop_broken_bluetooth(hass, caplog): assert len(bluetooth.async_discovered_service_info(hass)) == 0 +async def test_setup_and_stop_broken_bluetooth_hanging(hass, caplog): + """Test we fail gracefully when bluetooth/dbus is hanging.""" + mock_bt = [] + + async def _mock_hang(): + await asyncio.sleep(1) + + with patch.object(bluetooth, "START_TIMEOUT", 0), patch( + "homeassistant.components.bluetooth.HaBleakScanner.async_setup" + ), patch( + "homeassistant.components.bluetooth.HaBleakScanner.start", + side_effect=_mock_hang, + ), patch( + "homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt + ): + assert await async_setup_component( + hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}} + ) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert "Timed out starting Bluetooth" in caplog.text + + async def test_setup_and_retry_adapter_not_yet_available(hass, caplog): """Test we retry if the adapter is not yet available.""" mock_bt = [] From 2b1fbbfae3b55f300a892f178fa9e4b285254f2d Mon Sep 17 00:00:00 2001 From: Jc2k Date: Thu, 28 Jul 2022 19:10:37 +0100 Subject: [PATCH 21/83] Fix Xiaomi BLE not detecting encryption for some devices (#75851) --- .../components/bluetooth/__init__.py | 29 +++++ .../components/xiaomi_ble/config_flow.py | 44 ++++++- .../components/xiaomi_ble/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/bluetooth/test_init.py | 89 ++++++++++++- tests/components/xiaomi_ble/__init__.py | 12 ++ .../components/xiaomi_ble/test_config_flow.py | 123 ++++++++++++++++++ 8 files changed, 297 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index d2bdb54d5baf..eb8e31baef05 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from asyncio import Future from collections.abc import Callable from dataclasses import dataclass from datetime import datetime, timedelta @@ -98,6 +99,9 @@ BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT") BluetoothCallback = Callable[ [Union[BluetoothServiceInfoBleak, BluetoothServiceInfo], BluetoothChange], None ] +ProcessAdvertisementCallback = Callable[ + [Union[BluetoothServiceInfoBleak, BluetoothServiceInfo]], bool +] @hass_callback @@ -162,6 +166,31 @@ def async_register_callback( return manager.async_register_callback(callback, match_dict) +async def async_process_advertisements( + hass: HomeAssistant, + callback: ProcessAdvertisementCallback, + match_dict: BluetoothCallbackMatcher, + timeout: int, +) -> BluetoothServiceInfo: + """Process advertisements until callback returns true or timeout expires.""" + done: Future[BluetoothServiceInfo] = Future() + + @hass_callback + def _async_discovered_device( + service_info: BluetoothServiceInfo, change: BluetoothChange + ) -> None: + if callback(service_info): + done.set_result(service_info) + + unload = async_register_callback(hass, _async_discovered_device, match_dict) + + try: + async with async_timeout.timeout(timeout): + return await done + finally: + unload() + + @hass_callback def async_track_unavailable( hass: HomeAssistant, diff --git a/homeassistant/components/xiaomi_ble/config_flow.py b/homeassistant/components/xiaomi_ble/config_flow.py index e7c4a3e1f8ca..f352f43d0bfc 100644 --- a/homeassistant/components/xiaomi_ble/config_flow.py +++ b/homeassistant/components/xiaomi_ble/config_flow.py @@ -1,6 +1,7 @@ """Config flow for Xiaomi Bluetooth integration.""" from __future__ import annotations +import asyncio import dataclasses from typing import Any @@ -12,6 +13,7 @@ from homeassistant.components import onboarding from homeassistant.components.bluetooth import ( BluetoothServiceInfo, async_discovered_service_info, + async_process_advertisements, ) from homeassistant.config_entries import ConfigFlow from homeassistant.const import CONF_ADDRESS @@ -19,6 +21,9 @@ from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN +# How long to wait for additional advertisement packets if we don't have the right ones +ADDITIONAL_DISCOVERY_TIMEOUT = 5 + @dataclasses.dataclass class Discovery: @@ -44,6 +49,24 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): self._discovered_device: DeviceData | None = None self._discovered_devices: dict[str, Discovery] = {} + async def _async_wait_for_full_advertisement( + self, discovery_info: BluetoothServiceInfo, device: DeviceData + ) -> BluetoothServiceInfo: + """Sometimes first advertisement we receive is blank or incomplete. Wait until we get a useful one.""" + if not device.pending: + return discovery_info + + def _process_more_advertisements(service_info: BluetoothServiceInfo) -> bool: + device.update(service_info) + return not device.pending + + return await async_process_advertisements( + self.hass, + _process_more_advertisements, + {"address": discovery_info.address}, + ADDITIONAL_DISCOVERY_TIMEOUT, + ) + async def async_step_bluetooth( self, discovery_info: BluetoothServiceInfo ) -> FlowResult: @@ -53,6 +76,16 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): device = DeviceData() if not device.supported(discovery_info): return self.async_abort(reason="not_supported") + + # Wait until we have received enough information about this device to detect its encryption type + try: + discovery_info = await self._async_wait_for_full_advertisement( + discovery_info, device + ) + except asyncio.TimeoutError: + # If we don't see a valid packet within the timeout then this device is not supported. + return self.async_abort(reason="not_supported") + self._discovery_info = discovery_info self._discovered_device = device @@ -161,13 +194,20 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(address, raise_on_progress=False) discovery = self._discovered_devices[address] + # Wait until we have received enough information about this device to detect its encryption type + try: + self._discovery_info = await self._async_wait_for_full_advertisement( + discovery.discovery_info, discovery.device + ) + except asyncio.TimeoutError: + # If we don't see a valid packet within the timeout then this device is not supported. + return self.async_abort(reason="not_supported") + if discovery.device.encryption_scheme == EncryptionScheme.MIBEACON_LEGACY: - self._discovery_info = discovery.discovery_info self.context["title_placeholders"] = {"name": discovery.title} return await self.async_step_get_encryption_key_legacy() if discovery.device.encryption_scheme == EncryptionScheme.MIBEACON_4_5: - self._discovery_info = discovery.discovery_info self.context["title_placeholders"] = {"name": discovery.title} return await self.async_step_get_encryption_key_4_5() diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 2e1a502ca69b..415122917497 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -8,7 +8,7 @@ "service_uuid": "0000fe95-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["xiaomi-ble==0.6.1"], + "requirements": ["xiaomi-ble==0.6.2"], "dependencies": ["bluetooth"], "codeowners": ["@Jc2k", "@Ernst79"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 24a8a5ee1bdf..ed487c9b84cb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2470,7 +2470,7 @@ xbox-webapi==2.0.11 xboxapi==2.0.1 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.6.1 +xiaomi-ble==0.6.2 # homeassistant.components.knx xknx==0.22.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 81e192ce360d..73b2ad3adc0c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1662,7 +1662,7 @@ wolf_smartset==0.1.11 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.6.1 +xiaomi-ble==0.6.2 # homeassistant.components.knx xknx==0.22.0 diff --git a/tests/components/bluetooth/test_init.py b/tests/components/bluetooth/test_init.py index 66a9ed396ec9..0664f82dbab3 100644 --- a/tests/components/bluetooth/test_init.py +++ b/tests/components/bluetooth/test_init.py @@ -13,6 +13,7 @@ from homeassistant.components.bluetooth import ( UNAVAILABLE_TRACK_SECONDS, BluetoothChange, BluetoothServiceInfo, + async_process_advertisements, async_track_unavailable, models, ) @@ -22,7 +23,7 @@ from homeassistant.components.bluetooth.const import ( ) from homeassistant.config_entries import ConfigEntryState from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -819,6 +820,92 @@ async def test_register_callback_by_address( assert service_info.manufacturer_id == 89 +async def test_process_advertisements_bail_on_good_advertisement( + hass: HomeAssistant, mock_bleak_scanner_start, enable_bluetooth +): + """Test as soon as we see a 'good' advertisement we return it.""" + done = asyncio.Future() + + def _callback(service_info: BluetoothServiceInfo) -> bool: + done.set_result(None) + return len(service_info.service_data) > 0 + + handle = hass.async_create_task( + async_process_advertisements( + hass, _callback, {"address": "aa:44:33:11:23:45"}, 5 + ) + ) + + while not done.done(): + device = BLEDevice("aa:44:33:11:23:45", "wohand") + adv = AdvertisementData( + local_name="wohand", + service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51a"], + manufacturer_data={89: b"\xd8.\xad\xcd\r\x85"}, + service_data={"00000d00-0000-1000-8000-00805f9b34fa": b"H\x10c"}, + ) + + _get_underlying_scanner()._callback(device, adv) + await asyncio.sleep(0) + + result = await handle + assert result.name == "wohand" + + +async def test_process_advertisements_ignore_bad_advertisement( + hass: HomeAssistant, mock_bleak_scanner_start, enable_bluetooth +): + """Check that we ignore bad advertisements.""" + done = asyncio.Event() + return_value = asyncio.Event() + + device = BLEDevice("aa:44:33:11:23:45", "wohand") + adv = AdvertisementData( + local_name="wohand", + service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51a"], + manufacturer_data={89: b"\xd8.\xad\xcd\r\x85"}, + service_data={"00000d00-0000-1000-8000-00805f9b34fa": b""}, + ) + + def _callback(service_info: BluetoothServiceInfo) -> bool: + done.set() + return return_value.is_set() + + handle = hass.async_create_task( + async_process_advertisements( + hass, _callback, {"address": "aa:44:33:11:23:45"}, 5 + ) + ) + + # The goal of this loop is to make sure that async_process_advertisements sees at least one + # callback that returns False + while not done.is_set(): + _get_underlying_scanner()._callback(device, adv) + await asyncio.sleep(0) + + # Set the return value and mutate the advertisement + # Check that scan ends and correct advertisement data is returned + return_value.set() + adv.service_data["00000d00-0000-1000-8000-00805f9b34fa"] = b"H\x10c" + _get_underlying_scanner()._callback(device, adv) + await asyncio.sleep(0) + + result = await handle + assert result.service_data["00000d00-0000-1000-8000-00805f9b34fa"] == b"H\x10c" + + +async def test_process_advertisements_timeout( + hass, mock_bleak_scanner_start, enable_bluetooth +): + """Test we timeout if no advertisements at all.""" + + def _callback(service_info: BluetoothServiceInfo) -> bool: + return False + + with pytest.raises(asyncio.TimeoutError): + await async_process_advertisements(hass, _callback, {}, 0) + + async def test_wrapped_instance_with_filter( hass, mock_bleak_scanner_start, enable_bluetooth ): diff --git a/tests/components/xiaomi_ble/__init__.py b/tests/components/xiaomi_ble/__init__.py index a6269a02d121..1dd1eeed65a2 100644 --- a/tests/components/xiaomi_ble/__init__.py +++ b/tests/components/xiaomi_ble/__init__.py @@ -61,6 +61,18 @@ YLKG07YL_SERVICE_INFO = BluetoothServiceInfo( source="local", ) +MISSING_PAYLOAD_ENCRYPTED = BluetoothServiceInfo( + name="LYWSD02MMC", + address="A4:C1:38:56:53:84", + rssi=-56, + manufacturer_data={}, + service_data={ + "0000fe95-0000-1000-8000-00805f9b34fb": b"0X[\x05\x02\x84\x53\x568\xc1\xa4\x08", + }, + service_uuids=["0000fe95-0000-1000-8000-00805f9b34fb"], + source="local", +) + def make_advertisement(address: str, payload: bytes) -> BluetoothServiceInfo: """Make a dummy advertisement.""" diff --git a/tests/components/xiaomi_ble/test_config_flow.py b/tests/components/xiaomi_ble/test_config_flow.py index d4b4300d2c18..b424228cc6c0 100644 --- a/tests/components/xiaomi_ble/test_config_flow.py +++ b/tests/components/xiaomi_ble/test_config_flow.py @@ -1,5 +1,6 @@ """Test the Xiaomi config flow.""" +import asyncio from unittest.mock import patch from homeassistant import config_entries @@ -9,9 +10,11 @@ from homeassistant.data_entry_flow import FlowResultType from . import ( JTYJGD03MI_SERVICE_INFO, LYWSDCGQ_SERVICE_INFO, + MISSING_PAYLOAD_ENCRYPTED, MMC_T201_1_SERVICE_INFO, NOT_SENSOR_PUSH_SERVICE_INFO, YLKG07YL_SERVICE_INFO, + make_advertisement, ) from tests.common import MockConfigEntry @@ -38,6 +41,57 @@ async def test_async_step_bluetooth_valid_device(hass): assert result2["result"].unique_id == "00:81:F9:DD:6F:C1" +async def test_async_step_bluetooth_valid_device_but_missing_payload(hass): + """Test discovery via bluetooth with a valid device but missing payload.""" + with patch( + "homeassistant.components.xiaomi_ble.config_flow.async_process_advertisements", + side_effect=asyncio.TimeoutError(), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_BLUETOOTH}, + data=MISSING_PAYLOAD_ENCRYPTED, + ) + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "not_supported" + + +async def test_async_step_bluetooth_valid_device_but_missing_payload_then_full(hass): + """Test discovering a valid device. Payload is too short, but later we get full one.""" + + async def _async_process_advertisements(_hass, _callback, _matcher, _timeout): + service_info = make_advertisement( + "A4:C1:38:56:53:84", + b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e", + ) + assert _callback(service_info) + return service_info + + with patch( + "homeassistant.components.xiaomi_ble.config_flow.async_process_advertisements", + _async_process_advertisements, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_BLUETOOTH}, + data=MISSING_PAYLOAD_ENCRYPTED, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "get_encryption_key_4_5" + + with patch( + "homeassistant.components.xiaomi_ble.async_setup_entry", return_value=True + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"bindkey": "a115210eed7a88e50ad52662e732a9fb"}, + ) + + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert result2["data"] == {"bindkey": "a115210eed7a88e50ad52662e732a9fb"} + assert result2["result"].unique_id == "A4:C1:38:56:53:84" + + async def test_async_step_bluetooth_during_onboarding(hass): """Test discovery via bluetooth during onboarding.""" with patch( @@ -287,6 +341,75 @@ async def test_async_step_user_with_found_devices(hass): assert result2["result"].unique_id == "58:2D:34:35:93:21" +async def test_async_step_user_short_payload(hass): + """Test setup from service info cache with devices found but short payloads.""" + with patch( + "homeassistant.components.xiaomi_ble.config_flow.async_discovered_service_info", + return_value=[MISSING_PAYLOAD_ENCRYPTED], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + with patch( + "homeassistant.components.xiaomi_ble.config_flow.async_process_advertisements", + side_effect=asyncio.TimeoutError(), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"address": "A4:C1:38:56:53:84"}, + ) + assert result2["type"] == FlowResultType.ABORT + assert result2["reason"] == "not_supported" + + +async def test_async_step_user_short_payload_then_full(hass): + """Test setup from service info cache with devices found.""" + with patch( + "homeassistant.components.xiaomi_ble.config_flow.async_discovered_service_info", + return_value=[MISSING_PAYLOAD_ENCRYPTED], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + ) + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "user" + + async def _async_process_advertisements(_hass, _callback, _matcher, _timeout): + service_info = make_advertisement( + "A4:C1:38:56:53:84", + b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e", + ) + assert _callback(service_info) + return service_info + + with patch( + "homeassistant.components.xiaomi_ble.config_flow.async_process_advertisements", + _async_process_advertisements, + ): + result1 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"address": "A4:C1:38:56:53:84"}, + ) + assert result1["type"] == FlowResultType.FORM + assert result1["step_id"] == "get_encryption_key_4_5" + + with patch( + "homeassistant.components.xiaomi_ble.async_setup_entry", return_value=True + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"bindkey": "a115210eed7a88e50ad52662e732a9fb"}, + ) + + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert result2["title"] == "LYWSD02MMC" + assert result2["data"] == {"bindkey": "a115210eed7a88e50ad52662e732a9fb"} + + async def test_async_step_user_with_found_devices_v4_encryption(hass): """Test setup from service info cache with devices found, with v4 encryption.""" with patch( From c469bdea75badecf2bfbf4bc43260ae0cc1fccb2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Jul 2022 22:36:11 +0200 Subject: [PATCH 22/83] Fix AdGuard Home rules count sensor (#75879) --- homeassistant/components/adguard/sensor.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/adguard/sensor.py b/homeassistant/components/adguard/sensor.py index 07a483f03c4c..86104d15ef2d 100644 --- a/homeassistant/components/adguard/sensor.py +++ b/homeassistant/components/adguard/sensor.py @@ -26,7 +26,7 @@ PARALLEL_UPDATES = 4 class AdGuardHomeEntityDescriptionMixin: """Mixin for required keys.""" - value_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, int | float]]] + value_fn: Callable[[AdGuardHome], Coroutine[Any, Any, int | float]] @dataclass @@ -42,56 +42,56 @@ SENSORS: tuple[AdGuardHomeEntityDescription, ...] = ( name="DNS queries", icon="mdi:magnify", native_unit_of_measurement="queries", - value_fn=lambda adguard: adguard.stats.dns_queries, + value_fn=lambda adguard: adguard.stats.dns_queries(), ), AdGuardHomeEntityDescription( key="blocked_filtering", name="DNS queries blocked", icon="mdi:magnify-close", native_unit_of_measurement="queries", - value_fn=lambda adguard: adguard.stats.blocked_filtering, + value_fn=lambda adguard: adguard.stats.blocked_filtering(), ), AdGuardHomeEntityDescription( key="blocked_percentage", name="DNS queries blocked ratio", icon="mdi:magnify-close", native_unit_of_measurement=PERCENTAGE, - value_fn=lambda adguard: adguard.stats.blocked_percentage, + value_fn=lambda adguard: adguard.stats.blocked_percentage(), ), AdGuardHomeEntityDescription( key="blocked_parental", name="Parental control blocked", icon="mdi:human-male-girl", native_unit_of_measurement="requests", - value_fn=lambda adguard: adguard.stats.replaced_parental, + value_fn=lambda adguard: adguard.stats.replaced_parental(), ), AdGuardHomeEntityDescription( key="blocked_safebrowsing", name="Safe browsing blocked", icon="mdi:shield-half-full", native_unit_of_measurement="requests", - value_fn=lambda adguard: adguard.stats.replaced_safebrowsing, + value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(), ), AdGuardHomeEntityDescription( key="enforced_safesearch", name="Safe searches enforced", icon="mdi:shield-search", native_unit_of_measurement="requests", - value_fn=lambda adguard: adguard.stats.replaced_safesearch, + value_fn=lambda adguard: adguard.stats.replaced_safesearch(), ), AdGuardHomeEntityDescription( key="average_speed", name="Average processing speed", icon="mdi:speedometer", native_unit_of_measurement=TIME_MILLISECONDS, - value_fn=lambda adguard: adguard.stats.avg_processing_time, + value_fn=lambda adguard: adguard.stats.avg_processing_time(), ), AdGuardHomeEntityDescription( key="rules_count", name="Rules count", icon="mdi:counter", native_unit_of_measurement="rules", - value_fn=lambda adguard: adguard.stats.avg_processing_time, + value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False), entity_registry_enabled_default=False, ), ) @@ -144,7 +144,7 @@ class AdGuardHomeSensor(AdGuardHomeEntity, SensorEntity): async def _adguard_update(self) -> None: """Update AdGuard Home entity.""" - value = await self.entity_description.value_fn(self.adguard)() + value = await self.entity_description.value_fn(self.adguard) self._attr_native_value = value if isinstance(value, float): self._attr_native_value = f"{value:.2f}" From 97c6c949e71a1978f87ac79abe74fd0fdd5c2418 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 28 Jul 2022 17:07:32 -1000 Subject: [PATCH 23/83] Fix incorrect manufacturer_id for govee 5182 model (#75899) --- homeassistant/components/govee_ble/manifest.json | 6 +++++- homeassistant/generated/bluetooth.py | 5 +++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/govee_ble/manifest.json b/homeassistant/components/govee_ble/manifest.json index aa86215da597..270858d04d4b 100644 --- a/homeassistant/components/govee_ble/manifest.json +++ b/homeassistant/components/govee_ble/manifest.json @@ -18,9 +18,13 @@ { "manufacturer_id": 14474, "service_uuid": "00008151-0000-1000-8000-00805f9b34fb" + }, + { + "manufacturer_id": 10032, + "service_uuid": "00008251-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["govee-ble==0.12.3"], + "requirements": ["govee-ble==0.12.4"], "dependencies": ["bluetooth"], "codeowners": ["@bdraco"], "iot_class": "local_push" diff --git a/homeassistant/generated/bluetooth.py b/homeassistant/generated/bluetooth.py index 5dde90f1f7a1..8d92d6eab4a6 100644 --- a/homeassistant/generated/bluetooth.py +++ b/homeassistant/generated/bluetooth.py @@ -34,6 +34,11 @@ BLUETOOTH: list[dict[str, str | int | list[int]]] = [ "manufacturer_id": 14474, "service_uuid": "00008151-0000-1000-8000-00805f9b34fb" }, + { + "domain": "govee_ble", + "manufacturer_id": 10032, + "service_uuid": "00008251-0000-1000-8000-00805f9b34fb" + }, { "domain": "homekit_controller", "manufacturer_id": 76, diff --git a/requirements_all.txt b/requirements_all.txt index ed487c9b84cb..3bfd3880eeeb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ googlemaps==2.5.1 goslide-api==0.5.1 # homeassistant.components.govee_ble -govee-ble==0.12.3 +govee-ble==0.12.4 # homeassistant.components.remote_rpi_gpio gpiozero==1.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 73b2ad3adc0c..6ce69ed96783 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -561,7 +561,7 @@ google-nest-sdm==2.0.0 googlemaps==2.5.1 # homeassistant.components.govee_ble -govee-ble==0.12.3 +govee-ble==0.12.4 # homeassistant.components.gree greeclimate==1.2.0 From dfd503cc1aaf17aa7e95f6950814fec11a710dff Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 29 Jul 2022 00:53:08 -0700 Subject: [PATCH 24/83] Fix Roon media player being set up before hass.data set up (#75904) --- homeassistant/components/roon/__init__.py | 11 ++++++++++- homeassistant/components/roon/server.py | 9 +-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/roon/__init__.py b/homeassistant/components/roon/__init__.py index 9e5c38f02116..9969b6948950 100644 --- a/homeassistant/components/roon/__init__.py +++ b/homeassistant/components/roon/__init__.py @@ -1,12 +1,14 @@ """Roon (www.roonlabs.com) component.""" from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr from .const import CONF_ROON_NAME, DOMAIN from .server import RoonServer +PLATFORMS = [Platform.MEDIA_PLAYER] + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a roonserver from a config entry.""" @@ -28,10 +30,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: manufacturer="Roonlabs", name=f"Roon Core ({name})", ) + + # initialize media_player platform + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" + if not await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + return False + roonserver = hass.data[DOMAIN].pop(entry.entry_id) return await roonserver.async_reset() diff --git a/homeassistant/components/roon/server.py b/homeassistant/components/roon/server.py index df9dec3d9afd..997db44583df 100644 --- a/homeassistant/components/roon/server.py +++ b/homeassistant/components/roon/server.py @@ -4,7 +4,7 @@ import logging from roonapi import RoonApi, RoonDiscovery -from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, Platform +from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util.dt import utcnow @@ -13,7 +13,6 @@ from .const import CONF_ROON_ID, ROON_APPINFO _LOGGER = logging.getLogger(__name__) INITIAL_SYNC_INTERVAL = 5 FULL_SYNC_INTERVAL = 30 -PLATFORMS = [Platform.MEDIA_PLAYER] class RoonServer: @@ -53,7 +52,6 @@ class RoonServer: (host, port) = get_roon_host() return RoonApi(ROON_APPINFO, token, host, port, blocking_init=True) - hass = self.hass core_id = self.config_entry.data.get(CONF_ROON_ID) self.roonapi = await self.hass.async_add_executor_job(get_roon_api) @@ -67,11 +65,6 @@ class RoonServer: core_id if core_id is not None else self.config_entry.data[CONF_HOST] ) - # initialize media_player platform - await hass.config_entries.async_forward_entry_setups( - self.config_entry, PLATFORMS - ) - # Initialize Roon background polling asyncio.create_task(self.async_do_loop()) From f4defb660be0f407bb48d7f0c9fbebdc4544bb96 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 29 Jul 2022 11:57:19 +0200 Subject: [PATCH 25/83] Fix broken Yale lock (#75918) Yale fix lock --- homeassistant/components/yale_smart_alarm/lock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/yale_smart_alarm/lock.py b/homeassistant/components/yale_smart_alarm/lock.py index a97a98a2afb4..8f9ed6c9ce15 100644 --- a/homeassistant/components/yale_smart_alarm/lock.py +++ b/homeassistant/components/yale_smart_alarm/lock.py @@ -46,6 +46,7 @@ class YaleDoorlock(YaleEntity, LockEntity): """Initialize the Yale Lock Device.""" super().__init__(coordinator, data) self._attr_code_format = f"^\\d{code_format}$" + self.lock_name = data["name"] async def async_unlock(self, **kwargs: Any) -> None: """Send unlock command.""" @@ -65,7 +66,7 @@ class YaleDoorlock(YaleEntity, LockEntity): try: get_lock = await self.hass.async_add_executor_job( - self.coordinator.yale.lock_api.get, self._attr_name + self.coordinator.yale.lock_api.get, self.lock_name ) if command == "locked": lock_state = await self.hass.async_add_executor_job( From 48b97a1f2dcaaa9483207774cfadb8f2ee820024 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 29 Jul 2022 13:41:22 +0200 Subject: [PATCH 26/83] Bumped version to 2022.8.0b2 --- 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 dfa12ccd8244..a3753d36d078 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b1" +PATCH_VERSION: Final = "0b2" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index f5b35dd63dc7..558f366b357f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0b1" +version = "2022.8.0b2" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 176d44190e01dcdd3b201c541dea5fbb04b7fef7 Mon Sep 17 00:00:00 2001 From: Jan Stienstra <65826735+j-stienstra@users.noreply.github.com> Date: Fri, 29 Jul 2022 19:11:53 +0200 Subject: [PATCH 27/83] Fix incorrect check for media source (#75880) * Fix incorrect check for media source * Update homeassistant/components/jellyfin/media_source.py Co-authored-by: Franck Nijhof Co-authored-by: Paulus Schoutsen Co-authored-by: Franck Nijhof --- homeassistant/components/jellyfin/media_source.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/jellyfin/media_source.py b/homeassistant/components/jellyfin/media_source.py index 879f4a4d4c80..8a09fd8d5522 100644 --- a/homeassistant/components/jellyfin/media_source.py +++ b/homeassistant/components/jellyfin/media_source.py @@ -363,14 +363,18 @@ class JellyfinSource(MediaSource): def _media_mime_type(media_item: dict[str, Any]) -> str: """Return the mime type of a media item.""" - if not media_item[ITEM_KEY_MEDIA_SOURCES]: + if not media_item.get(ITEM_KEY_MEDIA_SOURCES): raise BrowseError("Unable to determine mime type for item without media source") media_source = media_item[ITEM_KEY_MEDIA_SOURCES][0] + + if MEDIA_SOURCE_KEY_PATH not in media_source: + raise BrowseError("Unable to determine mime type for media source without path") + path = media_source[MEDIA_SOURCE_KEY_PATH] mime_type, _ = mimetypes.guess_type(path) - if mime_type is not None: - return mime_type + if mime_type is None: + raise BrowseError(f"Unable to determine mime type for path {path}") - raise BrowseError(f"Unable to determine mime type for path {path}") + return mime_type From d7827d99020c77c986905637928d0f0e6dd7f6c3 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 29 Jul 2022 18:56:19 +0200 Subject: [PATCH 28/83] Fix SimplePush repairs issue (#75922) --- homeassistant/components/simplepush/notify.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/simplepush/notify.py b/homeassistant/components/simplepush/notify.py index 358d95c770ac..2e58748f3234 100644 --- a/homeassistant/components/simplepush/notify.py +++ b/homeassistant/components/simplepush/notify.py @@ -43,17 +43,16 @@ async def async_get_service( discovery_info: DiscoveryInfoType | None = None, ) -> SimplePushNotificationService | None: """Get the Simplepush notification service.""" - async_create_issue( - hass, - DOMAIN, - "deprecated_yaml", - breaks_in_ha_version="2022.9.0", - is_fixable=False, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml", - ) - if discovery_info is None: + async_create_issue( + hass, + DOMAIN, + "deprecated_yaml", + breaks_in_ha_version="2022.9.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + ) hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=config From c3c5442467be7179f4d5c3c8efc3f975ead576a8 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Fri, 29 Jul 2022 13:28:39 +0100 Subject: [PATCH 29/83] Fix xiaomi_ble discovery for devices that don't put the fe95 uuid in service_uuids (#75923) --- homeassistant/components/xiaomi_ble/manifest.json | 2 +- homeassistant/generated/bluetooth.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 415122917497..0d97dcbedf84 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/xiaomi_ble", "bluetooth": [ { - "service_uuid": "0000fe95-0000-1000-8000-00805f9b34fb" + "service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb" } ], "requirements": ["xiaomi-ble==0.6.2"], diff --git a/homeassistant/generated/bluetooth.py b/homeassistant/generated/bluetooth.py index 8d92d6eab4a6..2cbaebb6074e 100644 --- a/homeassistant/generated/bluetooth.py +++ b/homeassistant/generated/bluetooth.py @@ -80,6 +80,6 @@ BLUETOOTH: list[dict[str, str | int | list[int]]] = [ }, { "domain": "xiaomi_ble", - "service_uuid": "0000fe95-0000-1000-8000-00805f9b34fb" + "service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb" } ] From 241ffe07b977c245abb1849817fa62651618d6b2 Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Fri, 29 Jul 2022 18:54:49 +0200 Subject: [PATCH 30/83] Update xknx to 0.22.1 (#75932) --- homeassistant/components/knx/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 4197cb76209a..266eceaacee1 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -3,7 +3,7 @@ "name": "KNX", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.22.0"], + "requirements": ["xknx==0.22.1"], "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], "quality_scale": "platinum", "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 3bfd3880eeeb..b7b3ed89b270 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2473,7 +2473,7 @@ xboxapi==2.0.1 xiaomi-ble==0.6.2 # homeassistant.components.knx -xknx==0.22.0 +xknx==0.22.1 # homeassistant.components.bluesound # homeassistant.components.fritz diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6ce69ed96783..d04b537c9fd1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1665,7 +1665,7 @@ xbox-webapi==2.0.11 xiaomi-ble==0.6.2 # homeassistant.components.knx -xknx==0.22.0 +xknx==0.22.1 # homeassistant.components.bluesound # homeassistant.components.fritz From 0f0b51bee71ff87a39e9895645f6bfca236644cd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 28 Jul 2022 22:26:37 -0700 Subject: [PATCH 31/83] Move some bleak imports to be behind TYPE_CHECKING (#75894) --- .../components/bluetooth_le_tracker/device_tracker.py | 5 ++++- homeassistant/components/fjaraskupan/__init__.py | 8 ++++++-- homeassistant/components/switchbot/coordinator.py | 10 ++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index f85cc2bad0a1..e42d19ac547b 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -5,10 +5,10 @@ import asyncio from collections.abc import Awaitable, Callable from datetime import datetime, timedelta import logging +from typing import TYPE_CHECKING from uuid import UUID from bleak import BleakClient, BleakError -from bleak.backends.device import BLEDevice import voluptuous as vol from homeassistant.components import bluetooth @@ -31,6 +31,9 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType import homeassistant.util.dt as dt_util +if TYPE_CHECKING: + from bleak.backends.device import BLEDevice + _LOGGER = logging.getLogger(__name__) # Base UUID: 00000000-0000-1000-8000-00805F9B34FB diff --git a/homeassistant/components/fjaraskupan/__init__.py b/homeassistant/components/fjaraskupan/__init__.py index 85e95db5513d..36608fb026d3 100644 --- a/homeassistant/components/fjaraskupan/__init__.py +++ b/homeassistant/components/fjaraskupan/__init__.py @@ -5,10 +5,9 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import timedelta import logging +from typing import TYPE_CHECKING from bleak import BleakScanner -from bleak.backends.device import BLEDevice -from bleak.backends.scanner import AdvertisementData from fjaraskupan import Device, State, device_filter from homeassistant.config_entries import ConfigEntry @@ -24,6 +23,11 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import DISPATCH_DETECTION, DOMAIN +if TYPE_CHECKING: + from bleak.backends.device import BLEDevice + from bleak.backends.scanner import AdvertisementData + + PLATFORMS = [ Platform.BINARY_SENSOR, Platform.FAN, diff --git a/homeassistant/components/switchbot/coordinator.py b/homeassistant/components/switchbot/coordinator.py index 31f7f2d3992f..f461a3e0f4cd 100644 --- a/homeassistant/components/switchbot/coordinator.py +++ b/homeassistant/components/switchbot/coordinator.py @@ -3,11 +3,9 @@ from __future__ import annotations import asyncio import logging -from typing import Any, cast +from typing import TYPE_CHECKING, Any, cast -from bleak.backends.device import BLEDevice import switchbot -from switchbot import parse_advertisement_data from homeassistant.components import bluetooth from homeassistant.components.bluetooth.passive_update_coordinator import ( @@ -15,6 +13,10 @@ from homeassistant.components.bluetooth.passive_update_coordinator import ( ) from homeassistant.core import HomeAssistant, callback +if TYPE_CHECKING: + from bleak.backends.device import BLEDevice + + _LOGGER = logging.getLogger(__name__) @@ -52,7 +54,7 @@ class SwitchbotDataUpdateCoordinator(PassiveBluetoothDataUpdateCoordinator): """Handle a Bluetooth event.""" super()._async_handle_bluetooth_event(service_info, change) discovery_info_bleak = cast(bluetooth.BluetoothServiceInfoBleak, service_info) - if adv := parse_advertisement_data( + if adv := switchbot.parse_advertisement_data( discovery_info_bleak.device, discovery_info_bleak.advertisement ): self.data = flatten_sensors_data(adv.data) From 26c475d3dc51f39a588c540021a7f8f19f2f84ea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 Jul 2022 14:53:33 -1000 Subject: [PATCH 32/83] Update to bleak 0.15 (#75941) --- .../components/bluetooth/__init__.py | 29 ++++++++------- .../components/bluetooth/manifest.json | 2 +- homeassistant/components/bluetooth/models.py | 34 ++++++++++++----- .../bluetooth/passive_update_coordinator.py | 8 ++-- .../bluetooth/passive_update_processor.py | 17 +++++---- .../bluetooth/update_coordinator.py | 9 +++-- homeassistant/components/bluetooth/usage.py | 5 ++- .../bluetooth_le_tracker/device_tracker.py | 18 ++++----- .../components/govee_ble/__init__.py | 2 + .../components/govee_ble/config_flow.py | 6 +-- .../homekit_controller/config_flow.py | 7 +++- .../components/homekit_controller/utils.py | 2 +- homeassistant/components/inkbird/__init__.py | 5 +-- .../components/inkbird/config_flow.py | 6 +-- homeassistant/components/moat/__init__.py | 5 +-- homeassistant/components/moat/config_flow.py | 6 +-- .../components/sensorpush/__init__.py | 5 +-- .../components/sensorpush/config_flow.py | 6 +-- .../components/switchbot/config_flow.py | 8 ++-- .../components/switchbot/coordinator.py | 11 +++--- .../components/xiaomi_ble/__init__.py | 5 +-- .../components/xiaomi_ble/config_flow.py | 20 ++++++---- homeassistant/config_entries.py | 4 +- homeassistant/helpers/config_entry_flow.py | 4 +- .../helpers/service_info/bluetooth.py | 1 + homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/bluetooth/test_init.py | 21 +++++++++-- .../test_passive_update_coordinator.py | 29 ++++++++++----- .../test_passive_update_processor.py | 37 ++++++++++--------- .../test_device_tracker.py | 23 +++++++++--- tests/components/fjaraskupan/conftest.py | 6 +++ tests/components/govee_ble/test_sensor.py | 2 +- tests/components/inkbird/test_sensor.py | 2 +- tests/components/moat/test_sensor.py | 2 +- tests/components/sensorpush/test_sensor.py | 2 +- .../components/xiaomi_ble/test_config_flow.py | 8 +++- tests/components/xiaomi_ble/test_sensor.py | 12 +++--- 39 files changed, 223 insertions(+), 152 deletions(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index eb8e31baef05..9adaac84333b 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -8,7 +8,7 @@ from dataclasses import dataclass from datetime import datetime, timedelta from enum import Enum import logging -from typing import Final, Union +from typing import Final import async_timeout from bleak import BleakError @@ -96,12 +96,8 @@ SCANNING_MODE_TO_BLEAK = { BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT") -BluetoothCallback = Callable[ - [Union[BluetoothServiceInfoBleak, BluetoothServiceInfo], BluetoothChange], None -] -ProcessAdvertisementCallback = Callable[ - [Union[BluetoothServiceInfoBleak, BluetoothServiceInfo]], bool -] +BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None] +ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool] @hass_callback @@ -157,9 +153,15 @@ def async_register_callback( hass: HomeAssistant, callback: BluetoothCallback, match_dict: BluetoothCallbackMatcher | None, + mode: BluetoothScanningMode, ) -> Callable[[], None]: """Register to receive a callback on bluetooth change. + mode is currently not used as we only support active scanning. + Passive scanning will be available in the future. The flag + is required to be present to avoid a future breaking change + when we support passive scanning. + Returns a callback that can be used to cancel the registration. """ manager: BluetoothManager = hass.data[DOMAIN] @@ -170,19 +172,20 @@ async def async_process_advertisements( hass: HomeAssistant, callback: ProcessAdvertisementCallback, match_dict: BluetoothCallbackMatcher, + mode: BluetoothScanningMode, timeout: int, -) -> BluetoothServiceInfo: +) -> BluetoothServiceInfoBleak: """Process advertisements until callback returns true or timeout expires.""" - done: Future[BluetoothServiceInfo] = Future() + done: Future[BluetoothServiceInfoBleak] = Future() @hass_callback def _async_discovered_device( - service_info: BluetoothServiceInfo, change: BluetoothChange + service_info: BluetoothServiceInfoBleak, change: BluetoothChange ) -> None: if callback(service_info): done.set_result(service_info) - unload = async_register_callback(hass, _async_discovered_device, match_dict) + unload = async_register_callback(hass, _async_discovered_device, match_dict, mode) try: async with async_timeout.timeout(timeout): @@ -333,7 +336,7 @@ class BluetoothManager: ) try: async with async_timeout.timeout(START_TIMEOUT): - await self.scanner.start() + await self.scanner.start() # type: ignore[no-untyped-call] except asyncio.TimeoutError as ex: self._cancel_device_detected() raise ConfigEntryNotReady( @@ -500,7 +503,7 @@ class BluetoothManager: self._cancel_unavailable_tracking = None if self.scanner: try: - await self.scanner.stop() + await self.scanner.stop() # type: ignore[no-untyped-call] except BleakError as ex: # This is not fatal, and they may want to reload # the config entry to restart the scanner if they diff --git a/homeassistant/components/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index c6ca8b11400f..f215e8fa161a 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/bluetooth", "dependencies": ["websocket_api"], "quality_scale": "internal", - "requirements": ["bleak==0.14.3", "bluetooth-adapters==0.1.2"], + "requirements": ["bleak==0.15.0", "bluetooth-adapters==0.1.2"], "codeowners": ["@bdraco"], "config_flow": true, "iot_class": "local_push" diff --git a/homeassistant/components/bluetooth/models.py b/homeassistant/components/bluetooth/models.py index 408e06988794..6f814c7b66ba 100644 --- a/homeassistant/components/bluetooth/models.py +++ b/homeassistant/components/bluetooth/models.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio import contextlib import logging -from typing import Any, Final, cast +from typing import Any, Final from bleak import BleakScanner from bleak.backends.device import BLEDevice @@ -32,7 +32,7 @@ def _dispatch_callback( """Dispatch the callback.""" if not callback: # Callback destroyed right before being called, ignore - return + return # type: ignore[unreachable] if (uuids := filters.get(FILTER_UUIDS)) and not uuids.intersection( advertisement_data.service_uuids @@ -45,7 +45,7 @@ def _dispatch_callback( _LOGGER.exception("Error in callback: %s", callback) -class HaBleakScanner(BleakScanner): # type: ignore[misc] +class HaBleakScanner(BleakScanner): """BleakScanner that cannot be stopped.""" def __init__( # pylint: disable=super-init-not-called @@ -106,16 +106,29 @@ class HaBleakScanner(BleakScanner): # type: ignore[misc] _dispatch_callback(*callback_filters, device, advertisement_data) -class HaBleakScannerWrapper(BaseBleakScanner): # type: ignore[misc] +class HaBleakScannerWrapper(BaseBleakScanner): """A wrapper that uses the single instance.""" - def __init__(self, *args: Any, **kwargs: Any) -> None: + def __init__( + self, + *args: Any, + detection_callback: AdvertisementDataCallback | None = None, + service_uuids: list[str] | None = None, + **kwargs: Any, + ) -> None: """Initialize the BleakScanner.""" self._detection_cancel: CALLBACK_TYPE | None = None self._mapped_filters: dict[str, set[str]] = {} self._adv_data_callback: AdvertisementDataCallback | None = None - self._map_filters(*args, **kwargs) - super().__init__(*args, **kwargs) + remapped_kwargs = { + "detection_callback": detection_callback, + "service_uuids": service_uuids or [], + **kwargs, + } + self._map_filters(*args, **remapped_kwargs) + super().__init__( + detection_callback=detection_callback, service_uuids=service_uuids or [] + ) async def stop(self, *args: Any, **kwargs: Any) -> None: """Stop scanning for devices.""" @@ -153,9 +166,11 @@ class HaBleakScannerWrapper(BaseBleakScanner): # type: ignore[misc] def discovered_devices(self) -> list[BLEDevice]: """Return a list of discovered devices.""" assert HA_BLEAK_SCANNER is not None - return cast(list[BLEDevice], HA_BLEAK_SCANNER.discovered_devices) + return HA_BLEAK_SCANNER.discovered_devices - def register_detection_callback(self, callback: AdvertisementDataCallback) -> None: + def register_detection_callback( + self, callback: AdvertisementDataCallback | None + ) -> None: """Register a callback that is called when a device is discovered or has a property changed. This method takes the callback and registers it with the long running @@ -171,6 +186,7 @@ class HaBleakScannerWrapper(BaseBleakScanner): # type: ignore[misc] self._cancel_callback() super().register_detection_callback(self._adv_data_callback) assert HA_BLEAK_SCANNER is not None + assert self._callback is not None self._detection_cancel = HA_BLEAK_SCANNER.async_register_callback( self._callback, self._mapped_filters ) diff --git a/homeassistant/components/bluetooth/passive_update_coordinator.py b/homeassistant/components/bluetooth/passive_update_coordinator.py index 97e7ddc49ee5..31a6b0658300 100644 --- a/homeassistant/components/bluetooth/passive_update_coordinator.py +++ b/homeassistant/components/bluetooth/passive_update_coordinator.py @@ -6,10 +6,9 @@ import logging from typing import Any from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback -from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import BluetoothChange +from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak from .update_coordinator import BasePassiveBluetoothCoordinator @@ -25,9 +24,10 @@ class PassiveBluetoothDataUpdateCoordinator(BasePassiveBluetoothCoordinator): hass: HomeAssistant, logger: logging.Logger, address: str, + mode: BluetoothScanningMode, ) -> None: """Initialize PassiveBluetoothDataUpdateCoordinator.""" - super().__init__(hass, logger, address) + super().__init__(hass, logger, address, mode) self._listeners: dict[CALLBACK_TYPE, tuple[CALLBACK_TYPE, object | None]] = {} @callback @@ -65,7 +65,7 @@ class PassiveBluetoothDataUpdateCoordinator(BasePassiveBluetoothCoordinator): @callback def _async_handle_bluetooth_event( self, - service_info: BluetoothServiceInfo, + service_info: BluetoothServiceInfoBleak, change: BluetoothChange, ) -> None: """Handle a Bluetooth event.""" diff --git a/homeassistant/components/bluetooth/passive_update_processor.py b/homeassistant/components/bluetooth/passive_update_processor.py index 434677018797..1f2047c02cb8 100644 --- a/homeassistant/components/bluetooth/passive_update_processor.py +++ b/homeassistant/components/bluetooth/passive_update_processor.py @@ -6,14 +6,12 @@ import dataclasses import logging from typing import Any, Generic, TypeVar -from home_assistant_bluetooth import BluetoothServiceInfo - from homeassistant.const import ATTR_IDENTIFIERS, ATTR_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import BluetoothChange +from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak from .const import DOMAIN from .update_coordinator import BasePassiveBluetoothCoordinator @@ -62,9 +60,10 @@ class PassiveBluetoothProcessorCoordinator(BasePassiveBluetoothCoordinator): hass: HomeAssistant, logger: logging.Logger, address: str, + mode: BluetoothScanningMode, ) -> None: """Initialize the coordinator.""" - super().__init__(hass, logger, address) + super().__init__(hass, logger, address, mode) self._processors: list[PassiveBluetoothDataProcessor] = [] @callback @@ -92,7 +91,7 @@ class PassiveBluetoothProcessorCoordinator(BasePassiveBluetoothCoordinator): @callback def _async_handle_bluetooth_event( self, - service_info: BluetoothServiceInfo, + service_info: BluetoothServiceInfoBleak, change: BluetoothChange, ) -> None: """Handle a Bluetooth event.""" @@ -122,7 +121,7 @@ class PassiveBluetoothDataProcessor(Generic[_T]): The processor will call the update_method every time the bluetooth device receives a new advertisement data from the coordinator with the following signature: - update_method(service_info: BluetoothServiceInfo) -> PassiveBluetoothDataUpdate + update_method(service_info: BluetoothServiceInfoBleak) -> PassiveBluetoothDataUpdate As the size of each advertisement is limited, the update_method should return a PassiveBluetoothDataUpdate object that contains only data that @@ -135,7 +134,9 @@ class PassiveBluetoothDataProcessor(Generic[_T]): def __init__( self, - update_method: Callable[[BluetoothServiceInfo], PassiveBluetoothDataUpdate[_T]], + update_method: Callable[ + [BluetoothServiceInfoBleak], PassiveBluetoothDataUpdate[_T] + ], ) -> None: """Initialize the coordinator.""" self.coordinator: PassiveBluetoothProcessorCoordinator @@ -241,7 +242,7 @@ class PassiveBluetoothDataProcessor(Generic[_T]): @callback def async_handle_bluetooth_event( self, - service_info: BluetoothServiceInfo, + service_info: BluetoothServiceInfoBleak, change: BluetoothChange, ) -> None: """Handle a Bluetooth event.""" diff --git a/homeassistant/components/bluetooth/update_coordinator.py b/homeassistant/components/bluetooth/update_coordinator.py index b1cb2de14536..d0f38ce32c60 100644 --- a/homeassistant/components/bluetooth/update_coordinator.py +++ b/homeassistant/components/bluetooth/update_coordinator.py @@ -4,13 +4,13 @@ from __future__ import annotations import logging import time -from home_assistant_bluetooth import BluetoothServiceInfo - from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from . import ( BluetoothCallbackMatcher, BluetoothChange, + BluetoothScanningMode, + BluetoothServiceInfoBleak, async_register_callback, async_track_unavailable, ) @@ -27,6 +27,7 @@ class BasePassiveBluetoothCoordinator: hass: HomeAssistant, logger: logging.Logger, address: str, + mode: BluetoothScanningMode, ) -> None: """Initialize the coordinator.""" self.hass = hass @@ -36,6 +37,7 @@ class BasePassiveBluetoothCoordinator: self._cancel_track_unavailable: CALLBACK_TYPE | None = None self._cancel_bluetooth_advertisements: CALLBACK_TYPE | None = None self._present = False + self.mode = mode self.last_seen = 0.0 @callback @@ -61,6 +63,7 @@ class BasePassiveBluetoothCoordinator: self.hass, self._async_handle_bluetooth_event, BluetoothCallbackMatcher(address=self.address), + self.mode, ) self._cancel_track_unavailable = async_track_unavailable( self.hass, @@ -86,7 +89,7 @@ class BasePassiveBluetoothCoordinator: @callback def _async_handle_bluetooth_event( self, - service_info: BluetoothServiceInfo, + service_info: BluetoothServiceInfoBleak, change: BluetoothChange, ) -> None: """Handle a Bluetooth event.""" diff --git a/homeassistant/components/bluetooth/usage.py b/homeassistant/components/bluetooth/usage.py index da5d062a36fd..b3a6783cf30d 100644 --- a/homeassistant/components/bluetooth/usage.py +++ b/homeassistant/components/bluetooth/usage.py @@ -1,4 +1,5 @@ """bluetooth usage utility to handle multiple instances.""" + from __future__ import annotations import bleak @@ -10,9 +11,9 @@ ORIGINAL_BLEAK_SCANNER = bleak.BleakScanner def install_multiple_bleak_catcher() -> None: """Wrap the bleak classes to return the shared instance if multiple instances are detected.""" - bleak.BleakScanner = HaBleakScannerWrapper + bleak.BleakScanner = HaBleakScannerWrapper # type: ignore[misc, assignment] def uninstall_multiple_bleak_catcher() -> None: """Unwrap the bleak classes.""" - bleak.BleakScanner = ORIGINAL_BLEAK_SCANNER + bleak.BleakScanner = ORIGINAL_BLEAK_SCANNER # type: ignore[misc] diff --git a/homeassistant/components/bluetooth_le_tracker/device_tracker.py b/homeassistant/components/bluetooth_le_tracker/device_tracker.py index e42d19ac547b..01b3d48205af 100644 --- a/homeassistant/components/bluetooth_le_tracker/device_tracker.py +++ b/homeassistant/components/bluetooth_le_tracker/device_tracker.py @@ -5,7 +5,6 @@ import asyncio from collections.abc import Awaitable, Callable from datetime import datetime, timedelta import logging -from typing import TYPE_CHECKING from uuid import UUID from bleak import BleakClient, BleakError @@ -31,9 +30,6 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType import homeassistant.util.dt as dt_util -if TYPE_CHECKING: - from bleak.backends.device import BLEDevice - _LOGGER = logging.getLogger(__name__) # Base UUID: 00000000-0000-1000-8000-00805F9B34FB @@ -139,15 +135,12 @@ async def async_setup_scanner( # noqa: C901 async def _async_see_update_ble_battery( mac: str, now: datetime, - service_info: bluetooth.BluetoothServiceInfo, + service_info: bluetooth.BluetoothServiceInfoBleak, ) -> None: """Lookup Bluetooth LE devices and update status.""" battery = None - ble_device: BLEDevice | str = ( - bluetooth.async_ble_device_from_address(hass, mac) or mac - ) try: - async with BleakClient(ble_device) as client: + async with BleakClient(service_info.device) as client: bat_char = await client.read_gatt_char(BATTERY_CHARACTERISTIC_UUID) battery = ord(bat_char) except asyncio.TimeoutError: @@ -168,7 +161,8 @@ async def async_setup_scanner( # noqa: C901 @callback def _async_update_ble( - service_info: bluetooth.BluetoothServiceInfo, change: bluetooth.BluetoothChange + service_info: bluetooth.BluetoothServiceInfoBleak, + change: bluetooth.BluetoothChange, ) -> None: """Update from a ble callback.""" mac = service_info.address @@ -202,7 +196,9 @@ async def async_setup_scanner( # noqa: C901 _async_update_ble(service_info, bluetooth.BluetoothChange.ADVERTISEMENT) cancels = [ - bluetooth.async_register_callback(hass, _async_update_ble, None), + bluetooth.async_register_callback( + hass, _async_update_ble, None, bluetooth.BluetoothScanningMode.ACTIVE + ), async_track_time_interval(hass, _async_refresh_ble, interval), ] diff --git a/homeassistant/components/govee_ble/__init__.py b/homeassistant/components/govee_ble/__init__.py index 3099d401e9bf..7a134e43acef 100644 --- a/homeassistant/components/govee_ble/__init__.py +++ b/homeassistant/components/govee_ble/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from homeassistant.components.bluetooth import BluetoothScanningMode from homeassistant.components.bluetooth.passive_update_processor import ( PassiveBluetoothProcessorCoordinator, ) @@ -27,6 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, _LOGGER, address=address, + mode=BluetoothScanningMode.ACTIVE, ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/govee_ble/config_flow.py b/homeassistant/components/govee_ble/config_flow.py index 9f2efac0ce95..1e3a5566bfde 100644 --- a/homeassistant/components/govee_ble/config_flow.py +++ b/homeassistant/components/govee_ble/config_flow.py @@ -7,7 +7,7 @@ from govee_ble import GoveeBluetoothDeviceData as DeviceData import voluptuous as vol from homeassistant.components.bluetooth import ( - BluetoothServiceInfo, + BluetoothServiceInfoBleak, async_discovered_service_info, ) from homeassistant.config_entries import ConfigFlow @@ -24,12 +24,12 @@ class GoveeConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" - self._discovery_info: BluetoothServiceInfo | None = None + self._discovery_info: BluetoothServiceInfoBleak | None = None self._discovered_device: DeviceData | None = None self._discovered_devices: dict[str, str] = {} async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> FlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index d8b3fda2d06e..31677e37b205 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -20,13 +20,16 @@ from homeassistant.components import zeroconf from homeassistant.core import callback from homeassistant.data_entry_flow import AbortFlow, FlowResult from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.service_info import bluetooth from .connection import HKDevice from .const import DOMAIN, KNOWN_DEVICES from .storage import async_get_entity_storage from .utils import async_get_controller +if TYPE_CHECKING: + from homeassistant.components import bluetooth + + HOMEKIT_DIR = ".homekit" HOMEKIT_BRIDGE_DOMAIN = "homekit" @@ -359,7 +362,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self._async_step_pair_show_form() async def async_step_bluetooth( - self, discovery_info: bluetooth.BluetoothServiceInfo + self, discovery_info: bluetooth.BluetoothServiceInfoBleak ) -> FlowResult: """Handle the bluetooth discovery step.""" if not aiohomekit_const.BLE_TRANSPORT_SUPPORTED: diff --git a/homeassistant/components/homekit_controller/utils.py b/homeassistant/components/homekit_controller/utils.py index d77800293314..6e272067b54a 100644 --- a/homeassistant/components/homekit_controller/utils.py +++ b/homeassistant/components/homekit_controller/utils.py @@ -32,7 +32,7 @@ async def async_get_controller(hass: HomeAssistant) -> Controller: controller = Controller( async_zeroconf_instance=async_zeroconf_instance, - bleak_scanner_instance=bleak_scanner_instance, + bleak_scanner_instance=bleak_scanner_instance, # type: ignore[arg-type] ) hass.data[CONTROLLER] = controller diff --git a/homeassistant/components/inkbird/__init__.py b/homeassistant/components/inkbird/__init__.py index 5553b1c6ded0..0272114b83cd 100644 --- a/homeassistant/components/inkbird/__init__.py +++ b/homeassistant/components/inkbird/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from homeassistant.components.bluetooth import BluetoothScanningMode from homeassistant.components.bluetooth.passive_update_processor import ( PassiveBluetoothProcessorCoordinator, ) @@ -24,9 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = hass.data.setdefault(DOMAIN, {})[ entry.entry_id ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, + hass, _LOGGER, address=address, mode=BluetoothScanningMode.ACTIVE ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/inkbird/config_flow.py b/homeassistant/components/inkbird/config_flow.py index 679ff43b19e9..21ed85e117ea 100644 --- a/homeassistant/components/inkbird/config_flow.py +++ b/homeassistant/components/inkbird/config_flow.py @@ -7,7 +7,7 @@ from inkbird_ble import INKBIRDBluetoothDeviceData as DeviceData import voluptuous as vol from homeassistant.components.bluetooth import ( - BluetoothServiceInfo, + BluetoothServiceInfoBleak, async_discovered_service_info, ) from homeassistant.config_entries import ConfigFlow @@ -24,12 +24,12 @@ class INKBIRDConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" - self._discovery_info: BluetoothServiceInfo | None = None + self._discovery_info: BluetoothServiceInfoBleak | None = None self._discovered_device: DeviceData | None = None self._discovered_devices: dict[str, str] = {} async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> FlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) diff --git a/homeassistant/components/moat/__init__.py b/homeassistant/components/moat/__init__.py index 259b6b66709a..237948a8ff60 100644 --- a/homeassistant/components/moat/__init__.py +++ b/homeassistant/components/moat/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from homeassistant.components.bluetooth import BluetoothScanningMode from homeassistant.components.bluetooth.passive_update_processor import ( PassiveBluetoothProcessorCoordinator, ) @@ -24,9 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = hass.data.setdefault(DOMAIN, {})[ entry.entry_id ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, + hass, _LOGGER, address=address, mode=BluetoothScanningMode.PASSIVE ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/moat/config_flow.py b/homeassistant/components/moat/config_flow.py index a353f4963adf..6f51b62d1108 100644 --- a/homeassistant/components/moat/config_flow.py +++ b/homeassistant/components/moat/config_flow.py @@ -7,7 +7,7 @@ from moat_ble import MoatBluetoothDeviceData as DeviceData import voluptuous as vol from homeassistant.components.bluetooth import ( - BluetoothServiceInfo, + BluetoothServiceInfoBleak, async_discovered_service_info, ) from homeassistant.config_entries import ConfigFlow @@ -24,12 +24,12 @@ class MoatConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" - self._discovery_info: BluetoothServiceInfo | None = None + self._discovery_info: BluetoothServiceInfoBleak | None = None self._discovered_device: DeviceData | None = None self._discovered_devices: dict[str, str] = {} async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> FlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) diff --git a/homeassistant/components/sensorpush/__init__.py b/homeassistant/components/sensorpush/__init__.py index 0a9efcbc7524..d4a0872ba3f1 100644 --- a/homeassistant/components/sensorpush/__init__.py +++ b/homeassistant/components/sensorpush/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from homeassistant.components.bluetooth import BluetoothScanningMode from homeassistant.components.bluetooth.passive_update_processor import ( PassiveBluetoothProcessorCoordinator, ) @@ -24,9 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = hass.data.setdefault(DOMAIN, {})[ entry.entry_id ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, + hass, _LOGGER, address=address, mode=BluetoothScanningMode.PASSIVE ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/sensorpush/config_flow.py b/homeassistant/components/sensorpush/config_flow.py index 1a8e8b47abe6..d10c2f481a6b 100644 --- a/homeassistant/components/sensorpush/config_flow.py +++ b/homeassistant/components/sensorpush/config_flow.py @@ -7,7 +7,7 @@ from sensorpush_ble import SensorPushBluetoothDeviceData as DeviceData import voluptuous as vol from homeassistant.components.bluetooth import ( - BluetoothServiceInfo, + BluetoothServiceInfoBleak, async_discovered_service_info, ) from homeassistant.config_entries import ConfigFlow @@ -24,12 +24,12 @@ class SensorPushConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" - self._discovery_info: BluetoothServiceInfo | None = None + self._discovery_info: BluetoothServiceInfoBleak | None = None self._discovered_device: DeviceData | None = None self._discovered_devices: dict[str, str] = {} async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> FlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) diff --git a/homeassistant/components/switchbot/config_flow.py b/homeassistant/components/switchbot/config_flow.py index 3a34a89d9fda..eaad573d3707 100644 --- a/homeassistant/components/switchbot/config_flow.py +++ b/homeassistant/components/switchbot/config_flow.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging -from typing import Any, cast +from typing import Any from switchbot import SwitchBotAdvertisement, parse_advertisement_data import voluptuous as vol @@ -15,7 +15,6 @@ from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PASSWORD, CONF_SENSOR_TYPE from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo from .const import CONF_RETRY_COUNT, DEFAULT_RETRY_COUNT, DOMAIN, SUPPORTED_MODEL_TYPES @@ -46,15 +45,14 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN): self._discovered_advs: dict[str, SwitchBotAdvertisement] = {} async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> FlowResult: """Handle the bluetooth discovery step.""" _LOGGER.debug("Discovered bluetooth device: %s", discovery_info) await self.async_set_unique_id(format_unique_id(discovery_info.address)) self._abort_if_unique_id_configured() - discovery_info_bleak = cast(BluetoothServiceInfoBleak, discovery_info) parsed = parse_advertisement_data( - discovery_info_bleak.device, discovery_info_bleak.advertisement + discovery_info.device, discovery_info.advertisement ) if not parsed or parsed.data.get("modelName") not in SUPPORTED_MODEL_TYPES: return self.async_abort(reason="not_supported") diff --git a/homeassistant/components/switchbot/coordinator.py b/homeassistant/components/switchbot/coordinator.py index f461a3e0f4cd..43c576249df4 100644 --- a/homeassistant/components/switchbot/coordinator.py +++ b/homeassistant/components/switchbot/coordinator.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio import logging -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any import switchbot @@ -39,7 +39,9 @@ class SwitchbotDataUpdateCoordinator(PassiveBluetoothDataUpdateCoordinator): device: switchbot.SwitchbotDevice, ) -> None: """Initialize global switchbot data updater.""" - super().__init__(hass, logger, ble_device.address) + super().__init__( + hass, logger, ble_device.address, bluetooth.BluetoothScanningMode.ACTIVE + ) self.ble_device = ble_device self.device = device self.data: dict[str, Any] = {} @@ -48,14 +50,13 @@ class SwitchbotDataUpdateCoordinator(PassiveBluetoothDataUpdateCoordinator): @callback def _async_handle_bluetooth_event( self, - service_info: bluetooth.BluetoothServiceInfo, + service_info: bluetooth.BluetoothServiceInfoBleak, change: bluetooth.BluetoothChange, ) -> None: """Handle a Bluetooth event.""" super()._async_handle_bluetooth_event(service_info, change) - discovery_info_bleak = cast(bluetooth.BluetoothServiceInfoBleak, service_info) if adv := switchbot.parse_advertisement_data( - discovery_info_bleak.device, discovery_info_bleak.advertisement + service_info.device, service_info.advertisement ): self.data = flatten_sensors_data(adv.data) if "modelName" in self.data: diff --git a/homeassistant/components/xiaomi_ble/__init__.py b/homeassistant/components/xiaomi_ble/__init__.py index 4eb20dbd9437..791ac1447ad1 100644 --- a/homeassistant/components/xiaomi_ble/__init__.py +++ b/homeassistant/components/xiaomi_ble/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from homeassistant.components.bluetooth import BluetoothScanningMode from homeassistant.components.bluetooth.passive_update_processor import ( PassiveBluetoothProcessorCoordinator, ) @@ -24,9 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator = hass.data.setdefault(DOMAIN, {})[ entry.entry_id ] = PassiveBluetoothProcessorCoordinator( - hass, - _LOGGER, - address=address, + hass, _LOGGER, address=address, mode=BluetoothScanningMode.PASSIVE ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload( diff --git a/homeassistant/components/xiaomi_ble/config_flow.py b/homeassistant/components/xiaomi_ble/config_flow.py index f352f43d0bfc..a2fa2d185dca 100644 --- a/homeassistant/components/xiaomi_ble/config_flow.py +++ b/homeassistant/components/xiaomi_ble/config_flow.py @@ -11,7 +11,8 @@ from xiaomi_ble.parser import EncryptionScheme from homeassistant.components import onboarding from homeassistant.components.bluetooth import ( - BluetoothServiceInfo, + BluetoothScanningMode, + BluetoothServiceInfoBleak, async_discovered_service_info, async_process_advertisements, ) @@ -30,11 +31,11 @@ class Discovery: """A discovered bluetooth device.""" title: str - discovery_info: BluetoothServiceInfo + discovery_info: BluetoothServiceInfoBleak device: DeviceData -def _title(discovery_info: BluetoothServiceInfo, device: DeviceData) -> str: +def _title(discovery_info: BluetoothServiceInfoBleak, device: DeviceData) -> str: return device.title or device.get_device_name() or discovery_info.name @@ -45,18 +46,20 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" - self._discovery_info: BluetoothServiceInfo | None = None + self._discovery_info: BluetoothServiceInfoBleak | None = None self._discovered_device: DeviceData | None = None self._discovered_devices: dict[str, Discovery] = {} async def _async_wait_for_full_advertisement( - self, discovery_info: BluetoothServiceInfo, device: DeviceData - ) -> BluetoothServiceInfo: + self, discovery_info: BluetoothServiceInfoBleak, device: DeviceData + ) -> BluetoothServiceInfoBleak: """Sometimes first advertisement we receive is blank or incomplete. Wait until we get a useful one.""" if not device.pending: return discovery_info - def _process_more_advertisements(service_info: BluetoothServiceInfo) -> bool: + def _process_more_advertisements( + service_info: BluetoothServiceInfoBleak, + ) -> bool: device.update(service_info) return not device.pending @@ -64,11 +67,12 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): self.hass, _process_more_advertisements, {"address": discovery_info.address}, + BluetoothScanningMode.ACTIVE, ADDITIONAL_DISCOVERY_TIMEOUT, ) async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> FlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b5d5804f100f..b0c043230052 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -27,12 +27,12 @@ from .util import uuid as uuid_util from .util.decorator import Registry if TYPE_CHECKING: + from .components.bluetooth import BluetoothServiceInfoBleak from .components.dhcp import DhcpServiceInfo from .components.hassio import HassioServiceInfo from .components.ssdp import SsdpServiceInfo from .components.usb import UsbServiceInfo from .components.zeroconf import ZeroconfServiceInfo - from .helpers.service_info.bluetooth import BluetoothServiceInfo from .helpers.service_info.mqtt import MqttServiceInfo _LOGGER = logging.getLogger(__name__) @@ -1485,7 +1485,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): ) async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> data_entry_flow.FlowResult: """Handle a flow initialized by Bluetooth discovery.""" return await self._async_step_discovery_without_unique_id() diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index bf2f95c12c6c..c9d6ffe60658 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -15,11 +15,11 @@ from .typing import DiscoveryInfoType if TYPE_CHECKING: import asyncio + from homeassistant.components.bluetooth import BluetoothServiceInfoBleak from homeassistant.components.dhcp import DhcpServiceInfo from homeassistant.components.ssdp import SsdpServiceInfo from homeassistant.components.zeroconf import ZeroconfServiceInfo - from .service_info.bluetooth import BluetoothServiceInfo from .service_info.mqtt import MqttServiceInfo _R = TypeVar("_R", bound="Awaitable[bool] | bool") @@ -97,7 +97,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow, Generic[_R]): return await self.async_step_confirm() async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfo + self, discovery_info: BluetoothServiceInfoBleak ) -> FlowResult: """Handle a flow initialized by bluetooth discovery.""" if self._async_in_progress() or self._async_current_entries(): diff --git a/homeassistant/helpers/service_info/bluetooth.py b/homeassistant/helpers/service_info/bluetooth.py index 968d1dde95ff..0db3a39b1147 100644 --- a/homeassistant/helpers/service_info/bluetooth.py +++ b/homeassistant/helpers/service_info/bluetooth.py @@ -1,4 +1,5 @@ """The bluetooth integration service info.""" + from home_assistant_bluetooth import BluetoothServiceInfo __all__ = ["BluetoothServiceInfo"] diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 1f86ff4c7c50..c9d424daa33b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -10,7 +10,7 @@ atomicwrites-homeassistant==1.4.1 attrs==21.2.0 awesomeversion==22.6.0 bcrypt==3.1.7 -bleak==0.14.3 +bleak==0.15.0 bluetooth-adapters==0.1.2 certifi>=2021.5.30 ciso8601==2.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index b7b3ed89b270..58f2e5f73ecd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -405,7 +405,7 @@ bimmer_connected==0.10.1 bizkaibus==0.1.1 # homeassistant.components.bluetooth -bleak==0.14.3 +bleak==0.15.0 # homeassistant.components.blebox blebox_uniapi==2.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d04b537c9fd1..f7358e148dc8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -326,7 +326,7 @@ bellows==0.31.2 bimmer_connected==0.10.1 # homeassistant.components.bluetooth -bleak==0.14.3 +bleak==0.15.0 # homeassistant.components.blebox blebox_uniapi==2.0.2 diff --git a/tests/components/bluetooth/test_init.py b/tests/components/bluetooth/test_init.py index 0664f82dbab3..a47916506dff 100644 --- a/tests/components/bluetooth/test_init.py +++ b/tests/components/bluetooth/test_init.py @@ -12,6 +12,7 @@ from homeassistant.components.bluetooth import ( SOURCE_LOCAL, UNAVAILABLE_TRACK_SECONDS, BluetoothChange, + BluetoothScanningMode, BluetoothServiceInfo, async_process_advertisements, async_track_unavailable, @@ -675,6 +676,7 @@ async def test_register_callbacks(hass, mock_bleak_scanner_start, enable_bluetoo hass, _fake_subscriber, {"service_uuids": {"cba20d00-224d-11e6-9fb8-0002a5d5c51b"}}, + BluetoothScanningMode.ACTIVE, ) assert len(mock_bleak_scanner_start.mock_calls) == 1 @@ -760,6 +762,7 @@ async def test_register_callback_by_address( hass, _fake_subscriber, {"address": "44:44:33:11:23:45"}, + BluetoothScanningMode.ACTIVE, ) assert len(mock_bleak_scanner_start.mock_calls) == 1 @@ -799,6 +802,7 @@ async def test_register_callback_by_address( hass, _fake_subscriber, {"address": "44:44:33:11:23:45"}, + BluetoothScanningMode.ACTIVE, ) cancel() @@ -808,6 +812,7 @@ async def test_register_callback_by_address( hass, _fake_subscriber, {"address": "44:44:33:11:23:45"}, + BluetoothScanningMode.ACTIVE, ) cancel() @@ -832,7 +837,11 @@ async def test_process_advertisements_bail_on_good_advertisement( handle = hass.async_create_task( async_process_advertisements( - hass, _callback, {"address": "aa:44:33:11:23:45"}, 5 + hass, + _callback, + {"address": "aa:44:33:11:23:45"}, + BluetoothScanningMode.ACTIVE, + 5, ) ) @@ -873,7 +882,11 @@ async def test_process_advertisements_ignore_bad_advertisement( handle = hass.async_create_task( async_process_advertisements( - hass, _callback, {"address": "aa:44:33:11:23:45"}, 5 + hass, + _callback, + {"address": "aa:44:33:11:23:45"}, + BluetoothScanningMode.ACTIVE, + 5, ) ) @@ -903,7 +916,9 @@ async def test_process_advertisements_timeout( return False with pytest.raises(asyncio.TimeoutError): - await async_process_advertisements(hass, _callback, {}, 0) + await async_process_advertisements( + hass, _callback, {}, BluetoothScanningMode.ACTIVE, 0 + ) async def test_wrapped_instance_with_filter( diff --git a/tests/components/bluetooth/test_passive_update_coordinator.py b/tests/components/bluetooth/test_passive_update_coordinator.py index 4da14fb13d35..31530cd6995c 100644 --- a/tests/components/bluetooth/test_passive_update_coordinator.py +++ b/tests/components/bluetooth/test_passive_update_coordinator.py @@ -10,6 +10,7 @@ from homeassistant.components.bluetooth import ( DOMAIN, UNAVAILABLE_TRACK_SECONDS, BluetoothChange, + BluetoothScanningMode, ) from homeassistant.components.bluetooth.passive_update_coordinator import ( PassiveBluetoothCoordinatorEntity, @@ -42,9 +43,9 @@ GENERIC_BLUETOOTH_SERVICE_INFO = BluetoothServiceInfo( class MyCoordinator(PassiveBluetoothDataUpdateCoordinator): """An example coordinator that subclasses PassiveBluetoothDataUpdateCoordinator.""" - def __init__(self, hass, logger, device_id) -> None: + def __init__(self, hass, logger, device_id, mode) -> None: """Initialize the coordinator.""" - super().__init__(hass, logger, device_id) + super().__init__(hass, logger, device_id, mode) self.data: dict[str, Any] = {} def _async_handle_bluetooth_event( @@ -60,11 +61,13 @@ class MyCoordinator(PassiveBluetoothDataUpdateCoordinator): async def test_basic_usage(hass, mock_bleak_scanner_start): """Test basic usage of the PassiveBluetoothDataUpdateCoordinator.""" await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - coordinator = MyCoordinator(hass, _LOGGER, "aa:bb:cc:dd:ee:ff") + coordinator = MyCoordinator( + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE + ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -100,11 +103,13 @@ async def test_context_compatiblity_with_data_update_coordinator( ): """Test contexts can be passed for compatibility with DataUpdateCoordinator.""" await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - coordinator = MyCoordinator(hass, _LOGGER, "aa:bb:cc:dd:ee:ff") + coordinator = MyCoordinator( + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE + ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -149,11 +154,13 @@ async def test_unavailable_callbacks_mark_the_coordinator_unavailable( ): await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() - coordinator = MyCoordinator(hass, _LOGGER, "aa:bb:cc:dd:ee:ff") + coordinator = MyCoordinator( + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.PASSIVE + ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -208,13 +215,15 @@ async def test_unavailable_callbacks_mark_the_coordinator_unavailable( async def test_passive_bluetooth_coordinator_entity(hass, mock_bleak_scanner_start): """Test integration of PassiveBluetoothDataUpdateCoordinator with PassiveBluetoothCoordinatorEntity.""" await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - coordinator = MyCoordinator(hass, _LOGGER, "aa:bb:cc:dd:ee:ff") + coordinator = MyCoordinator( + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE + ) entity = PassiveBluetoothCoordinatorEntity(coordinator) assert entity.available is False saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None diff --git a/tests/components/bluetooth/test_passive_update_processor.py b/tests/components/bluetooth/test_passive_update_processor.py index ebb69d7c7d0a..6a092746a685 100644 --- a/tests/components/bluetooth/test_passive_update_processor.py +++ b/tests/components/bluetooth/test_passive_update_processor.py @@ -16,6 +16,7 @@ from homeassistant.components.bluetooth import ( DOMAIN, UNAVAILABLE_TRACK_SECONDS, BluetoothChange, + BluetoothScanningMode, ) from homeassistant.components.bluetooth.passive_update_processor import ( PassiveBluetoothDataProcessor, @@ -90,12 +91,12 @@ async def test_basic_usage(hass, mock_bleak_scanner_start): return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -192,12 +193,12 @@ async def test_unavailable_after_no_data(hass, mock_bleak_scanner_start): return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -277,12 +278,12 @@ async def test_no_updates_once_stopping(hass, mock_bleak_scanner_start): return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -336,12 +337,12 @@ async def test_exception_from_update_method(hass, caplog, mock_bleak_scanner_sta return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -389,12 +390,12 @@ async def test_bad_data_from_update_method(hass, mock_bleak_scanner_start): return GENERIC_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -731,12 +732,12 @@ async def test_integration_with_entity(hass, mock_bleak_scanner_start): return GOVEE_B5178_REMOTE_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -841,12 +842,12 @@ async def test_integration_with_entity_without_a_device(hass, mock_bleak_scanner return NO_DEVICES_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -905,12 +906,12 @@ async def test_passive_bluetooth_entity_with_entity_platform( return NO_DEVICES_PASSIVE_BLUETOOTH_DATA_UPDATE coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -992,12 +993,12 @@ async def test_integration_multiple_entity_platforms(hass, mock_bleak_scanner_st await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) coordinator = PassiveBluetoothProcessorCoordinator( - hass, _LOGGER, "aa:bb:cc:dd:ee:ff" + hass, _LOGGER, "aa:bb:cc:dd:ee:ff", BluetoothScanningMode.ACTIVE ) assert coordinator.available is False # no data yet saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None diff --git a/tests/components/bluetooth_le_tracker/test_device_tracker.py b/tests/components/bluetooth_le_tracker/test_device_tracker.py index dfe47b38b336..f9f0a51fc0fa 100644 --- a/tests/components/bluetooth_le_tracker/test_device_tracker.py +++ b/tests/components/bluetooth_le_tracker/test_device_tracker.py @@ -5,8 +5,9 @@ from datetime import timedelta from unittest.mock import patch from bleak import BleakError +from bleak.backends.scanner import AdvertisementData, BLEDevice -from homeassistant.components.bluetooth import BluetoothServiceInfo +from homeassistant.components.bluetooth import BluetoothServiceInfoBleak from homeassistant.components.bluetooth_le_tracker import device_tracker from homeassistant.components.bluetooth_le_tracker.device_tracker import ( CONF_TRACK_BATTERY, @@ -79,7 +80,7 @@ async def test_preserve_new_tracked_device_name( device_tracker, "MIN_SEEN_NEW", 3 ): - device = BluetoothServiceInfo( + device = BluetoothServiceInfoBleak( name=name, address=address, rssi=-19, @@ -87,6 +88,8 @@ async def test_preserve_new_tracked_device_name( service_data={}, service_uuids=[], source="local", + device=BLEDevice(address, None), + advertisement=AdvertisementData(local_name="empty"), ) # Return with name when seen first time mock_async_discovered_service_info.return_value = [device] @@ -100,7 +103,7 @@ async def test_preserve_new_tracked_device_name( assert result # Seen once here; return without name when seen subsequent times - device = BluetoothServiceInfo( + device = BluetoothServiceInfoBleak( name=None, address=address, rssi=-19, @@ -108,6 +111,8 @@ async def test_preserve_new_tracked_device_name( service_data={}, service_uuids=[], source="local", + device=BLEDevice(address, None), + advertisement=AdvertisementData(local_name="empty"), ) # Return with name when seen first time mock_async_discovered_service_info.return_value = [device] @@ -140,7 +145,7 @@ async def test_tracking_battery_times_out( device_tracker, "MIN_SEEN_NEW", 3 ): - device = BluetoothServiceInfo( + device = BluetoothServiceInfoBleak( name=name, address=address, rssi=-19, @@ -148,6 +153,8 @@ async def test_tracking_battery_times_out( service_data={}, service_uuids=[], source="local", + device=BLEDevice(address, None), + advertisement=AdvertisementData(local_name="empty"), ) # Return with name when seen first time mock_async_discovered_service_info.return_value = [device] @@ -202,7 +209,7 @@ async def test_tracking_battery_fails(hass, mock_bluetooth, mock_device_tracker_ device_tracker, "MIN_SEEN_NEW", 3 ): - device = BluetoothServiceInfo( + device = BluetoothServiceInfoBleak( name=name, address=address, rssi=-19, @@ -210,6 +217,8 @@ async def test_tracking_battery_fails(hass, mock_bluetooth, mock_device_tracker_ service_data={}, service_uuids=[], source="local", + device=BLEDevice(address, None), + advertisement=AdvertisementData(local_name="empty"), ) # Return with name when seen first time mock_async_discovered_service_info.return_value = [device] @@ -266,7 +275,7 @@ async def test_tracking_battery_successful( device_tracker, "MIN_SEEN_NEW", 3 ): - device = BluetoothServiceInfo( + device = BluetoothServiceInfoBleak( name=name, address=address, rssi=-19, @@ -274,6 +283,8 @@ async def test_tracking_battery_successful( service_data={}, service_uuids=[], source="local", + device=BLEDevice(address, None), + advertisement=AdvertisementData(local_name="empty"), ) # Return with name when seen first time mock_async_discovered_service_info.return_value = [device] diff --git a/tests/components/fjaraskupan/conftest.py b/tests/components/fjaraskupan/conftest.py index d60abcdb9ad3..4e06b2ad0463 100644 --- a/tests/components/fjaraskupan/conftest.py +++ b/tests/components/fjaraskupan/conftest.py @@ -17,6 +17,12 @@ def fixture_scanner(hass): class MockScanner(BaseBleakScanner): """Mock Scanner.""" + def __init__(self, *args, **kwargs) -> None: + """Initialize the scanner.""" + super().__init__( + detection_callback=kwargs.pop("detection_callback"), service_uuids=[] + ) + async def start(self): """Start scanning for devices.""" for device in devices: diff --git a/tests/components/govee_ble/test_sensor.py b/tests/components/govee_ble/test_sensor.py index 7a6ecbaed510..75d269ea0ba5 100644 --- a/tests/components/govee_ble/test_sensor.py +++ b/tests/components/govee_ble/test_sensor.py @@ -22,7 +22,7 @@ async def test_sensors(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None diff --git a/tests/components/inkbird/test_sensor.py b/tests/components/inkbird/test_sensor.py index a851cb92ec34..cafc22911c36 100644 --- a/tests/components/inkbird/test_sensor.py +++ b/tests/components/inkbird/test_sensor.py @@ -22,7 +22,7 @@ async def test_sensors(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None diff --git a/tests/components/moat/test_sensor.py b/tests/components/moat/test_sensor.py index 826bbc72cdb8..6424144106ba 100644 --- a/tests/components/moat/test_sensor.py +++ b/tests/components/moat/test_sensor.py @@ -22,7 +22,7 @@ async def test_sensors(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None diff --git a/tests/components/sensorpush/test_sensor.py b/tests/components/sensorpush/test_sensor.py index 31fbdd8d7127..34179985d787 100644 --- a/tests/components/sensorpush/test_sensor.py +++ b/tests/components/sensorpush/test_sensor.py @@ -22,7 +22,7 @@ async def test_sensors(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None diff --git a/tests/components/xiaomi_ble/test_config_flow.py b/tests/components/xiaomi_ble/test_config_flow.py index b424228cc6c0..86fda21aaa00 100644 --- a/tests/components/xiaomi_ble/test_config_flow.py +++ b/tests/components/xiaomi_ble/test_config_flow.py @@ -59,7 +59,9 @@ async def test_async_step_bluetooth_valid_device_but_missing_payload(hass): async def test_async_step_bluetooth_valid_device_but_missing_payload_then_full(hass): """Test discovering a valid device. Payload is too short, but later we get full one.""" - async def _async_process_advertisements(_hass, _callback, _matcher, _timeout): + async def _async_process_advertisements( + _hass, _callback, _matcher, _mode, _timeout + ): service_info = make_advertisement( "A4:C1:38:56:53:84", b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e", @@ -378,7 +380,9 @@ async def test_async_step_user_short_payload_then_full(hass): assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user" - async def _async_process_advertisements(_hass, _callback, _matcher, _timeout): + async def _async_process_advertisements( + _hass, _callback, _matcher, _mode, _timeout + ): service_info = make_advertisement( "A4:C1:38:56:53:84", b"XX\xe4\x16,\x84SV8\xc1\xa4+n\xf2\xe9\x12\x00\x00l\x88M\x9e", diff --git a/tests/components/xiaomi_ble/test_sensor.py b/tests/components/xiaomi_ble/test_sensor.py index dca00e922548..011c6daecaed 100644 --- a/tests/components/xiaomi_ble/test_sensor.py +++ b/tests/components/xiaomi_ble/test_sensor.py @@ -22,7 +22,7 @@ async def test_sensors(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -60,7 +60,7 @@ async def test_xiaomi_formaldeyhde(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -107,7 +107,7 @@ async def test_xiaomi_consumable(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -154,7 +154,7 @@ async def test_xiaomi_battery_voltage(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -208,7 +208,7 @@ async def test_xiaomi_HHCCJCY01(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None @@ -291,7 +291,7 @@ async def test_xiaomi_CGDK2(hass): saved_callback = None - def _async_register_callback(_hass, _callback, _matcher): + def _async_register_callback(_hass, _callback, _matcher, _mode): nonlocal saved_callback saved_callback = _callback return lambda: None From 8f8bccd982bf4a60016d98bb084244a251a08773 Mon Sep 17 00:00:00 2001 From: Bob van Mierlo <38190383+bobvmierlo@users.noreply.github.com> Date: Fri, 29 Jul 2022 22:46:30 +0200 Subject: [PATCH 33/83] Increase the discovery timeout (#75948) --- homeassistant/components/xiaomi_ble/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/xiaomi_ble/config_flow.py b/homeassistant/components/xiaomi_ble/config_flow.py index a2fa2d185dca..aa1ffc248950 100644 --- a/homeassistant/components/xiaomi_ble/config_flow.py +++ b/homeassistant/components/xiaomi_ble/config_flow.py @@ -23,7 +23,7 @@ from homeassistant.data_entry_flow import FlowResult from .const import DOMAIN # How long to wait for additional advertisement packets if we don't have the right ones -ADDITIONAL_DISCOVERY_TIMEOUT = 5 +ADDITIONAL_DISCOVERY_TIMEOUT = 60 @dataclasses.dataclass From e2a9ab18311b974a6e60f54e967db18460490c52 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 29 Jul 2022 17:55:32 -0700 Subject: [PATCH 34/83] Bumped version to 2022.8.0b3 --- 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 a3753d36d078..4a37902e79f3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b2" +PATCH_VERSION: Final = "0b3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 558f366b357f..8fca49a67f2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0b2" +version = "2022.8.0b3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 240890e496a156b47eb41b8d0c35287121d11b7c Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sun, 31 Jul 2022 14:10:29 -0600 Subject: [PATCH 35/83] Appropriately mark Guardian entities as `unavailable` during reboot (#75234) --- homeassistant/components/guardian/__init__.py | 13 ++++++- .../components/guardian/binary_sensor.py | 2 +- homeassistant/components/guardian/button.py | 3 ++ homeassistant/components/guardian/sensor.py | 4 +- homeassistant/components/guardian/util.py | 38 ++++++++++++++++++- 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py index da22066d7aa9..58d70667cdf3 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -137,6 +137,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # so we use a lock to ensure that only one API request is reaching it at a time: api_lock = asyncio.Lock() + async def async_init_coordinator( + coordinator: GuardianDataUpdateCoordinator, + ) -> None: + """Initialize a GuardianDataUpdateCoordinator.""" + await coordinator.async_initialize() + await coordinator.async_config_entry_first_refresh() + # Set up GuardianDataUpdateCoordinators for the valve controller: valve_controller_coordinators: dict[str, GuardianDataUpdateCoordinator] = {} init_valve_controller_tasks = [] @@ -151,13 +158,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api ] = GuardianDataUpdateCoordinator( hass, + entry=entry, client=client, api_name=api, api_coro=api_coro, api_lock=api_lock, valve_controller_uid=entry.data[CONF_UID], ) - init_valve_controller_tasks.append(coordinator.async_refresh()) + init_valve_controller_tasks.append(async_init_coordinator(coordinator)) await asyncio.gather(*init_valve_controller_tasks) @@ -352,6 +360,7 @@ class PairedSensorManager: coordinator = self.coordinators[uid] = GuardianDataUpdateCoordinator( self._hass, + entry=self._entry, client=self._client, api_name=f"{API_SENSOR_PAIRED_SENSOR_STATUS}_{uid}", api_coro=lambda: cast( @@ -422,7 +431,7 @@ class GuardianEntity(CoordinatorEntity[GuardianDataUpdateCoordinator]): @callback def _async_update_from_latest_data(self) -> None: - """Update the entity. + """Update the entity's underlying data. This should be extended by Guardian platforms. """ diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index eb6d49c3ec13..766e5d961e86 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -137,7 +137,7 @@ class PairedSensorBinarySensor(PairedSensorEntity, BinarySensorEntity): @callback def _async_update_from_latest_data(self) -> None: - """Update the entity.""" + """Update the entity's underlying data.""" if self.entity_description.key == SENSOR_KIND_LEAK_DETECTED: self._attr_is_on = self.coordinator.data["wet"] elif self.entity_description.key == SENSOR_KIND_MOVED: diff --git a/homeassistant/components/guardian/button.py b/homeassistant/components/guardian/button.py index 01efb7deba4e..740cce43c62d 100644 --- a/homeassistant/components/guardian/button.py +++ b/homeassistant/components/guardian/button.py @@ -15,6 +15,7 @@ from homeassistant.components.button import ( from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -111,3 +112,5 @@ class GuardianButton(ValveControllerEntity, ButtonEntity): raise HomeAssistantError( f'Error while pressing button "{self.entity_id}": {err}' ) from err + + async_dispatcher_send(self.hass, self.coordinator.signal_reboot_requested) diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py index 5b4c621cce6c..05de437b10af 100644 --- a/homeassistant/components/guardian/sensor.py +++ b/homeassistant/components/guardian/sensor.py @@ -128,7 +128,7 @@ class PairedSensorSensor(PairedSensorEntity, SensorEntity): @callback def _async_update_from_latest_data(self) -> None: - """Update the entity.""" + """Update the entity's underlying data.""" if self.entity_description.key == SENSOR_KIND_BATTERY: self._attr_native_value = self.coordinator.data["battery"] elif self.entity_description.key == SENSOR_KIND_TEMPERATURE: @@ -142,7 +142,7 @@ class ValveControllerSensor(ValveControllerEntity, SensorEntity): @callback def _async_update_from_latest_data(self) -> None: - """Update the entity.""" + """Update the entity's underlying data.""" if self.entity_description.key == SENSOR_KIND_TEMPERATURE: self._attr_native_value = self.coordinator.data["temperature"] elif self.entity_description.key == SENSOR_KIND_UPTIME: diff --git a/homeassistant/components/guardian/util.py b/homeassistant/components/guardian/util.py index 2cedcf9c1e4e..c88d6762e510 100644 --- a/homeassistant/components/guardian/util.py +++ b/homeassistant/components/guardian/util.py @@ -9,21 +9,28 @@ from typing import Any, cast from aioguardian import Client from aioguardian.errors import GuardianError -from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import LOGGER DEFAULT_UPDATE_INTERVAL = timedelta(seconds=30) +SIGNAL_REBOOT_REQUESTED = "guardian_reboot_requested_{0}" + class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): """Define an extended DataUpdateCoordinator with some Guardian goodies.""" + config_entry: ConfigEntry + def __init__( self, hass: HomeAssistant, *, + entry: ConfigEntry, client: Client, api_name: str, api_coro: Callable[..., Awaitable], @@ -41,6 +48,12 @@ class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): self._api_coro = api_coro self._api_lock = api_lock self._client = client + self._signal_handler_unsubs: list[Callable[..., None]] = [] + + self.config_entry = entry + self.signal_reboot_requested = SIGNAL_REBOOT_REQUESTED.format( + self.config_entry.entry_id + ) async def _async_update_data(self) -> dict[str, Any]: """Execute a "locked" API request against the valve controller.""" @@ -50,3 +63,26 @@ class GuardianDataUpdateCoordinator(DataUpdateCoordinator[dict]): except GuardianError as err: raise UpdateFailed(err) from err return cast(dict[str, Any], resp["data"]) + + async def async_initialize(self) -> None: + """Initialize the coordinator.""" + + @callback + def async_reboot_requested() -> None: + """Respond to a reboot request.""" + self.last_update_success = False + self.async_update_listeners() + + self._signal_handler_unsubs.append( + async_dispatcher_connect( + self.hass, self.signal_reboot_requested, async_reboot_requested + ) + ) + + @callback + def async_teardown() -> None: + """Tear the coordinator down appropriately.""" + for unsub in self._signal_handler_unsubs: + unsub() + + self.config_entry.async_on_unload(async_teardown) From bdb627539ec283b5625e66bb706f372168d4b3ab Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 Jul 2022 18:33:29 -1000 Subject: [PATCH 36/83] Fix switchbot failing to setup when last_run_success is not saved (#75887) --- homeassistant/components/switchbot/cover.py | 9 ++++++--- homeassistant/components/switchbot/switch.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/switchbot/cover.py b/homeassistant/components/switchbot/cover.py index dc9ddf4e6162..0ae225f55d7b 100644 --- a/homeassistant/components/switchbot/cover.py +++ b/homeassistant/components/switchbot/cover.py @@ -80,9 +80,12 @@ class SwitchBotCurtainEntity(SwitchbotEntity, CoverEntity, RestoreEntity): if not last_state or ATTR_CURRENT_POSITION not in last_state.attributes: return - self._attr_current_cover_position = last_state.attributes[ATTR_CURRENT_POSITION] - self._last_run_success = last_state.attributes["last_run_success"] - self._attr_is_closed = last_state.attributes[ATTR_CURRENT_POSITION] <= 20 + self._attr_current_cover_position = last_state.attributes.get( + ATTR_CURRENT_POSITION + ) + self._last_run_success = last_state.attributes.get("last_run_success") + if self._attr_current_cover_position is not None: + self._attr_is_closed = self._attr_current_cover_position <= 20 async def async_open_cover(self, **kwargs: Any) -> None: """Open the curtain.""" diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index 51f15c488d1e..e6ba77fa1647 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -69,7 +69,7 @@ class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity): if not (last_state := await self.async_get_last_state()): return self._attr_is_on = last_state.state == STATE_ON - self._last_run_success = last_state.attributes["last_run_success"] + self._last_run_success = last_state.attributes.get("last_run_success") async def async_turn_on(self, **kwargs: Any) -> None: """Turn device on.""" From a3276e00b963542eebf4590d7c91c7c1defb52fa Mon Sep 17 00:00:00 2001 From: Heine Furubotten Date: Sun, 31 Jul 2022 13:28:09 +0200 Subject: [PATCH 37/83] Bump enturclient to 0.2.4 (#75928) --- homeassistant/components/entur_public_transport/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/entur_public_transport/manifest.json b/homeassistant/components/entur_public_transport/manifest.json index c7f4fbeef53d..3bbacb8c3d4c 100644 --- a/homeassistant/components/entur_public_transport/manifest.json +++ b/homeassistant/components/entur_public_transport/manifest.json @@ -2,7 +2,7 @@ "domain": "entur_public_transport", "name": "Entur", "documentation": "https://www.home-assistant.io/integrations/entur_public_transport", - "requirements": ["enturclient==0.2.3"], + "requirements": ["enturclient==0.2.4"], "codeowners": ["@hfurubotten"], "iot_class": "cloud_polling", "loggers": ["enturclient"] diff --git a/requirements_all.txt b/requirements_all.txt index 58f2e5f73ecd..2ad42cf511c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -606,7 +606,7 @@ emulated_roku==0.2.1 enocean==0.50 # homeassistant.components.entur_public_transport -enturclient==0.2.3 +enturclient==0.2.4 # homeassistant.components.environment_canada env_canada==0.5.22 From d84bc20a58233b903bf25afc14ccea88f015b161 Mon Sep 17 00:00:00 2001 From: MasonCrawford Date: Sun, 31 Jul 2022 19:32:40 +0800 Subject: [PATCH 38/83] Small fixes for LG soundbar (#75938) --- homeassistant/components/lg_soundbar/media_player.py | 3 --- homeassistant/components/lg_soundbar/strings.json | 3 +-- homeassistant/components/lg_soundbar/translations/en.json | 3 +-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index f8f6fcf26fd5..941042d5bcef 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -47,7 +47,6 @@ class LGDevice(MediaPlayerEntity): self._port = port self._attr_unique_id = unique_id - self._name = None self._volume = 0 self._volume_min = 0 self._volume_max = 0 @@ -94,8 +93,6 @@ class LGDevice(MediaPlayerEntity): elif response["msg"] == "SPK_LIST_VIEW_INFO": if "i_vol" in data: self._volume = data["i_vol"] - if "s_user_name" in data: - self._name = data["s_user_name"] if "i_vol_min" in data: self._volume_min = data["i_vol_min"] if "i_vol_max" in data: diff --git a/homeassistant/components/lg_soundbar/strings.json b/homeassistant/components/lg_soundbar/strings.json index ef7bf32a0516..52d57eda8094 100644 --- a/homeassistant/components/lg_soundbar/strings.json +++ b/homeassistant/components/lg_soundbar/strings.json @@ -11,8 +11,7 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "existing_instance_updated": "Updated existing configuration.", - "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } } diff --git a/homeassistant/components/lg_soundbar/translations/en.json b/homeassistant/components/lg_soundbar/translations/en.json index a646279203fb..10441d215365 100644 --- a/homeassistant/components/lg_soundbar/translations/en.json +++ b/homeassistant/components/lg_soundbar/translations/en.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Service is already configured", - "existing_instance_updated": "Updated existing configuration." + "already_configured": "Device is already configured" }, "error": { "cannot_connect": "Failed to connect" From 38ae2f4e9e4fdcf02d10a6ab4a12289a77b48af4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 Jul 2022 06:15:06 -0700 Subject: [PATCH 39/83] Bump govee-ble to fix H5179 sensors (#75957) Changelog: https://github.com/Bluetooth-Devices/govee-ble/compare/v0.12.4...v0.12.5 --- homeassistant/components/govee_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/govee_ble/manifest.json b/homeassistant/components/govee_ble/manifest.json index 270858d04d4b..c7909d3e1af5 100644 --- a/homeassistant/components/govee_ble/manifest.json +++ b/homeassistant/components/govee_ble/manifest.json @@ -24,7 +24,7 @@ "service_uuid": "00008251-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["govee-ble==0.12.4"], + "requirements": ["govee-ble==0.12.5"], "dependencies": ["bluetooth"], "codeowners": ["@bdraco"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 2ad42cf511c8..0ce0cb1529b4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ googlemaps==2.5.1 goslide-api==0.5.1 # homeassistant.components.govee_ble -govee-ble==0.12.4 +govee-ble==0.12.5 # homeassistant.components.remote_rpi_gpio gpiozero==1.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f7358e148dc8..e619216cc0bc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -561,7 +561,7 @@ google-nest-sdm==2.0.0 googlemaps==2.5.1 # homeassistant.components.govee_ble -govee-ble==0.12.4 +govee-ble==0.12.5 # homeassistant.components.gree greeclimate==1.2.0 From d205fb5064f01681a768afa4c1e186accc091660 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sun, 31 Jul 2022 12:21:25 +0200 Subject: [PATCH 40/83] Handle failed connection attempts in opentherm_gw (#75961) --- .../components/opentherm_gw/__init__.py | 18 ++++++++++++++++-- .../components/opentherm_gw/config_flow.py | 15 +++++++++++---- homeassistant/components/opentherm_gw/const.py | 2 ++ .../components/opentherm_gw/strings.json | 3 ++- .../opentherm_gw/test_config_flow.py | 2 +- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/opentherm_gw/__init__.py b/homeassistant/components/opentherm_gw/__init__.py index 7c27eeceede4..cdf360c87954 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -1,9 +1,11 @@ """Support for OpenTherm Gateway devices.""" +import asyncio from datetime import date, datetime import logging import pyotgw import pyotgw.vars as gw_vars +from serial import SerialException import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -23,6 +25,7 @@ from homeassistant.const import ( Platform, ) from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import ConfigType @@ -37,6 +40,7 @@ from .const import ( CONF_PRECISION, CONF_READ_PRECISION, CONF_SET_PRECISION, + CONNECTION_TIMEOUT, DATA_GATEWAYS, DATA_OPENTHERM_GW, DOMAIN, @@ -107,8 +111,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b config_entry.add_update_listener(options_updated) - # Schedule directly on the loop to avoid blocking HA startup. - hass.loop.create_task(gateway.connect_and_subscribe()) + try: + await asyncio.wait_for( + gateway.connect_and_subscribe(), + timeout=CONNECTION_TIMEOUT, + ) + except (asyncio.TimeoutError, ConnectionError, SerialException) as ex: + raise ConfigEntryNotReady( + f"Could not connect to gateway at {gateway.device_path}: {ex}" + ) from ex await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) @@ -428,6 +439,9 @@ class OpenThermGatewayDevice: async def connect_and_subscribe(self): """Connect to serial device and subscribe report handler.""" self.status = await self.gateway.connect(self.device_path) + if not self.status: + await self.cleanup() + raise ConnectionError version_string = self.status[gw_vars.OTGW].get(gw_vars.OTGW_ABOUT) self.gw_version = version_string[18:] if version_string else None _LOGGER.debug( diff --git a/homeassistant/components/opentherm_gw/config_flow.py b/homeassistant/components/opentherm_gw/config_flow.py index 3f91496adab7..c3a955b23874 100644 --- a/homeassistant/components/opentherm_gw/config_flow.py +++ b/homeassistant/components/opentherm_gw/config_flow.py @@ -26,6 +26,7 @@ from .const import ( CONF_READ_PRECISION, CONF_SET_PRECISION, CONF_TEMPORARY_OVRD_MODE, + CONNECTION_TIMEOUT, ) @@ -62,15 +63,21 @@ class OpenThermGwConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): otgw = pyotgw.OpenThermGateway() status = await otgw.connect(device) await otgw.disconnect() + if not status: + raise ConnectionError return status[gw_vars.OTGW].get(gw_vars.OTGW_ABOUT) try: - res = await asyncio.wait_for(test_connection(), timeout=10) - except (asyncio.TimeoutError, SerialException): + await asyncio.wait_for( + test_connection(), + timeout=CONNECTION_TIMEOUT, + ) + except asyncio.TimeoutError: + return self._show_form({"base": "timeout_connect"}) + except (ConnectionError, SerialException): return self._show_form({"base": "cannot_connect"}) - if res: - return self._create_entry(gw_id, name, device) + return self._create_entry(gw_id, name, device) return self._show_form() diff --git a/homeassistant/components/opentherm_gw/const.py b/homeassistant/components/opentherm_gw/const.py index a50426285298..d72469759f1a 100644 --- a/homeassistant/components/opentherm_gw/const.py +++ b/homeassistant/components/opentherm_gw/const.py @@ -25,6 +25,8 @@ CONF_READ_PRECISION = "read_precision" CONF_SET_PRECISION = "set_precision" CONF_TEMPORARY_OVRD_MODE = "temporary_override_mode" +CONNECTION_TIMEOUT = 10 + DATA_GATEWAYS = "gateways" DATA_OPENTHERM_GW = "opentherm_gw" diff --git a/homeassistant/components/opentherm_gw/strings.json b/homeassistant/components/opentherm_gw/strings.json index f53ffeda6f6e..a80a059481dc 100644 --- a/homeassistant/components/opentherm_gw/strings.json +++ b/homeassistant/components/opentherm_gw/strings.json @@ -12,7 +12,8 @@ "error": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "id_exists": "Gateway id already exists", - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "timeout_connect": "[%key:common::config_flow::error::timeout_connect%]" } }, "options": { diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index 46d53bc54b59..080e9a96d581 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -164,7 +164,7 @@ async def test_form_connection_timeout(hass): ) assert result2["type"] == "form" - assert result2["errors"] == {"base": "cannot_connect"} + assert result2["errors"] == {"base": "timeout_connect"} assert len(mock_connect.mock_calls) == 1 From 58265664d1848f4e78cc745cc9e59d9e6b563c44 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 31 Jul 2022 18:00:42 +0200 Subject: [PATCH 41/83] Improve authentication handling for camera view (#75979) --- homeassistant/components/camera/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 3bf86dedea11..77bd0b57f1c7 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -14,7 +14,7 @@ import os from random import SystemRandom from typing import Final, Optional, cast, final -from aiohttp import web +from aiohttp import hdrs, web import async_timeout import attr import voluptuous as vol @@ -715,8 +715,11 @@ class CameraView(HomeAssistantView): ) if not authenticated: - if request[KEY_AUTHENTICATED]: + # Attempt with invalid bearer token, raise unauthorized + # so ban middleware can handle it. + if hdrs.AUTHORIZATION in request.headers: raise web.HTTPUnauthorized() + # Invalid sigAuth or camera access token raise web.HTTPForbidden() if not camera.is_on: From 26a3621bb3eda49cebffbb7e82fa5e952c1affbe Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sun, 31 Jul 2022 21:14:03 +0200 Subject: [PATCH 42/83] Bump pyotgw to 2.0.2 (#75980) --- homeassistant/components/opentherm_gw/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opentherm_gw/manifest.json b/homeassistant/components/opentherm_gw/manifest.json index 0bc69387d0b4..02b1604ea11e 100644 --- a/homeassistant/components/opentherm_gw/manifest.json +++ b/homeassistant/components/opentherm_gw/manifest.json @@ -2,7 +2,7 @@ "domain": "opentherm_gw", "name": "OpenTherm Gateway", "documentation": "https://www.home-assistant.io/integrations/opentherm_gw", - "requirements": ["pyotgw==2.0.1"], + "requirements": ["pyotgw==2.0.2"], "codeowners": ["@mvn23"], "config_flow": true, "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 0ce0cb1529b4..75f6000bd3cc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1725,7 +1725,7 @@ pyopnsense==0.2.0 pyoppleio==1.0.5 # homeassistant.components.opentherm_gw -pyotgw==2.0.1 +pyotgw==2.0.2 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e619216cc0bc..495c91e52ab9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1190,7 +1190,7 @@ pyopenuv==2022.04.0 pyopnsense==0.2.0 # homeassistant.components.opentherm_gw -pyotgw==2.0.1 +pyotgw==2.0.2 # homeassistant.auth.mfa_modules.notify # homeassistant.auth.mfa_modules.totp From e330147751d6305348fd4939692f52931f4dacb1 Mon Sep 17 00:00:00 2001 From: mkmer Date: Sun, 31 Jul 2022 16:14:30 -0400 Subject: [PATCH 43/83] Bump AIOAladdinConnect to 0.1.33 (#75986) Bump aladdin_connect 0.1.33 --- homeassistant/components/aladdin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index d551b91bce91..a142e838f3ef 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "aladdin_connect", "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", - "requirements": ["AIOAladdinConnect==0.1.31"], + "requirements": ["AIOAladdinConnect==0.1.33"], "codeowners": ["@mkmer"], "iot_class": "cloud_polling", "loggers": ["aladdin_connect"], diff --git a/requirements_all.txt b/requirements_all.txt index 75f6000bd3cc..fc5b29770d27 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.31 +AIOAladdinConnect==0.1.33 # homeassistant.components.adax Adax-local==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 495c91e52ab9..06b8d92a551d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.31 +AIOAladdinConnect==0.1.33 # homeassistant.components.adax Adax-local==0.1.4 From ebf91fe46b143a2abbfdca7c392deb85c26f56ea Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 31 Jul 2022 13:13:05 -0700 Subject: [PATCH 44/83] Bump pySwitchbot to 0.16.0 to fix compat with bleak 0.15 (#75991) --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index d6acb69431ea..dcb33c03882c 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.15.2"], + "requirements": ["PySwitchbot==0.16.0"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": ["@bdraco", "@danielhiversen", "@RenierM26", "@murtas"], diff --git a/requirements_all.txt b/requirements_all.txt index fc5b29770d27..aeaf75b987ef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.15.2 +PySwitchbot==0.16.0 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 06b8d92a551d..03cf9488f37a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.15.2 +PySwitchbot==0.16.0 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From ffd2813150c9375c6136ff04aac7bdf5776fe3c4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 31 Jul 2022 22:14:09 +0200 Subject: [PATCH 45/83] Fix Home Connect services not being set up (#75997) --- .../components/home_connect/__init__.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/home_connect/__init__.py b/homeassistant/components/home_connect/__init__.py index a2876dd86f5f..6e664ad07e44 100644 --- a/homeassistant/components/home_connect/__init__.py +++ b/homeassistant/components/home_connect/__init__.py @@ -117,24 +117,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up Home Connect component.""" hass.data[DOMAIN] = {} - if DOMAIN not in config: - return True - - await async_import_client_credential( - hass, - DOMAIN, - ClientCredential( - config[DOMAIN][CONF_CLIENT_ID], - config[DOMAIN][CONF_CLIENT_SECRET], - ), - ) - _LOGGER.warning( - "Configuration of Home Connect integration in YAML is deprecated and " - "will be removed in a future release; Your existing OAuth " - "Application Credentials have been imported into the UI " - "automatically and can be safely removed from your " - "configuration.yaml file" - ) + if DOMAIN in config: + await async_import_client_credential( + hass, + DOMAIN, + ClientCredential( + config[DOMAIN][CONF_CLIENT_ID], + config[DOMAIN][CONF_CLIENT_SECRET], + ), + ) + _LOGGER.warning( + "Configuration of Home Connect integration in YAML is deprecated and " + "will be removed in a future release; Your existing OAuth " + "Application Credentials have been imported into the UI " + "automatically and can be safely removed from your " + "configuration.yaml file" + ) async def _async_service_program(call, method): """Execute calls to services taking a program.""" From 5ab549653ba9ddc9a3b18000dbc772e4f154b624 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 31 Jul 2022 13:29:52 -0700 Subject: [PATCH 46/83] Bumped version to 2022.8.0b4 --- 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 4a37902e79f3..9ef458656654 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b3" +PATCH_VERSION: Final = "0b4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 8fca49a67f2b..284585734dcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0b3" +version = "2022.8.0b4" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 2a58bf06c1f972155f4ab261145946e58bcf7340 Mon Sep 17 00:00:00 2001 From: rhadamantys <46837767+rhadamantys@users.noreply.github.com> Date: Mon, 1 Aug 2022 17:42:47 +0200 Subject: [PATCH 47/83] Fix invalid enocean unique_id (#74508) Co-authored-by: Martin Hjelmare --- homeassistant/components/enocean/switch.py | 43 +++++++++++-- tests/components/enocean/test_switch.py | 73 ++++++++++++++++++++++ 2 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 tests/components/enocean/test_switch.py diff --git a/homeassistant/components/enocean/switch.py b/homeassistant/components/enocean/switch.py index a53f691df198..5edd2bb61557 100644 --- a/homeassistant/components/enocean/switch.py +++ b/homeassistant/components/enocean/switch.py @@ -5,12 +5,14 @@ from enocean.utils import combine_hex import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity -from homeassistant.const import CONF_ID, CONF_NAME +from homeassistant.const import CONF_ID, CONF_NAME, Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from .const import DOMAIN, LOGGER from .device import EnOceanEntity CONF_CHANNEL = "channel" @@ -25,10 +27,40 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -def setup_platform( +def generate_unique_id(dev_id: list[int], channel: int) -> str: + """Generate a valid unique id.""" + return f"{combine_hex(dev_id)}-{channel}" + + +def _migrate_to_new_unique_id(hass: HomeAssistant, dev_id, channel) -> None: + """Migrate old unique ids to new unique ids.""" + old_unique_id = f"{combine_hex(dev_id)}" + + ent_reg = entity_registry.async_get(hass) + entity_id = ent_reg.async_get_entity_id(Platform.SWITCH, DOMAIN, old_unique_id) + + if entity_id is not None: + new_unique_id = generate_unique_id(dev_id, channel) + try: + ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) + except ValueError: + LOGGER.warning( + "Skip migration of id [%s] to [%s] because it already exists", + old_unique_id, + new_unique_id, + ) + else: + LOGGER.debug( + "Migrating unique_id from [%s] to [%s]", + old_unique_id, + new_unique_id, + ) + + +async def async_setup_platform( hass: HomeAssistant, config: ConfigType, - add_entities: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up the EnOcean switch platform.""" @@ -36,7 +68,8 @@ def setup_platform( dev_id = config.get(CONF_ID) dev_name = config.get(CONF_NAME) - add_entities([EnOceanSwitch(dev_id, dev_name, channel)]) + _migrate_to_new_unique_id(hass, dev_id, channel) + async_add_entities([EnOceanSwitch(dev_id, dev_name, channel)]) class EnOceanSwitch(EnOceanEntity, SwitchEntity): @@ -49,7 +82,7 @@ class EnOceanSwitch(EnOceanEntity, SwitchEntity): self._on_state = False self._on_state2 = False self.channel = channel - self._attr_unique_id = f"{combine_hex(dev_id)}" + self._attr_unique_id = generate_unique_id(dev_id, channel) @property def is_on(self): diff --git a/tests/components/enocean/test_switch.py b/tests/components/enocean/test_switch.py new file mode 100644 index 000000000000..a7aafa6fc733 --- /dev/null +++ b/tests/components/enocean/test_switch.py @@ -0,0 +1,73 @@ +"""Tests for the EnOcean switch platform.""" + +from enocean.utils import combine_hex + +from homeassistant.components.enocean import DOMAIN as ENOCEAN_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry, assert_setup_component + +SWITCH_CONFIG = { + "switch": [ + { + "platform": ENOCEAN_DOMAIN, + "id": [0xDE, 0xAD, 0xBE, 0xEF], + "channel": 1, + "name": "room0", + }, + ] +} + + +async def test_unique_id_migration(hass: HomeAssistant) -> None: + """Test EnOcean switch ID migration.""" + + entity_name = SWITCH_CONFIG["switch"][0]["name"] + switch_entity_id = f"{SWITCH_DOMAIN}.{entity_name}" + dev_id = SWITCH_CONFIG["switch"][0]["id"] + channel = SWITCH_CONFIG["switch"][0]["channel"] + + ent_reg = er.async_get(hass) + + old_unique_id = f"{combine_hex(dev_id)}" + + entry = MockConfigEntry(domain=ENOCEAN_DOMAIN, data={"device": "/dev/null"}) + + entry.add_to_hass(hass) + + # Add a switch with an old unique_id to the entity registry + entity_entry = ent_reg.async_get_or_create( + SWITCH_DOMAIN, + ENOCEAN_DOMAIN, + old_unique_id, + suggested_object_id=entity_name, + config_entry=entry, + original_name=entity_name, + ) + + assert entity_entry.entity_id == switch_entity_id + assert entity_entry.unique_id == old_unique_id + + # Now add the sensor to check, whether the old unique_id is migrated + + with assert_setup_component(1, SWITCH_DOMAIN): + assert await async_setup_component( + hass, + SWITCH_DOMAIN, + SWITCH_CONFIG, + ) + + await hass.async_block_till_done() + + # Check that new entry has a new unique_id + entity_entry = ent_reg.async_get(switch_entity_id) + new_unique_id = f"{combine_hex(dev_id)}-{channel}" + + assert entity_entry.unique_id == new_unique_id + assert ( + ent_reg.async_get_entity_id(SWITCH_DOMAIN, ENOCEAN_DOMAIN, old_unique_id) + is None + ) From 990975e9083df99f29a686cb1215af997be7129a Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Mon, 1 Aug 2022 16:56:08 +0200 Subject: [PATCH 48/83] =?UTF-8?q?Convert=20fj=C3=A4r=C3=A5skupan=20to=20bu?= =?UTF-8?q?ilt=20in=20bluetooth=20(#75380)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add bluetooth discovery * Use home assistant standard api * Fixup manufacture data * Adjust config flow to use standard features * Fixup tests * Mock bluetooth * Simplify device check * Fix missing typing Co-authored-by: Martin Hjelmare --- .../components/fjaraskupan/__init__.py | 87 +++++++++---------- .../components/fjaraskupan/config_flow.py | 29 ++----- .../components/fjaraskupan/manifest.json | 9 +- homeassistant/generated/bluetooth.py | 12 +++ tests/components/fjaraskupan/__init__.py | 10 +++ tests/components/fjaraskupan/conftest.py | 46 +--------- .../fjaraskupan/test_config_flow.py | 48 +++++----- 7 files changed, 109 insertions(+), 132 deletions(-) diff --git a/homeassistant/components/fjaraskupan/__init__.py b/homeassistant/components/fjaraskupan/__init__.py index 36608fb026d3..fbd2f13d2b41 100644 --- a/homeassistant/components/fjaraskupan/__init__.py +++ b/homeassistant/components/fjaraskupan/__init__.py @@ -5,14 +5,20 @@ from collections.abc import Callable from dataclasses import dataclass from datetime import timedelta import logging -from typing import TYPE_CHECKING -from bleak import BleakScanner -from fjaraskupan import Device, State, device_filter +from fjaraskupan import Device, State +from homeassistant.components.bluetooth import ( + BluetoothCallbackMatcher, + BluetoothChange, + BluetoothScanningMode, + BluetoothServiceInfoBleak, + async_address_present, + async_register_callback, +) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform -from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -23,11 +29,6 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import DISPATCH_DETECTION, DOMAIN -if TYPE_CHECKING: - from bleak.backends.device import BLEDevice - from bleak.backends.scanner import AdvertisementData - - PLATFORMS = [ Platform.BINARY_SENSOR, Platform.FAN, @@ -70,16 +71,18 @@ class Coordinator(DataUpdateCoordinator[State]): async def _async_update_data(self) -> State: """Handle an explicit update request.""" if self._refresh_was_scheduled: - raise UpdateFailed("No data received within schedule.") + if async_address_present(self.hass, self.device.address): + return self.device.state + raise UpdateFailed( + "No data received within schedule, and device is no longer present" + ) await self.device.update() return self.device.state - def detection_callback( - self, ble_device: BLEDevice, advertisement_data: AdvertisementData - ) -> None: + def detection_callback(self, service_info: BluetoothServiceInfoBleak) -> None: """Handle a new announcement of data.""" - self.device.detection_callback(ble_device, advertisement_data) + self.device.detection_callback(service_info.device, service_info.advertisement) self.async_set_updated_data(self.device.state) @@ -87,59 +90,52 @@ class Coordinator(DataUpdateCoordinator[State]): class EntryState: """Store state of config entry.""" - scanner: BleakScanner coordinators: dict[str, Coordinator] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Fjäråskupan from a config entry.""" - scanner = BleakScanner(filters={"DuplicateData": True}) - - state = EntryState(scanner, {}) + state = EntryState({}) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = state - async def detection_callback( - ble_device: BLEDevice, advertisement_data: AdvertisementData + def detection_callback( + service_info: BluetoothServiceInfoBleak, change: BluetoothChange ) -> None: - if data := state.coordinators.get(ble_device.address): - _LOGGER.debug( - "Update: %s %s - %s", ble_device.name, ble_device, advertisement_data - ) - - data.detection_callback(ble_device, advertisement_data) + if change != BluetoothChange.ADVERTISEMENT: + return + if data := state.coordinators.get(service_info.address): + _LOGGER.debug("Update: %s", service_info) + data.detection_callback(service_info) else: - if not device_filter(ble_device, advertisement_data): - return + _LOGGER.debug("Detected: %s", service_info) - _LOGGER.debug( - "Detected: %s %s - %s", ble_device.name, ble_device, advertisement_data - ) - - device = Device(ble_device) + device = Device(service_info.device) device_info = DeviceInfo( - identifiers={(DOMAIN, ble_device.address)}, + identifiers={(DOMAIN, service_info.address)}, manufacturer="Fjäråskupan", name="Fjäråskupan", ) coordinator: Coordinator = Coordinator(hass, device, device_info) - coordinator.detection_callback(ble_device, advertisement_data) + coordinator.detection_callback(service_info) - state.coordinators[ble_device.address] = coordinator + state.coordinators[service_info.address] = coordinator async_dispatcher_send( hass, f"{DISPATCH_DETECTION}.{entry.entry_id}", coordinator ) - scanner.register_detection_callback(detection_callback) - await scanner.start() - - async def on_hass_stop(event: Event) -> None: - await scanner.stop() - entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop) + async_register_callback( + hass, + detection_callback, + BluetoothCallbackMatcher( + manufacturer_id=20296, + manufacturer_data_start=[79, 68, 70, 74, 65, 82], + ), + BluetoothScanningMode.ACTIVE, + ) ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) @@ -177,7 +173,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - entry_state: EntryState = hass.data[DOMAIN].pop(entry.entry_id) - await entry_state.scanner.stop() + hass.data[DOMAIN].pop(entry.entry_id) return unload_ok diff --git a/homeassistant/components/fjaraskupan/config_flow.py b/homeassistant/components/fjaraskupan/config_flow.py index ffac366500ba..dd1dc03d3ad9 100644 --- a/homeassistant/components/fjaraskupan/config_flow.py +++ b/homeassistant/components/fjaraskupan/config_flow.py @@ -1,42 +1,25 @@ """Config flow for Fjäråskupan integration.""" from __future__ import annotations -import asyncio - -import async_timeout -from bleak import BleakScanner -from bleak.backends.device import BLEDevice -from bleak.backends.scanner import AdvertisementData from fjaraskupan import device_filter +from homeassistant.components.bluetooth import async_discovered_service_info from homeassistant.core import HomeAssistant from homeassistant.helpers.config_entry_flow import register_discovery_flow from .const import DOMAIN -CONST_WAIT_TIME = 5.0 - async def _async_has_devices(hass: HomeAssistant) -> bool: """Return if there are devices that can be discovered.""" - event = asyncio.Event() + service_infos = async_discovered_service_info(hass) - def detection(device: BLEDevice, advertisement_data: AdvertisementData): - if device_filter(device, advertisement_data): - event.set() + for service_info in service_infos: + if device_filter(service_info.device, service_info.advertisement): + return True - async with BleakScanner( - detection_callback=detection, - filters={"DuplicateData": True}, - ): - try: - async with async_timeout.timeout(CONST_WAIT_TIME): - await event.wait() - except asyncio.TimeoutError: - return False - - return True + return False register_discovery_flow(DOMAIN, "Fjäråskupan", _async_has_devices) diff --git a/homeassistant/components/fjaraskupan/manifest.json b/homeassistant/components/fjaraskupan/manifest.json index 3ff6e599a6b2..bf7956d297d5 100644 --- a/homeassistant/components/fjaraskupan/manifest.json +++ b/homeassistant/components/fjaraskupan/manifest.json @@ -6,5 +6,12 @@ "requirements": ["fjaraskupan==1.0.2"], "codeowners": ["@elupus"], "iot_class": "local_polling", - "loggers": ["bleak", "fjaraskupan"] + "loggers": ["bleak", "fjaraskupan"], + "dependencies": ["bluetooth"], + "bluetooth": [ + { + "manufacturer_id": 20296, + "manufacturer_data_start": [79, 68, 70, 74, 65, 82] + } + ] } diff --git a/homeassistant/generated/bluetooth.py b/homeassistant/generated/bluetooth.py index 2cbaebb6074e..ef8193dad288 100644 --- a/homeassistant/generated/bluetooth.py +++ b/homeassistant/generated/bluetooth.py @@ -7,6 +7,18 @@ from __future__ import annotations # fmt: off BLUETOOTH: list[dict[str, str | int | list[int]]] = [ + { + "domain": "fjaraskupan", + "manufacturer_id": 20296, + "manufacturer_data_start": [ + 79, + 68, + 70, + 74, + 65, + 82 + ] + }, { "domain": "govee_ble", "local_name": "Govee*" diff --git a/tests/components/fjaraskupan/__init__.py b/tests/components/fjaraskupan/__init__.py index 26a5ecd66059..35c69f98d653 100644 --- a/tests/components/fjaraskupan/__init__.py +++ b/tests/components/fjaraskupan/__init__.py @@ -1 +1,11 @@ """Tests for the Fjäråskupan integration.""" + + +from bleak.backends.device import BLEDevice +from bleak.backends.scanner import AdvertisementData + +from homeassistant.components.bluetooth import SOURCE_LOCAL, BluetoothServiceInfoBleak + +COOKER_SERVICE_INFO = BluetoothServiceInfoBleak.from_advertisement( + BLEDevice("1.1.1.1", "COOKERHOOD_FJAR"), AdvertisementData(), source=SOURCE_LOCAL +) diff --git a/tests/components/fjaraskupan/conftest.py b/tests/components/fjaraskupan/conftest.py index 4e06b2ad0463..46ff5ae167aa 100644 --- a/tests/components/fjaraskupan/conftest.py +++ b/tests/components/fjaraskupan/conftest.py @@ -1,47 +1,9 @@ """Standard fixtures for the Fjäråskupan integration.""" from __future__ import annotations -from unittest.mock import patch - -from bleak.backends.device import BLEDevice -from bleak.backends.scanner import AdvertisementData, BaseBleakScanner -from pytest import fixture +import pytest -@fixture(name="scanner", autouse=True) -def fixture_scanner(hass): - """Fixture for scanner.""" - - devices = [BLEDevice("1.1.1.1", "COOKERHOOD_FJAR")] - - class MockScanner(BaseBleakScanner): - """Mock Scanner.""" - - def __init__(self, *args, **kwargs) -> None: - """Initialize the scanner.""" - super().__init__( - detection_callback=kwargs.pop("detection_callback"), service_uuids=[] - ) - - async def start(self): - """Start scanning for devices.""" - for device in devices: - self._callback(device, AdvertisementData()) - - async def stop(self): - """Stop scanning for devices.""" - - @property - def discovered_devices(self) -> list[BLEDevice]: - """Return discovered devices.""" - return devices - - def set_scanning_filter(self, **kwargs): - """Set the scanning filter.""" - - with patch( - "homeassistant.components.fjaraskupan.config_flow.BleakScanner", new=MockScanner - ), patch( - "homeassistant.components.fjaraskupan.config_flow.CONST_WAIT_TIME", new=0.01 - ): - yield devices +@pytest.fixture(autouse=True) +def mock_bluetooth(enable_bluetooth): + """Auto mock bluetooth.""" diff --git a/tests/components/fjaraskupan/test_config_flow.py b/tests/components/fjaraskupan/test_config_flow.py index a51b8c6f9fa9..bef53e180733 100644 --- a/tests/components/fjaraskupan/test_config_flow.py +++ b/tests/components/fjaraskupan/test_config_flow.py @@ -3,7 +3,6 @@ from __future__ import annotations from unittest.mock import patch -from bleak.backends.device import BLEDevice from pytest import fixture from homeassistant import config_entries @@ -11,6 +10,8 @@ from homeassistant.components.fjaraskupan.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from . import COOKER_SERVICE_INFO + @fixture(name="mock_setup_entry", autouse=True) async def fixture_mock_setup_entry(hass): @@ -24,31 +25,38 @@ async def fixture_mock_setup_entry(hass): async def test_configure(hass: HomeAssistant, mock_setup_entry) -> None: """Test we get the form.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + with patch( + "homeassistant.components.fjaraskupan.config_flow.async_discovered_service_info", + return_value=[COOKER_SERVICE_INFO], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) - assert result["type"] == FlowResultType.FORM - result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == FlowResultType.FORM + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Fjäråskupan" - assert result["data"] == {} + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Fjäråskupan" + assert result["data"] == {} - await hass.async_block_till_done() - assert len(mock_setup_entry.mock_calls) == 1 + await hass.async_block_till_done() + assert len(mock_setup_entry.mock_calls) == 1 -async def test_scan_no_devices(hass: HomeAssistant, scanner: list[BLEDevice]) -> None: +async def test_scan_no_devices(hass: HomeAssistant) -> None: """Test we get the form.""" - scanner.clear() - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} - ) + with patch( + "homeassistant.components.fjaraskupan.config_flow.async_discovered_service_info", + return_value=[], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) - assert result["type"] == FlowResultType.FORM - result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == FlowResultType.FORM + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - assert result["type"] == FlowResultType.ABORT - assert result["reason"] == "no_devices_found" + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "no_devices_found" From c6038380d68f1745759070d1b98aca14a2716eb6 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 1 Aug 2022 18:20:20 -0700 Subject: [PATCH 49/83] Add repair issues for nest app auth removal and yaml deprecation (#75974) * Add repair issues for nest app auth removal and yaml deprecation * Apply PR feedback * Re-apply suggestion that i force pushed over * Update criticality level --- homeassistant/components/nest/__init__.py | 33 +++++++++++++++---- homeassistant/components/nest/manifest.json | 2 +- homeassistant/components/nest/strings.json | 10 ++++++ .../components/nest/translations/en.json | 18 +++++----- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 72759ac0f52a..44658558b62f 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -29,6 +29,11 @@ from homeassistant.components.application_credentials import ( from homeassistant.components.camera import Image, img_util from homeassistant.components.http.const import KEY_HASS_USER from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.repairs import ( + IssueSeverity, + async_create_issue, + async_delete_issue, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_BINARY_SENSORS, @@ -187,6 +192,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: entry, unique_id=entry.data[CONF_PROJECT_ID] ) + async_delete_issue(hass, DOMAIN, "removed_app_auth") + subscriber = await api.new_subscriber(hass, entry) if not subscriber: return False @@ -255,6 +262,18 @@ async def async_import_config(hass: HomeAssistant, entry: ConfigEntry) -> None: if entry.data["auth_implementation"] == INSTALLED_AUTH_DOMAIN: # App Auth credentials have been deprecated and must be re-created # by the user in the config flow + async_create_issue( + hass, + DOMAIN, + "removed_app_auth", + is_fixable=False, + severity=IssueSeverity.ERROR, + translation_key="removed_app_auth", + translation_placeholders={ + "more_info_url": "https://www.home-assistant.io/more-info/nest-auth-deprecation", + "documentation_url": "https://www.home-assistant.io/integrations/nest/", + }, + ) raise ConfigEntryAuthFailed( "Google has deprecated App Auth credentials, and the integration " "must be reconfigured in the UI to restore access to Nest Devices." @@ -271,12 +290,14 @@ async def async_import_config(hass: HomeAssistant, entry: ConfigEntry) -> None: WEB_AUTH_DOMAIN, ) - _LOGGER.warning( - "Configuration of Nest integration in YAML is deprecated and " - "will be removed in a future release; Your existing configuration " - "(including OAuth Application Credentials) has been imported into " - "the UI automatically and can be safely removed from your " - "configuration.yaml file" + async_create_issue( + hass, + DOMAIN, + "deprecated_yaml", + breaks_in_ha_version="2022.10.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", ) diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index 72e0aed8420e..d826272b2073 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -2,7 +2,7 @@ "domain": "nest", "name": "Nest", "config_flow": true, - "dependencies": ["ffmpeg", "http", "application_credentials"], + "dependencies": ["ffmpeg", "http", "application_credentials", "repairs"], "after_dependencies": ["media_source"], "documentation": "https://www.home-assistant.io/integrations/nest", "requirements": ["python-nest==4.2.0", "google-nest-sdm==2.0.0"], diff --git a/homeassistant/components/nest/strings.json b/homeassistant/components/nest/strings.json index 0a13de41511a..07ba63ac479f 100644 --- a/homeassistant/components/nest/strings.json +++ b/homeassistant/components/nest/strings.json @@ -88,5 +88,15 @@ "camera_sound": "Sound detected", "doorbell_chime": "Doorbell pressed" } + }, + "issues": { + "deprecated_yaml": { + "title": "The Nest YAML configuration is being removed", + "description": "Configuring Nest in configuration.yaml is being removed in Home Assistant 2022.10.\n\nYour existing OAuth Application Credentials and access settings have been imported into the UI automatically. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + }, + "removed_app_auth": { + "title": "Nest Authentication Credentials must be updated", + "description": "To improve security and reduce phishing risk Google has deprecated the authentication method used by Home Assistant.\n\n**This requires action by you to resolve** ([more info]({more_info_url}))\n\n1. Visit the integrations page\n1. Click Reconfigure on the Nest integration.\n1. Home Assistant will walk you through the steps to upgrade to Web Authentication.\n\nSee the Nest [integration instructions]({documentation_url}) for troubleshooting information." + } } } diff --git a/homeassistant/components/nest/translations/en.json b/homeassistant/components/nest/translations/en.json index 5f026e55f310..cd8274d635a2 100644 --- a/homeassistant/components/nest/translations/en.json +++ b/homeassistant/components/nest/translations/en.json @@ -10,7 +10,6 @@ "missing_configuration": "The component is not configured. Please follow the documentation.", "no_url_available": "No URL available. For information about this error, [check the help section]({docs_url})", "reauth_successful": "Re-authentication was successful", - "single_instance_allowed": "Already configured. Only a single configuration possible.", "unknown_authorize_url_generation": "Unknown error generating an authorize URL." }, "create_entry": { @@ -26,13 +25,6 @@ "wrong_project_id": "Please enter a valid Cloud Project ID (was same as Device Access Project ID)" }, "step": { - "auth": { - "data": { - "code": "Access Token" - }, - "description": "To link your Google account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided Auth Token code below.", - "title": "Link Google Account" - }, "auth_upgrade": { "description": "App Auth has been deprecated by Google to improve security, and you need to take action by creating new application credentials.\n\nOpen the [documentation]({more_info_url}) to follow along as the next steps will guide you through the steps you need to take to restore access to your Nest devices.", "title": "Nest: App Auth Deprecation" @@ -96,5 +88,15 @@ "camera_sound": "Sound detected", "doorbell_chime": "Doorbell pressed" } + }, + "issues": { + "deprecated_yaml": { + "description": "Configuring Nest in configuration.yaml is being removed in Home Assistant 2022.10.\n\nYour existing OAuth Application Credentials and access settings have been imported into the UI automatically. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue.", + "title": "The Nest YAML configuration is being removed" + }, + "removed_app_auth": { + "description": "To improve security and reduce phishing risk Google has deprecated the authentication method used by Home Assistant.\n\n**This requires action by you to resolve** ([more info]({more_info_url}))\n\n1. Visit the integrations page\n1. Click Reconfigure on the Nest integration.\n1. Home Assistant will walk you through the steps to upgrade to Web Authentication.\n\nSee the Nest [integration instructions]({documentation_url}) for troubleshooting information.", + "title": "Nest Authentication Credentials must be updated" + } } } \ No newline at end of file From 75747ce319532c68a4897875a8b2ebace9b8a98b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 1 Aug 2022 14:22:24 +0200 Subject: [PATCH 50/83] Support MWh for gas consumption sensors (#76016) --- homeassistant/components/energy/validate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/energy/validate.py b/homeassistant/components/energy/validate.py index 02549ddfe96b..9d6b3bd53c76 100644 --- a/homeassistant/components/energy/validate.py +++ b/homeassistant/components/energy/validate.py @@ -40,7 +40,11 @@ GAS_USAGE_DEVICE_CLASSES = ( sensor.SensorDeviceClass.GAS, ) GAS_USAGE_UNITS = { - sensor.SensorDeviceClass.ENERGY: (ENERGY_WATT_HOUR, ENERGY_KILO_WATT_HOUR), + sensor.SensorDeviceClass.ENERGY: ( + ENERGY_WATT_HOUR, + ENERGY_KILO_WATT_HOUR, + ENERGY_MEGA_WATT_HOUR, + ), sensor.SensorDeviceClass.GAS: (VOLUME_CUBIC_METERS, VOLUME_CUBIC_FEET), } GAS_PRICE_UNITS = tuple( From a332eb154c3fc6a25364a4f88a2bbdedc302565c Mon Sep 17 00:00:00 2001 From: Jc2k Date: Mon, 1 Aug 2022 22:04:16 +0100 Subject: [PATCH 51/83] Add reauth flow to xiaomi_ble, fixes problem adding LYWSD03MMC (#76028) --- .../components/xiaomi_ble/config_flow.py | 136 +++++--- .../components/xiaomi_ble/manifest.json | 2 +- homeassistant/components/xiaomi_ble/sensor.py | 45 ++- .../components/xiaomi_ble/strings.json | 4 + .../xiaomi_ble/translations/en.json | 6 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/xiaomi_ble/test_config_flow.py | 293 +++++++++++++++++- 8 files changed, 439 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/config_flow.py b/homeassistant/components/xiaomi_ble/config_flow.py index aa1ffc248950..092c60e97135 100644 --- a/homeassistant/components/xiaomi_ble/config_flow.py +++ b/homeassistant/components/xiaomi_ble/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from collections.abc import Mapping import dataclasses from typing import Any @@ -12,7 +13,7 @@ from xiaomi_ble.parser import EncryptionScheme from homeassistant.components import onboarding from homeassistant.components.bluetooth import ( BluetoothScanningMode, - BluetoothServiceInfoBleak, + BluetoothServiceInfo, async_discovered_service_info, async_process_advertisements, ) @@ -31,11 +32,11 @@ class Discovery: """A discovered bluetooth device.""" title: str - discovery_info: BluetoothServiceInfoBleak + discovery_info: BluetoothServiceInfo device: DeviceData -def _title(discovery_info: BluetoothServiceInfoBleak, device: DeviceData) -> str: +def _title(discovery_info: BluetoothServiceInfo, device: DeviceData) -> str: return device.title or device.get_device_name() or discovery_info.name @@ -46,19 +47,19 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): def __init__(self) -> None: """Initialize the config flow.""" - self._discovery_info: BluetoothServiceInfoBleak | None = None + self._discovery_info: BluetoothServiceInfo | None = None self._discovered_device: DeviceData | None = None self._discovered_devices: dict[str, Discovery] = {} async def _async_wait_for_full_advertisement( - self, discovery_info: BluetoothServiceInfoBleak, device: DeviceData - ) -> BluetoothServiceInfoBleak: + self, discovery_info: BluetoothServiceInfo, device: DeviceData + ) -> BluetoothServiceInfo: """Sometimes first advertisement we receive is blank or incomplete. Wait until we get a useful one.""" if not device.pending: return discovery_info def _process_more_advertisements( - service_info: BluetoothServiceInfoBleak, + service_info: BluetoothServiceInfo, ) -> bool: device.update(service_info) return not device.pending @@ -72,7 +73,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): ) async def async_step_bluetooth( - self, discovery_info: BluetoothServiceInfoBleak + self, discovery_info: BluetoothServiceInfo ) -> FlowResult: """Handle the bluetooth discovery step.""" await self.async_set_unique_id(discovery_info.address) @@ -81,20 +82,21 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): if not device.supported(discovery_info): return self.async_abort(reason="not_supported") + title = _title(discovery_info, device) + self.context["title_placeholders"] = {"name": title} + + self._discovered_device = device + # Wait until we have received enough information about this device to detect its encryption type try: - discovery_info = await self._async_wait_for_full_advertisement( + self._discovery_info = await self._async_wait_for_full_advertisement( discovery_info, device ) except asyncio.TimeoutError: - # If we don't see a valid packet within the timeout then this device is not supported. - return self.async_abort(reason="not_supported") - - self._discovery_info = discovery_info - self._discovered_device = device - - title = _title(discovery_info, device) - self.context["title_placeholders"] = {"name": title} + # This device might have a really long advertising interval + # So create a config entry for it, and if we discover it has encryption later + # We can do a reauth + return await self.async_step_confirm_slow() if device.encryption_scheme == EncryptionScheme.MIBEACON_LEGACY: return await self.async_step_get_encryption_key_legacy() @@ -107,6 +109,8 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): ) -> FlowResult: """Enter a legacy bindkey for a v2/v3 MiBeacon device.""" assert self._discovery_info + assert self._discovered_device + errors = {} if user_input is not None: @@ -115,18 +119,15 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): if len(bindkey) != 24: errors["bindkey"] = "expected_24_characters" else: - device = DeviceData(bindkey=bytes.fromhex(bindkey)) + self._discovered_device.bindkey = bytes.fromhex(bindkey) # If we got this far we already know supported will # return true so we don't bother checking that again # We just want to retry the decryption - device.supported(self._discovery_info) + self._discovered_device.supported(self._discovery_info) - if device.bindkey_verified: - return self.async_create_entry( - title=self.context["title_placeholders"]["name"], - data={"bindkey": bindkey}, - ) + if self._discovered_device.bindkey_verified: + return self._async_get_or_create_entry(bindkey) errors["bindkey"] = "decryption_failed" @@ -142,6 +143,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): ) -> FlowResult: """Enter a bindkey for a v4/v5 MiBeacon device.""" assert self._discovery_info + assert self._discovered_device errors = {} @@ -151,18 +153,15 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): if len(bindkey) != 32: errors["bindkey"] = "expected_32_characters" else: - device = DeviceData(bindkey=bytes.fromhex(bindkey)) + self._discovered_device.bindkey = bytes.fromhex(bindkey) # If we got this far we already know supported will # return true so we don't bother checking that again # We just want to retry the decryption - device.supported(self._discovery_info) + self._discovered_device.supported(self._discovery_info) - if device.bindkey_verified: - return self.async_create_entry( - title=self.context["title_placeholders"]["name"], - data={"bindkey": bindkey}, - ) + if self._discovered_device.bindkey_verified: + return self._async_get_or_create_entry(bindkey) errors["bindkey"] = "decryption_failed" @@ -178,10 +177,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): ) -> FlowResult: """Confirm discovery.""" if user_input is not None or not onboarding.async_is_onboarded(self.hass): - return self.async_create_entry( - title=self.context["title_placeholders"]["name"], - data={}, - ) + return self._async_get_or_create_entry() self._set_confirm_only() return self.async_show_form( @@ -189,6 +185,19 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): description_placeholders=self.context["title_placeholders"], ) + async def async_step_confirm_slow( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Ack that device is slow.""" + if user_input is not None or not onboarding.async_is_onboarded(self.hass): + return self._async_get_or_create_entry() + + self._set_confirm_only() + return self.async_show_form( + step_id="confirm_slow", + description_placeholders=self.context["title_placeholders"], + ) + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: @@ -198,24 +207,28 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(address, raise_on_progress=False) discovery = self._discovered_devices[address] + self.context["title_placeholders"] = {"name": discovery.title} + # Wait until we have received enough information about this device to detect its encryption type try: self._discovery_info = await self._async_wait_for_full_advertisement( discovery.discovery_info, discovery.device ) except asyncio.TimeoutError: - # If we don't see a valid packet within the timeout then this device is not supported. - return self.async_abort(reason="not_supported") + # This device might have a really long advertising interval + # So create a config entry for it, and if we discover it has encryption later + # We can do a reauth + return await self.async_step_confirm_slow() + + self._discovered_device = discovery.device if discovery.device.encryption_scheme == EncryptionScheme.MIBEACON_LEGACY: - self.context["title_placeholders"] = {"name": discovery.title} return await self.async_step_get_encryption_key_legacy() if discovery.device.encryption_scheme == EncryptionScheme.MIBEACON_4_5: - self.context["title_placeholders"] = {"name": discovery.title} return await self.async_step_get_encryption_key_4_5() - return self.async_create_entry(title=discovery.title, data={}) + return self._async_get_or_create_entry() current_addresses = self._async_current_ids() for discovery_info in async_discovered_service_info(self.hass): @@ -241,3 +254,46 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=vol.Schema({vol.Required(CONF_ADDRESS): vol.In(titles)}), ) + + async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: + """Handle a flow initialized by a reauth event.""" + entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) + assert entry is not None + + device: DeviceData = self.context["device"] + self._discovered_device = device + + self._discovery_info = device.last_service_info + + if device.encryption_scheme == EncryptionScheme.MIBEACON_LEGACY: + return await self.async_step_get_encryption_key_legacy() + + if device.encryption_scheme == EncryptionScheme.MIBEACON_4_5: + return await self.async_step_get_encryption_key_4_5() + + # Otherwise there wasn't actually encryption so abort + return self.async_abort(reason="reauth_successful") + + def _async_get_or_create_entry(self, bindkey=None): + data = {} + + if bindkey: + data["bindkey"] = bindkey + + if entry_id := self.context.get("entry_id"): + entry = self.hass.config_entries.async_get_entry(entry_id) + assert entry is not None + + self.hass.config_entries.async_update_entry(entry, data=data) + + # Reload the config entry to notify of updated config + self.hass.async_create_task( + self.hass.config_entries.async_reload(entry.entry_id) + ) + + return self.async_abort(reason="reauth_successful") + + return self.async_create_entry( + title=self.context["title_placeholders"]["name"], + data=data, + ) diff --git a/homeassistant/components/xiaomi_ble/manifest.json b/homeassistant/components/xiaomi_ble/manifest.json index 0d97dcbedf84..a901439b2c97 100644 --- a/homeassistant/components/xiaomi_ble/manifest.json +++ b/homeassistant/components/xiaomi_ble/manifest.json @@ -8,7 +8,7 @@ "service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["xiaomi-ble==0.6.2"], + "requirements": ["xiaomi-ble==0.6.4"], "dependencies": ["bluetooth"], "codeowners": ["@Jc2k", "@Ernst79"], "iot_class": "local_push" diff --git a/homeassistant/components/xiaomi_ble/sensor.py b/homeassistant/components/xiaomi_ble/sensor.py index a96722620a91..b3cd51269674 100644 --- a/homeassistant/components/xiaomi_ble/sensor.py +++ b/homeassistant/components/xiaomi_ble/sensor.py @@ -11,8 +11,10 @@ from xiaomi_ble import ( Units, XiaomiBluetoothDeviceData, ) +from xiaomi_ble.parser import EncryptionScheme from homeassistant import config_entries +from homeassistant.components.bluetooth import BluetoothServiceInfoBleak from homeassistant.components.bluetooth.passive_update_processor import ( PassiveBluetoothDataProcessor, PassiveBluetoothDataUpdate, @@ -163,6 +165,45 @@ def sensor_update_to_bluetooth_data_update( ) +def process_service_info( + hass: HomeAssistant, + entry: config_entries.ConfigEntry, + data: XiaomiBluetoothDeviceData, + service_info: BluetoothServiceInfoBleak, +) -> PassiveBluetoothDataUpdate: + """Process a BluetoothServiceInfoBleak, running side effects and returning sensor data.""" + update = data.update(service_info) + + # If device isn't pending we know it has seen at least one broadcast with a payload + # If that payload was encrypted and the bindkey was not verified then we need to reauth + if ( + not data.pending + and data.encryption_scheme != EncryptionScheme.NONE + and not data.bindkey_verified + ): + flow_context = { + "source": config_entries.SOURCE_REAUTH, + "entry_id": entry.entry_id, + "title_placeholders": {"name": entry.title}, + "unique_id": entry.unique_id, + "device": data, + } + + for flow in hass.config_entries.flow.async_progress_by_handler(DOMAIN): + if flow["context"] == flow_context: + break + else: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context=flow_context, + data=entry.data, + ) + ) + + return sensor_update_to_bluetooth_data_update(update) + + async def async_setup_entry( hass: HomeAssistant, entry: config_entries.ConfigEntry, @@ -177,9 +218,7 @@ async def async_setup_entry( kwargs["bindkey"] = bytes.fromhex(bindkey) data = XiaomiBluetoothDeviceData(**kwargs) processor = PassiveBluetoothDataProcessor( - lambda service_info: sensor_update_to_bluetooth_data_update( - data.update(service_info) - ) + lambda service_info: process_service_info(hass, entry, data, service_info) ) entry.async_on_unload( processor.async_add_entities_listener( diff --git a/homeassistant/components/xiaomi_ble/strings.json b/homeassistant/components/xiaomi_ble/strings.json index e12a15a06714..48d5c3a87f7e 100644 --- a/homeassistant/components/xiaomi_ble/strings.json +++ b/homeassistant/components/xiaomi_ble/strings.json @@ -11,6 +11,9 @@ "bluetooth_confirm": { "description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]" }, + "slow_confirm": { + "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if its needed." + }, "get_encryption_key_legacy": { "description": "The sensor data broadcast by the sensor is encrypted. In order to decrypt it we need a 24 character hexadecimal bindkey.", "data": { @@ -25,6 +28,7 @@ } }, "abort": { + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", diff --git a/homeassistant/components/xiaomi_ble/translations/en.json b/homeassistant/components/xiaomi_ble/translations/en.json index b9f4e024f92e..836ccc516379 100644 --- a/homeassistant/components/xiaomi_ble/translations/en.json +++ b/homeassistant/components/xiaomi_ble/translations/en.json @@ -6,7 +6,8 @@ "decryption_failed": "The provided bindkey did not work, sensor data could not be decrypted. Please check it and try again.", "expected_24_characters": "Expected a 24 character hexadecimal bindkey.", "expected_32_characters": "Expected a 32 character hexadecimal bindkey.", - "no_devices_found": "No devices found on the network" + "no_devices_found": "No devices found on the network", + "reauth_successful": "Re-authentication was successful" }, "flow_title": "{name}", "step": { @@ -25,6 +26,9 @@ }, "description": "The sensor data broadcast by the sensor is encrypted. In order to decrypt it we need a 24 character hexadecimal bindkey." }, + "slow_confirm": { + "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if its needed." + }, "user": { "data": { "address": "Device" diff --git a/requirements_all.txt b/requirements_all.txt index aeaf75b987ef..4d3e28cce460 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2470,7 +2470,7 @@ xbox-webapi==2.0.11 xboxapi==2.0.1 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.6.2 +xiaomi-ble==0.6.4 # homeassistant.components.knx xknx==0.22.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 03cf9488f37a..431483efd328 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1662,7 +1662,7 @@ wolf_smartset==0.1.11 xbox-webapi==2.0.11 # homeassistant.components.xiaomi_ble -xiaomi-ble==0.6.2 +xiaomi-ble==0.6.4 # homeassistant.components.knx xknx==0.22.1 diff --git a/tests/components/xiaomi_ble/test_config_flow.py b/tests/components/xiaomi_ble/test_config_flow.py index 86fda21aaa00..0d123f0cd548 100644 --- a/tests/components/xiaomi_ble/test_config_flow.py +++ b/tests/components/xiaomi_ble/test_config_flow.py @@ -3,7 +3,10 @@ import asyncio from unittest.mock import patch +from xiaomi_ble import XiaomiBluetoothDeviceData as DeviceData + from homeassistant import config_entries +from homeassistant.components.bluetooth import BluetoothChange from homeassistant.components.xiaomi_ble.const import DOMAIN from homeassistant.data_entry_flow import FlowResultType @@ -52,8 +55,19 @@ async def test_async_step_bluetooth_valid_device_but_missing_payload(hass): context={"source": config_entries.SOURCE_BLUETOOTH}, data=MISSING_PAYLOAD_ENCRYPTED, ) - assert result["type"] == FlowResultType.ABORT - assert result["reason"] == "not_supported" + assert result["type"] == FlowResultType.FORM + assert result["step_id"] == "confirm_slow" + + with patch( + "homeassistant.components.xiaomi_ble.async_setup_entry", return_value=True + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert result2["title"] == "LYWSD02MMC" + assert result2["data"] == {} + assert result2["result"].unique_id == "A4:C1:38:56:53:84" async def test_async_step_bluetooth_valid_device_but_missing_payload_then_full(hass): @@ -318,6 +332,24 @@ async def test_async_step_user_no_devices_found(hass): assert result["reason"] == "no_devices_found" +async def test_async_step_user_no_devices_found_2(hass): + """ + Test setup from service info cache with no devices found. + + This variant tests with a non-Xiaomi device known to us. + """ + with patch( + "homeassistant.components.xiaomi_ble.config_flow.async_discovered_service_info", + return_value=[NOT_SENSOR_PUSH_SERVICE_INFO], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_USER}, + ) + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "no_devices_found" + + async def test_async_step_user_with_found_devices(hass): """Test setup from service info cache with devices found.""" with patch( @@ -363,8 +395,19 @@ async def test_async_step_user_short_payload(hass): result["flow_id"], user_input={"address": "A4:C1:38:56:53:84"}, ) - assert result2["type"] == FlowResultType.ABORT - assert result2["reason"] == "not_supported" + assert result2["type"] == FlowResultType.FORM + assert result2["step_id"] == "confirm_slow" + + with patch( + "homeassistant.components.xiaomi_ble.async_setup_entry", return_value=True + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result3["type"] == FlowResultType.CREATE_ENTRY + assert result3["title"] == "LYWSD02MMC" + assert result3["data"] == {} + assert result3["result"].unique_id == "A4:C1:38:56:53:84" async def test_async_step_user_short_payload_then_full(hass): @@ -755,3 +798,245 @@ async def test_async_step_user_takes_precedence_over_discovery(hass): # Verify the original one was aborted assert not hass.config_entries.flow.async_progress(DOMAIN) + + +async def test_async_step_reauth_legacy(hass): + """Test reauth with a legacy key.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="F8:24:41:C5:98:8B", + ) + entry.add_to_hass(hass) + saved_callback = None + + def _async_register_callback(_hass, _callback, _matcher, _mode): + nonlocal saved_callback + saved_callback = _callback + return lambda: None + + with patch( + "homeassistant.components.bluetooth.update_coordinator.async_register_callback", + _async_register_callback, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + + # WARNING: This test data is synthetic, rather than captured from a real device + # obj type is 0x1310, payload len is 0x2 and payload is 0x6000 + saved_callback( + make_advertisement( + "F8:24:41:C5:98:8B", + b"X0\xb6\x03\xd2\x8b\x98\xc5A$\xf8\xc3I\x14vu~\x00\x00\x00\x99", + ), + BluetoothChange.ADVERTISEMENT, + ) + + await hass.async_block_till_done() + + results = hass.config_entries.flow.async_progress() + assert len(results) == 1 + result = results[0] + + assert result["step_id"] == "get_encryption_key_legacy" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"bindkey": "b853075158487ca39a5b5ea9"}, + ) + assert result2["type"] == FlowResultType.ABORT + assert result2["reason"] == "reauth_successful" + + +async def test_async_step_reauth_legacy_wrong_key(hass): + """Test reauth with a bad legacy key, and that we can recover.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="F8:24:41:C5:98:8B", + ) + entry.add_to_hass(hass) + saved_callback = None + + def _async_register_callback(_hass, _callback, _matcher, _mode): + nonlocal saved_callback + saved_callback = _callback + return lambda: None + + with patch( + "homeassistant.components.bluetooth.update_coordinator.async_register_callback", + _async_register_callback, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + + # WARNING: This test data is synthetic, rather than captured from a real device + # obj type is 0x1310, payload len is 0x2 and payload is 0x6000 + saved_callback( + make_advertisement( + "F8:24:41:C5:98:8B", + b"X0\xb6\x03\xd2\x8b\x98\xc5A$\xf8\xc3I\x14vu~\x00\x00\x00\x99", + ), + BluetoothChange.ADVERTISEMENT, + ) + + await hass.async_block_till_done() + + results = hass.config_entries.flow.async_progress() + assert len(results) == 1 + result = results[0] + + assert result["step_id"] == "get_encryption_key_legacy" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"bindkey": "b85307515a487ca39a5b5ea9"}, + ) + assert result2["type"] == FlowResultType.FORM + assert result["step_id"] == "get_encryption_key_legacy" + assert result2["errors"]["bindkey"] == "decryption_failed" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"bindkey": "b853075158487ca39a5b5ea9"}, + ) + assert result2["type"] == FlowResultType.ABORT + assert result2["reason"] == "reauth_successful" + + +async def test_async_step_reauth_v4(hass): + """Test reauth with a v4 key.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="54:EF:44:E3:9C:BC", + ) + entry.add_to_hass(hass) + saved_callback = None + + def _async_register_callback(_hass, _callback, _matcher, _mode): + nonlocal saved_callback + saved_callback = _callback + return lambda: None + + with patch( + "homeassistant.components.bluetooth.update_coordinator.async_register_callback", + _async_register_callback, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + + # WARNING: This test data is synthetic, rather than captured from a real device + # obj type is 0x1310, payload len is 0x2 and payload is 0x6000 + saved_callback( + make_advertisement( + "54:EF:44:E3:9C:BC", + b"XY\x97\tf\xbc\x9c\xe3D\xefT\x01" b"\x08\x12\x05\x00\x00\x00q^\xbe\x90", + ), + BluetoothChange.ADVERTISEMENT, + ) + + await hass.async_block_till_done() + + results = hass.config_entries.flow.async_progress() + assert len(results) == 1 + result = results[0] + + assert result["step_id"] == "get_encryption_key_4_5" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}, + ) + assert result2["type"] == FlowResultType.ABORT + assert result2["reason"] == "reauth_successful" + + +async def test_async_step_reauth_v4_wrong_key(hass): + """Test reauth for v4 with a bad key, and that we can recover.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="54:EF:44:E3:9C:BC", + ) + entry.add_to_hass(hass) + saved_callback = None + + def _async_register_callback(_hass, _callback, _matcher, _mode): + nonlocal saved_callback + saved_callback = _callback + return lambda: None + + with patch( + "homeassistant.components.bluetooth.update_coordinator.async_register_callback", + _async_register_callback, + ): + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + + # WARNING: This test data is synthetic, rather than captured from a real device + # obj type is 0x1310, payload len is 0x2 and payload is 0x6000 + saved_callback( + make_advertisement( + "54:EF:44:E3:9C:BC", + b"XY\x97\tf\xbc\x9c\xe3D\xefT\x01" b"\x08\x12\x05\x00\x00\x00q^\xbe\x90", + ), + BluetoothChange.ADVERTISEMENT, + ) + + await hass.async_block_till_done() + + results = hass.config_entries.flow.async_progress() + assert len(results) == 1 + result = results[0] + + assert result["step_id"] == "get_encryption_key_4_5" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"bindkey": "5b51a7c91cde6707c9ef18dada143a58"}, + ) + assert result2["type"] == FlowResultType.FORM + assert result2["step_id"] == "get_encryption_key_4_5" + assert result2["errors"]["bindkey"] == "decryption_failed" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={"bindkey": "5b51a7c91cde6707c9ef18dfda143a58"}, + ) + assert result2["type"] == FlowResultType.ABORT + assert result2["reason"] == "reauth_successful" + + +async def test_async_step_reauth_abort_early(hass): + """ + Test we can abort the reauth if there is no encryption. + + (This can't currently happen in practice). + """ + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="54:EF:44:E3:9C:BC", + ) + entry.add_to_hass(hass) + + device = DeviceData() + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={ + "source": config_entries.SOURCE_REAUTH, + "entry_id": entry.entry_id, + "title_placeholders": {"name": entry.title}, + "unique_id": entry.unique_id, + "device": device, + }, + data=entry.data, + ) + + assert result["type"] == FlowResultType.ABORT + assert result["reason"] == "reauth_successful" From b962a6e7677aa36c443db1004604355f896a7803 Mon Sep 17 00:00:00 2001 From: krazos Date: Mon, 1 Aug 2022 12:45:18 -0400 Subject: [PATCH 52/83] Fix capitalization of Sonos "Status light" entity name (#76035) Tweak capitalization of "Status light" entity name Tweak capitalization of "Status light" entity name for consistency with blog post guidance, which states that entity names should start with a capital letter, with the rest of the words lower case --- homeassistant/components/sonos/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/switch.py b/homeassistant/components/sonos/switch.py index 8dcf47fd0a29..a348b40cb0f7 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -72,7 +72,7 @@ FRIENDLY_NAMES = { ATTR_MUSIC_PLAYBACK_FULL_VOLUME: "Surround music full volume", ATTR_NIGHT_SOUND: "Night sound", ATTR_SPEECH_ENHANCEMENT: "Speech enhancement", - ATTR_STATUS_LIGHT: "Status Light", + ATTR_STATUS_LIGHT: "Status light", ATTR_SUB_ENABLED: "Subwoofer enabled", ATTR_SURROUND_ENABLED: "Surround enabled", ATTR_TOUCH_CONTROLS: "Touch controls", From 6b588d41ffc9a6ecab655cde672d7a91c8ce7a20 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 1 Aug 2022 11:43:07 -0400 Subject: [PATCH 53/83] Enhance logging for ZHA device trigger validation (#76036) * Enhance logging for ZHA device trigger validation * use IntegrationError --- homeassistant/components/zha/core/gateway.py | 4 +++- homeassistant/components/zha/core/helpers.py | 18 ++++++++++++++++-- homeassistant/components/zha/device_trigger.py | 4 ++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zha/core/gateway.py b/homeassistant/components/zha/core/gateway.py index f2fd226249be..14fbf2cf701e 100644 --- a/homeassistant/components/zha/core/gateway.py +++ b/homeassistant/components/zha/core/gateway.py @@ -142,6 +142,7 @@ class ZHAGateway: self._log_relay_handler = LogRelayHandler(hass, self) self.config_entry = config_entry self._unsubs: list[Callable[[], None]] = [] + self.initialized: bool = False async def async_initialize(self) -> None: """Initialize controller and connect radio.""" @@ -183,6 +184,7 @@ class ZHAGateway: self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(self.coordinator_ieee) self.async_load_devices() self.async_load_groups() + self.initialized = True @callback def async_load_devices(self) -> None: @@ -217,7 +219,7 @@ class ZHAGateway: async def async_initialize_devices_and_entities(self) -> None: """Initialize devices and load entities.""" - _LOGGER.debug("Loading all devices") + _LOGGER.debug("Initializing all devices from Zigpy cache") await asyncio.gather( *(dev.async_initialize(from_cache=True) for dev in self.devices.values()) ) diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index b60f61b1e8e5..7fd789ac3f51 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -26,6 +26,7 @@ import zigpy.zdo.types as zdo_types from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, State, callback +from homeassistant.exceptions import IntegrationError from homeassistant.helpers import device_registry as dr from .const import ( @@ -42,6 +43,7 @@ if TYPE_CHECKING: from .gateway import ZHAGateway _T = TypeVar("_T") +_LOGGER = logging.getLogger(__name__) @dataclass @@ -170,10 +172,22 @@ def async_get_zha_device(hass: HomeAssistant, device_id: str) -> ZHADevice: device_registry = dr.async_get(hass) registry_device = device_registry.async_get(device_id) if not registry_device: + _LOGGER.error("Device id `%s` not found in registry", device_id) raise KeyError(f"Device id `{device_id}` not found in registry.") zha_gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] - ieee_address = list(list(registry_device.identifiers)[0])[1] - ieee = zigpy.types.EUI64.convert(ieee_address) + if not zha_gateway.initialized: + _LOGGER.error("Attempting to get a ZHA device when ZHA is not initialized") + raise IntegrationError("ZHA is not initialized yet") + try: + ieee_address = list(list(registry_device.identifiers)[0])[1] + ieee = zigpy.types.EUI64.convert(ieee_address) + except (IndexError, ValueError) as ex: + _LOGGER.error( + "Unable to determine device IEEE for device with device id `%s`", device_id + ) + raise KeyError( + f"Unable to determine device IEEE for device with device id `{device_id}`." + ) from ex return zha_gateway.devices[ieee] diff --git a/homeassistant/components/zha/device_trigger.py b/homeassistant/components/zha/device_trigger.py index 44682aaa559c..cdd98110f832 100644 --- a/homeassistant/components/zha/device_trigger.py +++ b/homeassistant/components/zha/device_trigger.py @@ -13,7 +13,7 @@ from homeassistant.components.device_automation.exceptions import ( from homeassistant.components.homeassistant.triggers import event as event_trigger from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE from homeassistant.core import CALLBACK_TYPE, HomeAssistant -from homeassistant.exceptions import HomeAssistantError +from homeassistant.exceptions import HomeAssistantError, IntegrationError from homeassistant.helpers.typing import ConfigType from . import DOMAIN @@ -39,7 +39,7 @@ async def async_validate_trigger_config( trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) try: zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID]) - except (KeyError, AttributeError) as err: + except (KeyError, AttributeError, IntegrationError) as err: raise InvalidDeviceAutomationConfig from err if ( zha_device.device_automation_triggers is None From 4f671bccbcca0175d377b785e7594eded83fb9a3 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Aug 2022 08:54:28 +0200 Subject: [PATCH 54/83] Support multiple trigger instances for a single webhook (#76037) --- homeassistant/components/webhook/trigger.py | 65 +++++++++++++++------ tests/components/mobile_app/test_webhook.py | 2 +- tests/components/webhook/test_trigger.py | 63 ++++++++++++++++++-- 3 files changed, 107 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/webhook/trigger.py b/homeassistant/components/webhook/trigger.py index 3f790b1ec420..498a7363a61b 100644 --- a/homeassistant/components/webhook/trigger.py +++ b/homeassistant/components/webhook/trigger.py @@ -1,5 +1,7 @@ """Offer webhook triggered automation rules.""" -from functools import partial +from __future__ import annotations + +from dataclasses import dataclass from aiohttp import hdrs import voluptuous as vol @@ -13,7 +15,7 @@ from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -from . import async_register, async_unregister +from . import DOMAIN, async_register, async_unregister # mypy: allow-untyped-defs @@ -26,20 +28,35 @@ TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend( } ) +WEBHOOK_TRIGGERS = f"{DOMAIN}_triggers" -async def _handle_webhook(job, trigger_data, hass, webhook_id, request): + +@dataclass +class TriggerInstance: + """Attached trigger settings.""" + + automation_info: AutomationTriggerInfo + job: HassJob + + +async def _handle_webhook(hass, webhook_id, request): """Handle incoming webhook.""" - result = {"platform": "webhook", "webhook_id": webhook_id} + base_result = {"platform": "webhook", "webhook_id": webhook_id} if "json" in request.headers.get(hdrs.CONTENT_TYPE, ""): - result["json"] = await request.json() + base_result["json"] = await request.json() else: - result["data"] = await request.post() + base_result["data"] = await request.post() - result["query"] = request.query - result["description"] = "webhook" - result.update(**trigger_data) - hass.async_run_hass_job(job, {"trigger": result}) + base_result["query"] = request.query + base_result["description"] = "webhook" + + triggers: dict[str, list[TriggerInstance]] = hass.data.setdefault( + WEBHOOK_TRIGGERS, {} + ) + for trigger in triggers[webhook_id]: + result = {**base_result, **trigger.automation_info["trigger_data"]} + hass.async_run_hass_job(trigger.job, {"trigger": result}) async def async_attach_trigger( @@ -49,20 +66,32 @@ async def async_attach_trigger( automation_info: AutomationTriggerInfo, ) -> CALLBACK_TYPE: """Trigger based on incoming webhooks.""" - trigger_data = automation_info["trigger_data"] webhook_id: str = config[CONF_WEBHOOK_ID] job = HassJob(action) - async_register( - hass, - automation_info["domain"], - automation_info["name"], - webhook_id, - partial(_handle_webhook, job, trigger_data), + + triggers: dict[str, list[TriggerInstance]] = hass.data.setdefault( + WEBHOOK_TRIGGERS, {} ) + if webhook_id not in triggers: + async_register( + hass, + automation_info["domain"], + automation_info["name"], + webhook_id, + _handle_webhook, + ) + triggers[webhook_id] = [] + + trigger_instance = TriggerInstance(automation_info, job) + triggers[webhook_id].append(trigger_instance) + @callback def unregister(): """Unregister webhook.""" - async_unregister(hass, webhook_id) + triggers[webhook_id].remove(trigger_instance) + if not triggers[webhook_id]: + async_unregister(hass, webhook_id) + triggers.pop(webhook_id) return unregister diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index 0bc237b1c110..b7b95dff3926 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -840,7 +840,7 @@ async def test_webhook_handle_scan_tag(hass, create_registrations, webhook_clien @callback def store_event(event): - """Helepr to store events.""" + """Help store events.""" events.append(event) hass.bus.async_listen("tag_scanned", store_event) diff --git a/tests/components/webhook/test_trigger.py b/tests/components/webhook/test_trigger.py index 2deac022b1e5..e8d88845f5ad 100644 --- a/tests/components/webhook/test_trigger.py +++ b/tests/components/webhook/test_trigger.py @@ -23,7 +23,7 @@ async def test_webhook_json(hass, hass_client_no_auth): @callback def store_event(event): - """Helepr to store events.""" + """Help store events.""" events.append(event) hass.bus.async_listen("test_success", store_event) @@ -62,7 +62,7 @@ async def test_webhook_post(hass, hass_client_no_auth): @callback def store_event(event): - """Helepr to store events.""" + """Help store events.""" events.append(event) hass.bus.async_listen("test_success", store_event) @@ -97,7 +97,7 @@ async def test_webhook_query(hass, hass_client_no_auth): @callback def store_event(event): - """Helepr to store events.""" + """Help store events.""" events.append(event) hass.bus.async_listen("test_success", store_event) @@ -126,13 +126,68 @@ async def test_webhook_query(hass, hass_client_no_auth): assert events[0].data["hello"] == "yo world" +async def test_webhook_multiple(hass, hass_client_no_auth): + """Test triggering multiple triggers with a POST webhook.""" + events1 = [] + events2 = [] + + @callback + def store_event1(event): + """Help store events.""" + events1.append(event) + + @callback + def store_event2(event): + """Help store events.""" + events2.append(event) + + hass.bus.async_listen("test_success1", store_event1) + hass.bus.async_listen("test_success2", store_event2) + + assert await async_setup_component( + hass, + "automation", + { + "automation": [ + { + "trigger": {"platform": "webhook", "webhook_id": "post_webhook"}, + "action": { + "event": "test_success1", + "event_data_template": {"hello": "yo {{ trigger.data.hello }}"}, + }, + }, + { + "trigger": {"platform": "webhook", "webhook_id": "post_webhook"}, + "action": { + "event": "test_success2", + "event_data_template": { + "hello": "yo2 {{ trigger.data.hello }}" + }, + }, + }, + ] + }, + ) + await hass.async_block_till_done() + + client = await hass_client_no_auth() + + await client.post("/api/webhook/post_webhook", data={"hello": "world"}) + await hass.async_block_till_done() + + assert len(events1) == 1 + assert events1[0].data["hello"] == "yo world" + assert len(events2) == 1 + assert events2[0].data["hello"] == "yo2 world" + + async def test_webhook_reload(hass, hass_client_no_auth): """Test reloading a webhook.""" events = [] @callback def store_event(event): - """Helepr to store events.""" + """Help store events.""" events.append(event) hass.bus.async_listen("test_success", store_event) From 7140a9d025496488d5feae17b3dc7f356a5abb4c Mon Sep 17 00:00:00 2001 From: mkmer Date: Tue, 2 Aug 2022 02:56:50 -0400 Subject: [PATCH 55/83] Bump AIOAladdinConnect to 0.1.37 (#76046) --- homeassistant/components/aladdin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index a142e838f3ef..008b8f81c898 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "aladdin_connect", "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", - "requirements": ["AIOAladdinConnect==0.1.33"], + "requirements": ["AIOAladdinConnect==0.1.37"], "codeowners": ["@mkmer"], "iot_class": "cloud_polling", "loggers": ["aladdin_connect"], diff --git a/requirements_all.txt b/requirements_all.txt index 4d3e28cce460..bd55f88f2df1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.33 +AIOAladdinConnect==0.1.37 # homeassistant.components.adax Adax-local==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 431483efd328..31e5e646ad89 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.33 +AIOAladdinConnect==0.1.37 # homeassistant.components.adax Adax-local==0.1.4 From 23488f392b0a7a4071051eed40020d2e9f6ecb12 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Aug 2022 20:46:22 -1000 Subject: [PATCH 56/83] Lower bluetooth startup timeout to 9s to avoid warning (#76050) --- homeassistant/components/bluetooth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index 9adaac84333b..ba079a426feb 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -46,7 +46,7 @@ _LOGGER = logging.getLogger(__name__) UNAVAILABLE_TRACK_SECONDS: Final = 60 * 5 -START_TIMEOUT = 15 +START_TIMEOUT = 9 SOURCE_LOCAL: Final = "local" From 66afd1e696e42853653b96e5ef86b3bfa1651ddf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Aug 2022 20:36:27 -1000 Subject: [PATCH 57/83] Bump bluetooth-adapters to 0.1.3 (#76052) --- homeassistant/components/bluetooth/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/bluetooth/manifest.json b/homeassistant/components/bluetooth/manifest.json index f215e8fa161a..40e63ec71803 100644 --- a/homeassistant/components/bluetooth/manifest.json +++ b/homeassistant/components/bluetooth/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/bluetooth", "dependencies": ["websocket_api"], "quality_scale": "internal", - "requirements": ["bleak==0.15.0", "bluetooth-adapters==0.1.2"], + "requirements": ["bleak==0.15.0", "bluetooth-adapters==0.1.3"], "codeowners": ["@bdraco"], "config_flow": true, "iot_class": "local_push" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c9d424daa33b..bea9b749445e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ attrs==21.2.0 awesomeversion==22.6.0 bcrypt==3.1.7 bleak==0.15.0 -bluetooth-adapters==0.1.2 +bluetooth-adapters==0.1.3 certifi>=2021.5.30 ciso8601==2.2.0 cryptography==36.0.2 diff --git a/requirements_all.txt b/requirements_all.txt index bd55f88f2df1..229a8ad90eca 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -424,7 +424,7 @@ blockchain==1.4.4 # bluepy==1.3.0 # homeassistant.components.bluetooth -bluetooth-adapters==0.1.2 +bluetooth-adapters==0.1.3 # homeassistant.components.bond bond-async==0.1.22 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 31e5e646ad89..b29ccb60cff5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -335,7 +335,7 @@ blebox_uniapi==2.0.2 blinkpy==0.19.0 # homeassistant.components.bluetooth -bluetooth-adapters==0.1.2 +bluetooth-adapters==0.1.3 # homeassistant.components.bond bond-async==0.1.22 From 30cd087f6f0e36f1fcfe13780ae28625ac609921 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Aug 2022 20:34:48 -1000 Subject: [PATCH 58/83] Fix govee H5074 data (#76057) --- homeassistant/components/govee_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/govee_ble/manifest.json b/homeassistant/components/govee_ble/manifest.json index c7909d3e1af5..624a38ebe9de 100644 --- a/homeassistant/components/govee_ble/manifest.json +++ b/homeassistant/components/govee_ble/manifest.json @@ -24,7 +24,7 @@ "service_uuid": "00008251-0000-1000-8000-00805f9b34fb" } ], - "requirements": ["govee-ble==0.12.5"], + "requirements": ["govee-ble==0.12.6"], "dependencies": ["bluetooth"], "codeowners": ["@bdraco"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 229a8ad90eca..2b38d1155e7d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -760,7 +760,7 @@ googlemaps==2.5.1 goslide-api==0.5.1 # homeassistant.components.govee_ble -govee-ble==0.12.5 +govee-ble==0.12.6 # homeassistant.components.remote_rpi_gpio gpiozero==1.6.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b29ccb60cff5..7ef74a1c6ddb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -561,7 +561,7 @@ google-nest-sdm==2.0.0 googlemaps==2.5.1 # homeassistant.components.govee_ble -govee-ble==0.12.5 +govee-ble==0.12.6 # homeassistant.components.gree greeclimate==1.2.0 From da00f5ba1e019867d9ba17cba1baf6955a9cbdfe Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Aug 2022 09:38:21 +0200 Subject: [PATCH 59/83] Bumped version to 2022.8.0b5 --- 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 9ef458656654..385db6904e4c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b4" +PATCH_VERSION: Final = "0b5" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 284585734dcd..fff0c129cec1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0b4" +version = "2022.8.0b5" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From b9ee81dfc33704577d13305665eedf2184073f7f Mon Sep 17 00:00:00 2001 From: lunmay <28674102+lunmay@users.noreply.github.com> Date: Tue, 2 Aug 2022 19:22:29 +0200 Subject: [PATCH 60/83] Fix capitalization in mitemp_bt strings (#76063) Co-authored-by: Franck Nijhof --- homeassistant/components/mitemp_bt/strings.json | 2 +- homeassistant/components/mitemp_bt/translations/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mitemp_bt/strings.json b/homeassistant/components/mitemp_bt/strings.json index d36c25eafec2..1f9f031a3bbb 100644 --- a/homeassistant/components/mitemp_bt/strings.json +++ b/homeassistant/components/mitemp_bt/strings.json @@ -2,7 +2,7 @@ "issues": { "replaced": { "title": "The Xiaomi Mijia BLE Temperature and Humidity Sensor integration has been replaced", - "description": "The Xiaomi Mijia BLE Temperature and Humidity Sensor integration stopped working in Home Assistant 2022.7 and was replaced by the Xiaomi BLE integration in the 2022.8 release.\n\nThere is no migration path possible, therefore, you have to add your Xiaomi Mijia BLE device using the new integration manually.\n\nYour existing Xiaomi Mijia BLE Temperature and Humidity sensor YAML configuration is no longer used by Home Assistant. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + "description": "The Xiaomi Mijia BLE Temperature and Humidity Sensor integration stopped working in Home Assistant 2022.7 and was replaced by the Xiaomi BLE integration in the 2022.8 release.\n\nThere is no migration path possible, therefore, you have to add your Xiaomi Mijia BLE device using the new integration manually.\n\nYour existing Xiaomi Mijia BLE Temperature and Humidity Sensor YAML configuration is no longer used by Home Assistant. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue." } } } diff --git a/homeassistant/components/mitemp_bt/translations/en.json b/homeassistant/components/mitemp_bt/translations/en.json index cd5113ee02f7..78ec041405bc 100644 --- a/homeassistant/components/mitemp_bt/translations/en.json +++ b/homeassistant/components/mitemp_bt/translations/en.json @@ -1,7 +1,7 @@ { "issues": { "replaced": { - "description": "The Xiaomi Mijia BLE Temperature and Humidity Sensor integration stopped working in Home Assistant 2022.7 and was replaced by the Xiaomi BLE integration in the 2022.8 release.\n\nThere is no migration path possible, therefore, you have to add your Xiaomi Mijia BLE device using the new integration manually.\n\nYour existing Xiaomi Mijia BLE Temperature and Humidity sensor YAML configuration is no longer used by Home Assistant. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue.", + "description": "The Xiaomi Mijia BLE Temperature and Humidity Sensor integration stopped working in Home Assistant 2022.7 and was replaced by the Xiaomi BLE integration in the 2022.8 release.\n\nThere is no migration path possible, therefore, you have to add your Xiaomi Mijia BLE device using the new integration manually.\n\nYour existing Xiaomi Mijia BLE Temperature and Humidity Sensor YAML configuration is no longer used by Home Assistant. Remove the YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue.", "title": "The Xiaomi Mijia BLE Temperature and Humidity Sensor integration has been replaced" } } From ed579515712ced262c88af69eeb9df031df5bdb1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Aug 2022 11:42:57 +0200 Subject: [PATCH 61/83] Small title adjustment to the Home Assistant Alerts integration (#76070) --- homeassistant/components/homeassistant_alerts/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/homeassistant_alerts/manifest.json b/homeassistant/components/homeassistant_alerts/manifest.json index 0d276c6f3ae6..7c9ddf4f9050 100644 --- a/homeassistant/components/homeassistant_alerts/manifest.json +++ b/homeassistant/components/homeassistant_alerts/manifest.json @@ -1,6 +1,6 @@ { "domain": "homeassistant_alerts", - "name": "Home Assistant alerts", + "name": "Home Assistant Alerts", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/homeassistant_alerts", "codeowners": ["@home-assistant/core"], From 676664022d5716b2347044e2d42112412e31136c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Aug 2022 14:13:07 +0200 Subject: [PATCH 62/83] Handle missing attributes in meater objects (#76072) --- homeassistant/components/meater/sensor.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/meater/sensor.py b/homeassistant/components/meater/sensor.py index 84ef3a2e2a99..a2753a423078 100644 --- a/homeassistant/components/meater/sensor.py +++ b/homeassistant/components/meater/sensor.py @@ -43,14 +43,18 @@ class MeaterSensorEntityDescription( def _elapsed_time_to_timestamp(probe: MeaterProbe) -> datetime | None: """Convert elapsed time to timestamp.""" - if not probe.cook: + if not probe.cook or not hasattr(probe.cook, "time_elapsed"): return None return dt_util.utcnow() - timedelta(seconds=probe.cook.time_elapsed) def _remaining_time_to_timestamp(probe: MeaterProbe) -> datetime | None: """Convert remaining time to timestamp.""" - if not probe.cook or probe.cook.time_remaining < 0: + if ( + not probe.cook + or not hasattr(probe.cook, "time_remaining") + or probe.cook.time_remaining < 0 + ): return None return dt_util.utcnow() + timedelta(seconds=probe.cook.time_remaining) @@ -99,7 +103,9 @@ SENSOR_TYPES = ( native_unit_of_measurement=TEMP_CELSIUS, state_class=SensorStateClass.MEASUREMENT, available=lambda probe: probe is not None and probe.cook is not None, - value=lambda probe: probe.cook.target_temperature if probe.cook else None, + value=lambda probe: probe.cook.target_temperature + if probe.cook and hasattr(probe.cook, "target_temperature") + else None, ), # Peak temperature MeaterSensorEntityDescription( @@ -109,7 +115,9 @@ SENSOR_TYPES = ( native_unit_of_measurement=TEMP_CELSIUS, state_class=SensorStateClass.MEASUREMENT, available=lambda probe: probe is not None and probe.cook is not None, - value=lambda probe: probe.cook.peak_temperature if probe.cook else None, + value=lambda probe: probe.cook.peak_temperature + if probe.cook and hasattr(probe.cook, "peak_temperature") + else None, ), # Remaining time in seconds. When unknown/calculating default is used. Default: -1 # Exposed as a TIMESTAMP sensor where the timestamp is current time + remaining time. From 654e26052bd8c94cef527ca85e36db500701524b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Aug 2022 13:03:34 +0200 Subject: [PATCH 63/83] Remove Somfy from Overkiz title in manifest (#76073) --- homeassistant/components/overkiz/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/overkiz/manifest.json b/homeassistant/components/overkiz/manifest.json index a75950652244..6e6e57f12e5f 100644 --- a/homeassistant/components/overkiz/manifest.json +++ b/homeassistant/components/overkiz/manifest.json @@ -1,6 +1,6 @@ { "domain": "overkiz", - "name": "Overkiz (by Somfy)", + "name": "Overkiz", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/overkiz", "requirements": ["pyoverkiz==1.4.2"], From 2eddbf2381613b39db88c5c6063cf8e0b4064904 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Tue, 2 Aug 2022 13:11:06 +0100 Subject: [PATCH 64/83] Fix typo in new xiaomi_ble string (#76076) --- homeassistant/components/xiaomi_ble/strings.json | 2 +- homeassistant/components/xiaomi_ble/translations/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/strings.json b/homeassistant/components/xiaomi_ble/strings.json index 48d5c3a87f7e..9d2a0ae40d8e 100644 --- a/homeassistant/components/xiaomi_ble/strings.json +++ b/homeassistant/components/xiaomi_ble/strings.json @@ -12,7 +12,7 @@ "description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]" }, "slow_confirm": { - "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if its needed." + "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if it's needed." }, "get_encryption_key_legacy": { "description": "The sensor data broadcast by the sensor is encrypted. In order to decrypt it we need a 24 character hexadecimal bindkey.", diff --git a/homeassistant/components/xiaomi_ble/translations/en.json b/homeassistant/components/xiaomi_ble/translations/en.json index 836ccc516379..4648b28cc93f 100644 --- a/homeassistant/components/xiaomi_ble/translations/en.json +++ b/homeassistant/components/xiaomi_ble/translations/en.json @@ -27,7 +27,7 @@ "description": "The sensor data broadcast by the sensor is encrypted. In order to decrypt it we need a 24 character hexadecimal bindkey." }, "slow_confirm": { - "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if its needed." + "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if it's needed." }, "user": { "data": { From c90a223cb6e9f06782eca0ee4abeca67137376a3 Mon Sep 17 00:00:00 2001 From: mkmer Date: Tue, 2 Aug 2022 10:10:20 -0400 Subject: [PATCH 65/83] Bump AIOAladdinConnect to 0.1.39 (#76082) --- homeassistant/components/aladdin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/aladdin_connect/manifest.json b/homeassistant/components/aladdin_connect/manifest.json index 008b8f81c898..5e55f391aa65 100644 --- a/homeassistant/components/aladdin_connect/manifest.json +++ b/homeassistant/components/aladdin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "aladdin_connect", "name": "Aladdin Connect", "documentation": "https://www.home-assistant.io/integrations/aladdin_connect", - "requirements": ["AIOAladdinConnect==0.1.37"], + "requirements": ["AIOAladdinConnect==0.1.39"], "codeowners": ["@mkmer"], "iot_class": "cloud_polling", "loggers": ["aladdin_connect"], diff --git a/requirements_all.txt b/requirements_all.txt index 2b38d1155e7d..52b514d7b0c8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -5,7 +5,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.37 +AIOAladdinConnect==0.1.39 # homeassistant.components.adax Adax-local==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7ef74a1c6ddb..fd332772724f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ AEMET-OpenData==0.2.1 # homeassistant.components.aladdin_connect -AIOAladdinConnect==0.1.37 +AIOAladdinConnect==0.1.39 # homeassistant.components.adax Adax-local==0.1.4 From cc9a130f580fe25422ede49a5e36d647484cf763 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Aug 2022 19:05:09 +0200 Subject: [PATCH 66/83] Refresh homeassistant_alerts when hass has started (#76083) --- homeassistant/components/homeassistant_alerts/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homeassistant_alerts/__init__.py b/homeassistant/components/homeassistant_alerts/__init__.py index d405b9e257d9..60386e3d080d 100644 --- a/homeassistant/components/homeassistant_alerts/__init__.py +++ b/homeassistant/components/homeassistant_alerts/__init__.py @@ -14,6 +14,7 @@ from homeassistant.components.repairs.models import IssueSeverity from homeassistant.const import __version__ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.start import async_at_start from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util.yaml import parse_yaml @@ -100,7 +101,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: coordinator = AlertUpdateCoordinator(hass) coordinator.async_add_listener(async_schedule_update_alerts) - await coordinator.async_refresh() + + async def initial_refresh(hass: HomeAssistant) -> None: + await coordinator.async_refresh() + + async_at_start(hass, initial_refresh) return True From c4906414ea807502ae2cf5c5a89e813fd77478f1 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Tue, 2 Aug 2022 11:29:32 -0400 Subject: [PATCH 67/83] Ensure ZHA devices load before validating device triggers (#76084) --- homeassistant/components/zha/__init__.py | 5 ++++- homeassistant/components/zha/core/const.py | 1 + homeassistant/components/zha/device_trigger.py | 9 +++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 70b9dfd9b46b..0a7d43120f7c 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -35,6 +35,7 @@ from .core.const import ( DOMAIN, PLATFORMS, SIGNAL_ADD_ENTITIES, + ZHA_DEVICES_LOADED_EVENT, RadioType, ) from .core.discovery import GROUP_PROBE @@ -75,7 +76,7 @@ _LOGGER = logging.getLogger(__name__) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up ZHA from config.""" - hass.data[DATA_ZHA] = {} + hass.data[DATA_ZHA] = {ZHA_DEVICES_LOADED_EVENT: asyncio.Event()} if DOMAIN in config: conf = config[DOMAIN] @@ -109,6 +110,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b zha_gateway = ZHAGateway(hass, config, config_entry) await zha_gateway.async_initialize() + hass.data[DATA_ZHA][ZHA_DEVICES_LOADED_EVENT].set() device_registry = dr.async_get(hass) device_registry.async_get_or_create( @@ -141,6 +143,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> """Unload ZHA config entry.""" zha_gateway: ZHAGateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] await zha_gateway.shutdown() + hass.data[DATA_ZHA][ZHA_DEVICES_LOADED_EVENT].clear() GROUP_PROBE.cleanup() api.async_unload_api(hass) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 4c8c6e03c793..dfa5f608cfe6 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -394,6 +394,7 @@ ZHA_GW_MSG_GROUP_REMOVED = "group_removed" ZHA_GW_MSG_LOG_ENTRY = "log_entry" ZHA_GW_MSG_LOG_OUTPUT = "log_output" ZHA_GW_MSG_RAW_INIT = "raw_device_initialized" +ZHA_DEVICES_LOADED_EVENT = "zha_devices_loaded_event" EFFECT_BLINK = 0x00 EFFECT_BREATHE = 0x01 diff --git a/homeassistant/components/zha/device_trigger.py b/homeassistant/components/zha/device_trigger.py index cdd98110f832..4ad8eccea1d2 100644 --- a/homeassistant/components/zha/device_trigger.py +++ b/homeassistant/components/zha/device_trigger.py @@ -16,8 +16,8 @@ from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.exceptions import HomeAssistantError, IntegrationError from homeassistant.helpers.typing import ConfigType -from . import DOMAIN -from .core.const import ZHA_EVENT +from . import DOMAIN as ZHA_DOMAIN +from .core.const import DATA_ZHA, ZHA_DEVICES_LOADED_EVENT, ZHA_EVENT from .core.helpers import async_get_zha_device CONF_SUBTYPE = "subtype" @@ -35,7 +35,8 @@ async def async_validate_trigger_config( """Validate config.""" config = TRIGGER_SCHEMA(config) - if "zha" in hass.config.components: + if ZHA_DOMAIN in hass.config.components: + await hass.data[DATA_ZHA][ZHA_DEVICES_LOADED_EVENT].wait() trigger = (config[CONF_TYPE], config[CONF_SUBTYPE]) try: zha_device = async_get_zha_device(hass, config[CONF_DEVICE_ID]) @@ -100,7 +101,7 @@ async def async_get_triggers( triggers.append( { CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, + CONF_DOMAIN: ZHA_DOMAIN, CONF_PLATFORM: DEVICE, CONF_TYPE: trigger, CONF_SUBTYPE: subtype, From e073f6b4391bc5177ebda1aa9bf6a2f80a42cb4d Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 2 Aug 2022 11:08:33 -0500 Subject: [PATCH 68/83] Bump Frontend to 20220802.0 (#76087) --- 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 45331491aa04..ed9b381ee9d4 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20220728.0"], + "requirements": ["home-assistant-frontend==20220802.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index bea9b749445e..f2140a9eca76 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -18,7 +18,7 @@ cryptography==36.0.2 fnvhash==0.1.0 hass-nabucasa==0.54.1 home-assistant-bluetooth==1.3.0 -home-assistant-frontend==20220728.0 +home-assistant-frontend==20220802.0 httpx==0.23.0 ifaddr==0.1.7 jinja2==3.1.2 diff --git a/requirements_all.txt b/requirements_all.txt index 52b514d7b0c8..71aee1700ab8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -839,7 +839,7 @@ hole==0.7.0 holidays==0.14.2 # homeassistant.components.frontend -home-assistant-frontend==20220728.0 +home-assistant-frontend==20220802.0 # homeassistant.components.home_connect homeconnect==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fd332772724f..61a4b29b3b7c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -616,7 +616,7 @@ hole==0.7.0 holidays==0.14.2 # homeassistant.components.frontend -home-assistant-frontend==20220728.0 +home-assistant-frontend==20220802.0 # homeassistant.components.home_connect homeconnect==0.7.1 From 19b0961084068c7e8865ed0db5a20d2f69518a14 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Aug 2022 19:37:52 +0200 Subject: [PATCH 69/83] Bumped version to 2022.8.0b6 --- 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 385db6904e4c..3e7204e1746f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b5" +PATCH_VERSION: Final = "0b6" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index fff0c129cec1..2fa072c6d305 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0b5" +version = "2022.8.0b6" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 213812f087f78d3997526097cf8e9a73e5822b1a Mon Sep 17 00:00:00 2001 From: Eloston Date: Tue, 2 Aug 2022 02:29:44 +0000 Subject: [PATCH 70/83] Add support for SwitchBot Plug Mini (#76056) --- CODEOWNERS | 4 ++-- homeassistant/components/switchbot/__init__.py | 3 +++ homeassistant/components/switchbot/const.py | 2 ++ homeassistant/components/switchbot/manifest.json | 10 ++++++++-- homeassistant/components/switchbot/sensor.py | 9 ++++++++- homeassistant/components/switchbot/switch.py | 6 +++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index bd39fd685906..5853186d0bb3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1044,8 +1044,8 @@ build.json @home-assistant/supervisor /tests/components/switch/ @home-assistant/core /homeassistant/components/switch_as_x/ @home-assistant/core /tests/components/switch_as_x/ @home-assistant/core -/homeassistant/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas -/tests/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas +/homeassistant/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston +/tests/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston /homeassistant/components/switcher_kis/ @tomerfi @thecode /tests/components/switcher_kis/ @tomerfi @thecode /homeassistant/components/switchmate/ @danielhiversen @qiz-li diff --git a/homeassistant/components/switchbot/__init__.py b/homeassistant/components/switchbot/__init__.py index 42ca0856b022..e32252a7615c 100644 --- a/homeassistant/components/switchbot/__init__.py +++ b/homeassistant/components/switchbot/__init__.py @@ -22,6 +22,7 @@ from .const import ( ATTR_CONTACT, ATTR_CURTAIN, ATTR_HYGROMETER, + ATTR_PLUG, CONF_RETRY_COUNT, DEFAULT_RETRY_COUNT, DOMAIN, @@ -30,6 +31,7 @@ from .coordinator import SwitchbotDataUpdateCoordinator PLATFORMS_BY_TYPE = { ATTR_BOT: [Platform.SWITCH, Platform.SENSOR], + ATTR_PLUG: [Platform.SWITCH, Platform.SENSOR], ATTR_CURTAIN: [Platform.COVER, Platform.BINARY_SENSOR, Platform.SENSOR], ATTR_HYGROMETER: [Platform.SENSOR], ATTR_CONTACT: [Platform.BINARY_SENSOR, Platform.SENSOR], @@ -37,6 +39,7 @@ PLATFORMS_BY_TYPE = { CLASS_BY_DEVICE = { ATTR_CURTAIN: switchbot.SwitchbotCurtain, ATTR_BOT: switchbot.Switchbot, + ATTR_PLUG: switchbot.SwitchbotPlugMini, } _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switchbot/const.py b/homeassistant/components/switchbot/const.py index dc5abc139e68..9cc2acebbf8e 100644 --- a/homeassistant/components/switchbot/const.py +++ b/homeassistant/components/switchbot/const.py @@ -7,12 +7,14 @@ ATTR_BOT = "bot" ATTR_CURTAIN = "curtain" ATTR_HYGROMETER = "hygrometer" ATTR_CONTACT = "contact" +ATTR_PLUG = "plug" DEFAULT_NAME = "Switchbot" SUPPORTED_MODEL_TYPES = { "WoHand": ATTR_BOT, "WoCurtain": ATTR_CURTAIN, "WoSensorTH": ATTR_HYGROMETER, "WoContact": ATTR_CONTACT, + "WoPlug": ATTR_PLUG, } # Config Defaults diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index dcb33c03882c..41d0d7efda63 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,10 +2,16 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.16.0"], + "requirements": ["PySwitchbot==0.17.1"], "config_flow": true, "dependencies": ["bluetooth"], - "codeowners": ["@bdraco", "@danielhiversen", "@RenierM26", "@murtas"], + "codeowners": [ + "@bdraco", + "@danielhiversen", + "@RenierM26", + "@murtas", + "@Eloston" + ], "bluetooth": [ { "service_data_uuid": "0000fd3d-0000-1000-8000-00805f9b34fb" diff --git a/homeassistant/components/switchbot/sensor.py b/homeassistant/components/switchbot/sensor.py index f796ea05e7bc..fb24ae22679d 100644 --- a/homeassistant/components/switchbot/sensor.py +++ b/homeassistant/components/switchbot/sensor.py @@ -33,6 +33,13 @@ SENSOR_TYPES: dict[str, SensorEntityDescription] = { entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), + "wifi_rssi": SensorEntityDescription( + key="wifi_rssi", + native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + device_class=SensorDeviceClass.SIGNAL_STRENGTH, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ), "battery": SensorEntityDescription( key="battery", native_unit_of_measurement=PERCENTAGE, @@ -98,7 +105,7 @@ class SwitchBotSensor(SwitchbotEntity, SensorEntity): super().__init__(coordinator, unique_id, address, name=switchbot_name) self._sensor = sensor self._attr_unique_id = f"{unique_id}-{sensor}" - self._attr_name = f"{switchbot_name} {sensor.title()}" + self._attr_name = f"{switchbot_name} {sensor.replace('_', ' ').title()}" self.entity_description = SENSOR_TYPES[sensor] @property diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index e6ba77fa1647..65c7588acbdf 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -33,7 +33,7 @@ async def async_setup_entry( assert unique_id is not None async_add_entities( [ - SwitchBotBotEntity( + SwitchBotSwitch( coordinator, unique_id, entry.data[CONF_ADDRESS], @@ -44,8 +44,8 @@ async def async_setup_entry( ) -class SwitchBotBotEntity(SwitchbotEntity, SwitchEntity, RestoreEntity): - """Representation of a Switchbot.""" +class SwitchBotSwitch(SwitchbotEntity, SwitchEntity, RestoreEntity): + """Representation of a Switchbot switch.""" _attr_device_class = SwitchDeviceClass.SWITCH diff --git a/requirements_all.txt b/requirements_all.txt index 71aee1700ab8..04fc820bd9fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.16.0 +PySwitchbot==0.17.1 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 61a4b29b3b7c..077121e7b224 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.16.0 +PySwitchbot==0.17.1 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From c22cb13bd0800e9abcf023b178ea2bf336661b3b Mon Sep 17 00:00:00 2001 From: Jc2k Date: Tue, 2 Aug 2022 17:20:37 +0100 Subject: [PATCH 71/83] Add optional context parameter to async_start_reauth (#76077) --- homeassistant/components/xiaomi_ble/sensor.py | 20 +------------- homeassistant/config_entries.py | 7 ++++- tests/test_config_entries.py | 27 +++++++++++++++++++ 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/sensor.py b/homeassistant/components/xiaomi_ble/sensor.py index b3cd51269674..fef9b334e751 100644 --- a/homeassistant/components/xiaomi_ble/sensor.py +++ b/homeassistant/components/xiaomi_ble/sensor.py @@ -181,25 +181,7 @@ def process_service_info( and data.encryption_scheme != EncryptionScheme.NONE and not data.bindkey_verified ): - flow_context = { - "source": config_entries.SOURCE_REAUTH, - "entry_id": entry.entry_id, - "title_placeholders": {"name": entry.title}, - "unique_id": entry.unique_id, - "device": data, - } - - for flow in hass.config_entries.flow.async_progress_by_handler(DOMAIN): - if flow["context"] == flow_context: - break - else: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context=flow_context, - data=entry.data, - ) - ) + entry.async_start_reauth(hass, context={"device": data}) return sensor_update_to_bluetooth_data_update(update) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b0c043230052..638aa0b8110c 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -640,7 +640,9 @@ class ConfigEntry: await asyncio.gather(*pending) @callback - def async_start_reauth(self, hass: HomeAssistant) -> None: + def async_start_reauth( + self, hass: HomeAssistant, context: dict[str, Any] | None = None + ) -> None: """Start a reauth flow.""" flow_context = { "source": SOURCE_REAUTH, @@ -649,6 +651,9 @@ class ConfigEntry: "unique_id": self.unique_id, } + if context: + flow_context.update(context) + for flow in hass.config_entries.flow.async_progress_by_handler(self.domain): if flow["context"] == flow_context: return diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 3e7245ed73ab..66f515084418 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -3273,3 +3273,30 @@ async def test_disallow_entry_reload_with_setup_in_progresss(hass, manager): with pytest.raises(config_entries.OperationNotAllowed): assert await manager.async_reload(entry.entry_id) assert entry.state is config_entries.ConfigEntryState.SETUP_IN_PROGRESS + + +async def test_reauth(hass): + """Test the async_reauth_helper.""" + entry = MockConfigEntry(title="test_title", domain="test") + + mock_setup_entry = AsyncMock(return_value=True) + mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry)) + mock_entity_platform(hass, "config_flow.test", None) + + await entry.async_setup(hass) + await hass.async_block_till_done() + + entry.async_start_reauth(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + assert flows[0]["context"]["entry_id"] == entry.entry_id + assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH + assert flows[0]["context"]["title_placeholders"] == {"name": "test_title"} + assert flows[0]["context"]["extra_context"] == "some_extra_context" + + # Check we can't start duplicate flows + entry.async_start_reauth(hass, {"extra_context": "some_extra_context"}) + await hass.async_block_till_done() + assert len(flows) == 1 From 690f051a87c11d63b9cdc06366f3cd6f8c17cda3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Aug 2022 09:11:50 -1000 Subject: [PATCH 72/83] Bump pyatv to 0.10.3 (#76091) --- homeassistant/components/apple_tv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/apple_tv/manifest.json b/homeassistant/components/apple_tv/manifest.json index dec195fddee9..5717f851b81e 100644 --- a/homeassistant/components/apple_tv/manifest.json +++ b/homeassistant/components/apple_tv/manifest.json @@ -3,7 +3,7 @@ "name": "Apple TV", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/apple_tv", - "requirements": ["pyatv==0.10.2"], + "requirements": ["pyatv==0.10.3"], "dependencies": ["zeroconf"], "zeroconf": [ "_mediaremotetv._tcp.local.", diff --git a/requirements_all.txt b/requirements_all.txt index 04fc820bd9fb..87ad8ffc67e3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1392,7 +1392,7 @@ pyatmo==6.2.4 pyatome==0.1.1 # homeassistant.components.apple_tv -pyatv==0.10.2 +pyatv==0.10.3 # homeassistant.components.aussie_broadband pyaussiebb==0.0.15 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 077121e7b224..058971731c1b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -968,7 +968,7 @@ pyatag==0.3.5.3 pyatmo==6.2.4 # homeassistant.components.apple_tv -pyatv==0.10.2 +pyatv==0.10.3 # homeassistant.components.aussie_broadband pyaussiebb==0.0.15 From a78da6a000a0f37f3921f1107b367bbf605d9300 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Tue, 2 Aug 2022 21:38:38 +0100 Subject: [PATCH 73/83] Fix serialization of Xiaomi BLE reauth flow (#76095) * Use data instead of context to fix serialisation bug * Test change to async_start_reauth --- homeassistant/components/xiaomi_ble/config_flow.py | 2 +- homeassistant/components/xiaomi_ble/sensor.py | 2 +- homeassistant/config_entries.py | 7 +++++-- tests/components/xiaomi_ble/test_config_flow.py | 3 +-- tests/test_config_entries.py | 12 ++++++++++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/config_flow.py b/homeassistant/components/xiaomi_ble/config_flow.py index 092c60e97135..725c513914f2 100644 --- a/homeassistant/components/xiaomi_ble/config_flow.py +++ b/homeassistant/components/xiaomi_ble/config_flow.py @@ -260,7 +260,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): entry = self.hass.config_entries.async_get_entry(self.context["entry_id"]) assert entry is not None - device: DeviceData = self.context["device"] + device: DeviceData = entry_data["device"] self._discovered_device = device self._discovery_info = device.last_service_info diff --git a/homeassistant/components/xiaomi_ble/sensor.py b/homeassistant/components/xiaomi_ble/sensor.py index fef9b334e751..dcb95422609e 100644 --- a/homeassistant/components/xiaomi_ble/sensor.py +++ b/homeassistant/components/xiaomi_ble/sensor.py @@ -181,7 +181,7 @@ def process_service_info( and data.encryption_scheme != EncryptionScheme.NONE and not data.bindkey_verified ): - entry.async_start_reauth(hass, context={"device": data}) + entry.async_start_reauth(hass, data={"device": data}) return sensor_update_to_bluetooth_data_update(update) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 638aa0b8110c..7c2f1c84ff93 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -641,7 +641,10 @@ class ConfigEntry: @callback def async_start_reauth( - self, hass: HomeAssistant, context: dict[str, Any] | None = None + self, + hass: HomeAssistant, + context: dict[str, Any] | None = None, + data: dict[str, Any] | None = None, ) -> None: """Start a reauth flow.""" flow_context = { @@ -662,7 +665,7 @@ class ConfigEntry: hass.config_entries.flow.async_init( self.domain, context=flow_context, - data=self.data, + data=self.data | (data or {}), ) ) diff --git a/tests/components/xiaomi_ble/test_config_flow.py b/tests/components/xiaomi_ble/test_config_flow.py index 0d123f0cd548..32ba6be3322e 100644 --- a/tests/components/xiaomi_ble/test_config_flow.py +++ b/tests/components/xiaomi_ble/test_config_flow.py @@ -1033,9 +1033,8 @@ async def test_async_step_reauth_abort_early(hass): "entry_id": entry.entry_id, "title_placeholders": {"name": entry.title}, "unique_id": entry.unique_id, - "device": device, }, - data=entry.data, + data=entry.data | {"device": device}, ) assert result["type"] == FlowResultType.ABORT diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 66f515084418..b923e37b636f 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -3286,8 +3286,14 @@ async def test_reauth(hass): await entry.async_setup(hass) await hass.async_block_till_done() - entry.async_start_reauth(hass, {"extra_context": "some_extra_context"}) - await hass.async_block_till_done() + flow = hass.config_entries.flow + with patch.object(flow, "async_init", wraps=flow.async_init) as mock_init: + entry.async_start_reauth( + hass, + context={"extra_context": "some_extra_context"}, + data={"extra_data": 1234}, + ) + await hass.async_block_till_done() flows = hass.config_entries.flow.async_progress() assert len(flows) == 1 @@ -3296,6 +3302,8 @@ async def test_reauth(hass): assert flows[0]["context"]["title_placeholders"] == {"name": "test_title"} assert flows[0]["context"]["extra_context"] == "some_extra_context" + assert mock_init.call_args.kwargs["data"]["extra_data"] == 1234 + # Check we can't start duplicate flows entry.async_start_reauth(hass, {"extra_context": "some_extra_context"}) await hass.async_block_till_done() From d7a418a219b1732c43c60165fcecc5b93a35247a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 Aug 2022 17:54:06 +0200 Subject: [PATCH 74/83] Guard imports for type hinting in Bluetooth (#75984) --- homeassistant/components/bluetooth/__init__.py | 12 ++++++++---- homeassistant/components/bluetooth/config_flow.py | 6 ++++-- homeassistant/components/bluetooth/match.py | 12 ++++++++---- homeassistant/components/bluetooth/models.py | 7 +++++-- .../bluetooth/passive_update_coordinator.py | 11 +++++++---- .../components/bluetooth/passive_update_processor.py | 12 ++++++++---- 6 files changed, 40 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index ba079a426feb..39629ab6d85f 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -8,12 +8,10 @@ from dataclasses import dataclass from datetime import datetime, timedelta from enum import Enum import logging -from typing import Final +from typing import TYPE_CHECKING, Final import async_timeout from bleak import BleakError -from bleak.backends.device import BLEDevice -from bleak.backends.scanner import AdvertisementData from homeassistant import config_entries from homeassistant.const import EVENT_HOMEASSISTANT_STOP @@ -27,7 +25,6 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import discovery_flow from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo -from homeassistant.helpers.typing import ConfigType from homeassistant.loader import async_get_bluetooth from . import models @@ -42,6 +39,13 @@ from .models import HaBleakScanner, HaBleakScannerWrapper from .usage import install_multiple_bleak_catcher, uninstall_multiple_bleak_catcher from .util import async_get_bluetooth_adapters +if TYPE_CHECKING: + from bleak.backends.device import BLEDevice + from bleak.backends.scanner import AdvertisementData + + from homeassistant.helpers.typing import ConfigType + + _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bluetooth/config_flow.py b/homeassistant/components/bluetooth/config_flow.py index bbba5f411b2d..1a0be8706bf0 100644 --- a/homeassistant/components/bluetooth/config_flow.py +++ b/homeassistant/components/bluetooth/config_flow.py @@ -1,18 +1,20 @@ """Config flow to configure the Bluetooth integration.""" from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any import voluptuous as vol from homeassistant.components import onboarding from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult from .const import CONF_ADAPTER, DEFAULT_NAME, DOMAIN from .util import async_get_bluetooth_adapters +if TYPE_CHECKING: + from homeassistant.data_entry_flow import FlowResult + class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN): """Config flow for Bluetooth.""" diff --git a/homeassistant/components/bluetooth/match.py b/homeassistant/components/bluetooth/match.py index 000f39eefd4e..2cd4f62ae5e0 100644 --- a/homeassistant/components/bluetooth/match.py +++ b/homeassistant/components/bluetooth/match.py @@ -1,17 +1,21 @@ """The bluetooth integration matchers.""" from __future__ import annotations -from collections.abc import Mapping from dataclasses import dataclass import fnmatch -from typing import Final, TypedDict +from typing import TYPE_CHECKING, Final, TypedDict -from bleak.backends.device import BLEDevice -from bleak.backends.scanner import AdvertisementData from lru import LRU # pylint: disable=no-name-in-module from homeassistant.loader import BluetoothMatcher, BluetoothMatcherOptional +if TYPE_CHECKING: + from collections.abc import Mapping + + from bleak.backends.device import BLEDevice + from bleak.backends.scanner import AdvertisementData + + MAX_REMEMBER_ADDRESSES: Final = 2048 diff --git a/homeassistant/components/bluetooth/models.py b/homeassistant/components/bluetooth/models.py index 6f814c7b66ba..51704a2f5307 100644 --- a/homeassistant/components/bluetooth/models.py +++ b/homeassistant/components/bluetooth/models.py @@ -4,10 +4,9 @@ from __future__ import annotations import asyncio import contextlib import logging -from typing import Any, Final +from typing import TYPE_CHECKING, Any, Final from bleak import BleakScanner -from bleak.backends.device import BLEDevice from bleak.backends.scanner import ( AdvertisementData, AdvertisementDataCallback, @@ -16,6 +15,10 @@ from bleak.backends.scanner import ( from homeassistant.core import CALLBACK_TYPE, callback as hass_callback +if TYPE_CHECKING: + from bleak.backends.device import BLEDevice + + _LOGGER = logging.getLogger(__name__) FILTER_UUIDS: Final = "UUIDs" diff --git a/homeassistant/components/bluetooth/passive_update_coordinator.py b/homeassistant/components/bluetooth/passive_update_coordinator.py index 31a6b0658300..5c6b5b79509a 100644 --- a/homeassistant/components/bluetooth/passive_update_coordinator.py +++ b/homeassistant/components/bluetooth/passive_update_coordinator.py @@ -1,16 +1,19 @@ """Passive update coordinator for the Bluetooth integration.""" from __future__ import annotations -from collections.abc import Callable, Generator -import logging -from typing import Any +from typing import TYPE_CHECKING, Any from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak from .update_coordinator import BasePassiveBluetoothCoordinator +if TYPE_CHECKING: + from collections.abc import Callable, Generator + import logging + + from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak + class PassiveBluetoothDataUpdateCoordinator(BasePassiveBluetoothCoordinator): """Class to manage passive bluetooth advertisements. diff --git a/homeassistant/components/bluetooth/passive_update_processor.py b/homeassistant/components/bluetooth/passive_update_processor.py index 1f2047c02cb8..78966d9b7ab2 100644 --- a/homeassistant/components/bluetooth/passive_update_processor.py +++ b/homeassistant/components/bluetooth/passive_update_processor.py @@ -1,20 +1,24 @@ """Passive update processors for the Bluetooth integration.""" from __future__ import annotations -from collections.abc import Callable, Mapping import dataclasses import logging -from typing import Any, Generic, TypeVar +from typing import TYPE_CHECKING, Any, Generic, TypeVar from homeassistant.const import ATTR_IDENTIFIERS, ATTR_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak from .const import DOMAIN from .update_coordinator import BasePassiveBluetoothCoordinator +if TYPE_CHECKING: + from collections.abc import Callable, Mapping + + from homeassistant.helpers.entity_platform import AddEntitiesCallback + + from . import BluetoothChange, BluetoothScanningMode, BluetoothServiceInfoBleak + @dataclasses.dataclass(frozen=True) class PassiveBluetoothEntityKey: From d2dc83c4c7fc9b7375a7f7adc72d9ccab7347f62 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Aug 2022 13:46:43 -1000 Subject: [PATCH 75/83] Handle additional bluetooth start exceptions (#76096) --- .../components/bluetooth/__init__.py | 33 +++++- tests/components/bluetooth/test_init.py | 110 ++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index 39629ab6d85f..c91563d7729c 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, Final import async_timeout from bleak import BleakError +from dbus_next import InvalidMessageError from homeassistant import config_entries from homeassistant.const import EVENT_HOMEASSISTANT_STOP @@ -26,6 +27,7 @@ from homeassistant.helpers import discovery_flow from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo from homeassistant.loader import async_get_bluetooth +from homeassistant.util.package import is_docker_env from . import models from .const import CONF_ADAPTER, DEFAULT_ADAPTERS, DOMAIN @@ -341,13 +343,42 @@ class BluetoothManager: try: async with async_timeout.timeout(START_TIMEOUT): await self.scanner.start() # type: ignore[no-untyped-call] + except InvalidMessageError as ex: + self._cancel_device_detected() + _LOGGER.debug("Invalid DBus message received: %s", ex, exc_info=True) + raise ConfigEntryNotReady( + f"Invalid DBus message received: {ex}; try restarting `dbus`" + ) from ex + except BrokenPipeError as ex: + self._cancel_device_detected() + _LOGGER.debug("DBus connection broken: %s", ex, exc_info=True) + if is_docker_env(): + raise ConfigEntryNotReady( + f"DBus connection broken: {ex}; try restarting `bluetooth`, `dbus`, and finally the docker container" + ) from ex + raise ConfigEntryNotReady( + f"DBus connection broken: {ex}; try restarting `bluetooth` and `dbus`" + ) from ex + except FileNotFoundError as ex: + self._cancel_device_detected() + _LOGGER.debug( + "FileNotFoundError while starting bluetooth: %s", ex, exc_info=True + ) + if is_docker_env(): + raise ConfigEntryNotReady( + f"DBus service not found; docker config may be missing `-v /run/dbus:/run/dbus:ro`: {ex}" + ) from ex + raise ConfigEntryNotReady( + f"DBus service not found; make sure the DBus socket is available to Home Assistant: {ex}" + ) from ex except asyncio.TimeoutError as ex: self._cancel_device_detected() raise ConfigEntryNotReady( f"Timed out starting Bluetooth after {START_TIMEOUT} seconds" ) from ex - except (FileNotFoundError, BleakError) as ex: + except BleakError as ex: self._cancel_device_detected() + _LOGGER.debug("BleakError while starting bluetooth: %s", ex, exc_info=True) raise ConfigEntryNotReady(f"Failed to start Bluetooth: {ex}") from ex self.async_setup_unavailable_tracking() self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) diff --git a/tests/components/bluetooth/test_init.py b/tests/components/bluetooth/test_init.py index a47916506dff..edc5eb024a68 100644 --- a/tests/components/bluetooth/test_init.py +++ b/tests/components/bluetooth/test_init.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock, patch from bleak import BleakError from bleak.backends.scanner import AdvertisementData, BLEDevice +from dbus_next import InvalidMessageError import pytest from homeassistant.components import bluetooth @@ -1409,3 +1410,112 @@ async def test_changing_the_adapter_at_runtime(hass): hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() + + +async def test_dbus_socket_missing_in_container(hass, caplog): + """Test we handle dbus being missing in the container.""" + + with patch( + "homeassistant.components.bluetooth.is_docker_env", return_value=True + ), patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch( + "homeassistant.components.bluetooth.HaBleakScanner.start", + side_effect=FileNotFoundError, + ): + assert await async_setup_component( + hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}} + ) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert "/run/dbus" in caplog.text + assert "docker" in caplog.text + + +async def test_dbus_socket_missing(hass, caplog): + """Test we handle dbus being missing.""" + + with patch( + "homeassistant.components.bluetooth.is_docker_env", return_value=False + ), patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch( + "homeassistant.components.bluetooth.HaBleakScanner.start", + side_effect=FileNotFoundError, + ): + assert await async_setup_component( + hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}} + ) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert "DBus" in caplog.text + assert "docker" not in caplog.text + + +async def test_dbus_broken_pipe_in_container(hass, caplog): + """Test we handle dbus broken pipe in the container.""" + + with patch( + "homeassistant.components.bluetooth.is_docker_env", return_value=True + ), patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch( + "homeassistant.components.bluetooth.HaBleakScanner.start", + side_effect=BrokenPipeError, + ): + assert await async_setup_component( + hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}} + ) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert "dbus" in caplog.text + assert "restarting" in caplog.text + assert "container" in caplog.text + + +async def test_dbus_broken_pipe(hass, caplog): + """Test we handle dbus broken pipe.""" + + with patch( + "homeassistant.components.bluetooth.is_docker_env", return_value=False + ), patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch( + "homeassistant.components.bluetooth.HaBleakScanner.start", + side_effect=BrokenPipeError, + ): + assert await async_setup_component( + hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}} + ) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert "DBus" in caplog.text + assert "restarting" in caplog.text + assert "container" not in caplog.text + + +async def test_invalid_dbus_message(hass, caplog): + """Test we handle invalid dbus message.""" + + with patch("homeassistant.components.bluetooth.HaBleakScanner.async_setup"), patch( + "homeassistant.components.bluetooth.HaBleakScanner.start", + side_effect=InvalidMessageError, + ): + assert await async_setup_component( + hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}} + ) + await hass.async_block_till_done() + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + assert "dbus" in caplog.text From 51a6899a60c6944ec9b66f778cd9eb7a7065cdee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Aug 2022 10:38:01 -1000 Subject: [PATCH 76/83] Only stat the .dockerenv file once (#76097) --- homeassistant/util/package.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/util/package.py b/homeassistant/util/package.py index 18ab43967ec3..49ab3c10f8c7 100644 --- a/homeassistant/util/package.py +++ b/homeassistant/util/package.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from functools import cache from importlib.metadata import PackageNotFoundError, version import logging import os @@ -23,6 +24,7 @@ def is_virtual_env() -> bool: ) +@cache def is_docker_env() -> bool: """Return True if we run in a docker env.""" return Path("/.dockerenv").exists() From ad14b5f3d7db2cf6d5debff8a295516eae8938b4 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Tue, 2 Aug 2022 22:05:36 +0100 Subject: [PATCH 77/83] Fix Xiaomi BLE UI string issues (#76099) --- homeassistant/components/xiaomi_ble/config_flow.py | 2 +- homeassistant/components/xiaomi_ble/strings.json | 12 +++++++----- .../components/xiaomi_ble/translations/en.json | 14 ++++++++------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/xiaomi_ble/config_flow.py b/homeassistant/components/xiaomi_ble/config_flow.py index 725c513914f2..a05e703db6a7 100644 --- a/homeassistant/components/xiaomi_ble/config_flow.py +++ b/homeassistant/components/xiaomi_ble/config_flow.py @@ -189,7 +189,7 @@ class XiaomiConfigFlow(ConfigFlow, domain=DOMAIN): self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Ack that device is slow.""" - if user_input is not None or not onboarding.async_is_onboarded(self.hass): + if user_input is not None: return self._async_get_or_create_entry() self._set_confirm_only() diff --git a/homeassistant/components/xiaomi_ble/strings.json b/homeassistant/components/xiaomi_ble/strings.json index 9d2a0ae40d8e..5ecbb8e1b889 100644 --- a/homeassistant/components/xiaomi_ble/strings.json +++ b/homeassistant/components/xiaomi_ble/strings.json @@ -11,7 +11,7 @@ "bluetooth_confirm": { "description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]" }, - "slow_confirm": { + "confirm_slow": { "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if it's needed." }, "get_encryption_key_legacy": { @@ -27,14 +27,16 @@ } } }, + "error": { + "decryption_failed": "The provided bindkey did not work, sensor data could not be decrypted. Please check it and try again.", + "expected_24_characters": "Expected a 24 character hexadecimal bindkey.", + "expected_32_characters": "Expected a 32 character hexadecimal bindkey." + }, "abort": { "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "decryption_failed": "The provided bindkey did not work, sensor data could not be decrypted. Please check it and try again.", - "expected_24_characters": "Expected a 24 character hexadecimal bindkey.", - "expected_32_characters": "Expected a 32 character hexadecimal bindkey." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } } diff --git a/homeassistant/components/xiaomi_ble/translations/en.json b/homeassistant/components/xiaomi_ble/translations/en.json index 4648b28cc93f..2cb77dd2c076 100644 --- a/homeassistant/components/xiaomi_ble/translations/en.json +++ b/homeassistant/components/xiaomi_ble/translations/en.json @@ -3,17 +3,22 @@ "abort": { "already_configured": "Device is already configured", "already_in_progress": "Configuration flow is already in progress", - "decryption_failed": "The provided bindkey did not work, sensor data could not be decrypted. Please check it and try again.", - "expected_24_characters": "Expected a 24 character hexadecimal bindkey.", - "expected_32_characters": "Expected a 32 character hexadecimal bindkey.", "no_devices_found": "No devices found on the network", "reauth_successful": "Re-authentication was successful" }, + "error": { + "decryption_failed": "The provided bindkey did not work, sensor data could not be decrypted. Please check it and try again.", + "expected_24_characters": "Expected a 24 character hexadecimal bindkey.", + "expected_32_characters": "Expected a 32 character hexadecimal bindkey." + }, "flow_title": "{name}", "step": { "bluetooth_confirm": { "description": "Do you want to setup {name}?" }, + "confirm_slow": { + "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if it's needed." + }, "get_encryption_key_4_5": { "data": { "bindkey": "Bindkey" @@ -26,9 +31,6 @@ }, "description": "The sensor data broadcast by the sensor is encrypted. In order to decrypt it we need a 24 character hexadecimal bindkey." }, - "slow_confirm": { - "description": "There hasn't been a broadcast from this device in the last minute so we aren't sure if this device uses encryption or not. This may be because the device uses a slow broadcast interval. Confirm to add this device anyway, then the next time a broadcast is received you will be prompted to enter its bindkey if it's needed." - }, "user": { "data": { "address": "Device" From d85129c52753a152c0af26fd7eaf5eb61c7deefd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Aug 2022 20:35:41 -1000 Subject: [PATCH 78/83] Bump aiohomekit to 1.2.3 to fix hang at startup (#76102) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 2de2a915d41a..ac1be5769066 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit==1.2.2"], + "requirements": ["aiohomekit==1.2.3"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."], "bluetooth": [{ "manufacturer_id": 76, "manufacturer_data_start": [6] }], "dependencies": ["bluetooth", "zeroconf"], diff --git a/requirements_all.txt b/requirements_all.txt index 87ad8ffc67e3..5e277e8e801d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -168,7 +168,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==1.2.2 +aiohomekit==1.2.3 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 058971731c1b..acf67deb250e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -152,7 +152,7 @@ aioguardian==2022.07.0 aioharmony==0.2.9 # homeassistant.components.homekit_controller -aiohomekit==1.2.2 +aiohomekit==1.2.3 # homeassistant.components.emulated_hue # homeassistant.components.http From 42a1f6ca20794f56b1b3afe09702e5ef884cf09f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Aug 2022 20:34:46 -1000 Subject: [PATCH 79/83] Bump pySwitchbot to 0.17.3 to fix hang at startup (#76103) --- homeassistant/components/switchbot/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 41d0d7efda63..f01eae4a938d 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -2,7 +2,7 @@ "domain": "switchbot", "name": "SwitchBot", "documentation": "https://www.home-assistant.io/integrations/switchbot", - "requirements": ["PySwitchbot==0.17.1"], + "requirements": ["PySwitchbot==0.17.3"], "config_flow": true, "dependencies": ["bluetooth"], "codeowners": [ diff --git a/requirements_all.txt b/requirements_all.txt index 5e277e8e801d..78d5a3db456c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.17.1 +PySwitchbot==0.17.3 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index acf67deb250e..01ba627194fc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -33,7 +33,7 @@ PyRMVtransport==0.3.3 PySocks==1.7.1 # homeassistant.components.switchbot -PySwitchbot==0.17.1 +PySwitchbot==0.17.3 # homeassistant.components.transport_nsw PyTransportNSW==0.1.1 From bc1e371cae25cad0491dd78879f51229dd474af2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Aug 2022 08:58:12 +0200 Subject: [PATCH 80/83] Bumped version to 2022.8.0b7 --- 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 3e7204e1746f..dd5a41a7aa58 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b6" +PATCH_VERSION: Final = "0b7" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 2fa072c6d305..8a65560b44aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0b6" +version = "2022.8.0b7" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst" From 29f6d7818a09f60f5abd0a24a935d5fa06b214e2 Mon Sep 17 00:00:00 2001 From: Heine Furubotten Date: Wed, 3 Aug 2022 10:31:09 +0200 Subject: [PATCH 81/83] Bump `azure-servicebus` to support py3.10 (#76092) Bump azure-servicebus --- .../azure_service_bus/manifest.json | 2 +- .../components/azure_service_bus/notify.py | 24 ++++++++++--------- requirements_all.txt | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/azure_service_bus/manifest.json b/homeassistant/components/azure_service_bus/manifest.json index 6cf5e2bf406a..26ccea446f25 100644 --- a/homeassistant/components/azure_service_bus/manifest.json +++ b/homeassistant/components/azure_service_bus/manifest.json @@ -2,7 +2,7 @@ "domain": "azure_service_bus", "name": "Azure Service Bus", "documentation": "https://www.home-assistant.io/integrations/azure_service_bus", - "requirements": ["azure-servicebus==0.50.3"], + "requirements": ["azure-servicebus==7.8.0"], "codeowners": ["@hfurubotten"], "iot_class": "cloud_push", "loggers": ["azure"] diff --git a/homeassistant/components/azure_service_bus/notify.py b/homeassistant/components/azure_service_bus/notify.py index 0d48ff6b2d6a..538733730111 100644 --- a/homeassistant/components/azure_service_bus/notify.py +++ b/homeassistant/components/azure_service_bus/notify.py @@ -2,11 +2,12 @@ import json import logging -from azure.servicebus.aio import Message, ServiceBusClient -from azure.servicebus.common.errors import ( - MessageSendFailed, +from azure.servicebus import ServiceBusMessage +from azure.servicebus.aio import ServiceBusClient +from azure.servicebus.exceptions import ( + MessagingEntityNotFoundError, ServiceBusConnectionError, - ServiceBusResourceNotFound, + ServiceBusError, ) import voluptuous as vol @@ -60,10 +61,10 @@ def get_service(hass, config, discovery_info=None): try: if queue_name: - client = servicebus.get_queue(queue_name) + client = servicebus.get_queue_sender(queue_name) else: - client = servicebus.get_topic(topic_name) - except (ServiceBusConnectionError, ServiceBusResourceNotFound) as err: + client = servicebus.get_topic_sender(topic_name) + except (ServiceBusConnectionError, MessagingEntityNotFoundError) as err: _LOGGER.error( "Connection error while creating client for queue/topic '%s'. %s", queue_name or topic_name, @@ -93,11 +94,12 @@ class ServiceBusNotificationService(BaseNotificationService): if data := kwargs.get(ATTR_DATA): dto.update(data) - queue_message = Message(json.dumps(dto)) - queue_message.properties.content_type = CONTENT_TYPE_JSON + queue_message = ServiceBusMessage( + json.dumps(dto), content_type=CONTENT_TYPE_JSON + ) try: - await self._client.send(queue_message) - except MessageSendFailed as err: + await self._client.send_messages(queue_message) + except ServiceBusError as err: _LOGGER.error( "Could not send service bus notification to %s. %s", self._client.name, diff --git a/requirements_all.txt b/requirements_all.txt index 78d5a3db456c..5027b7df0cda 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -375,7 +375,7 @@ axis==44 azure-eventhub==5.7.0 # homeassistant.components.azure_service_bus -azure-servicebus==0.50.3 +azure-servicebus==7.8.0 # homeassistant.components.baidu baidu-aip==1.6.6 From 81ee24738bbafccae01cf25c84060cb2da622ba1 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 3 Aug 2022 09:41:00 +0200 Subject: [PATCH 82/83] Fix deconz group log warning (#76114) --- homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 8019d0df2df3..6384ebfcd5fa 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==100"], + "requirements": ["pydeconz==101"], "ssdp": [ { "manufacturer": "Royal Philips Electronics", diff --git a/requirements_all.txt b/requirements_all.txt index 5027b7df0cda..6e1850d33a7a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1455,7 +1455,7 @@ pydaikin==2.7.0 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==100 +pydeconz==101 # homeassistant.components.delijn pydelijn==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 01ba627194fc..6f75b540b28b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1001,7 +1001,7 @@ pycoolmasternet-async==0.1.2 pydaikin==2.7.0 # homeassistant.components.deconz -pydeconz==100 +pydeconz==101 # homeassistant.components.dexcom pydexcom==0.2.3 From 80a053a4cde0ec75c532f3a4870c742561579994 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Aug 2022 11:42:18 +0200 Subject: [PATCH 83/83] Bumped version to 2022.8.0 --- 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 dd5a41a7aa58..18561a8bd2eb 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "0b7" +PATCH_VERSION: Final = "0" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/pyproject.toml b/pyproject.toml index 8a65560b44aa..c7e187e07f45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2022.8.0b7" +version = "2022.8.0" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"