Skip to content

Commit

Permalink
Changes
Browse files Browse the repository at this point in the history
  • Loading branch information
aneisch committed Nov 18, 2024
1 parent 0a9b8f8 commit 5a8bac8
Show file tree
Hide file tree
Showing 99 changed files with 2,346 additions and 5,043 deletions.
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Build Status](https://github.com/aneisch/home-assistant-config/actions/workflows/check-ha-release-compatibility.yml/badge.svg)](https://github.com/aneisch/home-assistant-config/actions)
[![GitHub last commit](https://img.shields.io/github/last-commit/aneisch/home-assistant-config)](https://github.com/aneisch/home-assistant-config/commits/master)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/aneisch/home-assistant-config)](https://github.com/aneisch/home-assistant-config/graphs/commit-activity)
[![HA Version](https://img.shields.io/badge/Running%20Home%20Assistant-2024.10.1%20(Latest)-brightgreen)](https://github.com/home-assistant/home-assistant/releases/latest)
[![HA Version](https://img.shields.io/badge/Running%20Home%20Assistant-2024.11.2%20(Latest)-brightgreen)](https://github.com/home-assistant/home-assistant/releases/latest)
<br><a href="https://www.buymeacoffee.com/aneisch" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-black.png" width="150px" height="35px" alt="Buy Me A Coffee" style="height: 35px !important;width: 150px !important;" ></a>

I do my best to keep [Home Assistant](https://github.com/home-assistant/home-assistant) on the [latest release](https://github.com/home-assistant/home-assistant/releases/latest). I'm heavily utilizing [AppDaemon](http://appdaemon.readthedocs.io/en/latest/) and [NodeRed](https://flows.nodered.org/node/node-red-contrib-home-assistant-websocket) for advanced/templated automations. See [Appdaemon config](https://github.com/aneisch/home-assistant-config/tree/master/extras/appdaemon) and my NodeRed screenshots below for details. Most of my setup is run as Docker containers (see [docker-compose](https://github.com/aneisch/home-assistant-config/tree/master/extras/docker-compose) for container list).
Expand All @@ -27,6 +27,7 @@ My Home Assistant installation runs on a [Gen7 i3 NUC](https://amzn.to/2K0vab6)
- 2 GE 12730 / ZW4002
- 1 Kwikset 910
- 1 Leviton DZPA1
- 1 None None
- 1 Nortek Security + Control LLC WADWAZ-1
- 1 Nortek Security + Control LLC WAPIRZ-1
- 1 Zooz ZAC36
Expand All @@ -35,8 +36,7 @@ My Home Assistant installation runs on a [Gen7 i3 NUC](https://amzn.to/2K0vab6)
- 6 Zooz ZEN30
- 3 Zooz ZEN37 800LR
- 1 Zooz ZEN55 LR
- 2 Zooz ZEN71
- 2 Zooz ZEN71 800LR
- 4 Zooz ZEN71
- 1 Zooz ZEN77
- 5 Zooz ZSE42
- MQTT remote and local server (via [Docker](https://github.com/aneisch/home-assistant-config/tree/master/extras/docker-compose))
Expand All @@ -58,17 +58,17 @@ Home Assistant and other containers have ingress handled automatically by [Traef
## Some statistics about my installation:
Description | value
-- | --
Lines of ESPHome YAML | 3246
Lines of Home Assistant YAML | 10611
[Integrations](https://www.home-assistant.io/integrations/) in use | 64
Lines of ESPHome YAML | 3297
Lines of Home Assistant YAML | 10787
[Integrations](https://www.home-assistant.io/integrations/) in use | 66
Zigbee devices in [`zha`](https://www.home-assistant.io/integrations/zha/) | 26
Z-Wave devices in [`zwave_js`](https://www.home-assistant.io/integrations/zwave_js/) | 37

Description | value
-- | --
Entities in the [`automation`](https://www.home-assistant.io/components/automation) domain | 134
Entities in the [`binary_sensor`](https://www.home-assistant.io/components/binary_sensor) domain | 170
Entities in the [`button`](https://www.home-assistant.io/components/button) domain | 46
Entities in the [`automation`](https://www.home-assistant.io/components/automation) domain | 135
Entities in the [`binary_sensor`](https://www.home-assistant.io/components/binary_sensor) domain | 175
Entities in the [`button`](https://www.home-assistant.io/components/button) domain | 53
Entities in the [`camera`](https://www.home-assistant.io/components/camera) domain | 16
Entities in the [`climate`](https://www.home-assistant.io/components/climate) domain | 1
Entities in the [`conversation`](https://www.home-assistant.io/components/conversation) domain | 2
Expand All @@ -78,7 +78,7 @@ Entities in the [`datetime`](https://www.home-assistant.io/components/datetime)
Entities in the [`device_tracker`](https://www.home-assistant.io/components/device_tracker) domain | 6
Entities in the [`event`](https://www.home-assistant.io/components/event) domain | 14
Entities in the [`fan`](https://www.home-assistant.io/components/fan) domain | 3
Entities in the [`group`](https://www.home-assistant.io/components/group) domain | 19
Entities in the [`group`](https://www.home-assistant.io/components/group) domain | 21
Entities in the [`image`](https://www.home-assistant.io/components/image) domain | 8
Entities in the [`input_boolean`](https://www.home-assistant.io/components/input_boolean) domain | 31
Entities in the [`input_datetime`](https://www.home-assistant.io/components/input_datetime) domain | 33
Expand All @@ -87,26 +87,26 @@ Entities in the [`input_select`](https://www.home-assistant.io/components/input_
Entities in the [`input_text`](https://www.home-assistant.io/components/input_text) domain | 17
Entities in the [`light`](https://www.home-assistant.io/components/light) domain | 39
Entities in the [`lock`](https://www.home-assistant.io/components/lock) domain | 4
Entities in the [`media_player`](https://www.home-assistant.io/components/media_player) domain | 19
Entities in the [`media_player`](https://www.home-assistant.io/components/media_player) domain | 20
Entities in the [`notify`](https://www.home-assistant.io/components/notify) domain | 2
Entities in the [`number`](https://www.home-assistant.io/components/number) domain | 16
Entities in the [`number`](https://www.home-assistant.io/components/number) domain | 18
Entities in the [`person`](https://www.home-assistant.io/components/person) domain | 2
Entities in the [`plant`](https://www.home-assistant.io/components/plant) domain | 1
Entities in the [`remote`](https://www.home-assistant.io/components/remote) domain | 4
Entities in the [`script`](https://www.home-assistant.io/components/script) domain | 52
Entities in the [`select`](https://www.home-assistant.io/components/select) domain | 4
Entities in the [`sensor`](https://www.home-assistant.io/components/sensor) domain | 562
Entities in the [`sensor`](https://www.home-assistant.io/components/sensor) domain | 569
Entities in the [`setter`](https://www.home-assistant.io/components/setter) domain | 1
Entities in the [`siren`](https://www.home-assistant.io/components/siren) domain | 1
Entities in the [`sun`](https://www.home-assistant.io/components/sun) domain | 1
Entities in the [`switch`](https://www.home-assistant.io/components/switch) domain | 191
Entities in the [`switch`](https://www.home-assistant.io/components/switch) domain | 197
Entities in the [`timer`](https://www.home-assistant.io/components/timer) domain | 7
Entities in the [`tts`](https://www.home-assistant.io/components/tts) domain | 1
Entities in the [`update`](https://www.home-assistant.io/components/update) domain | 83
Entities in the [`vacuum`](https://www.home-assistant.io/components/vacuum) domain | 1
Entities in the [`weather`](https://www.home-assistant.io/components/weather) domain | 1
Entities in the [`zone`](https://www.home-assistant.io/components/zone) domain | 7
**Total state objects** | **1547**
Entities in the [`zone`](https://www.home-assistant.io/components/zone) domain | 8
**Total state objects** | **1579**
## The HACS integrations/plugins that I use:

**Appdaemon**:<br>
Expand Down
2 changes: 1 addition & 1 deletion configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ homeassistant:
alexa: !include packages/alexa.yaml
#aarlo: !include packages/aarlo.yaml
aquarium: !include packages/aquarium.yaml
#christmas: !include packages/christmas.yaml
christmas: !include packages/christmas.yaml
# covid: !include packages/covid.yaml
frigate: !include packages/frigate.yaml
door_otp: !include packages/door_otp.yaml
Expand Down
24 changes: 12 additions & 12 deletions custom_components/alexa_media/.translations/de.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"config": {
"abort": {
"forgot_password": "Die \"Passwort vergessen Funktion\" wurde erkannt. Amazon verlangt möglicherweise Maßnahmen bevor ein erneuter Anmeldeversuch unternommen werden kann.",
"forgot_password": "Die \"Passwort vergessen Seite\" wurde erkannt. Amazon verlangt möglicherweise Maßnahmen bevor ein erneuter Anmeldeversuch unternommen werden kann.",
"login_failed": "Alexa Media Player konnte nicht angemeldet werden.",
"reauth_successful": "Alexa Media Player erfolgreich authentifiziert"
},
"error": {
"2fa_key_invalid": "Ungültiger 2-Faktor Schlüssel",
"connection_error": "Verbindungsfehler; Netzwerk prüfen und erneut versuchen",
"identifier_exists": "Diese E-Mail-Adresse ist bereits registriert",
"invalid_credentials": "Falsche Zugangsdaten",
"invalid_credentials": "Ungültige Zugangsdaten",
"invalid_url": "URL ist ungültig: {message}",
"unable_to_connect_hass_url": "Es kann keine Verbindung zur Home Assistant-URL hergestellt werden. Bitte überprüfen Sie die externe URL unter Konfiguration -> Allgemein",
"unknown_error": "Unbekannter Fehler: {message}"
Expand Down Expand Up @@ -39,7 +39,7 @@
"include_devices": "Eingebundene Geräte (Komma getrennt)",
"otp_secret": "Integrierter 2FA-App-Schlüssel (automatisch generierte 2FA-Code)",
"password": "Passwort",
"queue_delay": "Sekunden zu warten, um Befehle in die Warteschlange zu stellen",
"queue_delay": "Zu wartende Sekunden, um Befehle in die Warteschlange zu stellen",
"scan_interval": "Sekunden zwischen den Scans",
"securitycode": "2FA-Code (empfohlen, um Anmeldeprobleme zu vermeiden)",
"url": "Amazon Region (z.B. amazon.de)"
Expand All @@ -56,10 +56,10 @@
"debug": "Erweitertes Debugging",
"exclude_devices": "Ausgeschlossene Geräte (Komma getrennt)",
"extended_entity_discovery": "Schließen Sie Geräte ein, die über Echo verbunden sind",
"hass_url": "Öffentliche URL zum Zugriff auf Home Assistant (einschließlich '/' am Ende)",
"hass_url": "Öffentliche URL für den Zugriff auf Home Assistant (einschließlich '/' am Ende)",
"include_devices": "Eingebundene Geräte (Komma getrennt)",
"public_url": "Öffentliche URL zum Zugriff auf Home Assistant (einschließlich '/' am Ende)",
"queue_delay": "Sekunden zu warten, um Befehle in die Warteschlange zu stellen",
"public_url": "Öffentliche URL für den Zugriff auf Home Assistant (einschließlich '/' am Ende)",
"queue_delay": "Zu wartende Sekunden, um Befehle in die Warteschlange zu stellen",
"scan_interval": "Sekunden zwischen den Scans"
},
"description": "Erforderlich *",
Expand All @@ -73,7 +73,7 @@
"fields": {
"email": {
"description": "Zu löschende Accounts. Falls leer, werden alle gelöscht.",
"name": "E-Mail Adresse"
"name": "E-Mail-Adresse"
},
"entries": {
"description": "Zu löschende Einträge von 1 bis 50. Falls leer, lösche 50.",
Expand All @@ -83,21 +83,21 @@
"name": "Lösche Alexa Stimmbefehl-Historie."
},
"force_logout": {
"description": "Logout erzwingen. Primär für Debugging notwendig.",
"description": "Logout erzwingen. Primär für Debugging genutzt.",
"fields": {
"email": {
"description": "Zu löschende Accounts. Falls leer werden alle gelöscht.",
"name": "E-Mail Adresse"
"description": "Zu löschende Accounts. Falls leer werden alle gelöscht.",
"name": "E-Mail-Adresse"
}
},
"name": "Logout erzwingen"
},
"update_last_called": {
"description": "Erzwinge Updates der zuletzt aufgerufenen Echo Geräte für jeden Alexa Account ",
"description": "Erzwinge Updates der zuletzt aufgerufenen Echo Geräte für jeden Alexa Account.",
"fields": {
"email": {
"description": "Liste der zu aktualisierenden Alexa-Konten. Wenn leer, werden alle bekannten Konten aktualisiert.",
"name": "E-Mail Adresse"
"name": "E-Mail-Adresse"
}
},
"name": "Aktualisiere den zuletzt aufgerufenen Sensor"
Expand Down
111 changes: 79 additions & 32 deletions custom_components/alexa_media/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ async def async_setup(hass, config, discovery_info=None):
hass.config_entries.async_update_entry(
entry,
data={
CONF_URL: account[CONF_URL],
CONF_EMAIL: account[CONF_EMAIL],
CONF_PASSWORD: account[CONF_PASSWORD],
CONF_URL: account[CONF_URL],
CONF_PUBLIC_URL: account[CONF_PUBLIC_URL],
CONF_INCLUDE_DEVICES: account[CONF_INCLUDE_DEVICES],
CONF_EXCLUDE_DEVICES: account[CONF_EXCLUDE_DEVICES],
CONF_SCAN_INTERVAL: account[
Expand Down Expand Up @@ -186,7 +187,7 @@ async def async_setup(hass, config, discovery_info=None):
CONF_URL: account[CONF_URL],
CONF_EMAIL: account[CONF_EMAIL],
CONF_PASSWORD: account[CONF_PASSWORD],
CONF_PUBLIC_URL: account[CONF_PUBLIC_URL],
CONF_PUBLIC_URL: account.get(CONF_PUBLIC_URL, ""),
CONF_INCLUDE_DEVICES: account[CONF_INCLUDE_DEVICES],
CONF_EXCLUDE_DEVICES: account[CONF_EXCLUDE_DEVICES],
CONF_SCAN_INTERVAL: account[CONF_SCAN_INTERVAL].total_seconds(),
Expand Down Expand Up @@ -371,6 +372,11 @@ async def setup_alexa(hass, config_entry, login_obj: AlexaLogin):
# pylint: disable=too-many-statements,too-many-locals
"""Set up a alexa api based on host parameter."""

# Initialize throttling state and lock
last_dnd_update_times: dict[str, datetime] = {}
pending_dnd_updates: dict[str, bool] = {}
dnd_update_lock = asyncio.Lock()

async def async_update_data() -> Optional[AlexaEntityData]:
# noqa pylint: disable=too-many-branches
"""Fetch data from API endpoint.
Expand Down Expand Up @@ -644,31 +650,17 @@ async def async_update_data() -> Optional[AlexaEntityData]:
cleaned_config = config.copy()
cleaned_config.pop(CONF_PASSWORD, None)
# CONF_PASSWORD contains sensitive info which is no longer needed
for component in ALEXA_COMPONENTS:
entry_setup = len(
hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"][component]
# Load multiple platforms in parallel using async_forward_entry_setups
_LOGGER.debug("Loading platforms: %s", ", ".join(ALEXA_COMPONENTS))
try:
await hass.config_entries.async_forward_entry_setups(
config_entry, ALEXA_COMPONENTS
)
if not entry_setup:
_LOGGER.debug("Loading config entry for %s", component)
try:
await hass.config_entries.async_forward_entry_setups(
config_entry, [component]
)
except (asyncio.TimeoutError, TimeoutException) as ex:
raise ConfigEntryNotReady(
f"Timeout while loading config entry for {component}"
) from ex
else:
_LOGGER.debug("Loading %s", component)
hass.async_create_task(
async_load_platform(
hass,
component,
DOMAIN,
{CONF_NAME: DOMAIN, "config": cleaned_config},
cleaned_config,
)
)
except (asyncio.TimeoutError, TimeoutException) as ex:
_LOGGER.error(f"Error while loading platforms: {ex}")
raise ConfigEntryNotReady(
f"Timeout while loading platforms: {ex}"
) from ex

hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False
# prune stale devices
Expand Down Expand Up @@ -843,21 +835,76 @@ async def update_bluetooth_state(login_obj, device_serial):
)
return None

@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
async def schedule_update_dnd_state(email: str):
"""Schedule an update_dnd_state call after MIN_TIME_BETWEEN_FORCED_SCANS."""
await asyncio.sleep(MIN_TIME_BETWEEN_FORCED_SCANS)
async with dnd_update_lock:
if pending_dnd_updates.get(email, False):
pending_dnd_updates[email] = False
_LOGGER.debug(
"Executing scheduled forced DND update for %s", hide_email(email)
)
# Assume login_obj can be retrieved or passed appropriately
login_obj = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["login_obj"]
await update_dnd_state(login_obj)

@_catch_login_errors
async def update_dnd_state(login_obj) -> None:
"""Update the dnd state on ws dnd combo event."""
dnd = await AlexaAPI.get_dnd_state(login_obj)
"""Update the DND state on websocket DND combo event."""
email = login_obj.email
now = datetime.utcnow()

async with dnd_update_lock:
last_run = last_dnd_update_times.get(email)
cooldown = timedelta(seconds=MIN_TIME_BETWEEN_SCANS)

if last_run and (now - last_run) < cooldown:
# If within cooldown, mark a pending update if not already marked
if not pending_dnd_updates.get(email, False):
pending_dnd_updates[email] = True
_LOGGER.debug(
"Throttling active for %s, scheduling a forced DND update.",
hide_email(email),
)
asyncio.create_task(schedule_update_dnd_state(email))
else:
_LOGGER.debug(
"Throttling active for %s, forced DND update already scheduled.",
hide_email(email),
)
return

# Update the last run time
last_dnd_update_times[email] = now

_LOGGER.debug("Updating DND state for %s", hide_email(email))

try:
# Fetch the DND state using the Alexa API
dnd = await AlexaAPI.get_dnd_state(login_obj)
except asyncio.TimeoutError:
_LOGGER.error(
"Timeout occurred while fetching DND state for %s", hide_email(email)
)
return
except Exception as e:
_LOGGER.error(
"Unexpected error while fetching DND state for %s: %s",
hide_email(email),
e,
)
return

# Check if DND data is valid and dispatch an update event
if dnd is not None and "doNotDisturbDeviceStatusList" in dnd:
async_dispatcher_send(
hass,
f"{DOMAIN}_{hide_email(email)}"[0:32],
{"dnd_update": dnd["doNotDisturbDeviceStatusList"]},
)
return
_LOGGER.debug("%s: get_dnd_state failed: dnd:%s", hide_email(email), dnd)
return
else:
_LOGGER.debug("%s: get_dnd_state failed: dnd:%s", hide_email(email), dnd)

async def http2_connect() -> HTTP2EchoClient:
"""Open HTTP2 Push connection.
Expand Down Expand Up @@ -1363,7 +1410,7 @@ async def async_remove_entry(hass, entry) -> bool:
login_obj = AlexaLogin(
url="",
email=email,
password="",
password="", # nosec
outputpath=hass.config.path,
)
# Delete cookiefile
Expand Down
Loading

0 comments on commit 5a8bac8

Please sign in to comment.