From 91eaa72c041549a26633ea00d90a727619b646db Mon Sep 17 00:00:00 2001 From: eifinger Date: Fri, 14 May 2021 14:15:55 +0200 Subject: [PATCH] Update deps --- .../alexa_media/.translations/de.json | 2 + .../alexa_media/.translations/en.json | 10 +- .../alexa_media/.translations/es.json | 4 +- .../alexa_media/.translations/fr.json | 12 +- .../alexa_media/.translations/it.json | 30 +- .../alexa_media/.translations/nb.json | 4 +- .../alexa_media/.translations/nl.json | 2 + .../alexa_media/.translations/pl.json | 28 +- .../alexa_media/.translations/pt_BR.json | 114 ++--- .../alexa_media/.translations/ru.json | 4 +- .../alexa_media/.translations/zh-Hans.json | 6 +- custom_components/alexa_media/__init__.py | 78 ++- .../alexa_media/alarm_control_panel.py | 25 +- custom_components/alexa_media/alexa_entity.py | 110 ++++- custom_components/alexa_media/config_flow.py | 7 +- custom_components/alexa_media/const.py | 10 +- custom_components/alexa_media/helpers.py | 43 +- custom_components/alexa_media/light.py | 449 ++++++++++++++++-- custom_components/alexa_media/manifest.json | 5 +- custom_components/alexa_media/media_player.py | 4 +- custom_components/alexa_media/sensor.py | 45 +- .../alexa_media/translations/de.json | 2 + .../alexa_media/translations/en.json | 10 +- .../alexa_media/translations/es.json | 4 +- .../alexa_media/translations/fr.json | 12 +- .../alexa_media/translations/it.json | 30 +- .../alexa_media/translations/nb.json | 4 +- .../alexa_media/translations/nl.json | 2 + .../alexa_media/translations/pl.json | 28 +- .../alexa_media/translations/pt_BR.json | 114 ++--- .../alexa_media/translations/ru.json | 4 +- .../alexa_media/translations/zh-Hans.json | 6 +- custom_components/weenect/__init__.py | 36 +- custom_components/weenect/config_flow.py | 47 -- custom_components/weenect/const.py | 40 +- custom_components/weenect/manifest.json | 2 +- custom_components/weenect/sensor.py | 42 +- custom_components/weenect/services.py | 10 +- custom_components/weenect/util.py | 18 + .../numberbox-card/numberbox-card.js | 18 +- .../numberbox-card/numberbox-card.js.gz | Bin 3387 -> 3316 bytes 41 files changed, 1036 insertions(+), 385 deletions(-) create mode 100644 custom_components/weenect/util.py diff --git a/custom_components/alexa_media/.translations/de.json b/custom_components/alexa_media/.translations/de.json index 40b7b83..1947cbe 100644 --- a/custom_components/alexa_media/.translations/de.json +++ b/custom_components/alexa_media/.translations/de.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Verbindungsfehler; Netzwerk prüfen und erneut versuchen", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Diese Email ist bereits registriert", "invalid_credentials": "Falsche Zugangsdaten", "unknown_error": "Unbekannter Fehler, bitte Log-Info melden" @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Schließen Sie Geräte ein, die über Echo verbunden sind", "queue_delay": "Sekunden zu warten, um Befehle in die Warteschlange zu stellen" } } diff --git a/custom_components/alexa_media/.translations/en.json b/custom_components/alexa_media/.translations/en.json index 44b132e..cc25c70 100644 --- a/custom_components/alexa_media/.translations/en.json +++ b/custom_components/alexa_media/.translations/en.json @@ -8,9 +8,9 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Error connecting; check network and retry", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Email for Alexa URL already registered", "invalid_credentials": "Invalid credentials", - "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "unknown_error": "Unknown error, please enable advanced debugging and report log info" }, "step": { @@ -82,7 +82,7 @@ }, "user": { "data": { - "cookies_txt": "", + "cookies_txt": "Cookies.txt data", "debug": "Advanced debugging", "email": "Email Address", "exclude_devices": "Excluded device (comma separated)", @@ -93,7 +93,7 @@ "password": "Password", "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Seconds between scans", - "securitycode": "", + "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, "description": "Please confirm the information below. For legacy configuration, disable `Use Login Proxy method` option.", @@ -105,8 +105,8 @@ "step": { "init": { "data": { - "queue_delay": "Seconds to wait to queue commands together", - "extended_entity_discovery": "Include devices connected via Echo" + "extended_entity_discovery": "Include devices connected via Echo", + "queue_delay": "Seconds to wait to queue commands together" } } } diff --git a/custom_components/alexa_media/.translations/es.json b/custom_components/alexa_media/.translations/es.json index 866fd1a..932a64a 100644 --- a/custom_components/alexa_media/.translations/es.json +++ b/custom_components/alexa_media/.translations/es.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Error al conectar, verifique la red y vuelva a intentarlo", + "hass_url_invalid": "No se puede conectar a la url de Home Assistant. Compruebe la dirección URL interna en Configuración -> General", "identifier_exists": "Correo electrónico para la URL de Alexa ya registrado", "invalid_credentials": "Credenciales no válidas", "unknown_error": "Error desconocido, por favor revisa los registros en Home Assistant y reporta el error si es necesario." @@ -95,7 +96,7 @@ "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)", "url": "Región del dominio de Amazon (por ejemplo, amazon.es)" }, - "description": "Por favor introduce tu [información](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **El método más rápido es [Importar cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import).** \n**ADVERTENCIA: Amazon informará 'Introduce un correo electrónico o número de teléfono válido' si tu cuenta utiliza [códigos 2FA - Segundo Factor de Autenticación](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", + "description": "Confirme la siguiente información. Para la configuración heredada, desactive la opción `Usar método de proxy de inicio de sesión`.", "title": "Alexa Media Player - Configuración" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Incluir dispositivos conectados a través de Echo", "queue_delay": "Segundos a esperar para agrupar comandos" } } diff --git a/custom_components/alexa_media/.translations/fr.json b/custom_components/alexa_media/.translations/fr.json index 91abbb4..75932fe 100644 --- a/custom_components/alexa_media/.translations/fr.json +++ b/custom_components/alexa_media/.translations/fr.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Clé 2FA intégrée non valide", "connection_error": "Erreur de connexion; vérifier le réseau et réessayer", + "hass_url_invalid": "Impossible de se connecter à l'URL de Home Assistant. Veuillez vérifier l'URL interne sous Configuration - > Général", "identifier_exists": "Email pour l'URL Alexa déjà enregistré", "invalid_credentials": "Informations d'identification invalides", "unknown_error": "Erreur inconnue, veuillez signaler les informations du journal" @@ -15,7 +16,7 @@ "step": { "action_required": { "data": { - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)" }, "description": "** {email} - alexa. {url} ** \n Amazon enverra une notification push conformément au message ci-dessous. Veuillez répondre complètement avant de continuer. \n {message}", "title": "Alexa Media Player - Action requise" @@ -32,7 +33,7 @@ "data": { "captcha": "Captcha", "password": "Mot de passe", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)", "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)" }, "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", @@ -55,7 +56,7 @@ }, "twofactor": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)", "securitycode": "2FA Code" }, "description": "**{email} - alexa.{url}** \nEntrez le mot de passe unique (OTP). \n{message}", @@ -68,7 +69,7 @@ "email": "Adresse Email", "exclude_devices": "Appareil exclu (séparé par des virgules)", "include_devices": "Appareil inclus (séparé par des virgules)", - "oauth_login": "Enable oauth-token app method", + "oauth_login": "Activer la méthode d'application oauth-token", "otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)", "password": "Mot de passe", "proxy": "Use Login Proxy method (2FA not required)", @@ -95,7 +96,7 @@ "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)", "url": "Domaine de la région Amazon (exemple, amazon.fr)" }, - "description": "Veuillez saisir vos informations.", + "description": "Veuillez confirmer les informations ci-dessous. Pour la configuration héritée, désactivez l'option `Utiliser la méthode proxy de connexion`.", "title": "Alexa Media Player - Configuration" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Inclure les appareils connectés via Echo", "queue_delay": "Secondes à attendre pour mettre les commandes en file d'attente ensemble" } } diff --git a/custom_components/alexa_media/.translations/it.json b/custom_components/alexa_media/.translations/it.json index 237cd0d..de45a5c 100644 --- a/custom_components/alexa_media/.translations/it.json +++ b/custom_components/alexa_media/.translations/it.json @@ -8,14 +8,15 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Errore durante la connessione; controlla la rete e riprova", + "hass_url_invalid": "Impossibile collegarsi ad Home Assistant. Controllare l'URL interno nel menu Configurazione -> Generale", "identifier_exists": "L'Email per l'URL di Alexa è già stata registrata", "invalid_credentials": "Credenziali non valide", - "unknown_error": "Errore sconosciuto, si prega di riportrtare i log di informazione" + "unknown_error": "Errore sconosciuto, si prega di abilitare il debug avanzato e riportare i log informativi" }, "step": { "action_required": { "data": { - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" }, "description": "** {email} - alexa. {url} ** \n Amazon invierà una notifica push per il seguente messaggio. Si prega di rispondere completamente prima di continuare. \n {message}", "title": "Alexa Media Player - Azione Richiesta" @@ -23,7 +24,7 @@ "authselect": { "data": { "authselectoption": "Metodo password usa e getta (OTP)", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" }, "description": "**{email} - alexa.{url}** \n{message}", "title": "Alexa Media Player - Password Usa e Getta (One Time Password)" @@ -32,7 +33,7 @@ "data": { "captcha": "Captcha", "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", "securitycode": "config::step::captcha::data::securitycode" }, "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", @@ -41,7 +42,7 @@ "claimspicker": { "data": { "authselectoption": "Metodi di Verifica", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" }, "description": "**{email} - alexa.{url}** \nPrego selezionare un metodo di verifica. (e.g., `0` or `1`) \n{message}", "title": "Alexa Media Player - Metodi di Verifica" @@ -55,7 +56,7 @@ }, "twofactor": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", "securitycode": "Codice autenticazione a 2 fattori (2FA)" }, "description": "**{email} - alexa.{url}** \nInserisci la password usa e getta (OTP). \n{message}", @@ -68,20 +69,20 @@ "email": "Indirizzo email", "exclude_devices": "Dispositivi da escludere (separati da virgola)", "include_devices": "Dispositivi da includere (separati da virgola)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", + "oauth_login": "Abilitare il metodo oauth-token app", + "otp_secret": "Chiave 2FA interna (genera automaticamente i codici 2FA)", "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Usa il Proxy come metodo di login (2FA non richiesta)", "scan_interval": "Tempo in secondi fra le scansioni", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Regione del dominio Amazon (e.g., amazon.it)" + "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)", + "url": "Regione del dominio Amazon (e.g. amazon.it)" }, "description": "Prego inserisci le tue [informazioni](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) potrebbe essere più semplice!** \n**ATTENZIONE: Amazon risponde incorrettamente 'Inserire una mail valida o un numero di telefono' quando è richiesto il codice 2FA](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", "title": "Alexa Media Player - Legacy Configuration" }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", + "cookies_txt": "Dati cookies.txt", "debug": "Debug avanzato", "email": "Indirizzo email", "exclude_devices": "Dispositivi da escludere (separati da virgola)", @@ -92,10 +93,10 @@ "password": "Password", "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Tempo in secondi fra le scansioni", - "securitycode": "2FA Code (recommended to avoid login issues)", + "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)", "url": "Regione del dominio Amazon (e.g., amazon.it)" }, - "description": "Prego inserisci le tue [informazioni](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) potrebbe essere più semplice!** \n**ATTENZIONE: Amazon risponde incorrettamente 'Inserire una mail valida o un numero di telefono' quando è richiesto il codice 2FA](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", + "description": "Confermare le informazioni sottostanti. Per le vecchie configurazioni, disabilitare l'opzione `Usa Proxy come metodo di login`.", "title": "Alexa Media Player - Configurazione" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Includi i dispositivi collegati tramite Echo", "queue_delay": "Secondi di attesa per accodare i comandi insieme" } } diff --git a/custom_components/alexa_media/.translations/nb.json b/custom_components/alexa_media/.translations/nb.json index 55b6cc0..0c36c1d 100644 --- a/custom_components/alexa_media/.translations/nb.json +++ b/custom_components/alexa_media/.translations/nb.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Feil ved tilkobling; sjekk nettverket og prøv på nytt", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "E-post for Alexa URL allerede registrert", "invalid_credentials": "ugyldige legitimasjon", "unknown_error": "Ukjent feil, vennligst rapporter logginfo" @@ -95,7 +96,7 @@ "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)", "url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)" }, - "description": "Vennligst skriv inn informasjonen din. \n**WARNING: Amazon rapporterer feilaktig 'Angi en gyldig e-postadresse eller et mobilnummer' når 2FA-kode kreves.** \n>{message}", + "description": "Bekreft informasjonen nedenfor. For eldre konfigurasjon, deaktiver alternativet \"Bruk innlogging proxy-metode\".", "title": "Alexa Media Player - Konfigurasjon" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Inkluder enheter som er koblet til via Echo", "queue_delay": "Sekunder for å vente på køkommandoer sammen" } } diff --git a/custom_components/alexa_media/.translations/nl.json b/custom_components/alexa_media/.translations/nl.json index b10cbaf..902cda4 100644 --- a/custom_components/alexa_media/.translations/nl.json +++ b/custom_components/alexa_media/.translations/nl.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Fout bij verbinden; controleer netwerk en probeer opnieuw", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Dit e-mailadres is reeds geregistreerd", "invalid_credentials": "Ongeldige inloggegevens", "unknown_error": "Onbekende fout, meld de loggegevens" @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Inclusief apparaten die zijn verbonden via Echo", "queue_delay": "Seconden om te wachten om opdrachten in de wachtrij te plaatsen" } } diff --git a/custom_components/alexa_media/.translations/pl.json b/custom_components/alexa_media/.translations/pl.json index 622cc24..1d8c2d9 100644 --- a/custom_components/alexa_media/.translations/pl.json +++ b/custom_components/alexa_media/.translations/pl.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Nieprawidłowy klucz z wbudowanej aplikacji uwierzytelniania dwuskładnikowego", "connection_error": "Błąd podczas łączenia; sprawdź sieć i spróbuj ponownie", + "hass_url_invalid": "Nie można połączyć się z adresem URL Home Assistanta. Sprawdź wewnętrzny adres URL w sekcji Konfiguracja -> Ogólne", "identifier_exists": "Adres e-mail dla Alexy już jest zarejestrowany", "invalid_credentials": "Nieprawidłowe dane logowania", "unknown_error": "Nieznany błąd, zgłoś log z tego zdarzenia" @@ -15,7 +16,7 @@ "step": { "action_required": { "data": { - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" }, "description": "**{email} - alexa.{url}** \nAmazon wyśle powiadomienie push zgodnie z poniższą wiadomością. Przed kontynuowaniem odpowiedz na wiadomość. \n{message}", "title": "Alexa Media Player — wymagane działanie" @@ -23,7 +24,7 @@ "authselect": { "data": { "authselectoption": "Metoda OTP", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" }, "description": "**{email} - alexa.{url}** \n{message}", "title": "Alexa Media Player — hasło jednorazowe" @@ -32,7 +33,7 @@ "data": { "captcha": "Kod Captcha", "password": "Hasło", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "securitycode": "Kod uwierzytelniania dwuskładnikowego" }, "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", @@ -41,7 +42,7 @@ "claimspicker": { "data": { "authselectoption": "Metoda weryfikacji", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" }, "description": "**{email} - alexa.{url}** \nWybierz metodę weryfikacji. (np., `0` lub `1`) \n{message}", "title": "Alexa Media Player — metoda weryfikacji" @@ -55,7 +56,7 @@ }, "twofactor": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "securitycode": "Kod uwierzytelniania dwuskładnikowego" }, "description": "**{email} - alexa.{url}** \nWprowadź hasło jednorazowe (OTP). \n{message}", @@ -65,19 +66,19 @@ "data": { "cookies_txt": "zawartość pliku cookies.txt", "debug": "Zaawansowane debugowanie", - "email": "Adres email", + "email": "Adres e-mail", "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", "include_devices": "Dodawane urządzenia (oddzielone przecinkami)", - "oauth_login": "Enable oauth-token app method", + "oauth_login": "Włącz metodę tokena OAuth aplikacji", "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)", "password": "Hasło", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "scan_interval": "Interwał skanowania (sekundy)", "securitycode": "Kod uwierzytelniania dwuskładnikowego", "url": "Region/domena Amazon (np. amazon.co.uk)" }, "description": "Wprowadź [dane](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Import pliku Cookie](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) może być najłatwiejszą metodą!** \n**OSTRZEŻENIE: Amazon nieprawidłowo zgłasza 'Wprowadź prawidłowy adres e-mail lub numer telefonu komórkowego', gdy wymagany jest [kod uwierzytelniania dwuskładnikowego](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" + "title": "Alexa Media Player — starsza konfiguracja" }, "user": { "data": { @@ -85,17 +86,17 @@ "debug": "Zaawansowane debugowanie", "email": "Adres email", "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", - "hass_url": "Url to access Home Assistant", + "hass_url": "URL dostępu do Home Assistanta", "include_devices": "Dodawane urządzenia (oddzielone przecinkami)", - "oauth_login": "Enable oauth-token app method", + "oauth_login": "Włącz metodę tokena OAuth aplikacji", "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)", "password": "Hasło", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "scan_interval": "Interwał skanowania (sekundy)", "securitycode": "Kod uwierzytelniania dwuskładnikowego (zalecany w celu uniknięcia problemów z logowaniem)", "url": "Region/domena Amazon (np. amazon.co.uk)" }, - "description": "Wprowadź [dane](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Import pliku Cookie](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) może być najłatwiejszą metodą!** \n**OSTRZEŻENIE: Amazon nieprawidłowo zgłasza 'Wprowadź prawidłowy adres e-mail lub numer telefonu komórkowego', gdy wymagany jest [kod uwierzytelniania dwuskładnikowego](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", + "description": "Potwierdź poniższe informacje. W przypadku starszych konfiguracji wyłącz opcję 'Użyj metody logowania proxy'.", "title": "Alexa Media Player — konfiguracja" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Uwzględnij urządzenia podłączone przez Echo", "queue_delay": "Sekundy oczekiwania na kolejkowanie poleceń" } } diff --git a/custom_components/alexa_media/.translations/pt_BR.json b/custom_components/alexa_media/.translations/pt_BR.json index 7790d6f..750d1d1 100644 --- a/custom_components/alexa_media/.translations/pt_BR.json +++ b/custom_components/alexa_media/.translations/pt_BR.json @@ -1,103 +1,103 @@ { "config": { "abort": { - "forgot_password": "The Forgot Password page was detected. This normally is the result of too may failed logins. Amazon may require action before a relogin can be attempted.", - "login_failed": "Alexa Media Player failed to login.", - "reauth_successful": "Alexa Media Player successfully reauthenticated." + "forgot_password": "A página para senha esquecida foi detectada. Isto normalmente é o resultado de muitas tentativas de login falhas. A amazon pode requisitar ação antes de um novo login ser tentando.", + "login_failed": "Alexa Media Player falhou no login.", + "reauth_successful": "Alexa Media Player re-autenticado com sucesso." }, "error": { - "2fa_key_invalid": "Invalid Built-In 2FA key", - "connection_error": "Error connecting; check network and retry", - "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", - "identifier_exists": "Email for Alexa URL already registered", - "invalid_credentials": "Invalid credentials", - "unknown_error": "Unknown error, please enable advanced debugging and report log info" + "2fa_key_invalid": "Chave integrada 2FA inválida", + "connection_error": "Erro de conexão; Verifique a sua conexão e tente novamente", + "hass_url_invalid": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL interna em Configuração -> Geral", + "identifier_exists": "Email para URL Alexa já registrado", + "invalid_credentials": "Credenciais inválidas", + "unknown_error": "Erro desconhecido, favor habilitar a depuração avançada e reporte as informações de registro" }, "step": { "action_required": { "data": { - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Usar método Login Proxy (Não requer 2FA)" }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" + "description": "**{email} - alexa.{url}** \nA amazon enviará uma notificação push para a mensagem abaixo. Por favor responda ela completamente antes de continuar. \n{message}", + "title": "Alexa Media Player - Ação Requisitada" }, "authselect": { "data": { - "authselectoption": "OTP method", - "proxy": "Use Login Proxy method (2FA not required)" + "authselectoption": "Método OTP", + "proxy": "Usar método Login Proxy (Não requer 2FA)" }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - One Time Password" + "description": "**{email} - alexa.{url}**\n{message}", + "title": "Alexa Media Player - Senha de uso único" }, "captcha": { "data": { "captcha": "Captcha", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code (recommended to avoid login issues)" + "password": "Senha", + "proxy": "Usar método Login Proxy (Não requer 2FA)", + "securitycode": "Código 2FA (recomendado para evitar problemas de login)" }, "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", "title": "Alexa Media Player - Captcha" }, "claimspicker": { "data": { - "authselectoption": "Verification method", - "proxy": "Use Login Proxy method (2FA not required)" + "authselectoption": "Método de verificação", + "proxy": "Usar método Login Proxy (Não requer 2FA)" }, - "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - Verification Method" + "description": "**{email} - alexa.{url}** \nPor favor selecione um método de verificação por número. (ex: `0` ou `1`) \n{message}", + "title": "Alexa Media Player - Método de verificação" }, "totp_register": { "data": { - "registered": "OTP from the Built-in 2FA App Key confirmed successfully." + "registered": "OTP do aplicativo integrado de chave 2FA confirmado com sucesso." }, - "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", - "title": "Alexa Media Player - OTP Confirmation" + "description": "**{email} - alexa.{url}** \nVocê confirmou com sucesso um OTP no aplicativo integrado de chave 2FA com a amazon? \n >OTP Code {message}", + "title": "Alexa Media Player - Confirmação OTP" }, "twofactor": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code" + "proxy": "Usar método Login Proxy (Não requer 2FA)", + "securitycode": "Código 2FA" }, - "description": "**{email} - alexa.{url}** \nEnter the One Time Password (OTP). \n{message}", - "title": "Alexa Media Player - Two Factor Authentication" + "description": "**{email} - alexa.{url}** \nInsira a senha de uso único (OTP).\n{message}", + "title": "Alexa Media Player - Autenticação de dois fatores" }, "user_legacy": { "data": { "cookies_txt": "Cookies.txt data", - "debug": "Advanced debugging", - "email": "Email Address", - "exclude_devices": "Excluded device (comma separated)", - "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Seconds between scans", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Amazon region domain (e.g., amazon.co.uk)" + "debug": "Depuração avançada", + "email": "Endereço de email", + "exclude_devices": "Dispositivos exclusos (separados por virgula)", + "include_devices": "Dispositivos inclusos (separados por vírgula)", + "oauth_login": "Habilitar o aplicativo para método auth-token", + "otp_secret": "Aplicativo integrado para chaves 2FA (Automaticamente gera códigos 2FA)", + "password": "Senha", + "proxy": "Usar método Login Proxy (Não requer 2FA)", + "scan_interval": "Segundos entre varreduras", + "securitycode": "Código 2FA (recomendado para evitar problemas de login)", + "url": "Domínio regional da amazon (ex: amazon.co.uk)" }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" + "description": "Por favor insira sua [informação](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) pode ser mais fácil!** \n**AVISO! A amazon reporta incorretamente 'Insira um email ou número de telefone válido' quando [Código de 2FA é requirido](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", + "title": "Alexa Media Player - Configurações legado" }, "user": { "data": { "cookies_txt": "Dados de cookies.txt", - "debug": "Advanced debugging", - "email": "Email Address", - "exclude_devices": "Excluded device (comma separated)", - "hass_url": "Url to access Home Assistant", - "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Seconds between scans", + "debug": "Depuração avançada", + "email": "Endereço de email", + "exclude_devices": "Dispositivos exclusos (separados por virgula)", + "hass_url": "Url para acesso ao Home Assistant", + "include_devices": "Dispositivos inclusos (separados por vírgula)", + "oauth_login": "Habilitar o aplicativo para método auth-token", + "otp_secret": "Aplicativo integrado para chaves 2FA (Automaticamente gera códigos 2FA)", + "password": "Senha", + "proxy": "Usar método Login Proxy (Não requer 2FA)", + "scan_interval": "Segundos entre varreduras", "securitycode": "Código 2FA (recomendado para evitar problemas de login)", - "url": "Amazon region domain (e.g., amazon.co.uk)" + "url": "Domínio regional da amazon (ex: amazon.co.uk)" }, - "description": "Por favor, confirme as informações abaixo. Para configuração legada, desative a opção `Usar método de proxy de login`.", - "title": "Alexa Media Player - Configuration" + "description": "Por favor, confirme as informações abaixo. Para configuração legada, desative a opção `Usar método de proxy de login'.", + "title": "Alexa Media Player - Configurações" } } }, @@ -106,7 +106,7 @@ "init": { "data": { "extended_entity_discovery": "Inclui dispositivos conectados via Echo", - "queue_delay": "Seconds to wait to queue commands together" + "queue_delay": "Segundos para aguardar antes de enfileirar juntos" } } } diff --git a/custom_components/alexa_media/.translations/ru.json b/custom_components/alexa_media/.translations/ru.json index 40d67f9..1cba8a2 100644 --- a/custom_components/alexa_media/.translations/ru.json +++ b/custom_components/alexa_media/.translations/ru.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Ошибка подключения; проверьте сеть и повторите попытку", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Электронная почта для Alexa уже зарегистрирована", "invalid_credentials": "Неверные учетные данные", "unknown_error": "Неизвестная ошибка, пожалуйста, сообщите информацию журнала" @@ -95,7 +96,7 @@ "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Домен региона Amazon (например, amazon.co.uk)" }, - "description": "Пожалуйста, введите свои данные.{message}", + "description": "Пожалуйста, подтвердите информацию ниже. Для старой конфигурации отключите опцию `Use Login Proxy method`.", "title": "Alexa Media Player - Конфигурация" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Включить устройства, подключенные через Echo", "queue_delay": "Секунды ожидания, чтобы выполнить команды вместе" } } diff --git a/custom_components/alexa_media/.translations/zh-Hans.json b/custom_components/alexa_media/.translations/zh-Hans.json index 6fa4a55..64aae71 100644 --- a/custom_components/alexa_media/.translations/zh-Hans.json +++ b/custom_components/alexa_media/.translations/zh-Hans.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "连接错误;检查网络并重试", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Alexa URL的电子邮件已注册", "invalid_credentials": "Invalid credentials", "unknown_error": "Unknown error, please report log info" @@ -81,7 +82,7 @@ }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", + "cookies_txt": "Cookie.txt数据", "debug": "高级调试", "email": "电子邮件地址", "exclude_devices": "Excluded device (comma separated)", @@ -95,7 +96,7 @@ "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, - "description": "请输入您的信息。", + "description": "请确认以下信息。对于旧版配置,请禁用“使用登录代理方法”选项。", "title": "Alexa Media Player-配置" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Include devices connected via Echo", "queue_delay": "Seconds to wait to queue commands together" } } diff --git a/custom_components/alexa_media/__init__.py b/custom_components/alexa_media/__init__.py index 11b264f..7a7caa7 100644 --- a/custom_components/alexa_media/__init__.py +++ b/custom_components/alexa_media/__init__.py @@ -37,7 +37,7 @@ EVENT_HOMEASSISTANT_STOP, ) from homeassistant.data_entry_flow import UnknownFlow -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed @@ -70,7 +70,12 @@ SCAN_INTERVAL, STARTUP, ) -from .helpers import _catch_login_errors, _existing_serials, calculate_uuid +from .helpers import ( + _catch_login_errors, + _existing_serials, + alarm_just_dismissed, + calculate_uuid, +) from .notify import async_unload_entry as notify_async_unload_entry from .services import AlexaMediaServices @@ -253,7 +258,13 @@ async def login_success(event=None) -> None: "coordinator": None, "config_entry": config_entry, "setup_alexa": setup_alexa, - "devices": {"media_player": {}, "switch": {}, "guard": [], "light": [], "temperature": []}, + "devices": { + "media_player": {}, + "switch": {}, + "guard": [], + "light": [], + "temperature": [], + }, "entities": { "media_player": {}, "switch": {}, @@ -277,7 +288,7 @@ async def login_success(event=None) -> None: ), CONF_EXTENDED_ENTITY_DISCOVERY: config_entry.options.get( CONF_EXTENDED_ENTITY_DISCOVERY, DEFAULT_EXTENDED_ENTITY_DISCOVERY - ) + ), }, DATA_LISTENER: [config_entry.add_update_listener(update_listener)], }, @@ -356,7 +367,9 @@ async def async_update_data() -> Optional[AlexaEntityData]: ].values() auth_info = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get("auth_info") new_devices = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] - should_get_network = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["should_get_network"] + should_get_network = hass.data[DATA_ALEXAMEDIA]["accounts"][email][ + "should_get_network" + ] devices = {} bluetooth = {} @@ -374,7 +387,9 @@ async def async_update_data() -> Optional[AlexaEntityData]: tasks.append(AlexaAPI.get_authentication(login_obj)) entities_to_monitor = set() - for sensor in hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"]["sensor"].values(): + for sensor in hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"][ + "sensor" + ].values(): temp = sensor.get("Temperature") if temp and temp.enabled: entities_to_monitor.add(temp.alexa_entity_id) @@ -383,7 +398,9 @@ async def async_update_data() -> Optional[AlexaEntityData]: if light.enabled: entities_to_monitor.add(light.alexa_entity_id) - for guard in hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"]["alarm_control_panel"].values(): + for guard in hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"][ + "alarm_control_panel" + ].values(): if guard.enabled: entities_to_monitor.add(guard.unique_id) @@ -402,14 +419,20 @@ async def async_update_data() -> Optional[AlexaEntityData]: bluetooth, preferences, dnd, - *optional_task_results - ) = await asyncio.gather(*tasks) + *optional_task_results, + ) = await asyncio.gather(*tasks) if should_get_network: - _LOGGER.debug("Alexa entities have been loaded. Prepared for discovery.") + _LOGGER.debug( + "Alexa entities have been loaded. Prepared for discovery." + ) alexa_entities = parse_alexa_entities(optional_task_results.pop()) - hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"].update(alexa_entities) - hass.data[DATA_ALEXAMEDIA]["accounts"][email]["should_get_network"] = False + hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"].update( + alexa_entities + ) + hass.data[DATA_ALEXAMEDIA]["accounts"][email][ + "should_get_network" + ] = False if entities_to_monitor: entity_state = optional_task_results.pop() @@ -580,6 +603,23 @@ async def async_update_data() -> Optional[AlexaEntityData]: ) hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False + # prune stale devices + device_registry = await dr.async_get_registry(hass) + for device_entry in dr.async_entries_for_config_entry( + device_registry, config_entry.entry_id + ): + for (_, identifier) in device_entry.identifiers: + if ( + identifier + in hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][ + "media_player" + ].keys() + ): + break + else: + device_registry.async_remove_device(device_entry.id) + _LOGGER.debug("Removing stale device %s", device_entry.name) + await login_obj.save_cookiefile() if login_obj.access_token: hass.config_entries.async_update_entry( @@ -601,6 +641,9 @@ async def process_notifications(login_obj, raw_notifications=None): if not raw_notifications: raw_notifications = await AlexaAPI.get_notifications(login_obj) email: Text = login_obj.email + previous = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get( + "notifications", {} + ) notifications = {"process_timestamp": datetime.utcnow()} for notification in raw_notifications: n_dev_id = notification.get("deviceSerialNumber") @@ -620,6 +663,17 @@ async def process_notifications(login_obj, raw_notifications=None): notification["date_time"] = ( f"{n_date} {n_time}" if n_date and n_time else None ) + previous_alarm = previous.get(n_dev_id, {}).get("Alarm", {}).get(n_id) + if previous_alarm and alarm_just_dismissed( + notification, + previous_alarm.get("status"), + previous_alarm.get("version"), + ): + hass.bus.async_fire( + "alexa_media_alarm_dismissal_event", + event_data={"device": {"id": n_dev_id}, "event": notification}, + ) + if n_dev_id not in notifications: notifications[n_dev_id] = {} if n_type not in notifications[n_dev_id]: diff --git a/custom_components/alexa_media/alarm_control_panel.py b/custom_components/alexa_media/alarm_control_panel.py index 5f422ee..4ffe930 100644 --- a/custom_components/alexa_media/alarm_control_panel.py +++ b/custom_components/alexa_media/alarm_control_panel.py @@ -75,7 +75,10 @@ async def async_setup_platform( guard_entities = account_dict.get("devices", {}).get("guard", []) if guard_entities: alexa_client = AlexaAlarmControlPanel( - account_dict["login_obj"], account_dict["coordinator"], guard_entities[0], guard_media_players + account_dict["login_obj"], + account_dict["coordinator"], + guard_entities[0], + guard_media_players, ) else: _LOGGER.debug("%s: No Alexa Guard entity found", account) @@ -143,12 +146,12 @@ def __init__(self, login, coordinator, guard_entity, media_players=None) -> None self._media_players = {} or media_players self._attrs: Dict[Text, Text] = {} _LOGGER.debug( - "%s: Guard Discovered %s: %s %s", - self.account, - self._friendly_name, - hide_serial(self._appliance_id), - hide_serial(self._guard_entity_id), - ) + "%s: Guard Discovered %s: %s %s", + self.account, + self._friendly_name, + hide_serial(self._appliance_id), + hide_serial(self._guard_entity_id), + ) @_catch_login_errors async def _async_alarm_set(self, command: Text = "", code=None) -> None: @@ -207,7 +210,9 @@ def name(self): @property def state(self): """Return the state of the device.""" - _state = parse_guard_state_from_coordinator(self.coordinator, self._guard_entity_id) + _state = parse_guard_state_from_coordinator( + self.coordinator, self._guard_entity_id + ) if _state == "ARMED_AWAY": return STATE_ALARM_ARMED_AWAY elif _state == "ARMED_STAY": @@ -228,7 +233,9 @@ def supported_features(self) -> int: @property def assumed_state(self) -> bool: - last_refresh_success = self.coordinator.data and self._guard_entity_id in self.coordinator.data + last_refresh_success = ( + self.coordinator.data and self._guard_entity_id in self.coordinator.data + ) return not last_refresh_success @property diff --git a/custom_components/alexa_media/alexa_entity.py b/custom_components/alexa_media/alexa_entity.py index f871190..4a1a65a 100644 --- a/custom_components/alexa_media/alexa_entity.py +++ b/custom_components/alexa_media/alexa_entity.py @@ -6,10 +6,11 @@ For more details about this platform, please refer to the documentation at https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 """ - +from datetime import datetime import json import logging -from typing import Any, Dict, List, Optional, Text, TypedDict, Union +import re +from typing import Any, Dict, List, Optional, Text, Tuple, TypedDict, Union from alexapy import AlexaAPI, AlexaLogin, hide_serial from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -53,12 +54,26 @@ def is_hue_v1(appliance: Dict[Text, Any]) -> bool: def is_local(appliance: Dict[Text, Any]) -> bool: """Test whether locally connected. - connectedVia is a flag that determines which Echo devices holds the connection. Its blank for - skill derived devices and includes an Echo name for zigbee and local devices. This is used to limit - the scope of what devices will be discovered. This is mainly present to prevent loops with the official Alexa - integration. There is probably a better way to prevent that, but this works. + This is mainly present to prevent loops with the official Alexa integration. + There is probably a better way to prevent that, but this works. """ - return appliance["connectedVia"] + + if appliance.get("connectedVia"): + # connectedVia is a flag that determines which Echo devices holds the connection. Its blank for + # skill derived devices and includes an Echo name for zigbee and local devices. + return True + + # This catches the Echo/AVS devices. connectedVia isn't reliable in this case. + # Only the first appears to get that set. + if "ALEXA_VOICE_ENABLED" in appliance.get("applianceTypes", []): + namespace = appliance.get("driverIdentity", {}).get("namespace", "") + return namespace and namespace != "SKILL" + + # Zigbee devices are guaranteed to be local and have a particular pattern of id + zigbee_pattern = re.compile( + "AAA_SonarCloudService_([0-9A-F][0-9A-F]:){7}[0-9A-F][0-9A-F]", flags=re.I + ) + return zigbee_pattern.fullmatch(appliance.get("applianceId", "")) is not None def is_alexa_guard(appliance: Dict[Text, Any]) -> bool: @@ -70,10 +85,8 @@ def is_alexa_guard(appliance: Dict[Text, Any]) -> bool: def is_temperature_sensor(appliance: Dict[Text, Any]) -> bool: """Is the given appliance the temperature sensor of an Echo.""" - return ( - is_local(appliance) - and appliance["manufacturerName"] == "Amazon" - and has_capability(appliance, "Alexa.TemperatureSensor", "temperature") + return is_local(appliance) and has_capability( + appliance, "Alexa.TemperatureSensor", "temperature" ) @@ -216,26 +229,55 @@ def parse_temperature_from_coordinator( def parse_brightness_from_coordinator( - coordinator: DataUpdateCoordinator, entity_id: Text -) -> int: + coordinator: DataUpdateCoordinator, entity_id: Text, since: datetime +) -> Optional[int]: """Get the brightness in the range 0-100.""" return parse_value_from_coordinator( - coordinator, entity_id, "Alexa.BrightnessController", "brightness" + coordinator, entity_id, "Alexa.BrightnessController", "brightness", since + ) + + +def parse_color_temp_from_coordinator( + coordinator: DataUpdateCoordinator, entity_id: Text, since: datetime +) -> Optional[int]: + """Get the color temperature in kelvin""" + return parse_value_from_coordinator( + coordinator, + entity_id, + "Alexa.ColorTemperatureController", + "colorTemperatureInKelvin", + since, ) +def parse_color_from_coordinator( + coordinator: DataUpdateCoordinator, entity_id: Text, since: datetime +) -> Optional[Tuple[float, float, float]]: + """Get the color as a tuple of (hue, saturation, brightness)""" + value = parse_value_from_coordinator( + coordinator, entity_id, "Alexa.ColorController", "color", since + ) + if value is not None: + hue = value.get("hue", 0) + saturation = value.get("saturation", 0) + brightness = parse_brightness_from_coordinator(coordinator, entity_id, since) + if brightness is not None: + return hue, saturation, brightness / 100 + return None + + def parse_power_from_coordinator( - coordinator: DataUpdateCoordinator, entity_id: Text -) -> Text: + coordinator: DataUpdateCoordinator, entity_id: Text, since: datetime +) -> Optional[Text]: """Get the power state of the entity.""" return parse_value_from_coordinator( - coordinator, entity_id, "Alexa.PowerController", "powerState" + coordinator, entity_id, "Alexa.PowerController", "powerState", since ) def parse_guard_state_from_coordinator( coordinator: DataUpdateCoordinator, entity_id: Text -): +) -> Optional[Text]: """Get the guard state from the coordinator data.""" return parse_value_from_coordinator( coordinator, entity_id, "Alexa.SecurityPanelController", "armState" @@ -243,7 +285,11 @@ def parse_guard_state_from_coordinator( def parse_value_from_coordinator( - coordinator: DataUpdateCoordinator, entity_id: Text, namespace: Text, name: Text + coordinator: DataUpdateCoordinator, + entity_id: Text, + namespace: Text, + name: Text, + since: Optional[datetime] = None, ) -> Any: """Parse out values from coordinator for Alexa Entities.""" if coordinator.data and entity_id in coordinator.data: @@ -252,7 +298,31 @@ def parse_value_from_coordinator( cap_state.get("namespace") == namespace and cap_state.get("name") == name ): - return cap_state.get("value") + if is_cap_state_still_acceptable(cap_state, since): + return cap_state.get("value") + else: + _LOGGER.debug( + "Coordinator data for %s is too old to be returned.", + hide_serial(entity_id), + ) + return None else: _LOGGER.debug("Coordinator has no data for %s", hide_serial(entity_id)) return None + + +def is_cap_state_still_acceptable( + cap_state: Dict[Text, Any], since: Optional[datetime] +) -> bool: + """Determine if a particular capability state is still usable given its age.""" + if since is not None: + formatted_time_of_sample = cap_state.get("timeOfSample") + if formatted_time_of_sample: + try: + time_of_sample = datetime.strptime( + formatted_time_of_sample, "%Y-%m-%dT%H:%M:%S.%fZ" + ) + return time_of_sample >= since + except ValueError: + pass + return True diff --git a/custom_components/alexa_media/config_flow.py b/custom_components/alexa_media/config_flow.py index a049162..e70baae 100644 --- a/custom_components/alexa_media/config_flow.py +++ b/custom_components/alexa_media/config_flow.py @@ -1031,9 +1031,10 @@ async def async_step_init(self, user_input=None): vol.Required( CONF_EXTENDED_ENTITY_DISCOVERY, default=self.config_entry.options.get( - CONF_EXTENDED_ENTITY_DISCOVERY, DEFAULT_EXTENDED_ENTITY_DISCOVERY - ) - ): bool + CONF_EXTENDED_ENTITY_DISCOVERY, + DEFAULT_EXTENDED_ENTITY_DISCOVERY, + ), + ): bool, } ) return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/custom_components/alexa_media/const.py b/custom_components/alexa_media/const.py index f54c994..5c40046 100644 --- a/custom_components/alexa_media/const.py +++ b/custom_components/alexa_media/const.py @@ -8,7 +8,7 @@ """ from datetime import timedelta -__version__ = "3.9.0" +__version__ = "3.10.2" PROJECT_URL = "https://github.com/custom-components/alexa_media_player/" ISSUE_URL = f"{PROJECT_URL}issues" @@ -23,7 +23,13 @@ ALEXA_COMPONENTS = [ "media_player", ] -DEPENDENT_ALEXA_COMPONENTS = ["notify", "switch", "sensor", "alarm_control_panel", "light"] +DEPENDENT_ALEXA_COMPONENTS = [ + "notify", + "switch", + "sensor", + "alarm_control_panel", + "light", +] HTTP_COOKIE_HEADER = "# HTTP Cookie File" CONF_ACCOUNTS = "accounts" diff --git a/custom_components/alexa_media/helpers.py b/custom_components/alexa_media/helpers.py index 022e7ff..9964178 100644 --- a/custom_components/alexa_media/helpers.py +++ b/custom_components/alexa_media/helpers.py @@ -6,10 +6,9 @@ For more details about this platform, please refer to the documentation at https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 """ - import hashlib import logging -from typing import Any, Callable, List, Optional, Text +from typing import Any, Callable, Dict, List, Optional, Text from alexapy import AlexapyLoginCloseRequested, AlexapyLoginError, hide_email from alexapy.alexalogin import AlexaLogin @@ -283,3 +282,43 @@ async def calculate_uuid(hass, email: Text, url: Text) -> dict: result["index"] = return_index _LOGGER.debug("%s: Returning uuid %s", hide_email(email), result) return result + + +def alarm_just_dismissed( + alarm: Dict[Text, Any], + previous_status: Optional[Text], + previous_version: Optional[Text], +) -> bool: + """Given the previous state of an alarm, determine if it has just been dismissed.""" + + if previous_status not in ("SNOOZED", "ON"): + # The alarm had to be in a status that supported being dismissed + return False + + if previous_version is None: + # The alarm was probably just created + return False + + if not alarm: + # The alarm that was probably just deleted. + return False + + if alarm.get("status") not in ("OFF", "ON"): + # A dismissed alarm is guaranteed to be turned off(one-off alarm) or left on(recurring alarm) + return False + + if previous_version == alarm.get("version"): + # A dismissal always has a changed version. + return False + + if int(alarm.get("version", "0")) > 1 + int(previous_version): + # This is an absurd thing to check, but it solves many, many edge cases. + # Experimentally, when an alarm is dismissed, the version always increases by 1 + # When an alarm is edited either via app or voice, its version always increases by 2+ + return False + + # It seems obvious that a check involving time should be necessary. It is not. + # We know there was a change and that it wasn't an edit. + # We also know the alarm's status rules out a snooze. + # The only remaining possibility is that this alarm was just dismissed. + return True diff --git a/custom_components/alexa_media/light.py b/custom_components/alexa_media/light.py index f9dff50..e3f68af 100644 --- a/custom_components/alexa_media/light.py +++ b/custom_components/alexa_media/light.py @@ -6,18 +6,50 @@ For more details about this platform, please refer to the documentation at https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 """ -import asyncio import datetime import logging -from typing import Callable, List, Optional, Text # noqa pylint: disable=unused-import +from math import sqrt +from typing import ( # noqa pylint: disable=unused-import + Callable, + List, + Optional, + Text, + Tuple, +) from alexapy import AlexaAPI, hide_serial from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, LightEntity, ) + +try: + from homeassistant.components.light import ( + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + COLOR_MODE_ONOFF, + ) +except ImportError: + # Continue to support HA < 2021.4. + COLOR_MODE_BRIGHTNESS = "brightness" + COLOR_MODE_COLOR_TEMP = "color_temp" + COLOR_MODE_HS = "hs" + COLOR_MODE_ONOFF = "onoff" + from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util.color import ( + color_hs_to_RGB, + color_hsb_to_RGB, + color_name_to_rgb, + color_RGB_to_hs, + color_temperature_kelvin_to_mired, +) from . import ( CONF_EMAIL, @@ -28,6 +60,8 @@ ) from .alexa_entity import ( parse_brightness_from_coordinator, + parse_color_from_coordinator, + parse_color_temp_from_coordinator, parse_power_from_coordinator, ) from .const import CONF_EXTENDED_ENTITY_DISCOVERY @@ -46,17 +80,26 @@ async def async_setup_platform(hass, config, add_devices_callback, discovery_inf include_filter = config.get(CONF_INCLUDE_DEVICES, []) exclude_filter = config.get(CONF_EXCLUDE_DEVICES, []) coordinator = account_dict["coordinator"] - hue_emulated_enabled = "emulated_hue" in hass.config.as_dict().get("components", set()) + hue_emulated_enabled = "emulated_hue" in hass.config.as_dict().get( + "components", set() + ) light_entities = account_dict.get("devices", {}).get("light", []) if light_entities and account_dict["options"].get(CONF_EXTENDED_ENTITY_DISCOVERY): for le in light_entities: if not (le["is_hue_v1"] and hue_emulated_enabled): - _LOGGER.debug("Creating entity %s for a light with name %s", hide_serial(le["id"]), le["name"]) + _LOGGER.debug( + "Creating entity %s for a light with name %s", + hide_serial(le["id"]), + le["name"], + ) light = AlexaLight(coordinator, account_dict["login_obj"], le) account_dict["entities"]["light"].append(light) devices.append(light) else: - _LOGGER.debug("Light '%s' has not been added because it may originate from emulated_hue", le["name"]) + _LOGGER.debug( + "Light '%s' has not been added because it may originate from emulated_hue", + le["name"], + ) if devices: await coordinator.async_refresh() @@ -87,23 +130,39 @@ async def async_unload_entry(hass, entry) -> bool: return True -def ha_brightness_to_alexa(ha): - return ha / 255 * 100 - - -def alexa_brightness_to_ha(alexa): - return alexa / 100 * 255 +def color_modes(details): + if details["color"] and details["color_temperature"]: + return [COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP] + elif details["color"]: + return [COLOR_MODE_HS] + elif details["color_temperature"]: + return [COLOR_MODE_COLOR_TEMP] + elif details["brightness"]: + return [COLOR_MODE_BRIGHTNESS] + else: + return [COLOR_MODE_ONOFF] class AlexaLight(CoordinatorEntity, LightEntity): - """A light controlled by an Echo. """ + """A light controlled by an Echo.""" def __init__(self, coordinator, login, details): super().__init__(coordinator) self.alexa_entity_id = details["id"] self._name = details["name"] self._login = login - self._supported_features = SUPPORT_BRIGHTNESS if details["brightness"] else 0 + self._color_modes = color_modes(details) + + # Store the requested state from the last call to _set_state + # This is so that no new network call is needed just to get values that are already known + # This is useful because refreshing the full state can take a bit when many lights are in play. + # Especially since Alexa actually polls the lights and that appears to be error-prone with some Zigbee lights. + # That delay(1-5s in practice) causes the UI controls to jump all over the place after _set_state + self._requested_state_at = None # When was state last set in UTC + self._requested_power = None + self._requested_ha_brightness = None + self._requested_mired = None + self._requested_hs = None @property def name(self): @@ -115,36 +174,364 @@ def unique_id(self): @property def supported_features(self): - return self._supported_features + # The HA documentation marks every single feature that Alexa lights can support as deprecated. + # The new alternative is the supported_color_modes and color_mode properties(HA 2021.4) + # This SHOULD just need to return 0 according to the light entity docs. + # Actually doing that causes the UI to remove color controls even in HA 2021.4. + # So, continue to provide a backwards compatible method here until HA is fixed and the min HA version is raised. + if COLOR_MODE_BRIGHTNESS in self._color_modes: + return SUPPORT_BRIGHTNESS + elif ( + COLOR_MODE_HS in self._color_modes + and COLOR_MODE_COLOR_TEMP in self._color_modes + ): + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP + elif COLOR_MODE_HS in self._color_modes: + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR + elif COLOR_MODE_COLOR_TEMP in self._color_modes: + return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP + else: + + return 0 + + @property + def color_mode(self): + if ( + COLOR_MODE_HS in self._color_modes + and COLOR_MODE_COLOR_TEMP in self._color_modes + ): + hs = self.hs_color + if hs is None or (hs[0] == 0 and hs[1] == 0): + # (0,0) is white. When white, color temp is the better plan. + return COLOR_MODE_COLOR_TEMP + else: + return COLOR_MODE_HS + else: + return self._color_modes[0] + + @property + def supported_color_modes(self): + return self._color_modes @property def is_on(self): - return parse_power_from_coordinator(self.coordinator, self.alexa_entity_id) == "ON" + power = parse_power_from_coordinator( + self.coordinator, self.alexa_entity_id, self._requested_state_at + ) + if power is None: + return self._requested_power if self._requested_power is not None else False + else: + return power == "ON" @property def brightness(self): - bright = parse_brightness_from_coordinator(self.coordinator, self.alexa_entity_id) - return alexa_brightness_to_ha(bright) if bright is not None else 255 + bright = parse_brightness_from_coordinator( + self.coordinator, self.alexa_entity_id, self._requested_state_at + ) + if bright is None: + return self._requested_ha_brightness + else: + return alexa_brightness_to_ha(bright) + + @property + def min_mireds(self): + return 143 + + @property + def max_mireds(self): + return 454 + + @property + def color_temp(self): + kelvin = parse_color_temp_from_coordinator( + self.coordinator, self.alexa_entity_id, self._requested_state_at + ) + if kelvin is None: + return self._requested_mired + else: + return alexa_kelvin_to_mired(kelvin) + + @property + def hs_color(self): + hsb = parse_color_from_coordinator( + self.coordinator, self.alexa_entity_id, self._requested_state_at + ) + if hsb is None: + return self._requested_hs + else: + adjusted_hs, color_name = hsb_to_alexa_color(hsb) + return adjusted_hs @property def assumed_state(self) -> bool: - last_refresh_success = self.coordinator.data and self.alexa_entity_id in self.coordinator.data + last_refresh_success = ( + self.coordinator.data and self.alexa_entity_id in self.coordinator.data + ) return not last_refresh_success - @staticmethod - async def _wait_for_lights(): - await asyncio.sleep(2) + async def _set_state(self, power_on, brightness=None, mired=None, hs=None): + # This is "rounding" on mired to the closest value Alexa is willing to acknowledge the existence of. + # The alternative implementation would be to use effects instead. + # That is far more non-standard, and would lock users out of things like the Flux integration. + # The downsides to this approach is that the UI is giving the user a slider + # When the user picks a slider value, the UI will "jump" to the closest possible value. + # This trade-off doesn't feel as bad in practice as it sounds. + adjusted_mired, color_temperature_name = mired_to_alexa(mired) + if color_temperature_name is None: + # This is "rounding" on HS color to closest value Alexa supports. + # The alexa color list is short, but covers a pretty broad spectrum. + # Like for mired above, this sounds bad but works ok in practice. + adjusted_hs, color_name = hs_to_alexa_color(hs) + else: + # If a color temperature is being set, it is not possible to also adjust the color. + adjusted_hs = None + color_name = None - async def async_turn_on(self, **kwargs): - if self._supported_features & SUPPORT_BRIGHTNESS: - bright = ha_brightness_to_alexa(kwargs.get(ATTR_BRIGHTNESS, 255)) - await AlexaAPI.set_light_state(self._login, self.alexa_entity_id, power_on=True, brightness=bright) + response = await AlexaAPI.set_light_state( + self._login, + self.alexa_entity_id, + power_on, + brightness=ha_brightness_to_alexa(brightness), + color_temperature_name=color_temperature_name, + color_name=color_name, + ) + control_responses = response.get("controlResponses", []) + for response in control_responses: + if not response.get("code") == "SUCCESS": + # If something failed any state is possible, fallback to a full refresh + return await self.coordinator.async_request_refresh() + self._requested_power = power_on + self._requested_ha_brightness = ( + brightness if brightness is not None else self.brightness + ) + self._requested_mired = ( + adjusted_mired if adjusted_mired is not None else self.color_temp + ) + if adjusted_hs is not None: + self._requested_hs = adjusted_hs + elif adjusted_mired is not None: + # If a mired value was set, it is critical that color is cleared out so that color mode is set properly + self._requested_hs = None else: - await AlexaAPI.set_light_state(self._login, self.alexa_entity_id, power_on=True) - await self._wait_for_lights() - await self.coordinator.async_request_refresh() + self._requested_hs = self.hs_color + self._requested_state_at = ( + datetime.datetime.utcnow() + ) # must be set last so that previous getters work properly + self.async_write_ha_state() + + async def async_turn_on(self, **kwargs): + brightness = None + mired = None + hs = None + if COLOR_MODE_ONOFF not in self._color_modes and ATTR_BRIGHTNESS in kwargs: + brightness = kwargs[ATTR_BRIGHTNESS] + if COLOR_MODE_COLOR_TEMP in self._color_modes and ATTR_COLOR_TEMP in kwargs: + mired = kwargs[ATTR_COLOR_TEMP] + if COLOR_MODE_HS in self._color_modes and ATTR_HS_COLOR in kwargs: + hs = kwargs[ATTR_HS_COLOR] + await self._set_state(True, brightness, mired, hs) async def async_turn_off(self, **kwargs): - await AlexaAPI.set_light_state(self._login, self.alexa_entity_id, power_on=False) - await self._wait_for_lights() - await self.coordinator.async_request_refresh() + await self._set_state(False) + + +def mired_to_alexa(mired: Optional[float]) -> Tuple[Optional[float], Optional[Text]]: + """Convert a given color temperature in mired to the closest available value that Alexa has support for.""" + if mired is None: + return None, None + elif mired <= 162.5: + return 143, "cool_white" + elif mired <= 216: + return 182, "daylight_white" + elif mired <= 310: + return 250, "white" + elif mired <= 412: + return 370, "soft_white" + else: + return 454, "warm_white" + + +def alexa_kelvin_to_mired(kelvin: float) -> float: + """Convert a value in kelvin to the closest mired value that Alexa has support for.""" + raw_mired = color_temperature_kelvin_to_mired(kelvin) + return mired_to_alexa(raw_mired)[0] + + +def ha_brightness_to_alexa(ha: Optional[float]) -> Optional[float]: + return (ha / 255 * 100) if ha is not None else None + + +def alexa_brightness_to_ha(alexa: Optional[float]) -> Optional[float]: + return (alexa / 100 * 255) if alexa is not None else None + + +# This is a fairly complete list of all the colors that Alexa will respond to. +# A couple weirder ones are skipped because the HA color utility don't know the RGB value +ALEXA_COLORS = [ + "crimson", + "dark_red", + "firebrick", + "orange_red", + "red", + "deep_pink", + "hot_pink", + "light_pink", + "maroon", + "medium_violet_red", + "pale_violet_red", + "pink", + "plum", + "tomato", + "chocolate", + "dark_orange", + "maroon", + "coral", + "light_coral", + "light_salmon", + "peru", + "salmon", + "sienna", + "gold", + "goldenrod", + "lime", + "olive", + "yellow", + "chartreuse", + "dark_green", + "dark_olive_green", + "dark_sea_green", + "forest_green", + "green", + "green_yellow", + "lawn_green", + "light_green", + "lime_green", + "medium_sea_green", + "medium_spring_green", + "olive_drab", + "pale_green", + "sea_green", + "spring_green", + "yellow_green", + "blue", + "cadet_blue", + "cyan", + "dark_blue", + "dark_cyan", + "dark_slate_blue", + "dark_turquoise", + "deep_sky_blue", + "dodger_blue", + "light_blue", + "light_sea_green", + "light_sky_blue", + "medium_blue", + "medium_turquoise", + "midnight_blue", + "navy_blue", + "pale_turquoise", + "powder_blue", + "royal_blue", + "sky_blue", + "slate_blue", + "steel_blue", + "teal", + "turquoise", + "blue_violet", + "dark_magenta", + "dark_orchid", + "dark_violet", + "fuchsia", + "indigo", + "lavender", + "magenta", + "medium_orchid", + "medium_purple", + "orchid", + "purple", + "rosy_brown", + "violet", + "alice_blue", + "antique_white", + "blanched_almond", + "cornsilk", + "dark_khaki", + "floral_white", + "gainsboro", + "ghost_white", + "honeydew", + "ivory", + "khaki", + "lavender_blush", + "lemon_chiffon", + "light_cyan", + "light_steel_blue", + "light_yellow", + "linen", + "mint_cream", + "misty_rose", + "moccasin", + "old_lace", + "pale_goldenrod", + "papaya_whip", + "peach_puff", + "seashell", + "silver", + "snow", + "tan", + "thistle", + "wheat", + "white", + "white_smoke", +] + + +def red_mean(color1: Tuple[int, int, int], color2: Tuple[int, int, int]) -> float: + """Get an approximate 'distance' between two colors using red mean. + Wikipedia says this method is "one of the better low-cost approximations". + """ + r_avg = (color2[0] + color1[0]) / 2 + r_delta = color2[0] - color1[0] + g_delta = color2[1] - color1[1] + b_delta = color2[2] - color1[2] + r_term = (2 + r_avg / 256) * pow(r_delta, 2) + g_term = 4 * pow(g_delta, 2) + b_term = (2 + (255 - r_avg) / 256) * pow(b_delta, 2) + return sqrt(r_term + g_term + b_term) + + +def alexa_color_name_to_rgb(color_name: Text) -> Tuple[int, int, int]: + """Convert an alexa color name into RGB""" + return color_name_to_rgb(color_name.replace("_", "")) + + +def rgb_to_alexa_color( + rgb: Tuple[int, int, int] +) -> Tuple[Optional[Tuple[float, float]], Optional[Text]]: + """Convert a given RGB value into the closest Alexa color.""" + name = min( + ALEXA_COLORS, + key=lambda color_name: red_mean(rgb, alexa_color_name_to_rgb(color_name)), + ) + red, green, blue = alexa_color_name_to_rgb(name) + return color_RGB_to_hs(red, green, blue), name + + +def hs_to_alexa_color( + hs: Optional[Tuple[float, float]] +) -> Tuple[Optional[Tuple[float, float]], Optional[Text]]: + """Convert a given hue/saturation value into the closest Alexa color.""" + if hs is None: + return None, None + hue, saturation = hs + return rgb_to_alexa_color(color_hs_to_RGB(hue, saturation)) + + +def hsb_to_alexa_color( + hsb: Optional[Tuple[float, float, float]] +) -> Tuple[Optional[Tuple[float, float]], Optional[Text]]: + """Convert a given hue/saturation/brightness value into the closest Alexa color.""" + if hsb is None: + return None, None + hue, saturation, brightness = hsb + return rgb_to_alexa_color(color_hsb_to_RGB(hue, saturation, brightness)) diff --git a/custom_components/alexa_media/manifest.json b/custom_components/alexa_media/manifest.json index 6e61c93..c52a390 100644 --- a/custom_components/alexa_media/manifest.json +++ b/custom_components/alexa_media/manifest.json @@ -1,11 +1,12 @@ { "domain": "alexa_media", "name": "Alexa Media Player", - "version": "3.9.0", + "version": "3.10.2", "config_flow": true, "documentation": "https://github.com/custom-components/alexa_media_player/wiki", "issue_tracker": "https://github.com/custom-components/alexa_media_player/issues", "dependencies": ["persistent_notification", "http"], "codeowners": ["@keatontaylor", "@alandtse"], - "requirements": ["alexapy==1.25.0", "packaging~=20.3", "wrapt~=1.12.1"] + "requirements": ["alexapy==1.25.1", "packaging~=20.3", "wrapt~=1.12.1"], + "iot_class": "cloud_polling" } diff --git a/custom_components/alexa_media/media_player.py b/custom_components/alexa_media/media_player.py index 94746cb..3b2e78a 100644 --- a/custom_components/alexa_media/media_player.py +++ b/custom_components/alexa_media/media_player.py @@ -1261,7 +1261,9 @@ async def async_send_tts(self, message, **kwargs): ) ) else: - await self.alexa_api.send_tts(message, customer_id=self._customer_id, **kwargs) + await self.alexa_api.send_tts( + message, customer_id=self._customer_id, **kwargs + ) @_catch_login_errors async def async_send_announcement(self, message, **kwargs): diff --git a/custom_components/alexa_media/sensor.py b/custom_components/alexa_media/sensor.py index c12755a..05d3bb6 100644 --- a/custom_components/alexa_media/sensor.py +++ b/custom_components/alexa_media/sensor.py @@ -40,7 +40,7 @@ RECURRING_PATTERN, RECURRING_PATTERN_ISO_SET, ) -from .helpers import add_devices, retry_async +from .helpers import add_devices, alarm_just_dismissed _LOGGER = logging.getLogger(__name__) @@ -53,7 +53,7 @@ async def async_setup_platform(hass, config, add_devices_callback, discovery_inf SENSOR_TYPES = { "Alarm": AlarmSensor, "Timer": TimerSensor, - "Reminder": ReminderSensor + "Reminder": ReminderSensor, } account = config[CONF_EMAIL] if config else discovery_info["config"][CONF_EMAIL] include_filter = config.get(CONF_INCLUDE_DEVICES, []) @@ -116,8 +116,12 @@ async def async_setup_platform(hass, config, add_devices_callback, discovery_inf temperature_sensors = [] temperature_entities = account_dict.get("devices", {}).get("temperature", []) - if temperature_entities and account_dict["options"].get(CONF_EXTENDED_ENTITY_DISCOVERY): - temperature_sensors = await create_temperature_sensors(account_dict, temperature_entities) + if temperature_entities and account_dict["options"].get( + CONF_EXTENDED_ENTITY_DISCOVERY + ): + temperature_sensors = await create_temperature_sensors( + account_dict, temperature_entities + ) return await add_devices( hide_email(account), @@ -127,6 +131,7 @@ async def async_setup_platform(hass, config, add_devices_callback, discovery_inf exclude_filter, ) + async def async_setup_entry(hass, config_entry, async_add_devices): """Set up the Alexa sensor platform by config_entry.""" return await async_setup_platform( @@ -150,7 +155,11 @@ async def create_temperature_sensors(account_dict, temperature_entities): devices = [] coordinator = account_dict["coordinator"] for temp in temperature_entities: - _LOGGER.debug("Creating entity %s for a temperature sensor with name %s", temp["id"], temp["name"]) + _LOGGER.debug( + "Creating entity %s for a temperature sensor with name %s", + temp["id"], + temp["name"], + ) serial = temp["device_serial"] device_info = lookup_device_info(account_dict, serial) sensor = TemperatureSensor(coordinator, temp["id"], temp["name"], device_info) @@ -174,7 +183,7 @@ def lookup_device_info(account_dict, device_serial): class TemperatureSensor(CoordinatorEntity): - """A temperature sensor reported by an Echo. """ + """A temperature sensor reported by an Echo.""" def __init__(self, coordinator, entity_id, name, media_player_device_id): super().__init__(coordinator) @@ -202,7 +211,9 @@ def unit_of_measurement(self): @property def state(self): - return parse_temperature_from_coordinator(self.coordinator, self.alexa_entity_id) + return parse_temperature_from_coordinator( + self.coordinator, self.alexa_entity_id + ) @property def unique_id(self): @@ -241,6 +252,10 @@ def __init__( self._timestamp: Optional[datetime.datetime] = None self._tracker: Optional[Callable] = None self._state: Optional[datetime.datetime] = None + self._dismissed: Optional[datetime.datetime] = None + self._status: Optional[Text] = None + self._amz_id: Optional[Text] = None + self._version: Optional[Text] = None def _process_raw_notifications(self): self._all = ( @@ -252,12 +267,22 @@ def _process_raw_notifications(self): self._all = sorted(self._all, key=lambda x: x[1][self._sensor_property]) self._prior_value = self._next if self._active else None self._active = ( - list(filter(lambda x: x[1]["status"] == "ON", self._all)) + list(filter(lambda x: x[1]["status"] in ("ON", "SNOOZED"), self._all)) if self._all else [] ) self._next = self._active[0][1] if self._active else None + alarm = next( + (alarm[1] for alarm in self._all if alarm[1].get("id") == self._amz_id), + None, + ) + if alarm_just_dismissed(alarm, self._status, self._version): + self._dismissed = dt.now().isoformat() self._state = self._process_state(self._next) + self._status = self._next.get("status", "OFF") if self._next else "OFF" + self._version = self._next.get("version", "0") if self._next else None + self._amz_id = self._next.get("id") if self._next else None + if self._state == STATE_UNAVAILABLE or self._next != self._prior_value: # cancel any event triggers if self._tracker: @@ -266,7 +291,7 @@ def _process_raw_notifications(self): self, ) self._tracker() - if self._state != STATE_UNAVAILABLE: + if self._state != STATE_UNAVAILABLE and self._status != "SNOOZED": _LOGGER.debug( "%s: Scheduling event in %s", self, @@ -522,6 +547,8 @@ def device_state_attributes(self): "total_all": len(self._all), "sorted_active": json.dumps(self._active, default=str), "sorted_all": json.dumps(self._all, default=str), + "status": self._status, + "dismissed": self._dismissed, } return attr diff --git a/custom_components/alexa_media/translations/de.json b/custom_components/alexa_media/translations/de.json index 40b7b83..1947cbe 100644 --- a/custom_components/alexa_media/translations/de.json +++ b/custom_components/alexa_media/translations/de.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Verbindungsfehler; Netzwerk prüfen und erneut versuchen", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Diese Email ist bereits registriert", "invalid_credentials": "Falsche Zugangsdaten", "unknown_error": "Unbekannter Fehler, bitte Log-Info melden" @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Schließen Sie Geräte ein, die über Echo verbunden sind", "queue_delay": "Sekunden zu warten, um Befehle in die Warteschlange zu stellen" } } diff --git a/custom_components/alexa_media/translations/en.json b/custom_components/alexa_media/translations/en.json index 44b132e..cc25c70 100644 --- a/custom_components/alexa_media/translations/en.json +++ b/custom_components/alexa_media/translations/en.json @@ -8,9 +8,9 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Error connecting; check network and retry", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Email for Alexa URL already registered", "invalid_credentials": "Invalid credentials", - "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "unknown_error": "Unknown error, please enable advanced debugging and report log info" }, "step": { @@ -82,7 +82,7 @@ }, "user": { "data": { - "cookies_txt": "", + "cookies_txt": "Cookies.txt data", "debug": "Advanced debugging", "email": "Email Address", "exclude_devices": "Excluded device (comma separated)", @@ -93,7 +93,7 @@ "password": "Password", "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Seconds between scans", - "securitycode": "", + "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, "description": "Please confirm the information below. For legacy configuration, disable `Use Login Proxy method` option.", @@ -105,8 +105,8 @@ "step": { "init": { "data": { - "queue_delay": "Seconds to wait to queue commands together", - "extended_entity_discovery": "Include devices connected via Echo" + "extended_entity_discovery": "Include devices connected via Echo", + "queue_delay": "Seconds to wait to queue commands together" } } } diff --git a/custom_components/alexa_media/translations/es.json b/custom_components/alexa_media/translations/es.json index 866fd1a..932a64a 100644 --- a/custom_components/alexa_media/translations/es.json +++ b/custom_components/alexa_media/translations/es.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Error al conectar, verifique la red y vuelva a intentarlo", + "hass_url_invalid": "No se puede conectar a la url de Home Assistant. Compruebe la dirección URL interna en Configuración -> General", "identifier_exists": "Correo electrónico para la URL de Alexa ya registrado", "invalid_credentials": "Credenciales no válidas", "unknown_error": "Error desconocido, por favor revisa los registros en Home Assistant y reporta el error si es necesario." @@ -95,7 +96,7 @@ "securitycode": "Código 2FA (recomendado para evitar problemas de inicio de sesión)", "url": "Región del dominio de Amazon (por ejemplo, amazon.es)" }, - "description": "Por favor introduce tu [información](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **El método más rápido es [Importar cookies](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import).** \n**ADVERTENCIA: Amazon informará 'Introduce un correo electrónico o número de teléfono válido' si tu cuenta utiliza [códigos 2FA - Segundo Factor de Autenticación](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", + "description": "Confirme la siguiente información. Para la configuración heredada, desactive la opción `Usar método de proxy de inicio de sesión`.", "title": "Alexa Media Player - Configuración" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Incluir dispositivos conectados a través de Echo", "queue_delay": "Segundos a esperar para agrupar comandos" } } diff --git a/custom_components/alexa_media/translations/fr.json b/custom_components/alexa_media/translations/fr.json index 91abbb4..75932fe 100644 --- a/custom_components/alexa_media/translations/fr.json +++ b/custom_components/alexa_media/translations/fr.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Clé 2FA intégrée non valide", "connection_error": "Erreur de connexion; vérifier le réseau et réessayer", + "hass_url_invalid": "Impossible de se connecter à l'URL de Home Assistant. Veuillez vérifier l'URL interne sous Configuration - > Général", "identifier_exists": "Email pour l'URL Alexa déjà enregistré", "invalid_credentials": "Informations d'identification invalides", "unknown_error": "Erreur inconnue, veuillez signaler les informations du journal" @@ -15,7 +16,7 @@ "step": { "action_required": { "data": { - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)" }, "description": "** {email} - alexa. {url} ** \n Amazon enverra une notification push conformément au message ci-dessous. Veuillez répondre complètement avant de continuer. \n {message}", "title": "Alexa Media Player - Action requise" @@ -32,7 +33,7 @@ "data": { "captcha": "Captcha", "password": "Mot de passe", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)", "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)" }, "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", @@ -55,7 +56,7 @@ }, "twofactor": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Utilisez la méthode du proxy de connexion (2FA non requis)", "securitycode": "2FA Code" }, "description": "**{email} - alexa.{url}** \nEntrez le mot de passe unique (OTP). \n{message}", @@ -68,7 +69,7 @@ "email": "Adresse Email", "exclude_devices": "Appareil exclu (séparé par des virgules)", "include_devices": "Appareil inclus (séparé par des virgules)", - "oauth_login": "Enable oauth-token app method", + "oauth_login": "Activer la méthode d'application oauth-token", "otp_secret": "Clé d'application 2FA intégrée (génère automatiquement des codes 2FA)", "password": "Mot de passe", "proxy": "Use Login Proxy method (2FA not required)", @@ -95,7 +96,7 @@ "securitycode": "Code 2FA (recommandé pour éviter les problèmes de connexion)", "url": "Domaine de la région Amazon (exemple, amazon.fr)" }, - "description": "Veuillez saisir vos informations.", + "description": "Veuillez confirmer les informations ci-dessous. Pour la configuration héritée, désactivez l'option `Utiliser la méthode proxy de connexion`.", "title": "Alexa Media Player - Configuration" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Inclure les appareils connectés via Echo", "queue_delay": "Secondes à attendre pour mettre les commandes en file d'attente ensemble" } } diff --git a/custom_components/alexa_media/translations/it.json b/custom_components/alexa_media/translations/it.json index 237cd0d..de45a5c 100644 --- a/custom_components/alexa_media/translations/it.json +++ b/custom_components/alexa_media/translations/it.json @@ -8,14 +8,15 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Errore durante la connessione; controlla la rete e riprova", + "hass_url_invalid": "Impossibile collegarsi ad Home Assistant. Controllare l'URL interno nel menu Configurazione -> Generale", "identifier_exists": "L'Email per l'URL di Alexa è già stata registrata", "invalid_credentials": "Credenziali non valide", - "unknown_error": "Errore sconosciuto, si prega di riportrtare i log di informazione" + "unknown_error": "Errore sconosciuto, si prega di abilitare il debug avanzato e riportare i log informativi" }, "step": { "action_required": { "data": { - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" }, "description": "** {email} - alexa. {url} ** \n Amazon invierà una notifica push per il seguente messaggio. Si prega di rispondere completamente prima di continuare. \n {message}", "title": "Alexa Media Player - Azione Richiesta" @@ -23,7 +24,7 @@ "authselect": { "data": { "authselectoption": "Metodo password usa e getta (OTP)", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" }, "description": "**{email} - alexa.{url}** \n{message}", "title": "Alexa Media Player - Password Usa e Getta (One Time Password)" @@ -32,7 +33,7 @@ "data": { "captcha": "Captcha", "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", "securitycode": "config::step::captcha::data::securitycode" }, "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", @@ -41,7 +42,7 @@ "claimspicker": { "data": { "authselectoption": "Metodi di Verifica", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)" }, "description": "**{email} - alexa.{url}** \nPrego selezionare un metodo di verifica. (e.g., `0` or `1`) \n{message}", "title": "Alexa Media Player - Metodi di Verifica" @@ -55,7 +56,7 @@ }, "twofactor": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Usa metodo Proxy per il login (2FA non richiesto)", "securitycode": "Codice autenticazione a 2 fattori (2FA)" }, "description": "**{email} - alexa.{url}** \nInserisci la password usa e getta (OTP). \n{message}", @@ -68,20 +69,20 @@ "email": "Indirizzo email", "exclude_devices": "Dispositivi da escludere (separati da virgola)", "include_devices": "Dispositivi da includere (separati da virgola)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", + "oauth_login": "Abilitare il metodo oauth-token app", + "otp_secret": "Chiave 2FA interna (genera automaticamente i codici 2FA)", "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Usa il Proxy come metodo di login (2FA non richiesta)", "scan_interval": "Tempo in secondi fra le scansioni", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Regione del dominio Amazon (e.g., amazon.it)" + "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)", + "url": "Regione del dominio Amazon (e.g. amazon.it)" }, "description": "Prego inserisci le tue [informazioni](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) potrebbe essere più semplice!** \n**ATTENZIONE: Amazon risponde incorrettamente 'Inserire una mail valida o un numero di telefono' quando è richiesto il codice 2FA](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", "title": "Alexa Media Player - Legacy Configuration" }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", + "cookies_txt": "Dati cookies.txt", "debug": "Debug avanzato", "email": "Indirizzo email", "exclude_devices": "Dispositivi da escludere (separati da virgola)", @@ -92,10 +93,10 @@ "password": "Password", "proxy": "Use Login Proxy method (2FA not required)", "scan_interval": "Tempo in secondi fra le scansioni", - "securitycode": "2FA Code (recommended to avoid login issues)", + "securitycode": "Codice 2FA (raccomandato per evitare problemi di login)", "url": "Regione del dominio Amazon (e.g., amazon.it)" }, - "description": "Prego inserisci le tue [informazioni](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) potrebbe essere più semplice!** \n**ATTENZIONE: Amazon risponde incorrettamente 'Inserire una mail valida o un numero di telefono' quando è richiesto il codice 2FA](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", + "description": "Confermare le informazioni sottostanti. Per le vecchie configurazioni, disabilitare l'opzione `Usa Proxy come metodo di login`.", "title": "Alexa Media Player - Configurazione" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Includi i dispositivi collegati tramite Echo", "queue_delay": "Secondi di attesa per accodare i comandi insieme" } } diff --git a/custom_components/alexa_media/translations/nb.json b/custom_components/alexa_media/translations/nb.json index 55b6cc0..0c36c1d 100644 --- a/custom_components/alexa_media/translations/nb.json +++ b/custom_components/alexa_media/translations/nb.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Feil ved tilkobling; sjekk nettverket og prøv på nytt", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "E-post for Alexa URL allerede registrert", "invalid_credentials": "ugyldige legitimasjon", "unknown_error": "Ukjent feil, vennligst rapporter logginfo" @@ -95,7 +96,7 @@ "securitycode": "2FA-kode (anbefales for å unngå påloggingsproblemer)", "url": "Amazon-regiondomenet (f.eks. Amazon.co.uk)" }, - "description": "Vennligst skriv inn informasjonen din. \n**WARNING: Amazon rapporterer feilaktig 'Angi en gyldig e-postadresse eller et mobilnummer' når 2FA-kode kreves.** \n>{message}", + "description": "Bekreft informasjonen nedenfor. For eldre konfigurasjon, deaktiver alternativet \"Bruk innlogging proxy-metode\".", "title": "Alexa Media Player - Konfigurasjon" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Inkluder enheter som er koblet til via Echo", "queue_delay": "Sekunder for å vente på køkommandoer sammen" } } diff --git a/custom_components/alexa_media/translations/nl.json b/custom_components/alexa_media/translations/nl.json index b10cbaf..902cda4 100644 --- a/custom_components/alexa_media/translations/nl.json +++ b/custom_components/alexa_media/translations/nl.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Fout bij verbinden; controleer netwerk en probeer opnieuw", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Dit e-mailadres is reeds geregistreerd", "invalid_credentials": "Ongeldige inloggegevens", "unknown_error": "Onbekende fout, meld de loggegevens" @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Inclusief apparaten die zijn verbonden via Echo", "queue_delay": "Seconden om te wachten om opdrachten in de wachtrij te plaatsen" } } diff --git a/custom_components/alexa_media/translations/pl.json b/custom_components/alexa_media/translations/pl.json index 622cc24..1d8c2d9 100644 --- a/custom_components/alexa_media/translations/pl.json +++ b/custom_components/alexa_media/translations/pl.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Nieprawidłowy klucz z wbudowanej aplikacji uwierzytelniania dwuskładnikowego", "connection_error": "Błąd podczas łączenia; sprawdź sieć i spróbuj ponownie", + "hass_url_invalid": "Nie można połączyć się z adresem URL Home Assistanta. Sprawdź wewnętrzny adres URL w sekcji Konfiguracja -> Ogólne", "identifier_exists": "Adres e-mail dla Alexy już jest zarejestrowany", "invalid_credentials": "Nieprawidłowe dane logowania", "unknown_error": "Nieznany błąd, zgłoś log z tego zdarzenia" @@ -15,7 +16,7 @@ "step": { "action_required": { "data": { - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" }, "description": "**{email} - alexa.{url}** \nAmazon wyśle powiadomienie push zgodnie z poniższą wiadomością. Przed kontynuowaniem odpowiedz na wiadomość. \n{message}", "title": "Alexa Media Player — wymagane działanie" @@ -23,7 +24,7 @@ "authselect": { "data": { "authselectoption": "Metoda OTP", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" }, "description": "**{email} - alexa.{url}** \n{message}", "title": "Alexa Media Player — hasło jednorazowe" @@ -32,7 +33,7 @@ "data": { "captcha": "Kod Captcha", "password": "Hasło", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "securitycode": "Kod uwierzytelniania dwuskładnikowego" }, "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", @@ -41,7 +42,7 @@ "claimspicker": { "data": { "authselectoption": "Metoda weryfikacji", - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)" }, "description": "**{email} - alexa.{url}** \nWybierz metodę weryfikacji. (np., `0` lub `1`) \n{message}", "title": "Alexa Media Player — metoda weryfikacji" @@ -55,7 +56,7 @@ }, "twofactor": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "securitycode": "Kod uwierzytelniania dwuskładnikowego" }, "description": "**{email} - alexa.{url}** \nWprowadź hasło jednorazowe (OTP). \n{message}", @@ -65,19 +66,19 @@ "data": { "cookies_txt": "zawartość pliku cookies.txt", "debug": "Zaawansowane debugowanie", - "email": "Adres email", + "email": "Adres e-mail", "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", "include_devices": "Dodawane urządzenia (oddzielone przecinkami)", - "oauth_login": "Enable oauth-token app method", + "oauth_login": "Włącz metodę tokena OAuth aplikacji", "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)", "password": "Hasło", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "scan_interval": "Interwał skanowania (sekundy)", "securitycode": "Kod uwierzytelniania dwuskładnikowego", "url": "Region/domena Amazon (np. amazon.co.uk)" }, "description": "Wprowadź [dane](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Import pliku Cookie](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) może być najłatwiejszą metodą!** \n**OSTRZEŻENIE: Amazon nieprawidłowo zgłasza 'Wprowadź prawidłowy adres e-mail lub numer telefonu komórkowego', gdy wymagany jest [kod uwierzytelniania dwuskładnikowego](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" + "title": "Alexa Media Player — starsza konfiguracja" }, "user": { "data": { @@ -85,17 +86,17 @@ "debug": "Zaawansowane debugowanie", "email": "Adres email", "exclude_devices": "Wykluczone urządzenia (oddzielone przecinkami)", - "hass_url": "Url to access Home Assistant", + "hass_url": "URL dostępu do Home Assistanta", "include_devices": "Dodawane urządzenia (oddzielone przecinkami)", - "oauth_login": "Enable oauth-token app method", + "oauth_login": "Włącz metodę tokena OAuth aplikacji", "otp_secret": "Wbudowana aplikacja kluczy uwierzytelniania dwuskładnikowego (automatycznie generuje kody uwierzytelniania dwuskładnikowego)", "password": "Hasło", - "proxy": "Use Login Proxy method (2FA not required)", + "proxy": "Użyj metody logowania proxy (2FA nie jest wymagane)", "scan_interval": "Interwał skanowania (sekundy)", "securitycode": "Kod uwierzytelniania dwuskładnikowego (zalecany w celu uniknięcia problemów z logowaniem)", "url": "Region/domena Amazon (np. amazon.co.uk)" }, - "description": "Wprowadź [dane](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Import pliku Cookie](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) może być najłatwiejszą metodą!** \n**OSTRZEŻENIE: Amazon nieprawidłowo zgłasza 'Wprowadź prawidłowy adres e-mail lub numer telefonu komórkowego', gdy wymagany jest [kod uwierzytelniania dwuskładnikowego](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", + "description": "Potwierdź poniższe informacje. W przypadku starszych konfiguracji wyłącz opcję 'Użyj metody logowania proxy'.", "title": "Alexa Media Player — konfiguracja" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Uwzględnij urządzenia podłączone przez Echo", "queue_delay": "Sekundy oczekiwania na kolejkowanie poleceń" } } diff --git a/custom_components/alexa_media/translations/pt_BR.json b/custom_components/alexa_media/translations/pt_BR.json index 7790d6f..750d1d1 100644 --- a/custom_components/alexa_media/translations/pt_BR.json +++ b/custom_components/alexa_media/translations/pt_BR.json @@ -1,103 +1,103 @@ { "config": { "abort": { - "forgot_password": "The Forgot Password page was detected. This normally is the result of too may failed logins. Amazon may require action before a relogin can be attempted.", - "login_failed": "Alexa Media Player failed to login.", - "reauth_successful": "Alexa Media Player successfully reauthenticated." + "forgot_password": "A página para senha esquecida foi detectada. Isto normalmente é o resultado de muitas tentativas de login falhas. A amazon pode requisitar ação antes de um novo login ser tentando.", + "login_failed": "Alexa Media Player falhou no login.", + "reauth_successful": "Alexa Media Player re-autenticado com sucesso." }, "error": { - "2fa_key_invalid": "Invalid Built-In 2FA key", - "connection_error": "Error connecting; check network and retry", - "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", - "identifier_exists": "Email for Alexa URL already registered", - "invalid_credentials": "Invalid credentials", - "unknown_error": "Unknown error, please enable advanced debugging and report log info" + "2fa_key_invalid": "Chave integrada 2FA inválida", + "connection_error": "Erro de conexão; Verifique a sua conexão e tente novamente", + "hass_url_invalid": "Não foi possível conectar a URL do Home Assistant. Por favor verifique a URL interna em Configuração -> Geral", + "identifier_exists": "Email para URL Alexa já registrado", + "invalid_credentials": "Credenciais inválidas", + "unknown_error": "Erro desconhecido, favor habilitar a depuração avançada e reporte as informações de registro" }, "step": { "action_required": { "data": { - "proxy": "Use Login Proxy method (2FA not required)" + "proxy": "Usar método Login Proxy (Não requer 2FA)" }, - "description": "**{email} - alexa.{url}** \nAmazon will send a push notification per the below message. Please completely respond before continuing. \n{message}", - "title": "Alexa Media Player - Action Required" + "description": "**{email} - alexa.{url}** \nA amazon enviará uma notificação push para a mensagem abaixo. Por favor responda ela completamente antes de continuar. \n{message}", + "title": "Alexa Media Player - Ação Requisitada" }, "authselect": { "data": { - "authselectoption": "OTP method", - "proxy": "Use Login Proxy method (2FA not required)" + "authselectoption": "Método OTP", + "proxy": "Usar método Login Proxy (Não requer 2FA)" }, - "description": "**{email} - alexa.{url}** \n{message}", - "title": "Alexa Media Player - One Time Password" + "description": "**{email} - alexa.{url}**\n{message}", + "title": "Alexa Media Player - Senha de uso único" }, "captcha": { "data": { "captcha": "Captcha", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code (recommended to avoid login issues)" + "password": "Senha", + "proxy": "Usar método Login Proxy (Não requer 2FA)", + "securitycode": "Código 2FA (recomendado para evitar problemas de login)" }, "description": "**{email} - alexa.{url}** \n{message} \n {captcha_image}", "title": "Alexa Media Player - Captcha" }, "claimspicker": { "data": { - "authselectoption": "Verification method", - "proxy": "Use Login Proxy method (2FA not required)" + "authselectoption": "Método de verificação", + "proxy": "Usar método Login Proxy (Não requer 2FA)" }, - "description": "**{email} - alexa.{url}** \nPlease select verification method by number. (e.g., `0` or `1`) \n{message}", - "title": "Alexa Media Player - Verification Method" + "description": "**{email} - alexa.{url}** \nPor favor selecione um método de verificação por número. (ex: `0` ou `1`) \n{message}", + "title": "Alexa Media Player - Método de verificação" }, "totp_register": { "data": { - "registered": "OTP from the Built-in 2FA App Key confirmed successfully." + "registered": "OTP do aplicativo integrado de chave 2FA confirmado com sucesso." }, - "description": "**{email} - alexa.{url}** \nHave you successfully confirmed an OTP from the Built-in 2FA App Key with Amazon? \n >OTP Code {message}", - "title": "Alexa Media Player - OTP Confirmation" + "description": "**{email} - alexa.{url}** \nVocê confirmou com sucesso um OTP no aplicativo integrado de chave 2FA com a amazon? \n >OTP Code {message}", + "title": "Alexa Media Player - Confirmação OTP" }, "twofactor": { "data": { - "proxy": "Use Login Proxy method (2FA not required)", - "securitycode": "2FA Code" + "proxy": "Usar método Login Proxy (Não requer 2FA)", + "securitycode": "Código 2FA" }, - "description": "**{email} - alexa.{url}** \nEnter the One Time Password (OTP). \n{message}", - "title": "Alexa Media Player - Two Factor Authentication" + "description": "**{email} - alexa.{url}** \nInsira a senha de uso único (OTP).\n{message}", + "title": "Alexa Media Player - Autenticação de dois fatores" }, "user_legacy": { "data": { "cookies_txt": "Cookies.txt data", - "debug": "Advanced debugging", - "email": "Email Address", - "exclude_devices": "Excluded device (comma separated)", - "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Seconds between scans", - "securitycode": "2FA Code (recommended to avoid login issues)", - "url": "Amazon region domain (e.g., amazon.co.uk)" + "debug": "Depuração avançada", + "email": "Endereço de email", + "exclude_devices": "Dispositivos exclusos (separados por virgula)", + "include_devices": "Dispositivos inclusos (separados por vírgula)", + "oauth_login": "Habilitar o aplicativo para método auth-token", + "otp_secret": "Aplicativo integrado para chaves 2FA (Automaticamente gera códigos 2FA)", + "password": "Senha", + "proxy": "Usar método Login Proxy (Não requer 2FA)", + "scan_interval": "Segundos entre varreduras", + "securitycode": "Código 2FA (recomendado para evitar problemas de login)", + "url": "Domínio regional da amazon (ex: amazon.co.uk)" }, - "description": "Please enter your [information](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) may be easiest!** \n**WARNING: Amazon incorrectly reports 'Enter a valid email or mobile number' when [2FA Code is required](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", - "title": "Alexa Media Player - Legacy Configuration" + "description": "Por favor insira sua [informação](https://github.com/custom-components/alexa_media_player/wiki/Configuration#integrations-page). **[Cookie import](https://github.com/custom-components/alexa_media_player/wiki/Configuration#cookie-import) pode ser mais fácil!** \n**AVISO! A amazon reporta incorretamente 'Insira um email ou número de telefone válido' quando [Código de 2FA é requirido](https://github.com/custom-components/alexa_media_player/wiki/Configuration#enable-two-factor-authentication-for-your-amazon-account).** \n>{message}", + "title": "Alexa Media Player - Configurações legado" }, "user": { "data": { "cookies_txt": "Dados de cookies.txt", - "debug": "Advanced debugging", - "email": "Email Address", - "exclude_devices": "Excluded device (comma separated)", - "hass_url": "Url to access Home Assistant", - "include_devices": "Included device (comma separated)", - "oauth_login": "Enable oauth-token app method", - "otp_secret": "Built-in 2FA App Key (automatically generate 2FA Codes)", - "password": "Password", - "proxy": "Use Login Proxy method (2FA not required)", - "scan_interval": "Seconds between scans", + "debug": "Depuração avançada", + "email": "Endereço de email", + "exclude_devices": "Dispositivos exclusos (separados por virgula)", + "hass_url": "Url para acesso ao Home Assistant", + "include_devices": "Dispositivos inclusos (separados por vírgula)", + "oauth_login": "Habilitar o aplicativo para método auth-token", + "otp_secret": "Aplicativo integrado para chaves 2FA (Automaticamente gera códigos 2FA)", + "password": "Senha", + "proxy": "Usar método Login Proxy (Não requer 2FA)", + "scan_interval": "Segundos entre varreduras", "securitycode": "Código 2FA (recomendado para evitar problemas de login)", - "url": "Amazon region domain (e.g., amazon.co.uk)" + "url": "Domínio regional da amazon (ex: amazon.co.uk)" }, - "description": "Por favor, confirme as informações abaixo. Para configuração legada, desative a opção `Usar método de proxy de login`.", - "title": "Alexa Media Player - Configuration" + "description": "Por favor, confirme as informações abaixo. Para configuração legada, desative a opção `Usar método de proxy de login'.", + "title": "Alexa Media Player - Configurações" } } }, @@ -106,7 +106,7 @@ "init": { "data": { "extended_entity_discovery": "Inclui dispositivos conectados via Echo", - "queue_delay": "Seconds to wait to queue commands together" + "queue_delay": "Segundos para aguardar antes de enfileirar juntos" } } } diff --git a/custom_components/alexa_media/translations/ru.json b/custom_components/alexa_media/translations/ru.json index 40d67f9..1cba8a2 100644 --- a/custom_components/alexa_media/translations/ru.json +++ b/custom_components/alexa_media/translations/ru.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "Ошибка подключения; проверьте сеть и повторите попытку", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Электронная почта для Alexa уже зарегистрирована", "invalid_credentials": "Неверные учетные данные", "unknown_error": "Неизвестная ошибка, пожалуйста, сообщите информацию журнала" @@ -95,7 +96,7 @@ "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Домен региона Amazon (например, amazon.co.uk)" }, - "description": "Пожалуйста, введите свои данные.{message}", + "description": "Пожалуйста, подтвердите информацию ниже. Для старой конфигурации отключите опцию `Use Login Proxy method`.", "title": "Alexa Media Player - Конфигурация" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Включить устройства, подключенные через Echo", "queue_delay": "Секунды ожидания, чтобы выполнить команды вместе" } } diff --git a/custom_components/alexa_media/translations/zh-Hans.json b/custom_components/alexa_media/translations/zh-Hans.json index 6fa4a55..64aae71 100644 --- a/custom_components/alexa_media/translations/zh-Hans.json +++ b/custom_components/alexa_media/translations/zh-Hans.json @@ -8,6 +8,7 @@ "error": { "2fa_key_invalid": "Invalid Built-In 2FA key", "connection_error": "连接错误;检查网络并重试", + "hass_url_invalid": "Unable to connect to Home Assistant url. Please check the Internal Url under Configuration -> General", "identifier_exists": "Alexa URL的电子邮件已注册", "invalid_credentials": "Invalid credentials", "unknown_error": "Unknown error, please report log info" @@ -81,7 +82,7 @@ }, "user": { "data": { - "cookies_txt": "config::step::user::data::cookies_txt", + "cookies_txt": "Cookie.txt数据", "debug": "高级调试", "email": "电子邮件地址", "exclude_devices": "Excluded device (comma separated)", @@ -95,7 +96,7 @@ "securitycode": "2FA Code (recommended to avoid login issues)", "url": "Amazon region domain (e.g., amazon.co.uk)" }, - "description": "请输入您的信息。", + "description": "请确认以下信息。对于旧版配置,请禁用“使用登录代理方法”选项。", "title": "Alexa Media Player-配置" } } @@ -104,6 +105,7 @@ "step": { "init": { "data": { + "extended_entity_discovery": "Include devices connected via Echo", "queue_delay": "Seconds to wait to queue commands together" } } diff --git a/custom_components/weenect/__init__.py b/custom_components/weenect/__init__.py index a2c8b46..2277980 100644 --- a/custom_components/weenect/__init__.py +++ b/custom_components/weenect/__init__.py @@ -16,6 +16,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from .util import parse_duration from .const import ( CONF_PASSWORD, @@ -69,21 +70,16 @@ def __init__( self, hass: HomeAssistant, config_entry: ConfigEntry, client: AioWeenect ) -> None: """Initialize.""" - if CONF_UPDATE_RATE in config_entry.options: - update_interval = timedelta(seconds=config_entry.options[CONF_UPDATE_RATE]) - else: - update_interval = timedelta(seconds=DEFAULT_UPDATE_RATE) super().__init__( hass, _LOGGER, name=DOMAIN, - update_interval=update_interval, + update_interval=timedelta(seconds=DEFAULT_UPDATE_RATE), ) self.client = client self.config_entry = config_entry self.unsub_dispatchers = [] self.data = {} - self.add_options() async def _async_update_data(self): """Update data via library.""" @@ -91,6 +87,7 @@ async def _async_update_data(self): data = await self.client.get_trackers() data = self.transform_data(data) self._detect_added_and_removed_trackers(data) + self._adjust_update_rate(data) return data except Exception as exception: raise UpdateFailed(exception) from exception @@ -102,6 +99,16 @@ def _detect_added_and_removed_trackers(self, data: Any): self.hass, f"{self.config_entry.entry_id}_{TRACKER_ADDED}", added ) + def _adjust_update_rate(self, data: Any): + """Set the update rate to the shortest update rate of all trackers.""" + update_rate = timedelta(seconds=DEFAULT_UPDATE_RATE) + for tracker in data.values(): + tracker_rate = parse_duration(tracker["last_freq_mode"]) + if tracker_rate and tracker_rate < update_rate: + update_rate = tracker_rate + self.update_interval = update_rate + _LOGGER.debug("Setting update_interval to %s", update_rate) + @staticmethod def transform_data(data: Any): """Extract trackers from list and put them in a dict by tracker id.""" @@ -110,23 +117,6 @@ def transform_data(data: Any): result[tracker["id"]] = tracker return result - def add_options(self) -> None: - """Add options for weenect integration.""" - if not self.config_entry.options: - options = { - CONF_UPDATE_RATE: DEFAULT_UPDATE_RATE, - } - self.hass.config_entries.async_update_entry( - self.config_entry, options=options - ) - else: - options = dict(self.config_entry.options) - if CONF_UPDATE_RATE not in self.config_entry.options: - options[CONF_UPDATE_RATE] = DEFAULT_UPDATE_RATE - self.hass.config_entries.async_update_entry( - self.config_entry, options=options - ) - async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Handle removal of an entry.""" diff --git a/custom_components/weenect/config_flow.py b/custom_components/weenect/config_flow.py index f14e17a..69272e3 100644 --- a/custom_components/weenect/config_flow.py +++ b/custom_components/weenect/config_flow.py @@ -3,15 +3,12 @@ from aioweenect import AioWeenect from homeassistant import config_entries -from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_create_clientsession import voluptuous as vol from .const import ( CONF_PASSWORD, - CONF_UPDATE_RATE, CONF_USERNAME, - DEFAULT_UPDATE_RATE, DOMAIN, ) @@ -53,11 +50,6 @@ async def async_step_user(self, user_input=None): return await self._show_config_form(user_input) - @staticmethod - @callback - def async_get_options_flow(config_entry): - return WeenectOptionsFlowHandler(config_entry) - async def _show_config_form(self, user_input): # pylint: disable=unused-argument """Show the configuration form.""" return self.async_show_form( @@ -78,42 +70,3 @@ async def _test_credentials(self, username, password) -> bool: except Exception as exception: # pylint: disable=broad-except _LOGGER.debug(exception) return False - - -class WeenectOptionsFlowHandler(config_entries.OptionsFlow): - """weenect config flow options handler.""" - - def __init__(self, config_entry): - """Initialize weenect options flow.""" - self.config_entry = config_entry - self.options = dict(config_entry.options) - - async def async_step_init(self, user_input=None): # pylint: disable=unused-argument - """Manage the options.""" - return await self.async_step_user() - - async def async_step_user(self, user_input=None): - """Handle a flow initialized by the user.""" - if user_input is not None: - self.options.update(user_input) - return await self._update_options() - - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Optional( - CONF_UPDATE_RATE, - default=self.config_entry.options.get( - CONF_UPDATE_RATE, DEFAULT_UPDATE_RATE - ), - ): int - } - ), - ) - - async def _update_options(self): - """Update config entry options.""" - return self.async_create_entry( - title=self.config_entry.data.get(CONF_USERNAME), data=self.options - ) diff --git a/custom_components/weenect/const.py b/custom_components/weenect/const.py index c7ddf4e..7ee68ad 100644 --- a/custom_components/weenect/const.py +++ b/custom_components/weenect/const.py @@ -4,13 +4,15 @@ DEVICE_CLASS_BATTERY, DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TIMESTAMP, + PERCENTAGE, + SIGNAL_STRENGTH_DECIBELS, ) # Base component constants NAME = "Weenect" DOMAIN = "weenect" DOMAIN_DATA = f"{DOMAIN}_data" -VERSION = "1.1.0" +VERSION = "1.3.0" ATTRIBUTION = "Data provided by https://my.weenect.com/" ISSUE_URL = "https://github.com/eifinger/hass-weenect/issues" @@ -22,37 +24,71 @@ # Sensors SENSOR_TYPES = [ + { + "name": "Update Rate", + "value_name": "freq_mode", + "device_class": None, + "enabled": True, + "unit_of_measurement": None, + }, + { + "name": "Last Update Rate", + "value_name": "last_freq_mode", + "device_class": None, + "enabled": True, + "unit_of_measurement": None, + }, + { + "name": "Sensor Mode", + "value_name": "sensor_mode", + "device_class": None, + "enabled": True, + "unit_of_measurement": None, + }, + { + "name": "Last Sensor Mode", + "value_name": "last_sensor_mode", + "device_class": None, + "enabled": True, + "unit_of_measurement": None, + }, +] +LOCATION_SENSOR_TYPES = [ { "name": "Battery", "value_name": "battery", "device_class": DEVICE_CLASS_BATTERY, "enabled": True, + "unit_of_measurement": PERCENTAGE, }, { "name": "Cell Tower Id", "value_name": "cellid", "device_class": None, "enabled": True, + "unit_of_measurement": None, }, { "name": "GSM Strength", "value_name": "gsm", "device_class": DEVICE_CLASS_SIGNAL_STRENGTH, "enabled": True, + "unit_of_measurement": SIGNAL_STRENGTH_DECIBELS, }, { "name": "Last Message Received", "value_name": "last_message", "device_class": DEVICE_CLASS_TIMESTAMP, "enabled": True, + "unit_of_measurement": "ISO8601", }, { "name": "GPS Satellites", "value_name": "satellites", "device_class": None, "enabled": True, + "unit_of_measurement": None, }, - {"name": "Type", "value_name": "type", "device_class": None, "enabled": False}, ] BINARY_SENSOR_TYPES = [ diff --git a/custom_components/weenect/manifest.json b/custom_components/weenect/manifest.json index 0a799ee..25f2045 100644 --- a/custom_components/weenect/manifest.json +++ b/custom_components/weenect/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://github.com/eifinger/hass-weenect", "issue_tracker": "https://github.com/eifinger/hass-weenect/issues", "dependencies": [], - "version": "1.1.0", + "version": "1.3.0", "config_flow": true, "codeowners": [ "@eifinger" diff --git a/custom_components/weenect/sensor.py b/custom_components/weenect/sensor.py index 8a77034..2b6b473 100644 --- a/custom_components/weenect/sensor.py +++ b/custom_components/weenect/sensor.py @@ -6,7 +6,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DOMAIN, SENSOR_TYPES, TRACKER_ADDED +from .const import DOMAIN, LOCATION_SENSOR_TYPES, SENSOR_TYPES, TRACKER_ADDED from .entity import WeenectEntity _LOGGER = logging.getLogger(__name__) @@ -27,6 +27,10 @@ def async_add_sensors( for tracker_id in added: for sensor_type in SENSOR_TYPES: sensors.append(WeenectSensor(coordinator, tracker_id, sensor_type)) + for sensor_type in LOCATION_SENSOR_TYPES: + sensors.append( + WeenectLocationSensor(coordinator, tracker_id, sensor_type) + ) async_add_entities(sensors, True) @@ -40,8 +44,8 @@ def async_add_sensors( async_add_sensors(coordinator.data.keys()) -class WeenectSensor(WeenectEntity): - """weenect sensor.""" +class WeenectSensorBase(WeenectEntity): + """weenect Sensor Base.""" def __init__( self, @@ -54,6 +58,7 @@ def __init__( self._value_name = sensor_type["value_name"] self._enabled = sensor_type["enabled"] self._name = sensor_type["name"] + self._unit_of_measurement = sensor_type["unit_of_measurement"] @property def name(self): @@ -66,12 +71,6 @@ def unique_id(self): """Return a unique ID to use for this entity.""" return f"{self.id}_{self._value_name}" - @property - def state(self): - """Return the state of the resources if it has been received yet.""" - if self.id in self.coordinator.data: - return self.coordinator.data[self.id]["position"][0][self._value_name] - @property def device_class(self): """Device class of this entity.""" @@ -81,3 +80,28 @@ def device_class(self): def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" return self._enabled + + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return self._unit_of_measurement + + +class WeenectSensor(WeenectSensorBase): + """weenect sensor for general informatio.""" + + @property + def state(self): + """Return the state of the resources if it has been received yet.""" + if self.id in self.coordinator.data: + return self.coordinator.data[self.id][self._value_name] + + +class WeenectLocationSensor(WeenectSensorBase): + """weenect sensor for location informatio.""" + + @property + def state(self): + """Return the state of the resources if it has been received yet.""" + if self.id in self.coordinator.data: + return self.coordinator.data[self.id]["position"][0][self._value_name] diff --git a/custom_components/weenect/services.py b/custom_components/weenect/services.py index f9be546..4335de6 100644 --- a/custom_components/weenect/services.py +++ b/custom_components/weenect/services.py @@ -100,7 +100,7 @@ async def async_unload_services(hass): async def async_set_update_interval(hass, data): """Set the update interval for this tracker id.""" - tracker_id = data[TRACKER_ID] + tracker_id = int(data[TRACKER_ID]) update_interval = data[UPDATE_INTERVAL] for config_entry in hass.data[DOMAIN]: @@ -117,7 +117,7 @@ async def async_set_update_interval(hass, data): async def async_activate_super_live(hass, data): """Activate the super live mode for this tracker id""" - tracker_id = data[TRACKER_ID] + tracker_id = int(data[TRACKER_ID]) for config_entry in hass.data[DOMAIN]: if tracker_id in hass.data[DOMAIN][config_entry].data.keys(): @@ -131,7 +131,7 @@ async def async_activate_super_live(hass, data): async def async_refresh_location(hass, data): """Request a position refresh for this tracker id""" - tracker_id = data[TRACKER_ID] + tracker_id = int(data[TRACKER_ID]) for config_entry in hass.data[DOMAIN]: if tracker_id in hass.data[DOMAIN][config_entry].data.keys(): @@ -145,7 +145,7 @@ async def async_refresh_location(hass, data): async def async_ring(hass, data): """Send a ring command for this tracker id""" - tracker_id = data[TRACKER_ID] + tracker_id = int(data[TRACKER_ID]) for config_entry in hass.data[DOMAIN]: if tracker_id in hass.data[DOMAIN][config_entry].data.keys(): @@ -159,7 +159,7 @@ async def async_ring(hass, data): async def async_vibrate(hass, data): """Send a vibrate command for this tracker id""" - tracker_id = data[TRACKER_ID] + tracker_id = int(data[TRACKER_ID]) for config_entry in hass.data[DOMAIN]: if tracker_id in hass.data[DOMAIN][config_entry].data.keys(): diff --git a/custom_components/weenect/util.py b/custom_components/weenect/util.py new file mode 100644 index 0000000..0053a1a --- /dev/null +++ b/custom_components/weenect/util.py @@ -0,0 +1,18 @@ +"""Utility methods for weenect.""" + +import re +from datetime import timedelta +from typing import Optional + + +def parse_duration(duration: str) -> Optional[timedelta]: + """Parse a timedelta from a weenect duration.""" + pattern = re.compile(r"\d\d[S,M,H]") + + if pattern.match(duration) is not None: + if duration.endswith("S"): + return timedelta(seconds=float(duration[:-1])) + if duration.endswith("M"): + return timedelta(minutes=float(duration[:-1])) + if duration.endswith("H"): + return timedelta(hours=float(duration[:-1])) diff --git a/www/community/numberbox-card/numberbox-card.js b/www/community/numberbox-card/numberbox-card.js index bf779fc..1901168 100644 --- a/www/community/numberbox-card/numberbox-card.js +++ b/www/community/numberbox-card/numberbox-card.js @@ -1,6 +1,6 @@ ((LitElement) => { -console.info('NUMBERBOX_CARD 2.6'); +console.info('NUMBERBOX_CARD 2.7'); const html = LitElement.prototype.html; const css = LitElement.prototype.css; class NumberBox extends LitElement { @@ -104,18 +104,18 @@ niceNum(){ fix = v.toFixed(fix); const u=this.config.unit; if( u=="time" ){ - return html`${this.zeroFill(Math.floor(fix/3600), 2)}:${this.zeroFill(Math.floor(fix/60), 2)}:${this.zeroFill(fix%60, 2)}` + return html`${ + Math.floor(fix/3600).toString().padStart(2,'0') + }:${ + (Math.floor(fix/60)-Math.floor(fix/3600)*60).toString().padStart(2,'0') + }:${ + (fix%60).toString().padStart(2,'0') + }` } return u===false ? fix: html`${fix}${u}`; } -zeroFill(number, width){ - width -= number.toString().length; - if ( width > 0 ){ - return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number; - } - return number + ""; -} + moreInfo(type, options = {}) { const e = new Event(type, { diff --git a/www/community/numberbox-card/numberbox-card.js.gz b/www/community/numberbox-card/numberbox-card.js.gz index edef05ebc6c6af7b517e6e6ac4a4aca28d78e99e..909e8f3e0edfacc938753820a2992c069fa84804 100644 GIT binary patch literal 3316 zcmV`YbZ+Rjewm4(ecpRolk;dE;$vU;I_rJb-_4d=N5C2}i{@bTF>__jfmNO?A z$u@Euvjy9$@UlFWsl3a$hdd(}7R50!V1ZN|fW-T9yW;t)^p^2k$&;vPjWD=mUWPKw zZ3hUWqRcpaosUOyBMNVomPyEAqID1#+)y*nB$DerxgL*mpmD84tnDWxSja%~53Apt zD(AAy6I$KX%T3^hK^~pG6GZ`feCugN;(%r?u06mewpbEu0oh`~ zN|<>q5+1Sdzh|9!y+F!btV+oXZ=DN(7~d^voC7dNeQk@!4F)p|z!C8OSHL0h0~API z+&gA?@-h+fD<((rWx8H&c~F$O@`eK}D75$*6%Di}{Ut54xGbD%ki4+Ak?`@GH9t0d zE0V$SL{|#vt2N{&5s!nro^hP&P-JmBo6SJMc+_^*@yNIpCit277Cch0RO zRbMUNvg*khm=i<~T;%uP54_l6=Q=VXu`|_PWC7cZmcJZf+3CMC3y3x}Eiy9!v9lfM|cUM7l&57WpJD$U&{1H#N zqU4OjEVPLET@(B4z){*y zKs_0izT9uxTwHJDqm2@Vn}sM4e96XEv-a4GxjHNK(@*~3#E`g$X=#Fd+*i9W4z z{%&zx8m7P4Umzo84%%tH?}-mt_UH9I2}(W7c^!l#ftTd0c24m|v&g9KD+OX0z= zGcsL;^u?J2TNLku_x8>?4?I}6zrz4UU_rDC;zf9F@7SMdGRKFnE;BPJ{<}bKyd3JP z$R@%McGBlxzz%Cq?%tjCqj% z6T&VnrQIs`6t`Rd!7g+pvvL)SVgop)hbO5bk(!!=RrfF`^t~{M? zS&+033$zX7;-^&!wG1+KvK^0`a|c_=DN8d<#n@_AHT8DN(p=XLZ+GCOS`?wL%GE07 zg>S}rnm|==Uh*kc9|5G4H~{iqY)|V*rrRtnc+`^9l$I1lHiyI`1A(1$rQ+t7-}*B)W6x*V?YwSm>X{akb<|=J{GEC^4&_yE4#CM~KW z6#nTtC&OUD?+VGcg&%?o^Bgr}#akJ*xY9X(xRJ>qTJ1vkodDg4j`E%EApmo3WGh6mAs)86ZU`$2sVZ2NkW=G!1{ ze8FJCj9Tpx{WSY-o@PM^8ONV_e?ciIzqP0SX+5KHSX58(Gj9CtC-6s5SAbNO3bSbR z0L%l784FQ|I!6k52N(&+;NQkW7_@^tz|(@E=Q!$a#_Ke>0}5H&=uGclYbf3}sBs!3 z(7xW3q;8&aA;8JK4D+35?gON&aaxzB#mR0?&1 z?lT;q&@`LD9+%S80$?&o49{Ty_JF30{)5)(2Uxwrf3pEAxp07BLQm40JjjkOP0gb=YTb6xag?p7Qk~oiycBy$}Ev& zrCcUdOj>xXE3zRt`dadS2uz80fYjsmZ6+25pos2?SnJXV&tWyHA*-Lrz!tS3;}3O) zQ%Yx@+dpJr2589m!>FJ;gN`1eG;C^~yL=*{0QFN1Pjy#VSaj}w8h)3IMTOaFhDC~v zAI(1FkLH%RZp+L2}Kbk5F|Pz&2|W@j}VA3`ol%&pF`h3JcgxBZF1ryjN?fIMB}`6e<|4dO(zh zB|Wf&IV4nWq$bMc@a)IR!R3*VX+Cj!h2v*gu5_4n>PF*52uE#F4Joyd=~hnw)hRi* zZFMKIru#=7m~4D<@|I@S&q5*z<1*rJQmg{dz8BpHo0`bFG!IE@$k$W=gRq^X5mfT3 zp5Is0b-xdwM=jF|=&@hb4s>th&&dqzK&;;_yh>Au`@}x%8k`5(g$L#{a!osd>)5vx z>I=}(JpHZ#Kii$dj$T4wLwhf9YMYL{!#$$YU`?CB_Du$>=Z|a~yh21AiJcm2JQ^tD zK%D@(EHHN)!$G;q5Fl=)dDzGea_ zG(i<<4q~V@T$Nb#LdGL?@!nb1*;rq{4jMrfPLxOX>;G)d8iv;e%>F>J6vWC7gG=rn zPlrnfajPq1kY8h&Nmx*5F@?~ehbFt{qO)Ia=sDceHPYl5QF$XLVd&5>DwF@6YG^1N!1>2}p?7hF)J?Qq`n61igKFn?LpdB?n`bT#HAeNu6aW=1Asu z6_|!g-tI3h4wYTb>k!K=n>!U+u~jY%&p;l9({x$7s;HZ{s7_-D`{sEq@|0*paC~DD zeO$07{o1n2_B$pdy|sCY8a2f?ZN-#XY9`|&7CsP}YOf7izXR3d;hy10b29B$qu4;y zj(sDND82DoB`J6lv{gKm9w7f{{<7@D+V0HRRNCimGvCp6amy!GBN*lW$ma*j}Zwd+8LcoF6zQx{X# yC-uh?`rhEA-6F9fhD?+ffK+Z6CT|ub5u^0TR;82>PSzMVVEjMYZ2*VrCjbCJqjE|B literal 3387 zcmV-B4aD*viwFqV>5gCm|88|{Vr6n-Z+I+tQ&V~cI7Ge zS8!PsDXs4ML;z(xM}HBKnQ6 zQOcniHd(=cqJ*=C!9M=dZoIyxL) zkCdEon7I})ziVQjzdW@xpI@^nORFSd#I;w8U=`91R5aQ}mw;BcWlIjsI4thT2A z3hK$I^yOjG7UE_tN9S6dN^p=+L6sg&p9sGzfJ@nDui-Ui$PS*}p|3X*3tYKLn&{Ii z=Wpl7rD6I!2_x=0zB6_fc%Y#EmiaHYR}|$+8zc;t@>})>n1^yOyb}klt!XA#E(%L6 zAcmyaa$gF_uR#o|@Ef{7%>wj5;|&Y^QL}ThP;1zJXRaxkH0KIgb$1Y%fIJL;IpH~N5voJR+=cflM;N>v^^IGE{Wr6S_WpBVsszBhiqRf-3!LfQ8* zvrPKUqNm*duXpv_gpC><;r=JQi2}XG8B^XI9zCjNCVWcC^SP>s+%UjjI!HjJwiF%= zTO-p}NMF1eutoVkeD7|(i!gw7Pq!GL2rP`ZVKR>{+%0=blX+A4>N7Kw;=c>!Iw+v7 zid`c76^|Y76h9RhqOzBB#u}`f@Xo^nU9ryR7DuEW#k7fa`$Qu~t7Mz2IVrjaQNqLG zpAdFgC0(oBQ`~m_2fNUc%&TQ0$~E8^AD*O&L~3ddR^7v(&<~<8Nj~vn3;x$JvMT)+ zA`Z$t5z=)6$Gem|4~G%P#hb$2xq#ZL z-bYg2ufi%2?f9~M9)^1CWR5Bj3L=NEfy_R^$n?hbw7EvX$h$nd3KE{)$Te0?XBcN7 z58k*1`M--h9=q_{YXZEQck5R@e>I zvtu($O4~7Jw<4BnTqS*BKAiEyu`R4@O15FH8Ud%mq-7ke41O$%@ZJbKW$x*Jg3|!@ zR=Qd#!^-s;6hLPl^MYS9k-E&8I6!`?C1&=D^`NJ0G$QcL8Ha7_m}NPpeQaxYHT98d zSGlfJ-fkfxv?u~yR?B6=%c&U`XaZH(Ma9Qh>4cEJk`Tyyu|2INnQii{=C?}S=9kU~`e)slv6S~oXSS#;!CQ3+i&OizeEFM@{t8NDzuaUQ zEDIEF!@~9bJOspwg+|)h4y5icn6^Rs|Ubn+sn^zA2{>xG*nJGgj9-qZU^>!w)|eg{tAE zMRq$26LFKahR%MiN-0+Nrr)wO>e9Te^s_beOb`19U2DmWa$R;mHi0F+d)Q^mYPsfN zeBiVX`rm#~*8^=|PqShZCXFu`On4aV_K3cpd^5}PFoKLTodkbFDJb=|r~YL%p>bHi zkMT2U{Ox=2M^IOSR1T?IFB&}n^8jPULeveE@O|u#7^HQ2x089pn;W_Nz9?+Q4e{dlG6;`kC z->$(*J{%yJ(9`U;2=n7hQ*?s4XYwQwc_~WUz_c)?vV&~_+{BL1uDfy~lP*<oEX6ajE=AN{!d_6ctVIS3LUldSSIUk`%feK08+cBt#lw?5n z8(8l~^OlNsv8_v-6E}ham^mWNE4ffrh=a$vA{&CIuRtG$V5%4!NIkS~PO&fmMRX^{ ztP3hUht;TttbQT`+u??cKhzmcDV?=$8j*n+pdsTAqk`@yT6&1mu&GgR^NEB4)Vno2 z)yZLD(Yj}9_-!&46=tg$7AY=%H2aJ{nj2}$2L`RO2@H4m4!}f83YAoeHJcqbGpsl$_gj+7mhB{R0wAHatCjOEc>yD3L}<74tV4Rsm?=%XWl~ zO=Mk}N2?X&Ybt<2*iEw-DtT4U?K=9t-Xrb8Bl8)#rk%ib>{|-;1?Xs=Zr6aHcIU9AR~OjO&TF6ArXz2DpV4Wsrp;jc zCWFLwkhUMQz_n+((Q zBEzh9mO>FM&hS3UW|%u24Ab6Ys-4)?m!20-rmylT?d_O-#c(}ib?553b(Idwr(0`i z3{(p9a}oW<3#=i-B~Rv~w<;651^;gaAryP~&eFP5rz>?N(p6FSj#j^@(kMmx7l}RD zIf_LuCqX-)f!ija%nvH@H4{Lg393kQm_ViBtHh!gG90K&{?@Y2hx)3w*9fX`qCB!+ z|0i?SFuX2c?gxscFj02sU2^+)I$S!4J8c<x30q81=rbpio~4p2W-` zUPBR~{P3cAW+-n3#4=un2GN>>E#l@$M2Xgl*MX^D71iUK*?=~#;XqqjO>wTDpBh+S zVe|L+Ox{>}tZxrxhIJ-?!0=e<^qU(wUv1=GjQ9@uH5i+>$^(qZ`AUorSy;b!^y2>r z^%=V~kKk7iqt@?FJ%QYCDV96vyWVo%Ji)Zw_$bDASR*QsR4zV-QojcsgQ*_tzY3?a zG6cdG8TuFe`v~`Q-2)5iUhv5HKb!Xr{dI!-hOfdZk?a!Rfv(u5d;lI*=Dqlc^Jna; zpQ?vS(A{93{c9a|?BJbx$0W3qa7x#BS3lby(3jT>KtenldVPIGRg>lrbo=x+ ze^>%a4#p}ui$|kLo#QCxKo<8qFb$X7?k_G6m0iv12+J*(dpoqERW9_;KputDv{|~U zsGE1MR$~bJ76cY~Otd~YQ)3c+T(FaFZP{i|TP7sEwR!d$G{rY<#h5v2CgURxK1P{p z&jzjEoa$kJ&v2wU8F#BuY#?gSy%lMk-3C@k3f=^56^~>9$UmCDV*9XiJM#h89jzlsv&q2f$c*N5hlBYcI1!~p+Nk85JT|7gNs9>JKyYy}_y7BC#@oOjMMBRIV8&Zx*I8qx8s@m6Q-p R&JZ_X_&;bmX^&PY005<`fl2@X