Skip to content

Commit

Permalink
2021-04-23 (#1261)
Browse files Browse the repository at this point in the history
* feat: add lights and the temperature sensors (#1244)

Closes #1237 and #1202. Since this is based on polling it is disabled by default and is enabled in options. Please check the instructions on the wiki.
https://github.com/custom-components/alexa_media_player/wiki#discover-and-control-devices-connected-to-an-echo

* fix: check for existence of properties key
closes #1249

* fix: TypeError: _typeddict_new() missing typename

* style: fix lint errors

* fix: auto reload when extended entity discovery is enabled (#1254)

* Automatically reload the integration when extended entity discovery is enabled.

* fix: reload only after all values processed

Co-authored-by: Alan Tse <[email protected]>

* fix: detect and ignore lights created by emulated_hue (#1253)

* Detect and ignore lights created by emulated_hue plus some general cleanup.

* Adjust naming and comments to be more accurate.

Co-authored-by: Brady Mulhollem <[email protected]>
  • Loading branch information
alandtse and blm126 authored Apr 24, 2021
2 parents 5f77ed7 + 4cef90e commit faa4fde
Show file tree
Hide file tree
Showing 9 changed files with 615 additions and 195 deletions.
95 changes: 71 additions & 24 deletions custom_components/alexa_media/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,23 @@
from homeassistant.util import dt, slugify
import voluptuous as vol

from .alexa_entity import AlexaEntityData, get_entity_data, parse_alexa_entities
from .config_flow import in_progess_instances
from .const import (
ALEXA_COMPONENTS,
CONF_ACCOUNTS,
CONF_COOKIES_TXT,
CONF_DEBUG,
CONF_EXCLUDE_DEVICES,
CONF_EXTENDED_ENTITY_DISCOVERY,
CONF_INCLUDE_DEVICES,
CONF_OAUTH,
CONF_OAUTH_LOGIN,
CONF_OTPSECRET,
CONF_QUEUE_DELAY,
DATA_ALEXAMEDIA,
DATA_LISTENER,
DEFAULT_EXTENDED_ENTITY_DISCOVERY,
DEFAULT_QUEUE_DELAY,
DEPENDENT_ALEXA_COMPONENTS,
DOMAIN,
Expand Down Expand Up @@ -250,11 +253,12 @@ async def login_success(event=None) -> None:
"coordinator": None,
"config_entry": config_entry,
"setup_alexa": setup_alexa,
"devices": {"media_player": {}, "switch": {}},
"devices": {"media_player": {}, "switch": {}, "guard": [], "light": [], "temperature": []},
"entities": {
"media_player": {},
"switch": {},
"sensor": {},
"light": [],
"alarm_control_panel": {},
},
"excluded": {},
Expand All @@ -266,9 +270,13 @@ async def login_success(event=None) -> None:
"websocket": None,
"auth_info": None,
"second_account_index": 0,
"should_get_network": True,
"options": {
CONF_QUEUE_DELAY: config_entry.options.get(
CONF_QUEUE_DELAY, DEFAULT_QUEUE_DELAY
),
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)],
Expand Down Expand Up @@ -312,7 +320,7 @@ async def login_success(event=None) -> None:
async def setup_alexa(hass, config_entry, login_obj: AlexaLogin):
"""Set up a alexa api based on host parameter."""

async def async_update_data():
async def async_update_data() -> Optional[AlexaEntityData]:
"""Fetch data from API endpoint.
This is the place to pre-process the data to lookup tables
Expand All @@ -321,6 +329,9 @@ async def async_update_data():
This will ping Alexa API to identify all devices, bluetooth, and the last
called device.
If any guards, temperature sensors, or lights are configured, their
current state will be acquired. This data is returned directly so that it is available on the coordinator.
This will add new devices and services when discovered. By default this
runs every SCAN_INTERVAL seconds unless another method calls it. if
websockets is connected, it will increase the delay 10-fold between updates.
Expand All @@ -345,11 +356,14 @@ async def async_update_data():
].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"]

devices = {}
bluetooth = {}
preferences = {}
dnd = {}
raw_notifications = {}
entity_state = {}
tasks = [
AlexaAPI.get_devices(login_obj),
AlexaAPI.get_bluetooth(login_obj),
Expand All @@ -359,33 +373,58 @@ async def async_update_data():
if new_devices:
tasks.append(AlexaAPI.get_authentication(login_obj))

entities_to_monitor = set()
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)

for light in hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"]["light"]:
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():
if guard.enabled:
entities_to_monitor.add(guard.unique_id)

if entities_to_monitor:
tasks.append(get_entity_data(login_obj, list(entities_to_monitor)))

if should_get_network:
tasks.append(AlexaAPI.get_network_details(login_obj))

try:
# Note: asyncio.TimeoutError and aiohttp.ClientError are already
# handled by the data update coordinator.
async with async_timeout.timeout(30):
(
devices,
bluetooth,
preferences,
dnd,
*optional_task_results
) = await asyncio.gather(*tasks)

if should_get_network:
_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

if entities_to_monitor:
entity_state = optional_task_results.pop()

if new_devices:
(
devices,
bluetooth,
preferences,
dnd,
auth_info,
) = await asyncio.gather(*tasks)
else:
(
devices,
bluetooth,
preferences,
dnd,
) = await asyncio.gather(*tasks)
_LOGGER.debug(
"%s: Found %s devices, %s bluetooth",
hide_email(email),
len(devices) if devices is not None else "",
len(bluetooth.get("bluetoothStates", []))
if bluetooth is not None
else "",
)
auth_info = optional_task_results.pop()
_LOGGER.debug(
"%s: Found %s devices, %s bluetooth",
hide_email(email),
len(devices) if devices is not None else "",
len(bluetooth.get("bluetoothStates", []))
if bluetooth is not None
else "",
)

await process_notifications(login_obj, raw_notifications)
# Process last_called data to fire events
await update_last_called(login_obj)
Expand Down Expand Up @@ -554,6 +593,7 @@ async def async_update_data():
},
},
)
return entity_state

@_catch_login_errors
async def process_notifications(login_obj, raw_notifications=None):
Expand Down Expand Up @@ -732,6 +772,7 @@ async def ws_handler(message_obj):
seen_commands = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
"websocket_commands"
]
coord = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["coordinator"]
if command and json_payload:

_LOGGER.debug(
Expand Down Expand Up @@ -767,6 +808,7 @@ async def ws_handler(message_obj):
"timestamp": json_payload["timestamp"],
}
try:
await coord.async_request_refresh()
if serial and serial in existing_serials:
await update_last_called(login_obj, last_called)
async_dispatcher_send(
Expand Down Expand Up @@ -1144,6 +1186,7 @@ async def update_listener(hass, config_entry):
"""Update when config_entry options update."""
account = config_entry.data
email = account.get(CONF_EMAIL)
reload_needed: bool = False
for key, old_value in hass.data[DATA_ALEXAMEDIA]["accounts"][email][
"options"
].items():
Expand All @@ -1156,6 +1199,10 @@ async def update_listener(hass, config_entry):
old_value,
hass.data[DATA_ALEXAMEDIA]["accounts"][email]["options"][key],
)
if key == CONF_EXTENDED_ENTITY_DISCOVERY:
reload_needed = True
if reload_needed:
await hass.config_entries.async_reload(config_entry.entry_id)


async def test_login_status(hass, config_entry, login) -> bool:
Expand Down
Loading

0 comments on commit faa4fde

Please sign in to comment.