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 10523aa6d53..02896fcbe96 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 48d12964414..5306a589863 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 4359ab72349..8055ef5210f 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 fe97ce33686..db330a9b239 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 d0b2225cc9a..255d402175d 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 2d960e17df2..3220de5104e 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 2e1de25fde2..ccebea49c6f 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 622384629fe..d751349b005 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 12b0df56f0f..b8bec832581 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 309aca9b8ef..5a6038bb480 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 d1b286afd81..0751331f82f 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 c29eb8d1a2c..6e24178996f 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 43c208d69e8..0d2031c368f 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 00000000000..33cafb8bba3 --- /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 00000000000..33cafb8bba3 --- /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 00000000000..33cafb8bba3 --- /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 00000000000..33cafb8bba3 --- /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 00000000000..33cafb8bba3 --- /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 00000000000..33cafb8bba3 --- /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 00000000000..d9aa1210fa0 --- /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 00000000000..a2a7fd23272 --- /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 00000000000..9ce6c75dcbc --- /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 00000000000..29b458e5599 --- /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 00000000000..78235452297 --- /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 b067b299145..1ef7e65fbf4 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 907a396d5e2..d70832bfbf6 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 850507ec0b3..bb7fbc3aed6 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 7d4887ab638..3c9e6960aeb 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 00000000000..cc383e4184c --- /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 991a749a729..634f5dd71fd 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 00000000000..d517fe0b37f --- /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 00000000000..26ce80ee584 --- /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 00000000000..ac367d20809 --- /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 00000000000..96b2c244b5c --- /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 00000000000..8ec55e5a004 --- /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 9e35beb648a..fef1c64746b 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 ffbc7bb458f..fbae91321be 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 7e3ff2f64a9..02c31d97816 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 3bf08cf34c7..7094ee18a02 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 8d28b988834..379516e31be 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 a14492bf5c3..f9c6d512b2a 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 e707219f342..7446cbc5a06 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 08231b8571d..f3d8e8e8560 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 ce89e224a8c..52773f3e411 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 e3c725532e9..8b0c932735b 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 4a5882dbd95..ccc72d3d1dd 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 e60c37c9e5f..6cf5bf15bb9 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 7f788c1ebb8..d1bb02e84dd 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 9b8acdf5b87..b4ad58636a7 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 0b8e90f5ac0..99c519782db 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 ba54b4aba87..69b8ced6970 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 9505da31e80..546a2f77c31 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 5e211c00028..4af066f4e89 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 f94c27dc2ac..1ce2f08c045 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 d0d5e230d2f..683991d0f65 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 1459ae215d9..2daf350ac93 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 4717e2915c1..ba6df6e2e2d 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 fb7b2387d73..5337ccd36c3 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 6866e052368..7f4ff92380f 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 00000000000..17a7cf20111 --- /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 c48d502c16e..577383e8976 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 ea3f2dd3e6c..57ddf87a256 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 9f19f813747..1f650a56a30 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 ef64b1e0969..417769d6696 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 00000000000..6f847543a9f --- /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 76f5d94b589..8b6ecefc5f5 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 589e0462cf7..4e151f5578d 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 4b9b7a2c8d0..e844f478322 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 57ddf87a256..95da109691c 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 1aedd6c5419..12ba4dce8ba 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 89f277cf69b..00000000000 --- 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 c0b6f471033..cb39fb73108 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 12ba4dce8ba..d405b9e257d 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 8eff4ac64fe..c139026ec48 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 5c459309cc0..bd201f1007c 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 85ff2a16405..5b322cb776f 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 cb39fb73108..a0fb2e8557d 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 2f82a084968..d70f6c6e11d 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 73d1898fcb7..d778b043832 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 35333f6ea01..9a3da54d34f 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 95da109691c..9c48e9984eb 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 841619af6f1..c1a630d0d05 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 6e8492e45ca..c49fd55e8c8 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 5adfa54a901..8857a55d66d 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 accd6775941..68af0176fa8 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 0bb1266bded..2ef065af458 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 7c37546485a..6b4ee263ba6 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 e9cd9813175..358d95c770a 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 0031dc32340..77ed05c4b48 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 a36a3b2b273..bf373d8baf0 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 f7514664698..841848621ec 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 c4dc97727a9..6cb1bafdaca 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 9f4ee7ed918..ac61837afec 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 7ab1a3bb45b..aa01c798072 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 247f73c89f2..3bf86dedea1 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 2a1fb2d3b37..45331491aa0 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 2554f0185b4..1f86ff4c7c5 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 9c48e9984eb..24a8a5ee1bd 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 1f650a56a30..81e192ce360 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 02896fcbe96..dfa12ccd824 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 5306a589863..f5b35dd63dc 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 551e93d5bd9..d2bdb54d5ba 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 bb2c5f49cc9..66a9ed396ec 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 d2bdb54d5ba..eb8e31baef0 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 e7c4a3e1f8c..f352f43d0bf 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 2e1a502ca69..41512291749 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 24a8a5ee1bd..ed487c9b84c 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 81e192ce360..73b2ad3adc0 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 66a9ed396ec..0664f82dbab 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 a6269a02d12..1dd1eeed65a 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 d4b4300d2c1..b424228cc6c 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 07a483f03c4..86104d15ef2 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 aa86215da59..270858d04d4 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 5dde90f1f7a..8d92d6eab4a 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 ed487c9b84c..3bfd3880eee 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 73b2ad3adc0..6ce69ed9678 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 9e5c38f0211..9969b694895 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 df9dec3d9af..997db44583d 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 a97a98a2afb..8f9ed6c9ce1 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 dfa12ccd824..a3753d36d07 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 f5b35dd63dc..558f366b357 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 879f4a4d4c8..8a09fd8d552 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 358d95c770a..2e58748f323 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 41512291749..0d97dcbedf8 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 8d92d6eab4a..2cbaebb6074 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 4197cb76209..266eceaacee 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 3bfd3880eee..b7b3ed89b27 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 6ce69ed9678..d04b537c9fd 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 f85cc2bad0a..e42d19ac547 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 85e95db5513..36608fb026d 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 31f7f2d3992..f461a3e0f4c 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 eb8e31baef0..9adaac84333 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 c6ca8b11400..f215e8fa161 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 408e0698879..6f814c7b66b 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 97e7ddc49ee..31a6b065830 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 43467701879..1f2047c02cb 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 b1cb2de1453..d0f38ce32c6 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 da5d062a36f..b3a6783cf30 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 e42d19ac547..01b3d48205a 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 3099d401e9b..7a134e43ace 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 9f2efac0ce9..1e3a5566bfd 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 d8b3fda2d06..31677e37b20 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 d7780029331..6e272067b54 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 5553b1c6ded..0272114b83c 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 679ff43b19e..21ed85e117e 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 259b6b66709..237948a8ff6 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 a353f4963ad..6f51b62d110 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 0a9efcbc752..d4a0872ba3f 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 1a8e8b47abe..d10c2f481a6 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 3a34a89d9fd..eaad573d370 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 f461a3e0f4c..43c576249df 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 4eb20dbd943..791ac1447ad 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 f352f43d0bf..a2fa2d185dc 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 b5d5804f100..b0c04323005 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 bf2f95c12c6..c9d6ffe6065 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 968d1dde95f..0db3a39b114 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 1f86ff4c7c5..c9d424daa33 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 b7b3ed89b27..58f2e5f73ec 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 d04b537c9fd..f7358e148dc 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 0664f82dbab..a47916506df 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 4da14fb13d3..31530cd6995 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 ebb69d7c7d0..6a092746a68 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 dfe47b38b33..f9f0a51fc0f 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 d60abcdb9ad..4e06b2ad046 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 7a6ecbaed51..75d269ea0ba 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 a851cb92ec3..cafc22911c3 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 826bbc72cdb..6424144106b 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 31fbdd8d712..34179985d78 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 b424228cc6c..86fda21aaa0 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 dca00e92254..011c6daecae 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 a2fa2d185dc..aa1ffc24895 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 a3753d36d07..4a37902e79f 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 558f366b357..8fca49a67f2 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 da22066d7aa..58d70667cdf 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 eb6d49c3ec1..766e5d961e8 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 01efb7deba4..740cce43c62 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 5b4c621cce6..05de437b10a 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 2cedcf9c1e4..c88d6762e51 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 dc9ddf4e616..0ae225f55d7 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 51f15c488d1..e6ba77fa164 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 c7f4fbeef53..3bbacb8c3d4 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 58f2e5f73ec..2ad42cf511c 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 f8f6fcf26fd..941042d5bce 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 ef7bf32a051..52d57eda809 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 a646279203f..10441d21536 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 270858d04d4..c7909d3e1af 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 2ad42cf511c..0ce0cb1529b 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 f7358e148dc..e619216cc0b 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 7c27eeceede..cdf360c8795 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 3f91496adab..c3a955b2387 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 a5042628529..d72469759f1 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 f53ffeda6f6..a80a059481d 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 46d53bc54b5..080e9a96d58 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 3bf86dedea1..77bd0b57f1c 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 0bc69387d0b..02b1604ea11 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 0ce0cb1529b..75f6000bd3c 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 e619216cc0b..495c91e52ab 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 d551b91bce9..a142e838f3e 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 75f6000bd3c..fc5b29770d2 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 495c91e52ab..06b8d92a551 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 d6acb69431e..dcb33c03882 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 fc5b29770d2..aeaf75b987e 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 06b8d92a551..03cf9488f37 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 a2876dd86f5..6e664ad07e4 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 4a37902e79f..9ef45865665 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 8fca49a67f2..284585734dc 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 a53f691df19..5edd2bb6155 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 00000000000..a7aafa6fc73 --- /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 36608fb026d..fbd2f13d2b4 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 ffac366500b..dd1dc03d3ad 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 3ff6e599a6b..bf7956d297d 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 2cbaebb6074..ef8193dad28 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 26a5ecd6605..35c69f98d65 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 4e06b2ad046..46ff5ae167a 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 a51b8c6f9fa..bef53e18073 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 72759ac0f52..44658558b62 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 72e0aed8420..d826272b207 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 0a13de41511..07ba63ac479 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 5f026e55f31..cd8274d635a 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 02549ddfe96..9d6b3bd53c7 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 aa1ffc24895..092c60e9713 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 0d97dcbedf8..a901439b2c9 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 a96722620a9..b3cd5126967 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 e12a15a0671..48d5c3a87f7 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 b9f4e024f92..836ccc51637 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 aeaf75b987e..4d3e28cce46 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 03cf9488f37..431483efd32 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 86fda21aaa0..0d123f0cd54 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 8dcf47fd0a2..a348b40cb0f 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 f2fd226249b..14fbf2cf701 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 b60f61b1e8e..7fd789ac3f5 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 44682aaa559..cdd98110f83 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 3f790b1ec42..498a7363a61 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 0bc237b1c11..b7b95dff392 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 2deac022b1e..e8d88845f5a 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 a142e838f3e..008b8f81c89 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 4d3e28cce46..bd55f88f2df 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 431483efd32..31e5e646ad8 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 9adaac84333..ba079a426fe 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 f215e8fa161..40e63ec7180 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 c9d424daa33..bea9b749445 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 bd55f88f2df..229a8ad90ec 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 31e5e646ad8..b29ccb60cff 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 c7909d3e1af..624a38ebe9d 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 229a8ad90ec..2b38d1155e7 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 b29ccb60cff..7ef74a1c6dd 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 9ef45865665..385db6904e4 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 284585734dc..fff0c129cec 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 d36c25eafec..1f9f031a3bb 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 cd5113ee02f..78ec041405b 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 0d276c6f3ae..7c9ddf4f905 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 84ef3a2e2a9..a2753a42307 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 a7595065224..6e6e57f12e5 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 48d5c3a87f7..9d2a0ae40d8 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 836ccc51637..4648b28cc93 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 008b8f81c89..5e55f391aa6 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 2b38d1155e7..52b514d7b0c 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 7ef74a1c6dd..fd332772724 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 d405b9e257d..60386e3d080 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 70b9dfd9b46..0a7d43120f7 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 4c8c6e03c79..dfa5f608cfe 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 cdd98110f83..4ad8eccea1d 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 45331491aa0..ed9b381ee9d 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 bea9b749445..f2140a9eca7 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 52b514d7b0c..71aee1700ab 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 fd332772724..61a4b29b3b7 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 385db6904e4..3e7204e1746 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 fff0c129cec..2fa072c6d30 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 bd39fd68590..5853186d0bb 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 42ca0856b02..e32252a7615 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 dc5abc139e6..9cc2acebbf8 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 dcb33c03882..41d0d7efda6 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 f796ea05e7b..fb24ae22679 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 e6ba77fa164..65c7588acbd 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 71aee1700ab..04fc820bd9f 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 61a4b29b3b7..077121e7b22 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 b3cd5126967..fef9b334e75 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 b0c04323005..638aa0b8110 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 3e7245ed73a..66f51508441 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 dec195fddee..5717f851b81 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 04fc820bd9f..87ad8ffc67e 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 077121e7b22..058971731c1 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 092c60e9713..725c513914f 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 fef9b334e75..dcb95422609 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 638aa0b8110..7c2f1c84ff9 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 0d123f0cd54..32ba6be3322 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 66f51508441..b923e37b636 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 ba079a426fe..39629ab6d85 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 bbba5f411b2..1a0be8706bf 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 000f39eefd4..2cd4f62ae5e 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 6f814c7b66b..51704a2f530 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 31a6b065830..5c6b5b79509 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 1f2047c02cb..78966d9b7ab 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 39629ab6d85..c91563d7729 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 a47916506df..edc5eb024a6 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 18ab43967ec..49ab3c10f8c 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 725c513914f..a05e703db6a 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 9d2a0ae40d8..5ecbb8e1b88 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 4648b28cc93..2cb77dd2c07 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 2de2a915d41..ac1be576906 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 87ad8ffc67e..5e277e8e801 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 058971731c1..acf67deb250 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 41d0d7efda6..f01eae4a938 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 5e277e8e801..78d5a3db456 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 acf67deb250..01ba627194f 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 3e7204e1746..dd5a41a7aa5 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 2fa072c6d30..8a65560b44a 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 6cf5e2bf406..26ccea446f2 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 0d48ff6b2d6..53873373011 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 78d5a3db456..5027b7df0cd 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 8019d0df2df..6384ebfcd5f 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 5027b7df0cd..6e1850d33a7 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 01ba627194f..6f75b540b28 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 dd5a41a7aa5..18561a8bd2e 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 8a65560b44a..c7e187e07f4 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"